Frontend/ReactJS

리덕스를 통한 전역상태 관리

findmypiece 2021. 3. 11. 00:30
728x90

관련 코드는 아래를 참고 하도록 한다.

github.com/mypiece/react-example

 

mypiece/react-example

CRA 리액트+리덕스+리덕스사가 예제. Contribute to mypiece/react-example development by creating an account on GitHub.

github.com


리덕스의 개념 자체는 아래와 같이 그리 어렵지 않다.

 

상태 저장을 위한 스토어가 있고 컴포넌트에서는 여기에 있는 특정 상태를 구독한다.

초기상태와 이를 변경하는 액션타입을 미리 정의해놓고 이를 토대로 

액션을 만들어줄 액션생성함수도 미리 만들어놓고 사용한다.

상태를 변경하기 위해 디스패치를 통해 액션 발생되면

리듀서에서는 이를 감지해서 상태를 업데이트 한다.

 

다만 이를 위해 리액트 어플리케이션에 리덕스를 연동하고 

스토어, 구독, 액션타입, 액션생성함수, 디스패치, 리듀서 등의

구성요소를 구현하는 방법을 몰라서 어려울 뿐이다.

 

위 Todos 화면에서 리덕스를 사용했다.

여기에서 필요한 상태는 아래와 같은데

상태라 함은 단순하게 화면이 변화하는데 영향을 주는 요소를 생각하면 된다.

  • 화면에 로딩스피너를 보여주기 위한 비동기처리 실행 및 완료여부
  • input 값
  • 할일목록

구현방식은 비슷하기 때문에 여기에서는 input 값과 할일목록에 대한 리덕스 구현만 살펴본다.

리액트에서 리덕스 구현은 일반적으로 아래와 같은 순서로 진행된다.

  1. 컴포넌트 별로 초기상태, 액션타입, 액션생성함수, 리듀서 를 정의한 모듈 구현
  2. 리덕스에서 상태를 구독하고 디스패치 함수를 생성하는 컨테이너 구현
  3. 컨테이너에서 props로 넘겨받은 상태값과 디스패치 함수로 실제 컴포넌트를 렌더링
  4. 구현한 리듀서를 통합해서 리덕스 스토어를 생성하고 어플리케이션과 연동

1. 컴포넌트 별로 초기상태, 액션타입, 액션생성함수, 리듀서 를 정의한 모듈 구현

lib/redux/todos.js 에서 초기상태, 액션타입, 액션생성함수, 리듀서 를 아래와 같이 구현한다.

액션타입은 상태가 변경되는 상황을 정의한 것으로

액션타입 변수명 정의시에는 대문자와 언더바(_)를 사용하고 문자열 내용은 '모듈이름/액션이름' 형태로 작성한다.

Api 호출 등의 비동기작업의 경우 위와 같이 액션타입과 함께 액션수행결과도 타입으로 정의해야

Api 호출 결과에 따른 액션을 발생시킬 수 있다.

 

상태변경을 위해 컨테이너에서는 디스패치 함수가 호출되고 디스패치 함수의 인자로 액션이 필요하다.

이때 사용할 액션 생성함수를 생성한다. 외부에서 사용되어야 하니 반드시 export 해줘야 한다.

 

참고로 Api 호출의 경우 별도 공통함수를 정의해서 사용하고 

그 결과에 대한 액션도 그곳에서 공통으로 생성하기 때문에

여기에서 액션생성함수를 정의하지 않아도 된다.

 

redux-actions 모듈의 createAction 함수를 사용하며

첫번째 인자는 액션타입, 두번째 인자는 상태 변경에 필요한 payload를 리턴하는 함수를 정의한다.

이를 통해 생성되는 액션객체의 property 키는 type, payload 로 고정된다.

changeInput 를 예로 들면 이를 통해 생성되는 액션객체는 아래와 같은 포맷을 가지게 된다.

{
	type: todos/CHANGE_INPUT
	payload: input
}

 

redux-actions 모듈의 handleActions 를 통해 상태를 변경하는 리듀서 함수를 만든다.

첫번째 인자로 액션타입 별 상태변경 함수들을 넣고 두번째 인자로는 초기상태 값을 넣어준다.

각 액션 타입에 대한 상태변경 함수는 첫번째 인자로 과거상태 두번째 인자로 payload로 받은 데이터를 

비구조화 할당 문법으로 받아오고 있다.

 

상태 변경시 중요한 점은 이전 상태의 불변성은 유지되어야 한다는 것인데

immer 라이브러리의 produce 함수를 통해 이를 처리할 수 있다.

