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를 보낼 컴포넌트에 대한 정의가 필요함
- Container
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} />;
}