useState
1.
이 버튼을 누르면 값이 이전 값과 같기 때문에 렌더링하지 않음
...
<button onClick={()=>setCount(1)}>1</button>
...
객체는 참조형이기 때문에 주소값이 달라져 렌더링함
...
<button onClick={()=>setCount({count:1})}>1</button>
...
- 객체는 참조형이라서 값만 바뀌면 메모리 주소 안 바뀜
밑 예시는 렌더링 안 됨
...
<button onClick={ state.count = 1; setState(state) }>1</button>
...
2. 함수형 업데이트 쓰셈
...
<button onClick={()=>{setCount(count+1)}> {count} </button>
...
위 코드는 빠르게 두 번 누르면 한번만 카운팅됨
이건 상태 업데이트를 비동기적으로 하는 리액트 때문임
(근데 이 설명보다는 한번에 많은 요청이 오면 성능 최적화를 위해 일괄적으로 처리하려는 리액트 때문에 그런 거 같음)
그래서 보통 함수형 업데이트 씀
c => c + 1가 호출되면서 이전 값을 기반으로 갱신함
...
<button onClick={()=>setCount((c)=>c+1)}>+</button>
...
useReducer
import React, { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
리액트 구조로 보면 그냥 view-action을 나눈 구조임
reducer 함수에서 action.type 으로 받아서 쓰고 있는데 그냥 원시 값 넣어도 됨
reducer의 세번째 매개 변수 : 지연 초기화 (Lazy initialization) : 초기값을 나중에 계산하거나 생성하는 것
function init(initialCount) {
return { count: initialCount };
}
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
const initialCount = 0;
const [state, dispatch] = useReducer(reducer, initialCount, init);
지연 초기화 <= 번역 그대로
두번째 자리랑 좀 헷갈리는데
두번째는 초기값이라면
세번째 자리는 초기값을 동적으로 생성할 수 있는 함수임
초기값 생성하는 로직을 따로 분리할 수 있고
초기값이 복잡하거나 계산이 필요할 때 유용함 (마운트 될 때 한번만 실행되니까)
예를 들어서
게임을 실행할때마다 캐릭터의 좌표를 랜덤으로 생성해야하는 경우일 때 ( init을 랜덤하게 바꿔야 함) 씀
밑은 예시
function init() {
const initialPosition = { x: Math.random() * 100, y: Math.random() * 100 };
const initialLives = 3;
const initialScore = 0;
return { position: initialPosition, lives: initialLives, score: initialScore };
}
function reducer(state, action) {
switch (action.type) {
case 'move':
return { ...state, position: action.payload.newPosition };
case 'incrementScore':
return { ...state, score: state.score + 1 };
case 'loseLife':
return { ...state, lives: state.lives - 1 };
default:
throw new Error();
}
}
const [state, dispatch] = useReducer(reducer, undefined, init);
3. useState vs useReducer
둘다 16.8 버전때 나옴
뭐가 먼저라고 할 순 없지만
실제로 리액트 내부에서 useState는 useReducer로 구현되어 있음
useReducer로 useState 구현한다면
const useState = (initialState) => {
// 리듀서 함수, initState
const [state, dispatch] = useReducer((prev,action)=>{
typeof action === 'function' ? action(prev) : action, initialState
})
return [state, dispatch]
}
const [state, setState] = useState(초기값)
형태 만들기 가능
useState로 useReducer 구현한다면
const useReducer = (reducer, initalState) => {
const [state, useState] = useState(initalState)
const dispatch = (action) => setState(prev=>reducer(prev, action));
return [state, dispatch]
}
아까 3번째 지연 초기화도 구현 가능함
const useReducer = (reducer, initArg, init) => {
const [state, useState] = useState(init? () => init(initState) :initalArg)
const dispatch = useCallback((action) => setState(prev=>reducer(prev, action)),[reducer])
return [state, dispatch]
}
이렇게 쓰면 useReducer를 거의 완벽하게 대처함
-----------------------------------
책에서는 useReducer 사용을 지향하는 거 같았음
근데 선호도, 프로그래밍 스타일에 따라 선택하라는 거 보면 그냥 useState를 쓰되, action을 나눌 때가 있다면 그냥 useReducer를 쓰는게 좋을 거 같다
++
useReducer만 할 수 있는 거
const useScore = (bonus) => {
return useReducer((prev, delta)=> prev + delta + bonus, 0)
}
bonus 같은 외부변수에 의존 가능함
단, bonus와 delta 가 모두 갱신된 경우에만 올바르게 작동함
그렇기 떄문에 정말 필요한 경우가 아니라면 권장하진 않음
'TIL' 카테고리의 다른 글
리액트 지역 상태 전역 상태 (0) | 2024.04.11 |
---|---|
strategy template 패턴 (0) | 2024.04.11 |
Iterator 패턴 (0) | 2024.03.19 |
자바스크립트 접근 제한자 (0) | 2024.03.09 |
리액트 - 순수 함수 (0) | 2024.03.05 |
댓글