8. [React 리팩토링] 예발자 프로젝트에 Redux 적용하기

사실 예발자 프로젝트는 앱 규모가 복잡하지 않고, 동적으로 state 값이 변할 일이 없기 때문에 굳이 상태관리 라이브러리가 필요 없을수도 있겠지만, 편리함을 경험해보자는 취지로 redux를 사용해보기로 했다.

완성된 예발자닷컴arrow-up-right에 놀러오세요! 😊

1. 상태관리 라이브러리의 필요성

리액트 컴포넌트의 계층구조는 데이터 관리에 있어 때로는 비효율적다. 뎁스가 깊은 하위 컴포넌트에 필요한 데이터가 있을때 실제로 그 데이터를 사용하지 않는 컴포넌트를 거쳐가야하기 때문이다.

사실 예발자 프로젝트는 앱 규모가 복잡하지 않고, 동적으로 state값이 변할 일이 없기 때문에 굳이 상태관리 라이브러리가 필요 없을수도 있겠지만 편리함을 경험해보자는 취지로 redux를 사용해보기로 했다.

redux가 배우기 쉽지 않은 개념이었는데 백을 담당하신 sechoarrow-up-right님께서 프론트 팀보다 먼저 정말 열심히 공부하시고 우리 프로젝트에 바로 적용할 수 있는 정도까지의 사용법을 공유해주셨다. 덕분에 리덕스를 너무 잘 이해한 것 같아서 감사할따름.. 이 글은 secho님께 배운 내용을 정리한 글이다.

2. Redux의 동작방식

undefined

  • Store

    프로젝트당 1개만 등록한다. 모든 state와 reducer를 스토어에 모아서 관리한다.

  • Reducer

    컴포넌트의 상태값이 변하면 action을 전달받은 reducer 함수가 호출되고 action을 참고하여 새로운 state를 반환한다.

  • Action

    리덕스에서는 프로젝트에서 상태변화를 일으키는 것을 하나의 action 이라고 본다. 구체적으로 action은 type 값, 바뀐 data를 가지고 있는 객체이다. type은 문자열이며, 어떤 값을 변화시킬지 정해 reducer에게 알려주는 역할을 한다.

  • Dispatch

    action을 매개변수로 받아 reducer를 호출한다.

  • 액션생성함수 (Action Creator)

    type을 결정하고 action을 반환하는 함수.

더 자세한 내용은 여기arrow-up-right 확인

3. 적용하기

1. redux 폴더 만들기

redux 폴더를 만들고 그 안에 actions, reducers, store 폴더를 만든다.

2. store 만들기

store/configureStore.js 파일에 스토어를 정의한다.

몇 가지 유용한 리덕스 라이브러리들을 추가해줬는데 하나씩 알아보자면,

  • createWrapper

    컴포넌트에 store 등록하는 과정을 편하게 해준다.

  • composeWithDevTools

    state 변화를 한 눈에 확인할 수 있는 Redux DevTolls 라는 크롬 확장프로그램을 우리 프로젝트에 사용할 수 있게 해준다.

  • combineReducers

    sub reducer 들를 하나로 합쳐줌으로서 여러 리듀서를 한 store에 등록할 수 있게 해준다.

  • createLogger

    store가 바뀔때마다 로깅해주는 미들웨어. 리덕스 관련 정보들을 콘솔에서 예쁘게 확인할 수 있다.

  • thunk

    json 데이터를 불러올 때 비동기 처리를 하는데, 비동기 처리를 가능하게 해주는 미들웨어. 이 미들웨어를 사용하면 액션(객체)이 아닌 함수를 디스패치 할 수 있다.

    이제 reducer를 만들때마다 store에 추가하면서 관리하면 된다.

3. store 등록하기

원래 store를 사용하려면 provider로 컴포넌트를 감싸줘야되는데, _app.jswrapper를 쓰면 일일이 컴포넌트를 감싸주지 않아도 된다.

configureStore.js 파일 하단에 아래 코드를 추가해주자.

4. reducer 만들기

4.1. 초기상태값(initialState) 등록하기

컴포넌트 별로 리듀서의 초기 state값을 빈 객체로 만들어준다. 여러 리듀서를 결합해서 store를 만들기 때문에 store.state를 찍으면 각 컴포넌트 리듀서들의 초기값이 모두 들어있는 걸 확인할 수 있다. 이래서 중앙관리인가보다.

4.2. action.type에 따라 state 변환하기