produce 함수의 첫번째 인자는 이전 상태이고 두번째 인자는 이를 복사해서 새롭게 만든 상태로

함수 내부에서는 두번째 인자에만 변화를 적용하면 된다.

 

2. 리덕스에서 상태를 구독하고 디스패치 함수를 생성하는 컨테이너 구현

src/containers/TodosContainer.js 로 컨테이너를 구현한다.

react-redux 라이브러리의 useSelector 함수로 통해 리덕스의 특정 상태를 구독한다.

이렇게 구독한 상태가 변경되었다면 해당 컴포넌트는 리렌더링 된다.

 

실제 렌더링하는 컴포넌트의 이벤트와 연결될 디스패치 함수를 생성한다.

react-redux 라이브러리의 useCallback 함수가 사용된다.

첫번째 인자로 디스패치 함수를 리턴하는 함수를 정의하고

두번째 인자에 정의하는 값이 변할때마다 디스패치 함수 인스턴스를 새로 생성한다.

즉, 두번째 인자로 정의하는 값이 변하지 않으면 디스패치 함수의 인스턴스는 재활용된다.

 

컴포넌트 초기 렌더링시 초기 데이터를 읽어오도록 선처리를 정의한다.

useEffect 함수를 통해 컴포넌트가 업데이트 될때마다 특정 작업을 수행할 수 있다.

첫번째 인자의 함수 내부 로직으로 렌더링시 수행할 작업을 정의할 수 있고

해당 함수의 return으로 업데이트 or 언마운트시 수행될 함수를 정의할 수 있다.


참고로 첫번째 인자의 return 은 선택적으로 정의할 수 있는데

초기상태값이 특정 조건에 따라 다르게 할당되어야 한다면

반드시 상태값을 초기화하는 디스패치 함수를 호출하는 함수를 return 해야 한다.

그렇지 않으면 컴포넌트 렌더링시 아주 짧은 시간동안 이전 상태값이 나타나는 깜빡임 현상이 발생할 수 있다.

 

두번째 인자는 useCallback 의 두번째 인자와 같은 의미로 

해당 값이 변경되었을 때에만 useEffect에 의한 특정작업이 수행된다.

 

구독한 리덕스 상태값과 생성한 디스패치 함수로 실제 컴포넌트를 렌더링한다.

 

3. 컨테이너에서 props로 넘겨받은 상태값과 디스패치 함수로 실제 컴포넌트를 렌더링

src/components/Todos.js 컴포넌트는 TodoInput, TodoList, TodoItem 컴포넌트들이 결합되어 있다.

각각 필요로 하는 상태가 다르기 때문에 렌더링 범위를 줄이기 위함이다.

 

컴포넌트는 기본적으로 별도의 js 파일로 분리해서 재사용하지만

여기에서는 재사용되는 곳이 없기 때문에 src/components/Todos.js 파일에 함께 정의했다.

onClick, onChange 등의 이벤트는 상태변경을 위해 디스패치 함수를 연결하고

비동기작업이 수행되는 컴포넌트는 로딩여부에 따라 보여줄 컴포넌트를 구분한다.

 

styled-components 를 통해 js 파일에서 css를 정의해서 사용할 수 있다.

아래는 text-decoration 속성을 정의한 span 를 포함한 컴포넌트를 만들어준다.

props를 넘겨받아 조건식으로 활용할 수도 있다.

물론 JSX에서 Element 정의시 인라인스타일을 사용해도 되지만 인라인스타일은 권장되는 방식이 아니기 때문에 논외로 한다.

 

이렇게 정의한 컴포넌트는 아래와 같이 사용할 수 있다.

 

4. 구현한 리듀서를 통합해서 리덕스 스토어를 생성하고 어플리케이션과 연동

리덕스 연동을 위해서는 우선 리듀서들을 하나로 통합해야 한다.

src/lib/redux/index.js 에서 아래와 같이 리듀서들을 통합한 루트 리듀서를 생성한다.

 

이렇게 생성된 루트 리듀서로 src/index.js 에서 스토어를 생성하고

생성한 스토어를 최상위 컴포넌트인 App 컴포넌트에 아래와 같이 적용한다.

728x90

'Frontend > ReactJS' 카테고리의 다른 글

props 와 state  (0) 2021.09.30
리덕스 사가를 통해 리덕스에 특정작업 주입하기  (0) 2021.03.11
리액트에서 크롬 개발자도구 연동  (0) 2021.03.11
React 코딩컨벤션  (0) 2021.03.08
리액트 초기 설정  (2) 2021.03.08