복잡한 상태를 관리할때 사용하는 훅.Redux 라이브러리를 쓸 때 썼던 기억이 난다.
인자로 reducer와 initialState를 받고,
state와 dispatch를 리턴한다.
reducer는 previousState와 action(type과 payload를 가지는 객체)을 인자로 받고,
변경될 상태를 return하는 함수이다.
import React, { useReducer } from 'react';
const ACTIONS = {
INCREMENT: 'increment',
DECREMENT: 'decrement',
};
function reducer(state, action) {
switch (action.type) {
case ACTIONS.INCREMENT:
return { count: state.count + 1 };
case ACTIONS.DECREMENT:
return { count: state.count - 1 };
default:
return state;
}
}
const Reducer = () => {
const [state, dispatch] = useReducer(reducer, { count: 0 });
const increment = () => {
dispatch({ type: ACTIONS.INCREMENT });
};
const decrement = () => {
dispatch({ type: ACTIONS.DECREMENT });
};
return (
<>
<button onClick={decrement}>-</button>
<span>{state.count}</span>
<button onClick={increment}>+</button>
</>
);
};
export default Reducer;
하위컴포넌트에서 상위컴포넌트 상태를 변경해야할 때 dispatch만 넘겨주면 되니까 편할 것 같다.
여기서부턴 사용해 본 적 없던 훅이다. 최근에 봤던 토스 slash 컨퍼런스에서 언급이 된걸 기억하니 함 봐보자.
모든 setState함수는 기본적으로 우선순위가 같다. 특정함수에서 setState함수를 여러번 호출한다면 모든 작업이 끝난뒤 상태가 변경된 다음 렌더링이 이루어진다. 만약 그 사이에 비동기처리등 시간이 오래 걸리는 작업이 있다면? ⇒ 그 모든 작업이 끝나서야 렌더링이 이루어진다.
이 순서를 바꾸기 위해 useTransition 훅을 사용한다.
리턴하는 isPending은 transition이 끝났는지 확인하는 값이고
startTransition에 우선순위가 낮은 함수를 넘겨준다.
import React, { useState, useTransition } from 'react';
const Transition = () => {
const [isPending, startTransition] = useTransition();
const [input, setInput] = useState('');
const [list, setList] = useState([]);
function handleChange(e) {
setInput(e.target.value);
startTransition(() => {
const l = [];
for (let i = 0; i < 20000; i++) {
l.push(e.target.value);
}
setList(l);
});
}
return (
<>
<input type='text' value={input} onChange={handleChange} />
{isPending ? <div>loading...</div> : list.map((item) => <div>{item}</div>)}
</>
);
};
export default Transition;
위의 useTransition과 유사하게 낮은 우선순위를 지정하기 위한 훅이다. useTransition은 함수실행의 우선순위를 지정하고, useDeferredValue는 값의 우선순위를 지정한다.
우선순위가 높은 작업을 수행하면서 값의 업데이트를 지연시킨다.
import React, { useDeferredValue, useEffect, useMemo, useState } from 'react';
const List = ({ input }) => {
const LIST_SIZE = 20000;
const deferredInput = useDeferredValue(input);
const list = useMemo(() => {
const l = [];
for (let i = 0; i < LIST_SIZE; i++) {
l.push(<div key={i}>{deferredInput}</div>);
}
return l;
}, [deferredInput]);
useEffect(() => {
console.log(`input : ${input}\n deferredValue:${deferredInput}`);
}, [input, deferredInput]);
return <div>{list}</div>;
};
const DeferredValue = () => {
const [input, setInput] = useState('');
function handleChange(e) {
setInput(e.target.value);
}
return (
<>
<input type='text' value={input} onChange={handleChange} />
<List input={input} />
</>
);
};
export default DeferredValue;
input이 계속 업데이트 되는 동안 defferedInput은 업데이트가 지연이 된다.
useEffect와 비슷한데 useEffect는 DOM이 페인팅 된 이후에 동작하고 useLayoutEffect는 이전에 동작한다. 그리고 useEffect는 비동기적으로 동작하고 useLayoutEffect는 동기적으로 동작한다.
따라서 로직이 복잡할경우 useLayoutEffect를 사용하면 사용자가 컴포넌트를 보는데까지 오래걸린다. 화면이 깜빡이는 걸 보여주지 않는 단순한 작업을 할 때 사용하면 좋을듯 하다.
customHook을 사용할 때 react-dev-tool에서 편하게 상태를 확인하기 위해 사용한다.
유니크한 ID를 리턴해주는 훅