초기 state값을 등록했으면 액션을 받아 state를 바꿀 수 있도록 switch/case 문을 만들어준다.

아직 액션생성함수는 만들지 않았지만 'GET_REVIEW_DATA' type을 가진 액션이 dispatch를 통해 전달되면, state 즉 reviewReducer.data가 action.payload.data 로 바뀌도록 reducer를 만들었다.

이제 액션생성함수를 만들어 typepayload를 가진 액션 객체를 정의해주자.

5. 액션생성함수 만들기

axios를 사용해 백 서버에 올라와있는 json 파일을 불러오는 액션생성함수를 만든다. 이 함수는 type과 바꿀 데이터(payload)를 지정한 액션 객체를 반환한다.

axios를 사용하면 서버 데이터만 가져오는 게 아니라 header 등등 여러 정보들을 함께 가져온다. 따라서 payload에는 reviewData.data를 담아줘야 우리에게 필요한 json 데이터를 가져올 수 있다.

axios 에러 해결하기

image

엑시오스를 사용하면 Network Error가 난다. CORS(교차출처리소스공유) 정책에 의한 오류이다. 허가되지 않는 곳에서 리소스를 요청하면 보안상 위협이 되기 때문에 요청을 받는 곳에서 CORS를 허용해줘야한다.

CORS 패키지를 인스톨 한 뒤 json.js 에서 cors('http://localhost:3000') 를 추가해주자.

6. dispatch()를 통해 컴포넌트에서 action 발생시키기

useSelector를 통해 state 값을 가져올 수 있는데, reviewData 에 처음에는 빈 객체(아까 만든 초기값)이 들어갈 것이다. 왜냐면 dispatch 함수를 써야 action 이 발생하고 그래야 reducer 가 호출되어 state 값을 변화시킬 수 있기 때문이다.

그래서 dispatch는 useEffect 함수 안에 넣어줘야 한다. useEffect은 비동기적으로 리액트 앱의 렌더링이 끝난 뒤 가장 마지막에 실행되는 리액트 내장함수이기 때문이다.

즉, Review 컴포넌트는 아주 빠른시간에 두 번 렌더링 된다. 상태변화가 일어나고 액션이 발생하면 리렌더링을 통해 바뀐 state를 갖게 되는 것이다.

dispatch 사용법

dispatch를 구체적으로 어떻게 사용하는지, 코드를 살펴보자.

  • dispatchreducer를 호출한다. 따라서 action 객체를 매개변수로 받는다.

  • action을 생성하기위해 getData() 함수를 호출한다.

  • getData() 함수 비동기처리를 기다리기 위해 then 메서드와 콜백함수를 사용한다. 만약 위 코드에서 콜백함수를 사용하지 않는다면 데이터를 온전히 받아오지 못해 우리가 참조해 사용할 수 없게 된다. 자바스크립트 비동기처리의 문제점을 해결하고 싶다면 꼭 콜백함수를 사용하자.

  • result에는 getData()의 반환값이 담긴다. 이를 dispatch에 전달해 reducer를 호출한다!

초기 렌더링 예외처리

이제 reviewData에는 바뀐 state, 즉 우리에게 필요한 서버로부터 전달받은 데이터가 담기게 된다. 원래 프론트에서 만들었던 더미데이터처럼 마음껏 참조해 사용할 수 있는 것이다.

사실 이때 문제가 발생한다. 리뷰 컴포넌트가 두 번 렌더링 된다면 처음 렌더링 시점에서는 reviewData[program] 에 아무 값도 들어있지 않기 때문에 값을 참조할 수 없어 렌더링 ERROR가 나기 때문이다.

그래서 이 처음보는... ?? 이라는 자바스크립트 문법을 사용해줘야 한다.

?? 은 왼쪽 피연산자 값이 null이나 undefined 인 경우 오른쪽 피연산자를 반환한다. 이를 통해 초기 렌더링 시점에서 존재하지 않는 값에 대해 예외처리를 할 수 있다.

마찬가지로 ?. 연산자도 에러 예외처리를 위해 사용했다.

?. 는 참조한 값이 null이나 undefined 인 경우 실행을 멈추고 undefined를 리턴하는 연산자이다. programData는 빈 배열이라 map을 사용할 수 없다. 첫 번째 렌더링 시점에서 예외처리를 해주고, 리렌더링 된 두 번째 렌더링부터는 데이터를 참조해 가져와 사용할 수 있게 된다.

Last updated

Was this helpful?