Front/React

최적화 - React 배우기

oodada 2024. 3. 18. 00:00

최적화

리액트에서의 최적화 기법은 불필요한 연산을 줄여 성능을 향상시키는 것입니다.
리액트 공식 문서에서는 성능 최적화를 위한 방법을 다음과 같이 소개합니다.

최적화 방법

  • 코드, 폰트, 이미지 등의 리소스를 압축합니다.
  • 메모이제이션을 이용하여 불필요한 연산을 줄입니다.

메모이제이션

메모이제이션이란 연산의 결과를 기억했다가 필요할 때 사용함으로써 불필요한 연산을 방지하는 것입니다.

  • useMemo : 함수의 불필요한 재실행을 방지합니다.
  • React.memo : 컴포넌트의 불필요한 리렌더링을 방지합니다.
  • useCallback : 함수의 불필요한 재생성을 방지합니다.

useMemo 사용하기

문법 : useMemo(() => 연산 결과, [의존성 배열])

  • 목적: 값을 기억하고, 성능을 최적화합니다.
  • 사용 시점: 계산 비용이 높은 연산을 최적화할 때 사용합니다.
  • 사용 방법:
    • 값을 계산하는 함수와 의존성 배열을 전달하여 사용합니다.
    • 의존성 배열에 있는 값이 변경되지 않으면 이전에 계산된 값을 반환합니다.
  • 장점:
    • 성능 향상: 이전에 계산된 값을 재사용하여 성능을 향상시킵니다.
    • 불필요한 연산 방지: 의존성 배열이 변경되지 않으면 다시 계산하지 않습니다.

할 일 관리 앱 만들기 글의 예제를 기반으로 최적화를 진행해보겠습니다.

아래 예제에서 filteredTodo 함수가 빈번하게 호출되어 불필요한 연산이 발생합니다. 테스트를 위해 filteredTodo 함수에 로깅을 추가하고, 콘솔을 확인해보시면 빈번한 호출이 발생하는 것을 확인할 수 있습니다.

// src/components/TodoList.js
(...)

function TodoList() {
    (...)

    const filteredTodo = () => {
        if (Array.isArray(todo)) {
            return todo.filter((item) => item.task.toLowerCase().includes(search.toLowerCase()));
        } else {
            return [];
        }
    };

    // lookBack 함수가 빈번하게 호출됩니다.
    const lookBack = () => {
        console.log('lookBack')
        const total = todo.length
        const done = todo.filter((item) => item.isDone).length
        const left = total - done

        return { total, done, left }
    }

    return (
        <div>
            <h3>할 일 목록 📃</h3>
            <input type='text' placeholder="검색어를 입력하세요" onChange={onChangeSearch} value={search} />

            <div>
                {filteredTodo().map((item) => (
                    <TodoItem key={item.id} {...item} />
                ))}
            </div>

            <div>
                {lookBack().total}개 중에 {lookBack().done}개 완료, {lookBack().left}개 남음
            </div>
        </div>
    );
}

export default TodoList;

useMemo를 이용하여 할 일 목록 렌더링 최적화하기

useMemo를 이용하여 filteredTodo 함수의 결과를 기억합니다. useMemo를 사용함으로써 filteredTodo는 함수가 아니라 해당 값을 계산한 결과인 배열을 반환합니다.

// src/component/TodoList.js
import React, { useState, useMemo } from 'react'
import TodoItem from './TodoItem'

