[리액트 #2] useMemo, React.memo, useCallback, Custom Hook, Storybook

최훈오·2023년 12월 6일
0

데브코스

목록 보기
27/29

최적화에 관해서 다양한 훅들을 배웠다.

useMemo

함수 컴포넌트는 자신의 상태, 부모로부터 내려준 prop, 부모의 상태가 변경되면 리렌더링을 한다. 이때, 리렌더링 시 오래걸리는 연산이 있을 경우 리렌더링이 잦을 경우 똑같은 연산을 매번하여 성능이 안좋아져 버벅이는 심각한 문제가 발생한다.
알고리즘의 dp에서 사용하는 메모이제이션과 같이 똑같은 연산은 기억했다가 재연산하지않고 사용할 수 있다.

// const result = sum(n)
const result = useMemo(() => sum(n), [n])

콜백함수의 인수로, 실행할 함수, 어떤 변수에 의존할지를 정해주면 된다.

sum연산의 아무리 큰 n이 들어와도 n이 변하지 않는 이상 똑같은 n에 대하여 한번만 연산을 한다.

React.memo

위에서 언급한 함수 컴포넌트의 조건 중 세번째(부모 컴포넌트의 상태가 변하면 렌더링)는 약간 불필요해 보인다.
따라서, 이것을 막아주기 위해 사용하는 것이 React.memo이다.

// App.js
import { useState } from "react"
import Box from "./components/Box"

function App() {
	const [count, setCount] = useState(0)
	return (
		<div>
			{count}
			<button onClick={() => setCount(count + 1)}>+</button>
			<Box />
		</div>
	)
}

export default App
const Box = () => {
	console.log("자식 렌더링")
	return <div>자식</div>
}
export default Box

위의 코드에서 버튼을 누를 때마다 부모의 count상태에 따라 부모가 렌더링되면서 자식도 렌더링이 된다. 이것을 막기위해

import React from "react"

const Box = React.Memo(() => {
	console.log("Red")
	const style = {
		width: 100,
		height: 100,
		backgroundColor: "red",
	}
	return <div style={style} />
})

export default Box

자식 컴포넌트를 React.memo로 감싸주면 부모의 상태에 따라 자식이 렌더링되는것을 막을 수 있다.

useCallback

컴포넌트가 렌더링 되는 것은 함수가 정의된다는 것과 같은 말이다.

렌더링 될때마다 함수를 정의하는 것은 낭비이므로 useCallback을 사용해 막아보자.

// App.js

import { useCallback, useState } from "react"
import Checkbox from "./components/Checkbox"

function App() {
	const [foodOn, setFoodOn] = useState(false)
	const [clothesOn, setClothesOn] = useState(false)
	const [shelterOn, setShelterOn] = useState(false)
    
    // const foodChange = (e) => setFoodOn(e.target.checked)
	// const clothesChange = (e) => setClothesOn(e.target.checked)
	// const shelterChange = (e) => setShelterOn(e.target.checked)

	const foodChange = useCallback((e) => setFoodOn(e.target.checked), [])
	const clothesChange = useCallback((e) => setClothesOn(e.target.checked), [])
	const shelterChange = useCallback((e) => setShelterOn(e.target.checked), [])

	return (
		<div>
			<Checkbox
				label="Food"
				on={foodOn}
				onChange={foodChange}
			/>
			<Checkbox
				label="Clothes"
				on={clothesOn}
				onChange={clothesChange}
			/>
			<Checkbox
				label="Shelter"
				on={shelterOn}
				onChange={shelterChange}
			/>
		</div>
	)
}

export default App
// Checkbox.js

import React from "react"

const Checkbox = React.memo(({ label, on, onChange }) => {
	return (
		<label>
			{label}
			<input
				type="checkbox"
				defaultChecked={on}
				onChange={onChange}
			/>
		</label>
	)
})
export default Checkbox

기존의 React.memo만으로는 위의 예시에서 체크박스에 대한 이벤트를 동작 시킬 때 다른 체크박스들도 모두 렌더링이 된다. 이 경우는 함수를 useCallback으로 감싸면 해결 가능하다. 다른 훅들과 같이 두번째 인자로는 dependency가 위치한다.

CustomHook

커스텀훅은 특정 상태관리나 라이프사이클 로직들을 추상화하여 묶어서 재사용이 가능하도록 제작이 가능한 함수이다. 일반적으로 특정 상태와 특정 상태를 변화시키는 함수을 return한다.

사실 리액트를 처음 접한지 어느정도 지났음에도 커스텀 훅을 지금에서야 배우게 되었는데 그 전에는 이름만 듣고 좀 어려워보인다 생각했는데 그냥 재사용 가능하도록 구현한 함수이다. 이렇게 생각해보니 유틸함수와의 차이점이 뭐지? 라는 생각이 들었다.

찾아보니, 커스텀훅은 어디에서나 사용되는 유틸함수와 달리 내부에서 상태로직을 관리한다. 또한 리액트내에서 훅 규칙을 따라 use로 시작해야한다는 규칙도 있다.

// App.js

import useToggle from "./hooks/useToggle"

function App() {
	// 토글여부상태, 토글함수
	const [on, toggle] = useToggle()
	return (
		<div>
			<button onClick={toggle}>{on ? "True" : "False"}</button>
		</div>
	)
}

export default App
// useToggle.js

import { useCallback, useState } from "react"

const useToggle = (initialState = false) => {
	const [state, setState] = useState(initialState)
	const toggle = useCallback(() => setState((state) => !state), [])

	return [state, toggle]
}

export default useToggle

위와 같이 토글 여부를 커스텀 훅 내에서 관리하고 이것을 반환하여 App.js에서 사용이 된다. 커스텀 훅 특성상 컴포넌트마다 독립적인 상태를 관리하기 때문에 각 컴포넌트에서 커스텀 훅을 여러번 사용하는 경우에도 상태가 공유되지 않아 사이드 이펙트를 발생시키지 않는다.

마치며

이상하게 커스텀 훅 빼고는 다 한번씩 배웠던 것 같은데도 은근 이해하기 어렵고 심오한 부분 같다. 이해를 잘 못하면 깊은 수렁에 빠지는 느낌..?
강의에서는 아주 기본적인 부분만 설명해서 추가적으로 검색해서 공부하였는데 나중에 다시 볼때는 더 깊숙이 파봐야겠다!!
그리고 이 훅들은 직접 프로젝트에 적용하지 않는 이상 잘 와닿지 않을 것 같아 다음 프로젝트때 바로 사용해봐야겠다.

0개의 댓글