[개발일지] Context API, Custom Hooks in React

김선종·2021년 11월 9일
1

오늘 (그리고 앞으로) 할일

  • CEOS FE 과제중 하나인, 리액트를 통한 메신저 어플리케이션을 리팩토링
  • TypeScript, Context API를 통한 Flux 패턴 적용을 통해 더욱 상태관리를 용이하게 하기 위함
  • 오늘은, 메신저 어플리케이션에서 친구 목록에 대한 context를 적용해보았다.

Context API

그게 뭘까요...?

리액트 공식 홈페이지에 나와있는 Context의 설명은 다음과 같다.

context를 이용하면 단계마다 일일이 props를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있습니다.

리덕스의 store와 비슷한 개념으로, 어플리케이션 전역 스코프에서 가지는 상태라고 할 수 있다.

왜 등장했을까?

Context API 자체는 등장한지 꽤 되었지만 이러한 전역 상태관리 방법이 왜 필요한지는 리액트로 규모가 어느정도 되는 어플리케이션을 제작해본 사람이라면 바로 느낄것이다.

코딩 애플님의 영상인데, 왜 전역상태 관리가 필요한지 바로 느낄것이다. 어플리케이션의 규모가 조금만 커지게 되더라도 수많은 state와 props가 난무하게 되는데, 이걸 관리하기가 힘들어 지는 것 이다.

그래서 어떻게 쓸까요?

기존에 state를 선언했던 방식에서 React.createContext()로 대체할 수 있다.

원래 코드는 이랬습니다

현재 나의 어플리케이션은 친구 목록을 json 파일에 저장되어 있고, 이를 최상위 컴포넌트에서 state로 선언한 다음 하위 컴포넌트에서 props로 받아서 사용하는 형태이다.

function ChatApp() {
  /*
  페이지를 처음 로딩할 때, 채팅 정보가 들어있는 json 파일을 불러온다.
  이후 이 json 파일을 localStorage에 저장하여 component간 주고받는다.
  */
  useEffect(() => {
    localStorage.setItem('ChatList', JSON.stringify(Chats));
  }, []);

  return (
    <Fragment>
      <GlobalStyle />
      <ChatAppContainer>
        <Router>
          <Route
            exact
            path={['/', '/chatlist', '/settings']}
            component={NavBar}
          />
          <Switch>
            <Route exact path="/">
              <FriendsList friends={Friends} /> // 친구 목록을 props로 전달
            </Route>
            <Route path="/chatlist">
              <ChatList friends={Friends} key={Date.now()} />
            </Route>
            <Route path="/settings">
              <Settings />
            </Route>
          </Switch>
        </Router>
      </ChatAppContainer>
    </Fragment>
  );
}

하위 컴포넌트가 여러겹 구조를 가지고 있다 보니까 상태관리가 너무 힘들어지는 감이 있었다.

개선을 해보자!

/* userContext.tsx */

import Friends from '../data/Friends.json';

type friend = {
  id: number;
  name: string;
  statusMessage: string;
  profileImage: string;
};

const UserContext: React.Context<friend[]> = createContext([
  {
    id: 0,
    name: '',
    statusMessage: '',
    profileImage: '',
  },
]);

const UserContextProvider = ({ children }: any) => {
  const [friends] = useState(Friends);

  return (
    <UserContext.Provider value={friends}>{children}</UserContext.Provider>
  );
};

export default UserContextProvider;
export { UserContext };
export type { friend };

JSON으로 존재하던 친구 목록을 불러와 context로 선언하고 이를 사용할 provider 컴포넌트를 정의하였다. 이 provider는 최상위 컴포넌트를 감싸게 된다.

이와 관련해서 createContext()의 인자의 타입에 따라 UserContext.Providervalue의 타입이 결정되었다. 묵시적으로 타입을 선언해도 타입 추론이 가능하다는 점이 신기했다. 이래서 타입스크립트를 쓰는건지...

Context를 선언함에 따라, 이 컨텍스트를 유용하게 활용할 수 있는 custom hooks 또한 만들어야 했다.

/* useUserContext.ts */

import { useContext } from 'react';
import { friend, UserContext } from '../contexts/userContext';

const useUserContext = (): [() => friend[]] => {
  const friends = useContext(UserContext);

  const getFriendList = (): friend[] => friends;

  return [getFriendList];
};

export default useUserContext;

Context를 사용함에 따라 기존에 props로 불러오던 친구의 목록을 더욱 편하게 관리할 수 있게 되었다.

/* FriendList.js */

function FriendsList(props) {
  const [searchQuery, setSearchQuery] = useState('');
  const [friendList, setFriendList] = useState(props.friends);
  // some codes...

이랬었던 기존의 코드를

function FriendsList(props: any) {
  const [searchQuery, setSearchQuery] = useState('');
  const [getFriendList] = useUserContext();
  const friendList = getFriendList();

이렇게 수정하였다. 현재는 친구 목록 불러오기가 기능의 전부이지만, 추후 친구 추가, 삭제, 내 정보 수정등 user와 관련된 기능들을 컨텍스트에 추가하게 된다면(리듀서를 사용해서) 컨텍스트를 통한 상태관리의 이점이 더욱 드러날 것으로 기대한다.

Retrospect

  • 전역 상태관리는 짱짱이다
  • 어서 빨리 타입스크립트에 익숙해지자
profile
개발자가 되고싶다 열심히하자

0개의 댓글