13. 리엑트 내장 훅
1) useRef
렌더링과 상관 없는 데이터를 저장할 때 useRef가 유용하게 사용될 수 있다.
export default function App() {
const timerIdRef = useRef(-1);
useEffect(() => {
timerIdRef.current = setTimeout(() => {}, 1000);
});
useEffect(() => {
if (timerIdRef.current >= 0) {
clearTimeout(timerIdRef.current);
}
});
}
age
값을 기억하기 위해서 prevAgeRef를 사용한다.prevAge.current
는 age
가 변경되기 이전의 값을 기억하고 있게 되는 것이다.setAge
호출 -> age
가 초기값 20에서 28로 변경됨 -> 렌더링 -> 이 때 prevAgeRef.current
는 20임 -> 화면에는 age
로 28이 출력되고 prevAge
로 20이 출력됨 -> prevAgeRef.current
에 28을 저장 -> setAge
호출 -> age
가 19로 변경됨 -> 렌더링 -> 이 때 prevAgeRef.current
는 28임 -> 화면에는 age
로 19가 출력되고 prevAge
로 28이 출력됨 -> … 반복export default function App() {
const [age, setAge] = useState(20);
const prevAgeRef = useRef(20);
useEffect(() => {
prevAgeRef.current = age;
}, [age]);
const prevAge = prevAgeRef.current;
const text = (age === prevAge) ? ('same') : (age > prevAge ? 'older' : 'younger');
return (
// ...
// 버튼을 누르면 setAge 함수를 호출하여 랜덤값으로 age를 변경한다.
);
}
2) useMemo
메모이제이션 기능이 있어, 계산량이 많은 함수의 반환값을 재활용하는 용도로 사용된다.
export default function App() {
const [v1, setV1] = useState(0);
const [v2, setV2] = useState(0);
const [v3, setV3] = useState(0);
const value = useMemo(() => {
runExpensiveJob(v1, v2); // 계산량이 많은 함수 호출
}, [v1, v2]);
return (
//...
);
}
3) useCallback
useMemo와 마찬가지로 메모이제이션 기능이 있으며, 함수 메모이제이션에 특화된 훅이다.
onSave={() => saveToServer(name)}
: 자식 컴포넌트에 함수를 입력해서 속성값으로 전달할 때는 App 컴포넌트가 렌더링될 때마다 새로운 함수가 생성되어 입력이 된다. 이러면 App 컴포넌트가 렌더링될 때마다 매번 onSave
속성값이 변경되기 때문에, 자식 컴포넌트인 UserEdit
컴포넌트 입장에서는 불필요하게 새로운 속성값으로 입력받고 불필요하게 다시 렌더링된다.React.memo
를 사용하더라도 이러한 문제가 발생하는데 useCallback
훅을 사용하면 이 문제를 해결할 수 있다.export default function App() {
const [name, setName] = useState('');
return (
<div>
<p>{`name is ${name}`}</p>
<UserEdit
onSave={() => saveToServer(name)}
setName={setName}
/>
</div>
);
}
const UserEdit = React.memo(function ({ onSave, setName }) {
//...
});
export default function App() {
const [name, setName] = useState('');
const onSave = useCallback(() => {
saveToServer(name);
}, [name]);
return (
<div>
<p>{`name is ${name}`}</p>
<UserEdit
onSave={onSave}
setName={setName}
/>
</div>
);
}
const UserEdit = React.memo(function ({ onSave, setName }) {
//...
});
4) useReducer
useState와 비슷하지만 여러 개의 상태값 관리할 때 사용하면 좋은 훅
export default function App() {
const [state, dispatch] = useReducer(reducer, INITIAL_STATE);
return (
<div>
<p>{`name is ${state.name}`}</p>
<p>{`age is ${state.age}`}</p>
<input
type='text'
value={state.name}
onChange={e => dispatch({ type: 'setName', name: e.currentTarget.value})}
/>
<input
type='number'
value={state.age}
onChange={e => dispatch({ type: 'setAge', age: e.currentTarget.value})}
/>
</div>
);
}
function reducer(state, action) {
switch(action.type) {
case 'setName':
return { ...state, name: action.name };
case 'setAge':
return { ...state, age: action.age };
default:
return state;
}
}
5) useReducer와 Context ApI
보통 상위 컴포넌트에서 다수의 상태값을 관리한다. 이 때 자식 컴포넌트에서 발생한 이벤트에서 상위 컴포넌트의 상태값을 변경해야 하는 경우가 많다. 이를 위해서 상위 컴포넌트에서 트리의 깊은 곳까지 이벤트 처리 함수를 전달하기도 하는데 이는 비효율적이다.
useReducer 훅과 Context API를 같이 이용하면 쉽게 전달할 수 있다.
ProfileDispatch
라는 Context
를 만들었다.value
로 useReducer의 dispatch
함수를 내려준다.dispatch
함수를 사용할 수 있다.export const ProfileDispatch = React.createContext(null);
export default function App() {
const [state, dispatch] = useReducer(reducer, INITIAL_STATE);
return (
<div>
<p>{`name is ${state.name}`}</p>
<p>{`age is ${state.age}`}</p>
<ProfileDispatch.Provider value={dispatch}>
<SomeComponent />
</ProfileDispatch.Provider>
</div>
);
}