할일 목록 구현

정영찬·2022년 3월 14일
0

리액트

목록 보기
54/79
post-custom-banner

자바스크립트로 만들었던 할 일 목록 구현을 리덕스를 사용해서 구현한다.

프레젠테이셔널 컴포넌트 만들기

Todos.js에 컴포넌트를 작성한다.

  • TodoItem : 할일 목록에 표시되는 단일된 항목 표시
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);

useSelector 최적화

위의 과정을 거치면 input 에 내용을 입력해도 이미 추가된 todoItem들은 리렌더링 되지 않는다. 하지만 각각의 todoItem을 클릭하여 onToggle을 호출하면 전에 작성한 카운터 컴포넌트까지 같이 렌더링 된다. 그 이유는 CounterContainer() 내부에 있는 useSelector에 문제가 있다. state를 파라미터로 numberdiff라는 객체를 새로 만들어내고 있었기 때문이다. 이를 개선하기위해서 useSelector를 최적화 시켜야한다.

useSelector를 최적화하기 위한 방법은 2가지가 있다.

  • useSelector를 여러번 해준다.
const number = useSelector(state => ({ number: state.counter.number)}
const diff = useSelector(state => ({diff: state.counter.diff})
  • useSelector의 두번째 파라미터에 "이전 상태와 다음상태를 비교하는 함수" 를 추가하는데 이 함수를 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와 내부 컴포넌트만 리렌더링 된 모습을 볼수 있다.

profile
개발자 꿈나무
post-custom-banner

0개의 댓글