ABOUT ME

Today
Yesterday
Total
  • 최적화 방법
    Javascript/React.js 2020. 5. 6. 22:22

    느려지는 원인 분석

    컴포넌트는 다음과 같은 상황에서 리렌더링이 발생한다.

    1. 자신이 전달받은 props가 변경될 때

    2. 자신의 state가 바뀔 때

    3. 부모 컴포넌트가 리렌더링될 때

    4. forceUpdate 함수가 실행될 때

    기존 프로젝트의 상황을 분석해보면 '할 일 1' 항목을 체크할 경우 App 컴포넌트의 state가 변경되면서 App 컴포넌트가 리렌더링된다. 부모 컴포넌트가 리렌더링되었으니 TodoList 컴포넌트가 리렌더링되고 그 안의 무수히 많은 컴포넌트들도 리렌더링된다. '할 일 2' ~ '할 일 2500'은 사실 리렌더링 할 필요가 없다.

     

    React.memo

    클래스 컴포넌트는 shouldComponentUpdate를 사용하고 함수형 컴포넌트는 이것을 이용해 컴포넌트의 props가 바뀌지 않았다면, 리렌더링하지 않도록 설정할 수 있다.

    import React from 'react';
    import {
    MdCheckBoxOutlineBlank,
    MdCheckBox,
    MdRemoveCircleOutline,
    } from 'react-icons/md';
    import cn from 'classnames';
    import './TodoListItem.scss';
    const TodoListItem = ({ todo, onRemove, onToggle }) => {
    const { id, text, checked } = todo;
    return (
    <div className="TodoListItem">
    <div className={cn('checkbox', { checked })} onClick={() => onToggle(id)}>
    {checked ? <MdCheckBox/> : <MdCheckBoxOutlineBlank/>}
    <div className="text">{text}</div>
    </div>
    <div className="remove" onClick={() => onRemove(id)}>
    <MdRemoveCircleOutline/>
    </div>
    </div>
    );
    };
    // React.memo : 컴포넌트를 만들고 감싸주면 된다.
    // 컴포넌트의 props가 바뀌지 않았다면 리렌더링 하지 않도록 설정해준다.
    export default React.memo(TodoListItem);
    view raw ex.js hosted with ❤ by GitHub

    이렇게 하면 todos 배열이 아닌 todo 하나의 객체를 받는 TodoListItem 컴포넌트는 리렌더링 되지 않는다.

     

    useState의 함수형 업데이트

    현재는 이벤트 핸들러 함수들이 todos 배열이 바뀔 때마다 함수가 새로 만들어진다. 이렇게 함수가 계속 만들어지는 상황을 방지하는 방법이다. 

    import React, { useState, useCallback, useRef } from 'react';
    import TodoTemplate from './components/TodoTemplate';
    import TodoInsert from './components/TodoInsert';
    import TodoList from './components/TodoList';
    function createBulkTodos() {
    const arr = [];
    for(let i = 1; i <= 2500 ; i++) {
    arr.push({
    id: i,
    text: `할 일 ${i}`,
    checked: false,
    });
    }
    return arr;
    }
    const App = () => {
    const [todos, setTodos] = useState(createBulkTodos);
    const nextId = useRef(4);
    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;
    view raw ex.js hosted with ❤ by GitHub

    기존에 함수 생성 기준으로 todos를 넣었는데 그것을 빼고, 함수형 업데이트(todos => ~~)만 넣어주면 된다.

     

    useReducer

    useState 대신 useReducer를 사용할 수 있다.

    import React, { useState, useCallback, useRef, useReducer } from 'react';
    import TodoTemplate from './components/TodoTemplate';
    import TodoInsert from './components/TodoInsert';
    import TodoList from './components/TodoList';
    function createBulkTodos() {
    const arr = [];
    for(let i = 1; i <= 2500 ; i++) {
    arr.push({
    id: i,
    text: `할 일 ${i}`,
    checked: false,
    });
    }
    return arr;
    }
    function todoReducer(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(todoReducer, undefined, createBulkTodos);
    const nextId = useRef(2501);
    const onInsert = useCallback(
    text => {
    const todo = {
    id: nextId.current,
    text,
    checked: 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 (
    <TodoTemplate>
    <TodoInsert onInsert={onInsert}/>
    <TodoList todos={todos} onRemove={onRemove} onToggle={onToggle}/>
    </TodoTemplate>
    );
    }
    export default App;
    view raw ex.js hosted with ❤ by GitHub

    상태를 업데이트하는 로직을 모아서 컴포넌트 바깥에 둘 수 있는 장점이 있다.

     

    불변성의 중요성

    '불변성을 지킨다'란 기존의 값을 직접 수정하지 않고 새로운 값을 만드는 것을 의미한다. 우리가 지금까지 배열을 추가하거나 삭제했을 때, 새로 만든 것과 같은 뜻이다. 불변성을 지키지 않으면 객체 내부의 값이 바뀌어도 감지하지 못한다.

     

    List 최적화

    리스트를 최적화하기 위해서는 리스트의 요소와 함께 리스트 그 자체에서도 최적화 해야한다.

    import React from 'react';
    import TodoListItem from './TodoListItem';
    import './TodoList.scss';
    const TodoList = ({ todos, onRemove, onToggle }) => {
    return (
    <div className="TodoList">
    {todos.map(todo => (
    <TodoListItem todo={todo} key={todo.id} onRemove={onRemove} onToggle={onToggle}/>
    ))}
    </div>
    );
    };
    export default React.memo(TodoList);
    view raw ex.js hosted with ❤ by GitHub

     

    'Javascript > React.js' 카테고리의 다른 글

    SPA  (0) 2020.05.11
    immer  (0) 2020.05.06
    chrome 개발자 도구를 통한 성능 모니터링  (0) 2020.05.06
    컴포넌트 스타일링  (0) 2020.05.04
    Hooks  (0) 2020.05.04
Designed by Tistory.