
function reducer(state, action) {
switch (action.type) {
case "INCREASE":
return state + action.data
case "DECREASE":
return state - action.data
default:
return state
}
}
| 인자 | 설명 |
|---|---|
| state | 현재 상태 |
| action | 어떤 상태 변화를 할지에 대한 정보 |
dispatch는 reducer를 실행시키는 함수dispatch({
type: "INCREASE",
data: 1
})
| 속성 | 설명 |
|---|---|
| type | 어떤 동작인지 구분 |
| data | 상태 변경에 필요한 값 |
const [state, dispatch] = useReducer(reducer, initialState)
| 요소 | 설명 |
|---|---|
| state | 현재 상태 |
| dispatch | 상태 변경 요청 |
| reducer | 상태 변경 로직 |
| initialState | 초기 상태 |
dispatch 실행 reducer 호출 | useState | useReducer |
|---|---|
| 간단한 상태 관리 | 복잡한 상태 관리 |
| 상태 로직이 컴포넌트 내부 | 상태 로직을 외부로 분리 가능 |
| 직관적인 사용 | 구조적으로 상태 관리 가능 |
상태 변화 로직이 많아질수록 useReducer를 사용하면 상태 관리를 더 명확하게 할 수 있음
예를 들어 TODO 리스트의 통계를 계산하는 함수가 있다고 가정
const getAnalyzedData = () => {
const totalCount = todos.length
const doneCount = todos.filter((todo) => todo.isDone).length
const notDoneCount = totalCount - doneCount
return { totalCount, doneCount, notDoneCount }
}
const { totalCount, doneCount, notDoneCount } = getAnalyzedData()
이 경우 검색어를 입력할 때마다 컴포넌트가 리렌더링되면서
getAnalyzedData 함수도 매번 다시 실행됨
하지만 todos가 변경되지 않았다면 이 연산은 다시 실행할 필요가 없음
const { totalCount, doneCount, notDoneCount } =
useMemo(() => {
const totalCount = todos.length
const doneCount = todos.filter((todo) => todo.isDone).length
const notDoneCount = totalCount - doneCount
return { totalCount, doneCount, notDoneCount }
}, [todos])
todos가 변경될 때만 연산 실행useMemo 두 번째 인자로 전달하는 배열| 형태 | 실행 시점 |
|---|---|
| 없음 | 렌더링마다 실행 |
| [] | Mount 시 한 번 |
| [value] | value 변경 시 실행 |
예를 들어 TODO 리스트에서 체크박스를 클릭하면 상태가 변경되면서
부모 컴포넌트가 리렌더링된다.
이때 부모가 리렌더링되면 자식 컴포넌트들도 함께 리렌더링됨
하지만 자식 컴포넌트의 props가 변경되지 않았다면
이 리렌더링은 불필요한 작업이 될 수 있다.
→React.memo로 리렌더링 방지 가능
import { memo } from "react"
export default memo(Component)
React.memo는 props를 얕은 비교로 판단
이 때문에 함수 props가 전달되는 경우 문제가 발생할 수 있음
컴포넌트가 리렌더링될 때마다 함수가 새롭게 생성되기 때문에
참조값이 계속 변경되는 것으로 판단됨
→ React.memo를 사용해도 리렌더링이 발생할 수 있음
memo는 두 번째 인자로 비교 함수를 전달할 수 있다.
export default memo(Component, (prevProps, nextProps) => {
// true → props 변경 없음 (리렌더링 방지)
// false → props 변경 있음 (리렌더링 발생)
if (prevProps.id !== nextProps.id) return false
if (prevProps.isDone !== nextProps.isDone) return false
if (prevProps.content !== nextProps.content) return false
if (prevProps.date !== nextProps.date) return false
return true
})
memo == 고차 컴포넌트컴포넌트가 리렌더링되면 내부에 선언된 함수도
매번 새롭게 생성됨
이때 함수가 자식 컴포넌트에 props로 전달되면
참조값이 변경된 것으로 판단됨
→ React.memo를 사용해도 리렌더링이 발생할 수 있음
const onCreate = useCallback((content) => {
dispatch({
type: "CREATE",
data: {
id: idRef.current++,
isDone: false,
content: content,
date: new Date().getTime(),
}
})
}, [])
→ React.memo와 함께 사용하면 불필요한 리렌더링 방지 가능
최적화는 기능 구현 이후에 진행하는 것이 일반적
너무 이른 시점에 적용하면
그래서 보통
기능 구현 → 테스트 → 최적화
순서로 진행
모든 코드에 최적화를 적용하는 것은 권장되지 않음
이런 경우에 선택적으로 적용
반대로 Header와 같은 단순한 컴포넌트는
리렌더링이 발생해도 비용이 매우 작기 때문에
굳이 최적화를 적용하지 않아도 됨
props를 전달해야 하는예를 들어 다음과 같은 구조가 있다고 가정한다면,
App
└ Header
└ Navigation
└ Profile
Profile 컴포넌트에서 필요한 데이터를
App → Header → Navigation → Profile 순서로 계속 전달해야 한다.
이처럼 중간 컴포넌트들이 사용하지 않는 props를 전달만 하는 문제를
Props Drilling이라고 한다.
→ Context를 사용하면 중간 컴포넌트를 거치지 않고 데이터 전달 가능함
import { createContext } from "react"
export const TodoContext = createContext()
createContext()로 Context 객체 생성Context 값을 하위 컴포넌트에 전달하는 컴포넌트
<TodoContext.Provider value={value}>
<App />
</TodoContext.Provider>
| 속성 | 설명 |
|---|---|
| Provider | Context 값을 공급 |
| value | 전달할 데이터 |
Provider로 감싸진 컴포넌트들은
해당 Context 값을 사용할 수 있다.
Context 값을 사용하는 리액트 훅
import { useContext } from "react"
import { TodoContext } from "./TodoContext"
const value = useContext(TodoContext)
Provider의 value를 가져옴props 없이 데이터 사용 가능createContext()로 Context 생성 Provider로 컴포넌트 감싸기 useContext로 값 사용Provider의 value가 변경되면
해당 Context를 사용하는 컴포넌트들이 모두 리렌더링된다
<TodoContext.Provider value={{ todos, onCreate }}>
이 경우 컴포넌트가 리렌더링될 때마다
객체가 새롭게 생성됨
→ Context 값이 변경된 것으로 판단
→ useContext를 사용하는 컴포넌트 모두 리렌더링
React.memo는 props만 비교한다.
하지만 Context 값이 변경되면
useContext를 사용하는 컴포넌트는 리렌더링이 발생
Context를 두 개로 분리하여 해결 가능
const TodoStateContext = createContext()
const TodoDispatchContext = createContext()
| Context | 역할 |
|---|---|
| State Context | 변경되는 값 |
| Dispatch Context | 변경되지 않는 함수 |
이렇게 분리하면
정리
Provider의 value가 변경되면 모든 소비 컴포넌트 리렌더링React.memo로 막을 수 없음