React hooks 최적화를 위한 간편 가이드 (1)

WE SEE WHAT WE WANT·2023년 11월 8일
0

React

목록 보기
2/6
post-thumbnail

우선, 최적화를 해야하는 이유는?

📌 컴포넌트의 성능 향상

  • 불필요한 렌더링을 방지하고, 불필요한 함수 재생성을 방지함으로써 애플리케이션의 반응성과 효율성을 향상시켜 사용자에게 더 나은 경험 제공
  • 유지 보수성이 편해지고, 개발 생산성을 향상시킴

React Hooks는 ?

함수형 컴포넌트에서 상태(state)와 생명주기(lifecycle) 기능을 제공하는 기술입니다. 이전에는 클래스 컴포넌트에서만 상태 관리와 생명주기 메서드를 사용할 수 있었지만, Hooks를 사용하면 함수형 컴포넌트에서도 이러한 기능을 사용할 수 있게 됩니다.

  1. useState: 상태(state)를 관리하기 위한 훅. 상태를 선언하고 새로운 값으로 업데이트
  2. useEffect: 생명주기(lifecycle) 기능을 제공하는 훅. 컴포넌트가 렌더링될 때마다 특정 작업을 수행하고, 상태가 업데이트될 때 특정 작업을 처리
  3. useContext: 전역으로 상태를 공유하고 접근할 수 있는 훅.
  4. useReducer: 복잡한 상태 관리를 위해 사용되는 훅, useState보다 다양한 상태 업데이트 로직 구현 가능.
  5. useRef: DOM 요소나 컴포넌트에 접근하기 위한 Ref를 생성
  6. useMemo: 연산 결과를 기억하는 훅. 이전에 계산된 값을 재사용하여 불필요한 연산 방지.
  7. useCallback: 콜백 함수를 기억하는 훅. 불필요한 함수 재생성을 방지

React Hooks를 최적화를 위한 3가지 방법(기초)

State 최적화란 ?

  • 프로그램의 성능을 향상시키기 위해 상태(state)를 효율적으로 관리하는 과정을 말합니다.
  • 프로그램의 상태는 변수, 데이터 구조, 객체 등을 포함하며, 이러한 상태를 올바르게 조작하고 저장하는 것은 중요합니다.

1. React.memo를 사용한다

React.memo : 함수 컴포넌트의 이전 렌더링 결과를 기억, 입력이 변경되었을 때만 재렌더링. 자식 컴포넌트의 불필요한 렌더링을 방지

  • 예시:
    import React, { memo } from 'react';
    
    function MemoComponent({ data }) {
      // data가 변경되지 않으면 이전에 렌더링된 결과를 재사용
      return <div>{data}</div>;
    }
    
    export default memo(MemoComponent);
  • 단점: 부모가 원시타입이 아닌 유형을 props의 배열로 전달하면 중단됨.

  • 이 문제를 해결하기 위해 변수값을 메모화하는 useMemo를 사용해보자!(아래)

2. useMemo를 사용한다

useMemo : 이전에 계산된 값을 캐시 → 동일한 입력에 대한 계산을 다시 수행하지 않음. 비용이 높은 함수나 연산 결과를 캐싱 → 불필요한 계산 방지

  • 예시:
    import React, { useMemo } from 'react';
    
    function UseMemoComponent({ data }) {
      const processedData = useMemo(() => {
        // 복잡한 데이터 처리 로직
        return processData(data);
      }, [data]);
    
      // 렌더링에 processedData 사용
      return <div>{processedData}</div>;
    }

3. useCallback을 사용한다

useCallback : 동일한 콜백 함수를 재생성하지 않고 재사용. 자식 컴포넌트에 props로 전달되는 콜백 함수를 최적화하는 데 유용함!

  • 예시
    import React, { useCallback } from 'react';
    
    function UseCallbackComponent({ onClick }) {
      const handleClick = useCallback(() => {
        // 클릭 핸들러 로직
        onClick();
      }, [onClick]);
    
      return <button onClick={handleClick}>Click Me</button>;
    }

4. useReducer를 사용한다

useReducer : 복잡한 상태 로직을 관리하는 데 유용. state와 state를 업데이트하는 reducer 함수를 정의해서 효율적으로 관리.

  • 예시:
    import React, { useReducer } from 'react';
    
    function CounterReducer(state, action) {
      switch (action.type) {
        case 'INCREMENT':
          return { count: state.count + 1 };
        case 'DECREMENT':
          return { count: state.count - 1 };
        default:
          return state;
      }
    }
    
    function Counter() {
      const [state, dispatch] = useReducer(counterReducer, { count: 0 });
    
      const increment = () => {
        dispatch({ type: 'INCREMENT' });
      };
    
      const decrement = () => {
        dispatch({ type: 'DECREMENT' });
      };
    
      return (
        <div>
          <p>Count: {state.count}</p>
          <button onClick={increment}>Increment</button>
          <button onClick={decrement}>Decrement</button>
        </div>
      );
    }

React State 최적화를 위한 방법 (심화)

hooks에 대한 기본적인 최적화 방법 외에 state를 최적화 할 수 있는 법을 알아보자.

