본문 바로가기

Redux

20210616 Redux02 : React에 redux 연결01(props, Context전달, Hooks&모듈로 관리), react-redux로 연결(Presentational vs Container , Container 만들기, reac-redux hook 방식)

Redux 02





React에 Redux 연결하기 01 : react-redux 안쓰고 연결


store를 props로 전달하는 경우


  • 만들어진 store를 props로 전달하여, store를 사용할 수 있게 함

Props 전달

// index.js
ReactDOM.render(
  <React.StrictMode>
    <App store={store} />
  </React.StrictMode>,
  document.getElementById("root")
);

전달 받은 Props로 store 활용하기


  • Store만 컴포넌트에서 가지게 되면 store의 변화에 반응하거나, store에 변화를 줄수 있음
import logo from "./logo.svg";
import "./App.css";
import { useEffect, useState } from "react";
import { addTodo } from "./redux/actions";

function App({ store }) {
  // store의 값을 state에 받아서 사용함
  const [state, setState] = useState(store.getState());

  // update된 store의 값을 state에도 반영 하기 위해서 useEffect 사용
  useEffect(() => {
    const unsubscribe = store.subscribe(() => {
      setState(store.getState());
    });
    // unmount 시에는 반영 안함
    return () => {
      unsubscribe();
    };
  }, [store]);
  // 들어오는 store가 변하는 경우 다시 useEffect가 실행됨 하지만,
  //store를 App.js에서 다시 다른것을 넣어주진 않기 때문에 한번만 실행되는 것으로 보아도 됨

  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;



Store를 ComtextAPI로 전달하는 경우 01


Context 작성

// contexts/ReduxContext.js
import { createContext } from "react";

const ReduxContext = createContext();

export default ReduxContext;

Provider 사용

// index.js
import store from "./redux/store";
import ReduxContext from "./contexts/ReduxContext";
ReactDOM.render(
  <React.StrictMode>
    <ReduxContext.Provider value={store}>
      <App />
    </ReduxContext.Provider>
  </React.StrictMode>,
  document.getElementById("root")
);

Context 사용

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

function App() {
  // get Store by useContext
  const store = useContext(ReduxContext);

  // set State of Store
  const [state, setState] = useState(store.getState());
  useEffect(() => {
    const unsubscribe = store.subscribe(() => {
      setState(store.getState());
    });
    return () => {
      unsubscribe();
    };
  }, [store]);

  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;



Store를 ComtextAPI로 전달하는 경우 02 : Store 관련 State, dispatch Hooks로 정리하기


Custom Hooks 만들어 정리하기

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

// State
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
function useReduxDispatch() {
  const store = useContext(ReduxContext);
  return store.dispatch;
}

// App
function App() {
  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>
  );

  function click() {
    dispatch(addTodo("todo"));
  }
}

export default App;



Store를 ComtextAPI로 전달하는 경우 03 : Store 관련 State, dispatch Hooks & Component 모듈화


  • hooks 폴더 - useReduxState.js, useReduxDispatch.js
  • components 폴더 - TodoList,jsx, TodoForm.jsx

hooks


// hooks/useReduxState.js
import { useContext, useEffect, useState } from "react";
import ReduxContext from "../contexts/ReduxContext";

// State
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;
}
// hooks/useReduxDispatch.js
import { useContext } from "react";
import ReduxContext from "../contexts/ReduxContext";

// Dispatch
export default function useReduxDispatch() {
  const store = useContext(ReduxContext);
  return store.dispatch;
}

Component 만들기


  • TodoForm Component
    • 입력을 받아서, todo list에 항목을 추가하는 Component
    • uncontrolled Component로 From을 만듦 (즉, useRef 사용)
// 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} />
      <button onClick={click}>추가</button>
    </div>
  );
  function click() {
    dispatch(addTodo(inputRef.current.value));
  }
}

  • TodoList Component
    • store에 있는 todo text들을 화면에 표시함
import useReduxState from "../hooks/useReduxState";

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







React에 Redux 연결하기 02 : react-redux 사용해서 연결


react-redux


  • redux에서 Provider 컴포넌트를 제공해 줌 (context 사용안해도 됨)
  • connect 함수를 통해서 "Container(컨테이너)"를 만들어줌
    • Container
      • Store State, Dispatch(액션)를 연결한 컴포넌트에 props로 (?) 넣어주는 역할을 함
      • 어떤 state를 어떤 props에 연결할 것인지에 대한 정의가 필요함
      • 어떤 dispatch(액션)을 어떤 props에 연결할 것인지에 대한 정의가 필요함
      • 그 props를 보낼 컴포넌트에 대한 정의가 필요함
  • npm i react-redux



Provider 연결


  • react-redux는 contextAPI의 Provider 대신 자체적인 Provider를 제공함
  • contextAPI에선 prop의 이름이 value였던 것과 달리 react-redux에서는 store를 사용함

import store from "./redux/store";
import { Provider } from "react-redux";

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById("root")
);

