11-4. 클라이언트에서 소켓 접속 및 새 포스트 띄우기

이제 클라이언트쪽에서 소켓엣 접속하고, 연결이 끊기면 재접속하는 로직을 생성하고, 새 포스트를 전송 받았을때는 리덕스 스토어에 dispatch 하여 화면에 새 데이터를 띄우는 작업을 진행하겠습니다.

소켓 헬퍼 모듈 작성하기

웹소켓에서 데이터를 전달 받았을 때 데이터를 처리 할 모듈을 만들어주겠습니다.

src/lib/socket.js

// 에러처리된 JSON 파싱함수
const parseJSON = (str) => {
    let parsed = null;
    try {
        parsed = JSON.parse(str);
    } catch (e) {
        return null;
    }
    return parsed;
}

export default (function socketHelper() {
    let _store = null;
    let _socket = null;
    let _uri = null;

    const listener = (message) => {
        const data = parseJSON(message.data); // JSON 파싱
        if(!data || !data.type) return; // 파싱 실패했거나, type 값이 없으면 무시
        _store.dispatch(data); // 제대로 된 데이터면 store 에 디스패치
    }

    const reconnect = () => {
        // 연결이 끊겼을 때 3초마다 재연결
        console.log('reconnecting..');
        setTimeout(() => connect(_uri), 3000);
    }

    const connect = (uri) => {
        _uri = uri;
        _socket = new WebSocket(uri);
        _socket.onmessage = listener;
        _socket.onopen = (event) => {
            console.log('connected to ' + uri);
        }
        _socket.onclose = reconnect; // 연결이 끊기 면 재연결 시도
    }

    return {
        initialize: (store, uri) => {
            _store = store;
            connect(uri);
        }
    }
})()

위 코드는, 웹소켓에서 사용 할 스토어와 주소를 정의하고, 데이터가 들어올 때 마다 스토어에 dispatch 를 해주며, 접속이 끊기면 재연결을 시도합니다. 재연결이 실패했을때도, 웹소켓의 onclose 이벤트가 실행되는데요, 이 때 3초 후 다시 연결을 시도합니다.

클라이언트 엔트리에서 소켓 헬퍼 사용하기

index.js 에서 소켓 헬퍼를 불러오고, 스토어와 웹소켓 주소를 설정해주겠습니다. 우리가 현재 웹팩 개발서버에서는 프록시를 통해서 저희 백엔드 서버에 연결을 하고 있는데요, 웹소켓은 프록시로 처리되지 않습니다. 따라서, 개발환경에서는 직접 백엔드서버의 주소에 연결을 하고, 프로덕션 환경에선 현재 호스트와 프로토콜에 기반하여 웹주소를 설정하도록 합니다.

src/index.js

(...)
import socket from 'lib/socket';

const store = configureStore();

// 개발환경에선 localhost:4000 에 연결하고, 프로덕션에선 현재 호스트에 알맞는 프로토콜로 접속합니다
const socketURI = process.env.NODE_ENV === 'production' 
                    ? ((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + "/ws"
                    : 'ws://localhost:4000/ws';

socket.initialize(store, socketURI);

(...)

파일을 저장하고나서, 브라우저에서 리액트 개발서버를 띄운뒤 개발자도구의 콘솔을 확인해보세요

connected to ws://localhost:4000/ws

위 텍스트가 떴나요?

그 다음에는, 새 포스트를 작성해보세요. (Postman 으로 하셔도 좋고, 직접 브라우저에서 작성을 해도 좋습니다) 그 다음에 리덕스 개발자도구를 열어서 RECEIVE_NEW_POST 액션이 나타나는지 확인해보세요.

RECEIVE_NEW_POST 액션처리하기

이제 리듀서에서 RECEIVE_NEW_POST 액션을 처리만 해주면, 새 데이터를 바로 페이지에서 볼 수 있습니다.

src/redux/modules/posts.js

(...)
const RECEIVE_NEW_POST = 'posts/RECEIVE_NEW_POST'; // 새 데이터 수신 

(...)

export default handleActions({
    (...)
    [RECEIVE_NEW_POST]: (state, action) => {
        // 전달받은 포스트를 데이터의 앞부분에 넣어줍니다.
        return state.update('data', data=>data.unshift(fromJS(action.payload)));
    }
}, initialState);

RECEIVE_NEW_POST 액션이 발생하면 unshift 를 통하여 데이터를 현재 데이터의 앞부분에 넣어주었습니다. 이 액션은 웹소켓에서 store 에 직접 dispatch 하는 것 이기 때문에 액션 생성자를 따로 만들 필요 없습니다.

여기까지 작업을 하고나면, 새 포스트를 작성 했을때, 새 포스트가 현재 열고있는 창에서는 물론, 다른 접속자들 화면에서도 나타나게 됩니다.

한글 잔상 남는 버그 고치기

포스트를 작성 할 때 영어일때는 상관 없지만, 한글을 입력하면, 포스트를 작성하고 나서 다시 타이핑을 시작하면 맨 마지막에 입력하던 글자가 다시 나타나게되는 버그가 있습니다.

이 버그를 고치려면, 포스트 작성을 하고 나서 잠깐 포커스를 풀어주어야합니다.

그러기 위해선 우선 Textarea 에 ref 를 달아주어야합니다. react-textarea-autosize 에서는, 컴포넌트 내부의 인풋에 ref 를 달아 줄 때 inputRef 라는 props 를 사용합니다.

그러면 WritePost 컴포넌트에서도 inputRef 라는 props 를 받아오게 설정한 뒤 이 props 를 Textarea 에도 전달을 해주도록 설정합시다.

src/components/Home/WritePost

(...)

const WritePost = ({onChange, onPost, value, inputRef}) => (
    <Wrapper>
        <StyledTextarea
            minRows={3} 
            maxRows={10} 
            placeholder={`의식의 흐름대로 당신의 생각을 적어보세요.\n5초이상 아무것도 입력하지 않으면 자동으로 포스팅됩니다.`}
            value={value}
            onChange={onChange}
            onPaste={e=>e.preventDefault()}
            inputRef={inputRef}
        />
        <Progress onPost={onPost} value={value}/>
    </Wrapper>
);

export default WritePost;

그 다음에는, WritePostContainer 에서 WritePost 를 렌더링 하게 될 때 inputRef 를 설정하여 레퍼런스를 만들어주고, handlePost 가 실행 될 때 blur 를 통해 포커스를 풀어주고, 다시 포커스를 설정해줍시다.

src/containers/Home/WritePostContainer.js

(...)

class WritePostContainer extends Component {
    (...)
    handlePost = async () => {
        this.input.blur();
        setTimeout(
            () => {
                this.input.focus();
            }, 100
        );

        const { HomeActions, value } = this.props;

        (...)
    }
    render() {
        const { handleChange, handlePost } = this;
        const { value } = this.props;

        return (
            <WritePost 
                value={value}
                onChange={handleChange}
                onPost={handlePost}
                inputRef={(ref)=>this.input=ref}
            />
        );
    }
}

(...)

이렇게 수정하고나면, 한글의 잔상이 남는 버그가 수정됩니다.

results matching ""

    No results matching ""