Redux로 상태관리하기 - Redux Basic 4

lbr·2022년 8월 16일
0

Redux를 React에 연결(react-redux 안쓰고)

react-redux 안쓰고 지금까지 만들었던 store를 react에 연결해 보겠습니다.

컴포넌트에 연결하는법

단일 store 를 만들고,
subscribe 와 getState 를 이용하여,
변경되는 state 데이터를 얻어,
props 로 계속 아래로 전달

componentDidMount부분에 subscribe 연결,
componentWillUnmount부분에 unsubscribe 연결
하여 구현해보겠습니다.

index.js

root.render(
  <React.StrictMode>
    <App store={store} />
  </React.StrictMode>
);

index.js에서 <App />컴포넌트에 store를 props로 내려주겠습니다.

이러면 App 컴포넌트에서 props로 store를 받아서,
componentDidMount부분에서 subscribe하고
componentWillUnmount부분에서 unsubscribe 를 해보겠습니다.

App.js

import logo from "./logo.svg";
import "./App.css";
import { useEffect, useState } from "react";

function App({ store }) {
  const [state, setState] = useState(store.getState());

  useEffect(() => {
    const unsubscribe = store.subscribe(() => {
      setState(store.getState());
    });
    return () => {
      unsubscribe();
    };
  }, [store]); // Lint 에러로 store를 넣으라고 하고있지만,
  // 사실 넣어도 되고 넣지 않아도 됩니다. 넣어도 넣지 않은 것 처럼 동작합니다.
// store가 바뀔때마다 useEffect가 동작한다고 디펜던시를 주어도
// 사실 store의 레퍼런스는 <App store={store} />에서 props로 넘기고 나서 계속 바뀌지 않기 때문입니다. 

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        {JSON.stringify(state)}
        <button onClick={click}>추가</button>
      </header>
    </div>
  );
  
  function click() {
    store.dispatch(addTodo("todo"));
  }
}

export default App;
  1. props로 받은 store의 값으로(getState()) 초기화합니다.
  2. useEffect 디펜던시[ ] 로 subscribe로 store에 변화가 있을 때마다 state에 값을 세팅하게 합니다.
  3. 버튼을 누르면 dispatch로 액션을 보냅니다.
  4. 컴포넌트가 unmount 될때 unsubscribe 합니다.

이 로직으로 store의 값이 변경 될때마다 구독한 함수가 동작하게 되고 구독한 함수안에서 setState()로 변경된 store의 값으로 세팅하게 되므로 결과적으로 store가 변경될 때마다 컴포넌트가 재실행되어 리렌더링이 일어납니다.

이처럼 컴포넌트가 store만 가지고 있다면, store의 변화에 반응하거나 store에 변화를 줄 수 있는 컴포넌트로 만드는 것이 가능합니다.

이제 store를 전체 컴포넌트에 전달할 수 있는 방법으로 context를 사용해보겠습니다.

전체 컴포넌트에 store를 주기위해 context로 연결해보기

// ReduxContext.js

import { createContext } from "react";

const ReduxContext = createContext();

export default ReduxContext;

이제 원래 Context 사용방법 대로 가장 상위에서 ReduxContext를 Provider로 만든다음에 value로 store를 주입해 주면 됩니다.

// index.js

root.render(
  <ReduxContext.Provider value={store}>
    <App />
  </ReduxContext.Provider>
);

ReduxContext는 Provider를 사용할 경우에 props로 value만 넣어줄 수 있습니다.

context의 Provider로 모든 컴포넌트에 store를 전달할 수 있기 때문에 App 컴포넌트 하나에 props로 store를 전달하지 않아도 됩니다.

이제 App 하위에 있는 모든 컴포넌트들은 store를 꺼내서 사용할 수 있는 상태가 되었습니다.

// App.js

function App() {
  const store = useContext(ReduxContext);
  const [state, setState] = useState(store.getState());

  useEffect(() => {
    console.log("useEffect");
    const unsubscribe = store.subscribe(() => {
      setState(store.getState());
    });
    return () => {
      unsubscribe();
    };
  }, [store]);
  
// ...
// ...

props로 전달했던 store를 제거하고 useContext로 store를 가져옵니다.

store가 필요한 App 하위의 모든 컴포넌트 들은 위의 로직들을 공통적으로 사용하게 될 것입니다.
그래서 우리는 위의 store를 가져오고, state를 업데이트하는 공통된 코드들을 custom Hook으로 따로 만들 수 있습니다.

store를 가져오고, state를 업데이트하는 공통된 코드들을 custom Hook으로 따로 만들기

// customHook
function useReduxState() { 
  const store = useContext(ReduxContext);
  const [state, setState] = useState(store.getState());

  useEffect(() => {
    const unsubscribe = store.subscribe(() => {
      setState(store.getState());
    });
    return () => {
      unsubscribe();
    };
  }, [store]);

  return state;
}

function App() {
  const state = useReduxState();
  // ...

이제 dispatch를 가져오는 Hook을 만들어보겠습니다.

dispatch를 가져오는 custom Hook 만들기

// dispatch를 가져오는 Hook
function useReduxDispatch() {
  const store = useContext(ReduxContext);

  return store.dispatch;
}

function App() {
  const state = useReduxState();
  const dispatch = useReduxDispatch();
  
// ...
// ...
  function click() {
    dispatch(addTodo("todo"));
  }
}

하위 컴포넌트를 추가하여 복잡하게 만들고, 폴더와 파일로 나누어 관리하기.

context를 사용한 효과를 확실히 확인해보기 위해
좀 더 복잡하게 하위 컴포넌트를 2개 더 추가하고,
작성한 custom Hook들을 폴더와 파일을 이용해서 나누어 관리하겠습니다.

// useReduxState.js

// useReduxDispatch.js

// App.js
function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <TodoList />
        <TodoForm />
      </header>
    </div>
  );
}
export default App;

// TodoList.jsx
import useReduxState from "../hooks/useReduxState";

export default function TodoList() {
  const state = useReduxState();
  return (
    <ul>
      {state.todos.map((todo) => {
        return <li>{todo.text}</li>;
      })}
    </ul>
  );
}

// TodoForm.jsx
import { useRef } from "react";
import useReduxDispatch from "../hooks/useReduxDispatch";
import { addTodo } from "../redux/actions";

export default function TodoForm() {
  const inputRef = useRef();
  const dispatch = useReduxDispatch();

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={click}>추가</button>
    </div>
  );

  function click() {
    dispatch(addTodo(inputRef.current.value));
  }
}

이제 App.js에서는 그냥 렌더하는 것 말고는 하는일이 없습니다.

결과화면

정리

여기까지 redux-react를 사용하지 않고 redux와 컴포넌트를 연결해봤습니다. 사실 위에서 작성한 이런 코드들은 redux-react 라이브러리 안에 들어있는 코드와 매우 유사합니다.

0개의 댓글