자바스크립트로 만들었던 할 일 목록 구현을 리덕스를 사용해서 구현한다.
Todos.js
에 컴포넌트를 작성한다.
function TodoItem({todo, onToggle}){
return (
<li
style={{
textDecoration: todo.done ? 'line-through': 'none'
}}
onClick={() => onToggle(todo.id)}
>
{todo.text}
</li>
)
}
-TodoList : TodoItem들을 나열해서 보여주는 컴포넌트
function TodoList({todos, onToggle}){
return(
<ul>
{
todos.map(todo => <TodoItem
key={todo.id}
todo={todo}
onToggle={onToggle}
/>)
}
</ul>
)
}
-Todos : TodoList와 함께 새로운 항목을 추가시켜주는 input이 추가되어있다.
function Todos({todos, onCreate, onToggle}) {
const [text, setText] = useState('');
const onChange = e => setText(e.target.value);
const onSubmit = e => {
e.preventDefault();
onCreate(text);
setText('');
};
return (
<div>
<form onSubmit={onSubmit}>
<input value={text} onChange={onChange} placeholder="할일을 입력하세요" />
<button type="submit">등록</button>
</form>
<TodoList
todos={todos}
onToggle={onToggle}
/>
</div>
);
}
TodosContainer
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import Todos from '../components/Todos';
import { addTodo, toggleTodo } from '../modules/todos';
function TodosContainer(){
const todos = useSelector(state => state.todos);
const dispatch = useDispatch();
const onCreate = useCallback(text => dispatch(addTodo(text)), [dispatch]);
const onToggle = useCallback(id => dispatch(toggleTodo(id)), [dispatch]);
return <Todos
todos={todos}
onCreate={onCreate}
onToggle={onToggle}
/>
}
export default TodosContainer
App에 추가해보자.
function App() {
return (
<div>
<CounterContainer/>
<hr/>
<TodosContainer/>
</div>
);
}
최적화를 위해서 Todos
내부 의 컴포넌트를 React.Memo를 이용하여 수정한다.
import React, {useState} from "react";
const TodoItem = React.memo(function TodoItem({todo, onToggle}){
return (
<li
style={{
textDecoration: todo.done ? 'line-through': 'none'
}}
onClick={() => onToggle(todo.id)}
>
{todo.text}
</li>
)
})
const TodoList = React.memo(function TodoList({todos, onToggle}){
return(
<ul>
{
todos.map(todo => <TodoItem
key={todo.id}
todo={todo}
onToggle={onToggle}
/>)
}
</ul>
)
})
function Todos({todos, onCreate, onToggle}) {
const [text, setText] = useState('');
const onChange = e => setText(e.target.value);
const onSubmit = e => {
e.preventDefault();
onCreate(text);
setText('');
};
return (
<div>
<form onSubmit={onSubmit}>
<input value={text} onChange={onChange} placeholder="할일을 입력하세요" />
<button type="submit">등록</button>
</form>
<TodoList
todos={todos}
onToggle={onToggle}
/>
</div>
);
}
export default React.memo(Todos);
위의 과정을 거치면 input
에 내용을 입력해도 이미 추가된 todoItem들은 리렌더링 되지 않는다. 하지만 각각의 todoItem
을 클릭하여 onToggle
을 호출하면 전에 작성한 카운터 컴포넌트까지 같이 렌더링 된다. 그 이유는 CounterContainer()
내부에 있는 useSelector
에 문제가 있다. state
를 파라미터로 number
와 diff
라는 객체를 새로 만들어내고 있었기 때문이다. 이를 개선하기위해서 useSelector를 최적화 시켜야한다.
useSelector를 최적화하기 위한 방법은 2가지가 있다.
const number = useSelector(state => ({ number: state.counter.number)}
const diff = useSelector(state => ({diff: state.counter.diff})
equalityFn
이라고 한다. 기본 형식은 아래와 같다. function CounterContainer() {
const {number,diff} = useSelector(state => ({
number: state.counter.number,
diff: state.counter.diff
}),(left, right) => {
return left.diff === right.diff && left.number === right.number;
});
이전의 값과 현재 값을 비교해서 값이 다를 경우에만 렌더링을 하는 방식이지만, 작성하기 너무 길고 귀찮다는 사람들을 위해서 shallowEqual
이라는 함수가 있다. 저 긴 문구 대신 한단어로 작성하면 된다.
const {number,diff} = useSelector(state => ({
number: state.counter.number,
diff: state.counter.diff
}),shallowEqual);
확인을 위해 다시 할일을 작성하여 추가한뒤 onToggle을 호출시키고 나서 Profiler를 확인해보자.
TodoContainer
와 내부 컴포넌트만 리렌더링 된 모습을 볼수 있다.