코드숨 리액트 6기 7주차 회고

Woohyun Jang
7 min readJan 16, 2022

이번 수업시간에는 로그인 기능을 구현해보고, 로그인한 토큰을 기반으로 새 글을 작성하는 기능을 구현했습니다.

먼저 로그인 기능 구현을 위해 email과 paassword를 받는 form을 구현하고 submit event가 발생하면 POST 요청을 통해 받은 access token을 store에 저장합니다. 이후 새 글을 작성할 때는 store에 저장했던 access token과 작성 내용을 함께 보냅니다.

로그인 완료 후 브라우저를 닫고 새로 실행할 경우에도 access token을 유지하기 위해 localStorage Web API에 토큰을 저장합니다.

지난 비동기 통신 수업때 redux-thunk를 이용하여 API통신과 그 결과를 store에 저장하여 사용하는 전략을 이미 배웠기 때문에 수월하게 이해하고 과제를 수행할 수 있었습니다:)

개인적으로 이번 과제에서 도전해보고 싶었던 일은 redux를 사용하지 않고 react 기본 API만을 통해 효과적으로 상태관리를 해보기였습니다.

react 앱을 개발할 때 사용하는 상태관리 라이브러리는 redux, mobx, recoil등 다양하게 존재합니다.

각각의 특징과 장단점들이 존재하고 아직까지 모든 라이브러리를 극한으로 경험해본 것은 아니지만, third-party library 중 하나일 뿐인 상태관리 라이브러리들이 이를 사용하려면 해당 라이브러리에 종속적인 코드들이 프로젝트 아키텍처 전반에 너무 깊숙히 침투한다는 인상을 받았습니다.

가령 mobx의 경우 상태를 관리하는 비즈니스 로직이 포함되어있는 class는 maxAutoObservable 등의 함수를, 렌더링하는 view는 observer 등의 함수를 호출해야 하고,

https://mobx.js.org/README.html

redux의 경우에는 action과 reducer 등 (거의)redux에서만 사용되는 특별한 형태의 코드가 필요합니다.

https://ko.redux.js.org/introduction/getting-started/

그럼에도 불구하고 지금까지 상태관리 라이브러리를 사용했던 이유는 단방향으로 내려주는 상태를 관리하는 react 앱에서 발생하는 props drilling과 이 과정에서 발생하는 렌더링 이슈를 해결하기 위해서였습니다.

예를 들어 이번 과제에서는 비록 input이 두개밖에 없는 login form이었지만, input이 매우 많아질 경우

  1. 각각의 input에서는 form에서 시작해서 props로 타고타고 넘겨주는 value와 onChange 등의 함수를 사용해야 하고

2. form에서 관리하는 상태의 일부가 변경 되더라도 form 전체를 re-rendering하게 됩니다.

react 기본 API만을 활용하여 위 문제를 해결하기 위해 context, memo API를 사용하여 Form, FormItem 컴포넌트와 useForm custom hook을 구현해보았습니다.

useForm

먼저 useForm hook의 경우 전달받은 초기화 값을 기반으로 values state를 만들고, 이 state를 업데이트하는 changeValues를 반환합니다.

// userForm.jsexport default function useForm({ initialValues = {} }) {  const [values, setValues] = useState(initialValues);  const changeValues = (newValues) => {    setValues((prevValues) => ({ ...prevValues, ...newValues }));  };  return { values, changeValues };}

특이사항으로 changeValues는 newValues를 바로 setValues parameter로 전달하지 않고 이전 상태인 prevValues에서 새로 변경할 값만 수정하는 방식으로 values를 갱신합니다.

이는 아래 나올 FormItem에서 사용하는 메모라이징 때문에 onChange 이벤트 핸들러에서 values가 실시간으로 갱신되지 않아 발생하는 문제를 해결하기 위함입니다.

여담으로 처음 개발할 때는 setValues에 함수를 넘겨줄 생각을 못해서 useState를 initialValues의 키에 따라 동적으로 여러번 호출한 뒤 각 state와 setState를 키 기반으로 그룹핑하여 구현했었는데, react-hook-testing library를 통해 작성해둔 test code 덕분에 안정적으로 리팩토링할 수 있었습니다.

Form

Form 컴포넌트는 useForm을 호출하여 리턴받은 values와 changeValues를 context에 주입하고 내부에 children을 렌더링합니다.

// Form.jsx// { form: { values, changeValues } }
export default function Form({ form, children }) {
return ( <FormContext.Provider value={{ form }}> <form> {children} </form> </FormContext.Provider> );}

context는 위에 언급되었던 첫번째 문제인 props drilling을 해결하기 위해 사용합니다.

다만 context API를 사용하더라도 state가 갱신되면 provider 전체가 업데이트되는 문제는 해결할 수 없기 때문에 useMemo를 사용하는 FormItem 컴포넌트로 각 input 요소를 감싸줍니다.

FormItem

export default function FormItem({ label, name, children }) {  const { form } = useContext(FormContext);  const value = form.values[name];  const onChange = (newValue) => {    form.changeValues({ [name]: newValue });  };  const content = useMemo(() => (    <FormItemContext.Provider value={{ value, name, onChange }}>      <div>        <label htmlFor={name}>{label}</label>        {children}      </div>    </FormItemContext.Provider>  ), [value, label, name]);  return (    <>      {content}    </>  );}

추가적으로 각 input역시 value, name, onChange 등 전달받아야 하는 props를 props drilling없이 전달받기 위해 provider로 다시 감싸주었습니다.

이 과정을 통해 별도의 상태관리 라이브러리 없이도 props drilling을 해결하면서도 불필요한 렌더링을 최소화하는 Form 컴포넌트를 구현해보았습니다:)

antd의 인터페이스를 많이 참고했고, react-hook-form 등 form의 상태관리를 위한 라이브러리들을 공부하면서 더 개선시켜보고 싶다는 생각이 들었습니다!

--

--