Redux.js 완벽 정복

2021년 1월 17일

Flux?

Redux를 좀 더 쉽게 이해하기 위해 Flux 패턴을 이해해야 합니다.

먼저 React.jsMVC 패턴에서 View 에 집중해있습니다.

Redux_1

하나의 controller에서 데이터를 model에 전달하고 view단으로 화면을 구성하는것이 일반적인 MVC패턴입니다.


하지만 만약 이러한 방식에서 View가 많아진다면 어떡할까요?

Redux_2

ModelView가 추가될 때 마다 복잡도가 증가하고, 프로젝트 규모가 기하급수적으로 늘어나게 되며 논리적인 관계 또한 파악하기 힘들어집니다.


이러한 단점을 보완해줄 수 있는 패턴이 Flux 입니다.

Redux_3

Flux 패턴에서 store는 개념이 등장하는데 이 store는 해당 어플리케이션의 모든 데이터 변화를 담고 있는 개념이라고 생각하시면 됩니다.


action이 발생 했을 때, dispatcherstore에서 갖고 있는 데이터 들을 어떻게 수정할 것인지 결정합니다.


이 후 store가 변경되면 store 내부의 데이터도 바뀌므로 view가 갱신되게 됩니다.


위의 내용을 간단히 정리하자면, dispatcheraction으로 인한 데이터 변경 로직을 결정하면 store에 변경된 데이터가 쌓여 view가 바뀌게 됩니다.

React와 React + Redux의 차이

  • React.jsReact component 자신이 개별적으로 상태관리를 합니다.
  • React.js + Redux는 데이터들을 한 곳(store)에서 상태 관리를 하고, React component(view)는 그걸 보여주기만 하는 용도로 쓰입니다.

Redux에서 중요한 개념

  1. Action
  2. Reducer
  3. Store

Action

Action에서는 state 변화과 있을법한 상황들을 미리 정의합니다.

Reducer

Reducer에서는 Action이 일어났을 때 어떻게 state를 바꿔줄지에 대한 로직을 담습니다.

Action의 결과가 Reducer로 전달되고 Reduxstate를 해당 결과로 값을 수정합니다.

Store

변경된 state들은 store에 담기게 됩니다. 이 때, store 내부의 상태 변화에 따라 view도 함께 변화해야하기 때문에 storeview를 연결해줄 필요가 있습니다.

간단 예제 - class형 component
  1. 최상위 React Componentstore를 만듭니다.
import { createStore } from 'redux';

...

store = createStore(reducer);
  1. Provider로 전체 앱을 한 번 감싸줍니다.
import { Provider } from 'react-redux';

...

<Provider store = {store}>
	<App />
</Provider>,
document.getElementById('root');
  1. Componentstore를 연결합니다.
const Profile = (props) => {
  const { profile, updateProfile, thunkGetProfile } = props;

  // 첫번째 아이디 수정 
  const updateId = () => {
    updateProfile({
      id: 'test',
    });
  };

  // 두번째 아이디 수정
  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);

classcomponent에서는 위와 같이 mapStateToProps를 이용해서 propsstate를 정의하고 connect를 이용해서 props를 바인딩하는 방식으로 구현합니다.

Redux + Hooks

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

  // 첫번째 아이디 수정 
	const updateId = () => {
			dispatch(updateExampleProfile({ id : "test"}));
	};

  // 두번째 아이디 수정
	const updateId2 = () => {
		dispatch(updateEampleProdile({ 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들을 이용해서 componentwrapping하여 props에 연결해주는 방식이 아닌, hooks를 통해 더 직관적으로 접근이 가능해졌습니다.

useSelector의 경우 mapStateToProps와 유사한 기능을 하며, storestate의 데이터를 할당할 수 있도록 하는 함수입니다.

해당 selector의 경우는 연결된 actiondispatch 될 때마다 selector에 접근하여 값을 수정하게 됩니다.

여기서 profile 객체는 연결된 storeexample이라는 stateprofile을 할당하도록 되어 있고, 실제 하단의 updateIdupdateId2 에서 dispatch되는 action에 따라 해당 profile에 관련된 state가 변경되도록 설정되어 있습니다.

이를 통해 profile이라는 객체는 store에서 반환되는 값을 통해 update가 가능합니다.

useDispatchredux store에 설정된 action에 대한 dispatch를 연결하는 hook으로써, 실제 updateExampleProfile이라는 action을 연결할 수 있도록 선언해줍니다.