1. Independent child && Careless parent

💡 자식 컴포넌트가 부모 컴포넌트의 상태에 의존하지 않고 독립적으로 동작할 수 있도록 설계하는 것을 의미

  • count상태는 구성 요소 내에서만 사용되고, 상위 컴포넌트에 해당 내용을 공유할 필요가 없음.
  • 상태를 로컬로 유지하고, count 개수가 업데이트될 때 부모 구성 요소에서 불필요한 재렌더링을 방지함.

2. Minimal states && Minimal render

💡 최소한의 상태와 렌더링을 유지하여 성능을 향상시키는 것을 의미 → 불필요한 상태 제거 & 변경 없는경우 랜더링 방지

Minimal States

	import React, { useState } from 'react';
           
    function TodoList() {
            
    const [todos, setTodos] = useState([
       { id: 1, text: '장보기', completed: false },
       { id: 2, text: '강아지 산책시키기', completed: true },
       { id: 3, text: '빨리하기', completed: false },
      ]);
            
    const toggleTodoCompletion = (id) => {
        setTodos((prevTodos) =>
        prevTodos.map((todo) =>
        [todo.id](http://todo.id/) === id ? { ...todo, completed: !todo.completed } : todo));
       };
         
      return (
    		 <ul>
      		   {todos.map((todo) => (
               <li
            	 key={[todo.id](http://todo.id/)}
            	 style={{textDecoration: todo.completed ? 'line-through' : 'none',}}
            	 onClick={() => toggleTodoCompletion([todo.id](http://todo.id/))}
            	>
            		{todo.text}
               </li>
              ))}
            </ul>
            );
           }
            
            export default TodoList;

적용한방식

  • TodoList 에서 ‘todo’라는 하나의 상태만 유지함(할일항목배열 저장)
  • toddleTodoCompletion 함수를 통해 항목의 완료 여부를 토글할때에도 상태를 업데이트 하는데, 이때 이전 상태를 받아와서 필요한 항목만 업데이트 함.

Minimal Render

            import React, { useState } from "react";
            const user = {
            name: "테스트 이름",
            email: "test@test.com"
            }; 
            
            function UserProfile() {
            const [isEditing, setEditing] = useState(false);
            const toggleEditing = () => {
            				setEditing(!isEditing);
            };
            
            return (
            	<div>
            		<h2>Minimal render 예시</h2>
            		{isEditing ? <input value={user.name} /> : <p>{user.name}</p>}
            		<button onClick={toggleEditing}>{isEditing ? "Save" : "Edit"}</button>
            </div>
            );
            }
            export default UserProfile;

적용한방식

  • 조건부 렌더링을 통해 현재 isEditing 상태에 따라 다른 요소를 렌더링
  • toggleEditing 함수는 클릭할 때마다 isEditing 상태를 토글하여 편집 모드와 일반 모드를 번갈아가면서 전환
  • isEditing 상태에 따라 불필요한 렌더링을 방지, 필요한 상황에만 렌더링되게함

3. Lifting the state

💡 하위 컴포넌트 간 공통 상태상위 컴포넌트로 올려서 관리하는 것을 의미 → 상태업데이트를 중앙에서 처리 & 상태 일관성 유지

        import React, { useState } from 'react';
        
        // 사용자 이름 관리
        function ParentComponent() {
          const [name, setName] = useState('홍길동');
        
          const handleNameChange = (newName) => {
            setName(newName);
          };
        
          return (
            <div>
              <h2>Parent Component 입니당</h2>
              <ChildComponent name={name} onNameChange={handleNameChange} />
            </div>
          );
        }
        
        // 이름표시+변
        function ChildComponent({ name, onNameChange }) {
          const handleChange = (event) => {
            const newName = event.target.value;
            onNameChange(newName);
          };
        
          return (
            <div>
              <h2>Child Component 입니당</h2>
              <p>Name: {name}</p>
              <input type="text" value={name} onChange={handleChange} />
            </div>
          );
        }
        
        export default ParentComponent;

적용한방식

  • 부모 컴포넌트 한곳에서 상태(state)를 생성하고 관리 ⇒ 자식컴포넌트로 state와 관련된 데이터를 props 로 전달
  • 부모 컴포넌트→ state를 업데이트 ⇒ 자식 컴포넌트로 전달되는 데이터 흐름!!
  • 필요에 따라 자식 컴포넌트에서 상태를 변경할 때, 부모 컴포넌트로 변경된 데이터를 전달하여 상태를 업데이트

참고링크

https://velog.io/@shin6403/React-렌더링-성능-최적화하는-7가지-방법-Hooks-기준

https://kellis.tistory.com/150

https://dev.to/redraushan/memoizing-react-components-2km7

https://lucianohgo.com/posts/react-memoization-cheatsheet

https://medium.com/@taraparakj75/react-state-management-everything-you-need-to-know-3175f04da6a

https://medium.com/wantedjobs/react-profiler를-사용하여-성능-측정하기-5981dfb3d934

profile
프론트엔드 주니어입니다. 그런데 서비스 기획을 곁들인,,,

0개의 댓글