react-redux의 hook을 사용하기

September 25, 2019

해당 포스트는 medium 포스트 에도 포스팅 되어 있습니다. :)

react에 대한 내용을 공부하면서, react-redux를 이용한 react 데모를 만들고 있었습니다. redux를 개인적으로는 좀 맘에 안들어했던 이유중 하나가, mapStateToPropsconnect 라는 react-redux 에서 제공하는 function을 이용하여 컴포넌트를 wrapping 하는 형태가 실제 컴포넌트에서 사용하는 형태와 다르게 사용하고 있다는 느낌도 들고, 가독성도 좋아보이지 않는 느낌이어서 별로라고 생각했던것 같습니다.

무언가 props에 연결되어 있다는 느낌도 크게 받기 어려웠던 것도 있습니다. (개인적인 의견입니다. 아마도 제가 내공이 부족한 탓이겠지요 ㅠㅠ)

react의 functional component와 hook를 공부하면서, redux를 이용한 개발에 있어서 react에 제공되는 hook들처럼 직관적으로 component를 개발할 수 없을지 고민하던 중에, react-redux도 state 관리 및 dispatch에 대한 내용을 hook으로 제공하고 있다는 것을 알았습니다! (무지한 제잘못..)

이전 방식으로 사용하기

mapStateToPropsconnect 를 사용했다면, component는 이러한 형태로 개발했을 것입니다.

const Profile = (props) => {
  const { profile, updateProfile, thunkGetProfile } = props;

  // update id
  const updateId = () => {
    updateProfile({
      id: 'test',
    });
  };

  // update id twice
  const updateId2 = () => {
    updateProfile({
      id: 'test2',
    });
  };
  
  return (
    <div>
      <h1>useState, useEffect Example</h1>
      Profile! <br />
      ID: {profile.id} <br/>
      ButtonClick Count is: {counter} <br />
      <button onClick={updateId}>Update profile</button>
      <button onClick={updateId2}>Update profile</button>
    </div>
  );
};

const mapStateToProps = (state: AppState) => ({
  profile: state.profile,
});

export default connect(
  mapStateToProps,
  { getProfile, updateProfile, thunkGetProfile }
)(Profile);

이전 방식으로 사용하는 경우 mapStateToProps를 이용해서 props내 state를 정의하고, connect를 이용해서 props를 바인딩합니다.

Hook을 사용해보기

react-redux에는 해당 기능들을 구현할 수 있도록 useSelectoruseDispatch 라는 hook을 통해 제공해 주고 있습니다.

const Profile = (props) => {
  const dispatch = useDispatch();
  const profile = useSelector((store) => store.example.profile);

  // update id
  const updateId = () => {
    dispatch(updateExampleProfile({ id: 'test' }));
  };

  // update id twice
  const updateId2 = () => {
    dispatch(updateExampleProfile({ id: 'test2' }));
  };
  
  return (
    <div>
      <h1>useState, useEffect Example</h1>
      Profile! <br />
      ID: {profile.id} <br/>
      ButtonClick Count is: {counter} <br />
      <button onClick={updateId}>Update profile</button>
      <button onClick={updateId2}>Update profile</button>
    </div>
  );
};

export default Profile;

개인적으로 너무 만족스럽습니다!! 실제로 props내에 function들을 이용해서 컴포넌트를 wrapping해서 props에 연결해주는 후속작업이 아닌, hook을 통해 더 직관적으로 접근이 가능할 수 있게 되었습니다.

useSelector 의 경우 mapStateToProps와 유사한 기능이며, store의 state의 데이터를 할당할 수 있도록 하는 function입니다. 해당 selector의 경우는 연결된 action이 dispatch 될때마다, selector에 접근되어 값을 반환하게 됩니다.

여기서 profile은 연결된 store의 example이라는 state의 profile을 할당하도록 되어 있고, 실제 하단의 updateIdupdateId2 에서 dispatch되는 action에 따라 해당 profile에 관련된 state가 변경되도록 설정되어 있습니다. 이를 통해 profile이라는 객체는 store에서 반환되는 값을 통해 update가 가능하며, react-redux documentation에는 해당 값이 지속적으로 변경될 수 있기 때문에, 관련 값들은 변형하지 않은 순수한 값으로 유지되어야 한다고 설명하고 있습니다.

useDispatch function은 redux store에 설정된 action에 대한 dispatch를 연결하는 hook으로써, 실제 updateExampleProfile 이라는 action을 연결할 수 있도록 선언해줍니다. (관련된 부분은 상단에 props내의 action을 사용할때와 동일합니다.)

이외에도 다양한 hook들을 제공해주고 있네요!! (이걸 이제야 활용하는 저도 반성하고 있습니다 ㅠㅠ) 공부하면서 더 알아봐야겠습니다. :) 개인적으로는 component wrapping보다 훨씬 직관적으로 보여 더 만족스럽습니다. :)

해당 내용에 대한 부분을 확인하시려면, 제 github repository 에서 확인하실 수 있습니다. (typescript고 현재 계속 테스트중이라 양해 부탁드립니다. :))

...