react-redux를 안 쓰고 연결하기
단일 store를 만들고, subscribe와 getState를 이용하여,
변경되는 state 데이터를 얻어, props로 계속 아래로 전달
- componentDidMount - subscribe
- componentWillUnmount - unsubscribe
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의 변화에 반응을 줄 수 있는 컴포넌트 생성이 가능하다
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;
결과
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();
...
- 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;
결과