Presentational vs Container Component


  • 각 Component 들이 하나의 전문적인 기능을 가지게 해야 관리가 편해짐
  • Presentational Component
    • 기존 react의 component로서 단지 props를 받아서 화면을 출력하는 Component
    • Componenet의 디자인 관련 화면 출력을 전문적으로 다룰수 있음
  • Container Component
    • Presentational Component를 받아 redux store의 State, dispatch를 연결하는 Component
    • redux와 관련하여 Presentational Component에서 사용할 props를 연결하는 용도로만 사용할 수 있어 관리가 편해진다.
  • Redux Official Site : presentational vs Container



Container Component 만드는 방법


  • react-redux의 connect() 함수로 생성함
  • connect 함수는 두개의 함수를 인자로 받음 -> return function에 presentaition Component를 넣어 return -> Container
    • mapStateToProps : store의 state를 받아 state를 presentational Component에 어떤 props 이름으로 store state의 어떤 값으로 보낼건지 객체를 return 하는 함수를 만듦
    • mapDispatchToProps : store의 dispatch를 받아 presentational Component에 어떤 함수의 이름으로, store dispatch를 활용한 어떤 함수 로직을 보낼건지 객체를 return 하는 함수를 만듦
import { connect } from "react-redux";

const container1 = connect(
  mapStateToProps,
  mapDispatchToProps
)(presentationalComponent);

const mapStateToProps = (state) => ({ propName: state.storeStatePropName });
const mapDispatchToProps = (dispatch) => ({
  methodName: (methodInput) => {
    dispatch(actionCreator(methodInput));
  },
});

// 약식
const container2 = connect(
  (state) => ({}),
  (dispatch) => ({})
)(presentationalComponent);

export default container2;



Presentational Component, Container Component 모듈화


Container Component 모듈화


  • containers라는 폴더에 Container Component만 따로 보관함
  • 필요한 것들 : ActionCreator, PresentationalComponent, connect()
// containers/ToDoFormContainer.jsx

import { connect } from "react-redux";
import TodoForm from "../components/TodoForm";
import { addTodo } from "../redux/actions";

const TodoFormContainer = connect(
  (state) => ({}),
  (dispatch) => ({
    add: (text) => {
      dispatch(addTodo(text));
    },
  })
)(TodoForm);

export default TodoFormContainer;
// TodoForm(Presentational Component)는 add 이름으로 dispatch를 사용하는 함수를 prop으로 받음
// containers/ToDoListContainer.jsx

import { connect } from "react-redux";
import TodoList from "../components/TodoList";

const TodoListContainer = connect(
  (state) => ({
    todos: state.todos,
  }),
  () => ({})
)(TodoList);

export default TodoListContainer;
// TodoList(Presentational Component)는 todos 이름으로 storeState의 todos를 prop으로 받음


Presentational Component : 받은 prop을 활용하여 화면 구성하기


  • 기존의 component 폴더에서 관리함
  • Container가 해당 컴포넌트들에게 전달한 prop 이름으로 prop 받기
  • prop 활용해서 화면 구성하기
  • Container 안에서 Presentational Component가 작동하기 때문에 prop 이름만 알아보게 잘 써서 사용하면 되고 딱히 뭐 import할 필요 없음
export default function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo) => {
        return <li>{todo.text}</li>;
      })}
    </ul>
  );
}
import { useRef } from "react";

export default function TodoForm({ add }) {
  const inputRef = useRef();
  return (
    <div>
      <input ref={inputRef} />
      <button onClick={click}>추가</button>
    </div>
  );
  function click() {
    add(inputRef.current.value);
  }
}



Container Component Hooks로 만들기


  • react-redux에서 제공하고 있는 hook을 사용하면 명시적으로 작업을 할 수 있음
    • useSelector() : store에 있는 state를 가져오는 경우
    • useDispatch() : store에 있는 dispatch를 가져오는 경우

useSelector

import { connect, useSelector } from "react-redux";
import TodoList from "../components/TodoList";

export default function TodoListContainer() {
  // storeState 가져오기
  const todos = useSelector((state) => state.todos);

  // Component에 전달하기
  return <TodoList todos={todos} />;
}

useDispatch

  • 그냥 함수를 사용하여 만든다면, 계속 새롭게 함수가 들어가 render 됨으로 useCallback을 사용해서 동일성을 유지해 준다.
  • dispatch를 dependency로 하는 경우 dispatch 함수가 바뀌었을 때, useCallback 함수가 새로 만들어 짐 (하지만, dispatch 함수가 달라질 일은 없으므로 상관 없음)
import { useCallback } from "react";
import { useDispatch } from "react-redux";
import TodoForm from "../components/TodoForm";
import { addTodo } from "../redux/actions";

export default function TodoFormContainer() {
  // dispatch 가져오기
  const dispatch = useDispatch();

  // dispatch로 사용할 함수 만들기
  const add = useCallback(
    (text) => {
      dispatch(addTodo(text));
    },
    [dispatch]
  );

  // Component에 전달하기
  return <TodoForm add={add} />;
}