export default function TodoList({ todo, onUpdate, onDelete }) {
    (...)

    // useMemo를 이용하여 filteredTodo 함수의 결과를 기억합니다.
    // useMemo를 사용함으로써 filteredTodo는  함수가 아니라 해당 값을 계산한 결과인 배열을 반환합니다.
    const filteredTodo = useMemo(() => {
        return todo.filter((item) => item.task.toLowerCase().includes(search.toLowerCase()));
    }, [search, todo]); // 의존성 배열에 search와 todo를 추가합니다.

    // 최적화 테스트
    const lookBack = useMemo(() => {
        console.log('lookBack')
        const total = todo.length
        const done = todo.filter((item) => item.isDone).length
        const left = total - done

        return { total, done, left }
    }, [todo]) // 의존성 배열에 todo를 추가합니다.

    return (
        <div>
            (...)
            <ul>
            // filteredTodo useMemo를 적용한 경우, filteredTodo 자체가 함수가 아니라 해당 값을 계산한 결과인 객체를 반환하기 때문에 ()를 제거합니다.
                {filteredTodo.map((item) => (
                    <TodoItem key={item.id} onUpdate={onUpdate} onDelete={onDelete} {...item} />
                ))}
            </ul>

            <div>
                // lookBack에 useMemo를 적용한 경우, lookBack 자체가 함수가 아니라 해당 값을 계산한 결과인 객체를 반환하기 때문에 ()를 제거합니다.
                {lookBack.total}개 중에 {lookBack.done}개 완료, {lookBack.left}개 남음
            </div>
        </div>
    )
}

- useCallback을 이용하여 함수 재생성 최적화하기

문법 : useCallback(() => 함수, [의존성 배열])

  • 목적: 콜백 함수를 기억하고, 성능을 최적화합니다.
  • 사용 시점: 콜백 함수가 자주 변경되지 않고, 의존성 배열에 있는 값이 변경될 때 사용됩니다.
  • 사용 방법:
    • 콜백 함수와 의존성 배열을 전달하여 사용합니다.
    • 의존성 배열에 있는 값이 변경되지 않으면 이전에 생성된 콜백 함수를 반환합니다.
  • 장점:
    • 성능 향상: 이전에 생성된 콜백 함수를 재사용하여 성능을 향상시킵니다.
    • 불필요한 함수 생성 방지: 의존성 배열이 변경되지 않으면 새로운 콜백 함수를 생성하지 않습니다.

useCallback을 이용하여 할 일 관리 앱에서 App이 리렌더링될 때마다 addTodo, onUpdate, onDelete 함수가 재생성되는 것을 방지합니다.

// src/component/App.js
import React, { useReducer, useCallback } from 'react'
(...)

function App() {
    const [todo, dispatch] = useReducer(reducer, mockTodo)

    const addTodo = useCallback((task) => {
        const newTodo = {
            id: Date.now(),
            isDone: false,
            task,
            createdDate: new Date().getTime(),
        }
        dispatch({ type: 'ADD', payload: newTodo })
    }, [])

    const onUpdate = useCallback((id) => {
        dispatch({ type: 'UPDATE', payload: id })
    }, [])

    const onDelete = useCallback((id) => {
        dispatch({ type: 'DELETE', payload: id })
    }, [])

    return (
        <div>
            <TodoHd />
            <TodoEditor addTodo={addTodo} />
            <TodoList todo={todo} onUpdate={onUpdate} onDelete={onDelete} />
        </div>
    )
}

export default App

- React.memo를 이용하여 컴포넌트 최적화하기

문법 : React.memo(컴포넌트)

  • 목적: 함수형 컴포넌트의 리렌더링을 방지하여 성능을 최적화합니다.
  • 사용 시점: 컴포넌트의 리렌더링이 불필요한 경우에 사용됩니다.
  • 사용 방법:
    • 함수형 컴포넌트를 React.memo로 래핑하여 사용합니다.
    • props가 변경되지 않으면 이전 결과를 재사용하여 리렌더링을 방지합니다.
  • 장점:
    • 성능 향상: 불필요한 리렌더링을 방지하여 성능을 향상시킵니다.
    • 코드 간소화: 명시적인 shouldComponentUpdate 메서드 없이 컴포넌트를 최적화할 수 있습니다.

할 일 관리 앱 적용하기

// src/component/TodoItem.js
(...)

// React.memo를 이용하여 컴포넌트를 최적화합니다.
export default React.memo(TodoItem)
티스토리 친구하기