[React]React.memo, useReducer를 통한 컴포넌트 최적화

UkiUkhui·2021년 10월 17일
0

React 공부중

목록 보기
16/25
  • 앞전에 만든 todoList의 경우, 리스트 항목이 많아지게 될 경우 리렌더링 시 너무 많은 시간 소요
  • 함수형 컴포넌트의 경우, 라이프사이클 메서드 사용이 불가능함

React.memo

  • 컴포넌트 props가 변하지 않았다면 리렌더링하지 않음
  • 컴포넌트에서 리렌더링이 필요한 상황에서만 리렌더링함
import React from "react";
import styled, { css } from "styled-components";
import { MdDone, MdDelete } from "react-icons/md";

const Remove = styled.div`
  display: flex;
  align-items: center;
  justify-content: center;
  color: #dee2e6;
  font-size: 24px;
  cursor: pointer;
  &:hover {
    color: #ff6b6b;
  }
  display: none;
`;

const TodoItemBlock = styled.div`
  display: flex;
  align-items: center;
  padding-top: 12px;
  padding-bottom: 12px;
  &:hover {
    ${Remove} {
      display: initial;
    }
  }
`;

const CheckCircle = styled.div`
  width: 32px;
  height: 32px;
  border-radius: 16px;
  border: 1px solid #ced4da;
  font-size: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-right: 20px;
  cursor: pointer;
  ${(props) =>
    props.done &&
    css`
      border: 1px solid #38d9a9;
      color: #38d9a9;
    `}
`;

const Text = styled.div`
  flex: 1;
  font-size: 21px;
  color: #495057;
  ${(props) =>
    props.done &&
    css`
      color: #ced4da;
    `}
`;

function TodoItem({ id, done, text, onRemove, onToggle }) {
  return (
    <TodoItemBlock>
      <CheckCircle done={done} onClick={() => onToggle(id)}>
        {done && <MdDone />}
      </CheckCircle>
      <Text done={done}>{text}</Text>
      <Remove onClick={() => onRemove(id)}>
        <MdDelete />
      </Remove>
    </TodoItemBlock>
  );
}

export default React.memo(TodoItem);
  • 받아오는 props(id, done, text, onRemove, onToggle)이 바뀌지 않는다면 굳이 리렌더링하지 않음

onToggle, onRemove 변하지 않게 하기

  • todos가 업데이트 되면 onRemove, onToggle도 새롭게 변함(useCallback에서 두번째 인자로 넘겨주기 때문)

1. useState의 함수형 업데이트

함수형 업데이트란? setTodos를 사용할 때, 새로운 상태를 파라미터로 넣는 대신에 상태 업데이트를 어떻게 할지 정의해 주는 업데이트 함수 넣는 것

const [number, setNumber]= useState(0);
const onIncrease = useCallback(
	()=>setNumber(prev=>prev+1);,
    [],
);
  • setNumber(number+1) 이 아닌 어떻게 업데이트할지 넣어주면 됨. 그렇게 되면 두번째 인자에 빈 배열을 넣어도 되기 때문.
import { createGlobalStyle } from "styled-components";
import React, { useCallback, useRef, useState } from "react";
import TodoTemplate from "./TodoTemplate";
import TodoHead from "./TodoHead";
import TodoList from "./TodoList";
import TodoCreate from "./TodoCreate";

const GlobalStyle = createGlobalStyle`
  body {
    background: #74992e;
  }
`;

const App = () => {
  const [todos, setTodos] = useState([
    {
      id: 1,
      text: "todo 1",
      done: true
    },
    {
      id: 2,
      text: "todo 2",
      done: true
    },
    {
      id: 3,
      text: "todo 3",
      done: false
    }
  ]);

  const nextId = useRef(4);

  const onInsert = useCallback((text) => {
    const todo = {
      id: nextId.current,
      text,
      done: false
    };
    setTodos((todos) => todos.concat(todo));
    nextId.current += 1;
  }, []);

  const onRemove = useCallback((id) => {
    setTodos((todos) => todos.filter((todo) => todo.id !== id));
  }, []);

  const onToggle = useCallback((id) => {
    setTodos((todos) =>
      todos.map((todo) =>
        todo.id === id ? { ...todo, done: !todo.done } : todo
      )
    );
  }, []);
  return (
    <>
      <GlobalStyle />
      <TodoTemplate>
        <TodoHead />
        <TodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />
        <TodoCreate onInsert={onInsert} />
      </TodoTemplate>
    </>
  );
};
export default App;
  • onInsert, onRemove, onToggle 내 setTodos에서 인자로 todos를 받아서 이후 무슨 일을 할지 명시

2. useReducer

import { createGlobalStyle } from "styled-components";
import React, { useCallback, useReducer, useRef, useState } from "react";
import TodoTemplate from "./TodoTemplate";
import TodoHead from "./TodoHead";
import TodoList from "./TodoList";
import TodoCreate from "./TodoCreate";


function createBulkTodos() {
  const array = [
      {
        id: 1,
        text: "todo 1",
        done: true
      },
      {
        id: 2,
        text: "todo 2",
        done: true
      },
      {
        id: 3,
        text: "todo 3",
        done: false
      }
    ]
  return array;
}

const GlobalStyle = createGlobalStyle`
  body {
    background: #74992e;
  }
`;

const reducer = (todos, action) => {
  switch (action.type) {
    case "INSERT":
      return todos.concat(action.todo);
    case "REMOVE":
      return todos.filter((todo) => todo.id !== action.id);
    case "TOGGLE":
      return todos.map((todo) =>
        todo.id === action.id ? { ...todo, checked: !todo.checked } : todo
      );
    default:
      return todos;
  }
};

const App = () => {
  const [todos, dispatch] = useReducer(reducer, undefined, createBulkTodos);

  const nextId = useRef(4);

  const onInsert = useCallback((text) => {
    const todo = {
      id: nextId.current,
      text,
      done: false
    };
    dispatch({ type: "INSERT", todo });
    nextId.current += 1;
  }, []);

  const onRemove = useCallback((id) => {
    dispatch({ type: "REMOVE", id });
  }, []);

  const onToggle = useCallback((id) => {
    dispatch({ type: "TOGGLE", id });
  }, []);

  return (
    <>
      <GlobalStyle />
      <TodoTemplate>
        <TodoHead />
        <TodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />
        <TodoCreate onInsert={onInsert} />
      </TodoTemplate>
    </>
  );
};
export default App;
  • 두 번째 파라미터에는 원래 초기상태를 넣어줘야하는데, undefined을 넣고 세 번째 파라미터에 함수를 넣음으로써, 컴포넌트가 맨처음 렌더링될 때 이 함수가 호출되도록 설정
  • 상태를 업데이트하는 로직을 모아서 바깥에 둘 수 있다는 장점.
profile
hello world!

0개의 댓글

관련 채용 정보