[React.js] useState의 함수형 업데이트를 통한 useCallback 상태 의존성 없애기

apro_xo·2021년 11월 17일
0
post-thumbnail

useState 업데이트 기존 방식

import React, { useState, useCallback } from 'react';
const myCompo = () => {
  const [todos, setTodos] 
  = useState([{id:1, text:'병원가기', checked:false}]);

  const onAdd = useCallback((e)=>{
    e.preventDafault();
    setTodos(todos.concat({id:2, text:'애플스토어가기', checked:false}));
  }, [todos])

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


  return(
    // 생략..
  )
}

이벤트 핸들러 onAdd, onRemove가 있다.

onAdd에서는 concat 함수가 반환하는 새로운 배열을 그대로 setTodos()에 사용하고 있다.

onRemove에서는 filter 함수가 반환하는 새로운 배열을 임시 변수 tempData에 저장하고 tempDatasetTodos()에 사용하고 있다.

두 이벤트 핸들러 모두 기존의 todos를 조회하여 새로운 배열을 만들기 때문에 todos에 의존적이다.

따라서 useCallback의 두 번째 인자로 todos가 명시되어야 한다. todos가 새롭게 갱신되거나 변경되면 이벤트 핸들러가 다시 재선언되어야 하기 때문이다.

useState 함수형 업데이트 방식

import React, { useState, useCallback } from 'react';
const myCompo = () => {
  const [todos, setTodos] 
  = useState([{id:1, text:'병원가기', checked:false}]);

  const onAdd = useCallback((e)=>{
    e.preventDafault();
    setTodos(todos=>
             todos.concat({id:2, text:'애플스토어가기', checked:false}));
  }, [])

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


  return(
    // 생략..
  )
}

useState를 함수형 업데이트로 바꿨다. setTodos의 업데이트 방식을 함수형으로 바꾼 것인데 아래와 같이 Arrow Function의 형태로 바꿨다.

  • setTodos in onAdd()
setTodos(todos=>todos.concat({id:2, text:'애플스토어가기', checked:false}));
  • setTodos in onRemove()
setTodos(todos=>todos.filter(todo=>todo.id!==id));

이렇게 함수형 업데이트 방식으로 바꾸면 useCallback() 에서 state 값의 의존성을 없애 두 번째 인자를 빈 배열로 명시할 수 있다.

왜 의존성이 없어질까?

함수형 업데이트 방식으로 바뀌면서 함수의 매개변수로 todos를 넘긴다.
기존의 변수가 매개변수로 넘겨지면 기존 변수 !== 매개변수 이기 때문에 의존성이 없어지는 것이 아닌가 생각해본다.

let num = 10;
function addfunc(value) {
  value = value + 10;
}
addfunc(num);
console.log(num) // 10

위 코드와 같이 addfunc() 밖에 선언된 num이라는 변수가 함수의 매개변수로 전달이 되어 10을 더하는 로직을 진행했다 하더라도 실제 함수 밖의 num이라는 변수를 출력해보면 10이 출력된다. 이처럼 함수 안의 num함수 밖의 num은 다르다는 것이다.

따라서 함수형 업데이트 방식으로 useState를 사용한다면 useCallback에서 state 값을 직접적으로 조회하는 것이 아니라 간접적으로 조회하는 것이 되므로 의존성이 없어지는 것이 아닐까 생각해본다.

성능 향상

useState를 단지 함수형 업데이트 방식으로 사용한다 하더라도 성능이 좋아지지는 않는다. 하지만 아래의 코드를 참고해서 설명하면,

// tempCompo.jsx
import React, { useState, useCallback } from 'react';
import listItem from './listItem';

const tempCompo = () => {
  const [todos, setTodos] = useState(
    {
      id: 1,
      text : '학교가기',
      checked: false
    },
    {
      id: 2,
      text: '애플스토어가기',
      checked: false
    }
  )
  
  // 함수형 업데이트 방식 X
  const onToggle = useCallback(()=> {
     setTodos(todos =>
              todos.map(todo=>
              todo.id ===id ? {...todo, checked: !todo.checked} : todo))
  }, [todos])
  
  
  return(
    {todos.map(
     (<listItem key={todo.id} checked={todo.checked}
        text={todo.text} onToggle={onToggle}/>))}
  )
}

//listItem.jsx
import React from 'react';
const listItem = ({checked, text, onToggle}) => {
  return(
    <div> {text} </div>
    <button onClick={onToggle}>버튼</button>
  )
}
export default React.memo(listItem);

위와 같이 useState를 함수형 업데이트 방식이 아니라 원래의 방식으로 구현하고, listItem.jsx에서 React.memo()를 이용하여 컴포넌트 최적화를 진행하였다. listItem 컴포넌트 하나만 보면 버튼을 눌러 "학교가기" 항목의 checked를 true로 변경했다 하더라도 "애플스토어가기" 컴포넌트가 리렌더링 되지는 않는다. 하지만 state관점으로 본다면 state값인 todos에 변화가 생겼기 때문에 onToggle()을 재생성 하기 때문에 리소스 낭비가 있다.

이를 useState 함수형 업데이트 방식으로 구현한다면 의도한대로 컴포넌트 최적화를 이룰 수 있을 것이다.

profile
유능한 프론트엔드 개발자가 되고픈 사람😀

0개의 댓글