[fastcampus] Ch11. Redux로 상태관리하기(2)

productuidev·2022년 7월 27일
0

React Study

목록 보기
51/52
post-thumbnail

1. Redux Basic

2) Redux를 React에 연결(1)

  • 지금까지 만든 프로젝트를 React에 연결하기
  • react-redux를 안 쓰고 연결하기
  • TodoList 만들어보기

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

  • componentDidMount - subscribe
  • componentWillUnmount - unsubscribe

(1) App 컴포넌트에 store 내려주기

src/index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import store from './redux/store';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App store={store} />
  </React.StrictMode>
);

src/App.js

import logo from './logo.svg';
import './App.css';
import { useState, useEffect } from 'react';
import { addTodo } from './redux/actions';

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

  useEffect(()=>{
    const unsubscribe = store.subscribe(()=>{
      setState(store.getState());
    });

    return () => {
      unsubscribe();
    };
  }, [store]); // dependency
  
  // store가 들어올 때, 다른 store면 다시 실행하는 것이므로 검토해보면
  // index.js에서 다른 store를 다시 넣어주는 경우는 없음
  // 결과적으로 1번만 실행

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        {JSON.stringify(state)} {/* 객체를 JSON 문자열로 변환*/}
        <button onClick={click}>추가</button>
      </header>
    </div>
  );

  // 버튼 클릭 시 addTodo 액션
  function click() {
    store.dispatch(addTodo("todo"));
  }
}

export default App;

store만 컴포넌트에서 가지고 있게 되면 store의 변화에 반응하거나
store의 변화에 반응을 줄 수 있는 컴포넌트 생성이 가능하다

(2) context 만들기

src/contexts/ReduxContext.js

import { createContext } from "react";

const ReduxContext = createContext();

export default ReduxContext;

가장 상위에서 ReduxContext를 Provider로 만들고, value로 store를 주입

src/index.js

import ReduxContext from './contexts/ReduxContext';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <ReduxContext.Provider value={store}>
      <App />
    </ReduxContext.Provider>
  </React.StrictMode>
);

store를 context로부터 가져오기

src/App.js

import ReduxContext from './contexts/ReduxContext';

function App() {
  const store = useContext(ReduxContext);
  
  ...
  

공통적인 state에 관한 로직은 따로 custom hook로 뺄 수 있다

src/App.js

import logo from './logo.svg';
import './App.css';
import { useState, useEffect, useContext } from 'react';
import { addTodo } from './redux/actions';
import ReduxContext from './contexts/ReduxContext';

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;
}

 // 공통 로직
// dispatch를 반환하면 그 결과물을 useReduxDispatch로 가져올 수 있음
function useReduxDispatch() {
  const store = useContext(ReduxContext);

  return store.dispatch;
}

function App() {
  // 공통 로직
  // state를 반환하면 그 결과물을 useReudxState로 가져올 수 있음
  const state = useReduxState();
  const dispatch = useReduxDispatch();

  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>
  );

  // 버튼 클릭 시 addTodo 액션
  // dispatch
  function click() {
    dispatch(addTodo("todo"));
  }
}

export default App;

결과

(3) 복잡한 Context

App 컴포넌트 안에 하위 컴포넌트 2개 추가하기

  • src/components/TodoList.jsx
  • src/components/TodoForm.jsx

src/App.js

import TodoList from './components/TodoList';
import TodoForm from './components/TodoForm';

...

function App() {
  const state = useReduxState();
  const dispatch = useReduxDispatch();

  return (
    <div className="App">
      <TodoList />
      <TodoForm />
    </div>
  );
  
  ...
  
 }
 
 ...

TodoList 만들기 전에 만든 Hook들을 빼내서 범용적으로 사용할 수 있게 하기

  • src/hooks/useReduxState.js
  • src/hooks/useReduxDispatch.js

=> src/App.js에 있는 useReduxState와 useReduxDisptch 옮겨주기

src/hooks/useReduxState.js

import { useState, useEffect, useContext } from 'react';
import ReduxContext from '../contexts/ReduxContext';

export default 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;
}

src/hooks/useReduxDispatch.js

import { useContext } from 'react';
import ReduxContext from '../contexts/ReduxContext';

export default function useReduxDispatch() {
  const store = useContext(ReduxContext);

  return store.dispatch;
}

state와 dispatch는 hooks를 import해서 사용

src/App.js

import { addTodo } from './redux/actions';
import useReduxState from './hooks/useReduxState';
import useReduxDispatch from './hooks/useReduxDispatch';
import TodoList from './components/TodoList';
import TodoForm from './components/TodoForm';

function App() {
  const state = useReduxState();
  const dispatch = useReduxDispatch();
  
  ...
  

(4) TodoList 완성하기

  • TodoList : 보여주는 역할 (useReduxState, map)
  • TodoForm : 추가하는 역할 (useReduxDispatch, useRef, addTodo)
  • App.js : 렌더만 하는 역할 (위 컴포넌트들 생성 후 App.js에 중복되는 내용 삭제)

src/components/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>
  );
}

src/components/TodoForm.jsx

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

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

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

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

src/App.js

import logo from './logo.svg';
import './App.css';
import TodoList from './components/TodoList';
import TodoForm from './components/TodoForm';

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;

결과

  • react-redux를 쓰지 않았지만 redux 라이브러리 코드와 유사한 방식
  • 다음 시간 : Redux 안에 있는 함수를 이용해 컴포넌트 연결하기
profile
필요한 내용을 공부하고 저장합니다.

0개의 댓글