혼자 안보고 해보니까 리덕스 사용방법을 더 잘 알 것 같다. 글로 정리해놓을 때는 탑다운 순서로 써놓을 것이나 직접 코드를 쓸때는 바텀업 방식으로 했다.
index.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from "react-redux";
import { createStore } from "redux";
import reducers from "./modules";
import {composeWithDevTools} from "redux-devtools-extension";
const store = createStore(reducers, composeWithDevTools())
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
App.jsx
import React from 'react';
import TodosContainer from "./containers/TodosContainer";
function App() {
return (
<TodosContainer />
);
}
export default App;
modules/index.jsx
import {combineReducers} from "redux";
import todos from './todos';
const reducers = combineReducers({todos});
export default reducers;
modules/todos.jsx
// 액션 상태
const ADD_TODO = 'modules/ADD_TODO';
const TOGGLE_TODO = 'modules/TOGGLE_TODO';
const initialState = [
/*
{
id: 1,
text: '',
done: false,
}
*/
]
// 액션 생성함수
export const addTodo = text => ({
type: ADD_TODO,
text
})
export const toggleTodo = id => ({
type: TOGGLE_TODO,
id
})
let nextId = 1;
// 리듀서
export default function todos (state = initialState, action) {
switch(action.type) {
case ADD_TODO:
return [
...state,
{id: nextId++, text: action.text, done: false}
]
case TOGGLE_TODO:
return state.map(state => state.id === action.id ? {...state, done: !state.done} : state);
default:
return state;
}
}
TodosContainer.jsx
import React from 'react';
import {useSelector, useDispatch} from "react-redux";
import Todos from "../components/Todos";
import {addTodo, toggleTodo} from "../modules/todos";
export default function TodosContainer() {
const todos = useSelector(state => state.todos);
const dispatch = useDispatch();
const onCreate = (text) => {
dispatch(addTodo(text));
}
const onToggle = (id) => {
dispatch(toggleTodo(id))
}
return <Todos onCreate={onCreate} onToggle={onToggle} todos={todos}/>
}
구조
Todo
┠ TodoInput
┗ TodoList
┗ Todo
Todos.jsx
import React, {useState} from 'react';
import TodoInput from "./TodoInput";
import TodoList from "./TodoList";
export default function Todos({todos, onCreate, onToggle}) {
const [text, setText] = useState('');
const onSetText = (text) => {
setText(text);
}
return (
<>
<TodoInput text={text} onSetText={onSetText} onCreate={onCreate}/>
<TodoList todos={todos} onToggle={onToggle}/>
</>
)
}
TodoInput.jsx
import React from 'react';
export default function TodoInput({text, onSetText, onCreate}) {
const onSubmit = (e) => {
e.preventDefault();
onCreate(text);
}
const onChange = (e) => {
onSetText(e.target.value);
}
return (
<form onSubmit={onSubmit}>
<input value={text} onChange={onChange} />
<button type={"submit"}>추가하기</button>
</form>
)
}
TodoList.jsx
import React from 'react';
import Todo from "./Todo";
export default function TodoList({todos, onToggle}) {
return (
<ul>
{todos.map(todo => <Todo todo={todo} onToggle={onToggle}/>)}
</ul>
)
}
Todo.jsx
import React from 'react';
import './Todo.scss'
export default function Todo({todo, onToggle}) {
const onClick = () => {
onToggle(todo.id);
}
return <li className={todo.done? 'complete' : null} onClick={onClick}>{todo.text}</li>
}
todo에 추가하기 위해 input에 쓰게 되면 리스트 전체가 다시 리렌더링되는 것을 확인할 수 있다. input에 작성할 동안은 리스트가 변하지 않기 때문에 현 상태를 메모이제이션해둘 수 있다.
위 예제에서 TodoItem, TodoList 이런 컴포넌트들을 하나씩 React.memo(component) 식으로 감싸주면서 확인해보면 리렌더링을 하지않는 것을 확인할 수 있다.
크롬브라우저에서 React관련 extension을 설치하고 Profiler탭에서 녹화버튼을 누르고 확인하는 방법도 있고 Components탭에서 렌더될 때 highlight되는 옵션을 체크해도 된다. Profiler탭에서 회색은 렌더링되지 않았음을 의미하고 빗금회색은 기억되었음을 의미하고 주황색은 비교적 오래걸렸다는 것을, 초록색은 빠른 시간안에 렌더링 되었다는 것을 의미한다.