기본값을 선언하기 위해 useState()의 인수로 원시값을 넎는 경우 가 일반적이다. useState의 인수로 함수를 넘겨줄 수 있다. 이를 게으른 초기화(Lazy Initialization)이라고 한다.
일반적으로
// 일반적인 useState 사용 방식, 인자로 함수의 결과값으로 나오는 원시값을 넘겨준다.
const getValue = () => {
console.log(window.localStorage.getItem("value"));
return 0;
};
const [count, setCount] = useState(getValue());
이렇게 작성하는 경우 위 실행 화면 처럼 리랜더링이 발생할 떄마다, 함수를 실행하게 된다.
// 초기값으로 함수를 넘겨주는 경우
const getValue = () => {
console.log(window.localStorage.getItem("value"));
return 0;
};
const [count, setCount] = useState(() => getValue());
반면에 함수로 초기값을 설정하게 되면 위 예시 처럼 첫 랜더링 에서만 함수를 실행하고, 이후 state 변화에 따른 리랜더링에서는 함수를 실행하지 않는 것을 확인할 수 있다.
게으른 초기화는 주로 초기화 함수의 연산이 무거운 경우 사용을 권장하고 있다. 이 함수는 처음 만들어질 때만 실행되며 이후 리랜더링이 발생했을 때는 함수의 실행이 무시된다.
이런 특징을 활용해서 초기값이 어떤 무거운 로직에 의해서 결정된다면, 단순히 연산 값을 넘겨주는 것이아니라 함수를 넘겨주어 랜더링 시 한번만 연산을 수행하고 이후에는 기존 값을 사용하도록 하는 것이 경제적이다.
"useEffect가 동기적으로 부수효과를 만든다."는 설명에서 useEffect의 실행 자체의 동작과 약간 헷갈리는 부분이 생겼다.
생각하기에 동기적으로 부수효과를 만든다는 것은 useEffect가 동기적으로 실행되면서 전달받은 콜백함수를 실행하는 것으로 이해했는데 이것이 아니었다. 내가 생각한 방식대로면 하나의 컴포넌트 내부에서 실행되는 여러 useEffect는 동기적으로 동작해야한다.
하지만 이것은 내가 잘못 이해하고 있었다.
useEffect 훅 자체가 실행되는 것은 컴포넌트 랜더링 이후에 비동기적으로 실행된다. 헷갈렸던 동기적으로 부수효과를 만든다는 것은 useEffect가 실행 대기열에 들어가 실행과정에서 호출된 콜백함수의 동작 방식이 JS의 기본 동작처럼 동기적으로 실행됨을 의미한다.
이걸 미리 알았더라면…
const MyContext = createContext<{ hello: string } | undefined >(undefined)
function ContextProvider({ children, text }: PropsWithChildren<{ text: string }>) {
return (
<MyContext.Provider value={{ hello: text }}>{ children }</MyContext.Provider>
)
}
function useMyContext() {
const context = useContext(MyContext);
if (context == undefined) {
throw new Error(
'useMyContext는 ContextProvider 내부에서만 사용할 수 있습니다.',
)
}
return context;
}
function ChildContext() {
// useMyContext 훅에서 context가 undefined에 대한 예외를 처리하고 있기 때문에
// 컴포넌트에서 체크할 필요가 없다.
const { hello } = useMyContext()
return <>{ hello }</>
}
function ParentComponent() {
return (
<>
<ContextProvider text='react'>
<ChildComponent />
</ContextProvider>
</>
)
}
forwardRef를 통해 전달받은 ref에 대해서 커스터마이징된 동작을 정의할 수 있게 해주는 훅이다
.기본적으로 ref
는 DOM 요소나 컴포넌트 인스턴스를 직접 참조하지만 useImperativeHandle
을 사용하면 부모 컴포넌트에서 자식 컴포넌트의 특정 동작을 제어할 수 있는 메서드들을 명시적으로 노출할 수 있습니다.
// example
const ChildComponent = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
customMethod() {
alert("This is a custom method exposed by useImperativeHandle");
},
}));
function ParentComponent() {
const childRef = useRef(null);
const handleClick = () => {
if (childRef.current) {
childRef.current.customMethod(); // 자식의 메서드를 호출
}
};
return (
<>
<ChildComponent ref={childRef} />
<button onClick={handleClick}>Call Child Method</button>
</>
);
}
useImperativeHandle에는 forwardRef로 전달받은 ref를 첫번째 인자로 받고, 두번째 인자로 createHandle이라는 함수를 전달받는데, 함수의 결과값으로 전달하는 객체를 부모 컴포넌트가 참조하여 정의된 메소드를 활용한다. 일반적으로는 노출하려는 메서드가 있는 객체를 반환한다.
세번째 인자로는 의존성 배열을 전달 받는데 다른 훅들과 마찬가지로 의존성 배열 내에 있는 상태가 변경되었을 때 두번째 인자로 받은 createHandle함수가 다시 실행된다.
위 예시를 통해 이해하자면, Ref를 전달받은 자식 컴포넌트가 useImperativeHandle훅을 통해 customMethod를 정의해두었고, 부모 컴포넌트는 자식 컴포넌트의 ref를 활용하여 customMethod를 호출하여 활용할 수 있다.
프로덕선 애플리케이션에 사용되는 훅이아니라 개발 과정에서 주로 사용되는 훅이다.
해당 훅은 훅 내부에서 특정 내용에 대한 정보를 남길 수 있다.
useDebugValue(value, (value) => `현재 값: ${value}`)
첫번째 인자로는 사용할 데이터를 받고 있고, 두번째 인자로는 첫번째 값을 포맷팅할 수 있는 함수를 전달받는다.
useDebugValue는 다른 훅 내부에서만 활용할 수 있고, 컴포넌트 레벨에서는 활용할 수 없다.