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
에 저장하고 tempData
를 setTodos()
에 사용하고 있다.
두 이벤트 핸들러 모두 기존의 todos를 조회하여 새로운 배열을 만들기 때문에 todos에 의존적이다.
따라서 useCallback
의 두 번째 인자로 todos
가 명시되어야 한다. todos
가 새롭게 갱신되거나 변경되면 이벤트 핸들러가 다시 재선언되어야 하기 때문이다.
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 함수형 업데이트 방식으로 구현한다면 의도한대로 컴포넌트 최적화를 이룰 수 있을 것이다.