ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [React TodoList] 많은 데이터 렌더링, React.memo, 성능 최적화
    React/일정관리 프로젝트 2023. 3. 14. 18:34

    React TodoList 보완하기

     

    많은 데이터 렌더링 생성하기

    import React , {useCallback, useState,useRef} from "react";
    import TodoTemplate from "./TodoTemplate";
    import TodoInsert from "./TodoInsert";
    import TodoList from "./TodoList";
    
    function createBulkTodos() {
      const array = [];
      for(let i=1;i<2500;i++){
        array.push({
          id:i,
          text : `할 일 : ${i}`,
          checked:false
        })
      }
      return array
    }
    const App = () => {
      const [todos,setTodos] = useState(createBulkTodos)
    
     const nextId = useRef(2500);
    
     const onInsert = useCallback(
      text => {
        const todo = {
          id : nextId.current,
          text,
          checked :false,
        };
        setTodos(todos.concat(todo))
        nextId.current +=1;
      },[todos],
    )
    const onRemove = useCallback(
     id => {
      setTodos(todos.filter(todo=>todo.id !== id))
     },[todos] 
    )
    const onToggle = useCallback(
      id => {
        setTodos(
          todos.map(todo => 
            todo.id === id ? {...todo, checked:!todo.checked}:todo,)
        )
      },[todos]
    )
      return (
      <TodoTemplate> 
          <TodoInsert onInsert={onInsert}/>
          <TodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />
      </TodoTemplate>
      )
    }
    
    export default App;

     

    성능 모니터링 (개발자 도구 - performance - 녹화) 

    1.4초로 성능이 매우 나쁘다는 것을 알 수 있다.

     

     

     성능이 느린 이유

    '할 일 : 1 ' 항목 체크시 app.state가 변경 => app.js 리렌더링 => todoList 등 자식 컴포넌트 렌더링

    리렌더링 발생 원인 

    1. 자신이 전달받은 props가 변경될 때
    2. 자신의 state가 변경될 때
    3. 부모 컴포넌트가 리렌더링 될 때
    4. forceUpdate 함수가 실행될 때 

     

     

    React.memo를 사용해 컴포넌트 성능 최적화

    리렌더링을 방지하기 위해 React.memo를 사용하여 props가 바뀌지 않으면 렌더링하지 않도록 설정한다.

     

    todoListItem.js

    import React from 'react';
    
    (...)
    
    export default React.memo(TodoListItem);

     

     

     

     

     

    onToggle, onRemove 함수가 바뀌지 않게 하기 

    현재 프로젝트는 todos 배열이 업데이트 되면 onRemove와 onToggle 함수도 새로 바뀐다. 

    컴포넌트 최적화를 위해 useState 또는 useReducer를 사용한다.

     

    1. useState의 함수형 업데이트

    setTodos 함수를 사용할 때 새로운 상태를 파라미터로 넣어 준다. 

     

    app.js

    import React , {useCallback, useState,useRef} from "react";
    import TodoTemplate from "./TodoTemplate";
    import TodoInsert from "./TodoInsert";
    import TodoList from "./TodoList";
    
    function createBulkTodos() {
      const array = [];
      for(let i=1;i<2500;i++){
        array.push({
          id:i,
          text : `할 일 : ${i}`,
          checked:false
        })
      }
      return array
    }
    const App = () => {
      const [todos,setTodos] = useState(createBulkTodos)
    
    
    const nextId = useRef(2500);
    
    const onInsert = useCallback(
      text => {
        const todo = {
          id : nextId.current,
          text,
          checked :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, checked:!todo.checked}:todo,)
        )
      },[]
    )
      return (
      <TodoTemplate> 
          <TodoInsert onInsert={onInsert}/>
          <TodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />
      </TodoTemplate>
      )
    }
    
    export default App;

    500ms로 줄은 걸로 보아 성능이 향상된 것을 알 수 있다.

     

     

     

    2. useReducer 사용하기

     

    두 번째 파라미터에 초기 상태를 넣어주어야 하지만 undefined를 설정하고 세 번째 파라미터에 초기 상태 생성하는 함수를 넣어 컴포넌트가 맨 처음 렌더링 될 때 1회 함수를 호출한다. 

     

    import React , {useCallback, useState,useRef, useReducer} from "react";
    import TodoTemplate from "./TodoTemplate";
    import TodoInsert from "./TodoInsert";
    import TodoList from "./TodoList";
    
    function createBulkTodos() {
      const array = [];
      for(let i=1;i<2500;i++){
        array.push({
          id:i,
          text : `할 일 : ${i}`,
          checked:false
        })
      }
      return array
    }
    
    function todoReducer(todos,action) {
      switch(action.type) {
        case 'Insert' :
          return todos.concat(action.todo);
        case 'Delete' :
          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,setTodos] = useState(createBulkTodos)
      const [todos,dispatch] = useReducer(todoReducer, undefined, createBulkTodos)
    
    const nextId = useRef(2500);
    
    const onInsert = useCallback(
      text => {
        const todo = {
          id : nextId.current,
          text,
          checked :false,
        };
        dispatch({type:'Insert',todo})
        // setTodos(todos=> todos.concat(todo))
        nextId.current +=1;
      },[],
    )
    const onRemove = useCallback(
     id => {
      // setTodos(todos=> todos.filter(todo=>todo.id !== id))
      dispatch({type:'Delete',id})
     },[] 
    )
    const onToggle = useCallback(
      id => {
        dispatch({type:'Toggle',id})
        // setTodos(
        //   todos=> todos.map(todo => 
        //     todo.id === id ? {...todo, checked:!todo.checked}:todo,)
        // )
      },[]
    )
      return (
      <TodoTemplate> 
          <TodoInsert onInsert={onInsert}/>
          <TodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />
      </TodoTemplate>
      )
    }
    
    export default App;

     

    useState와 useReducer 둘 중 어느 것을 사용해도 되고 성능은 비슷하다.

    useReducer를 사용하면 상태를 업데이트 하는 로직을 컴포넌트 바깥에 둘 수 있다는 장점이 있다. 

Designed by Tistory.