취업 준비시절 react 프로젝트를 빠르게 도입하고자, 기술문서를 스킵하고 필요한 내용을 그때 그때 찾아서 학습하였다. 그 결과... 항상 쓰던 Hook들만 사용하고 정확히 어떤 역할을 하는지, 어떤 Hook들이 있는지 알지 못하고 그냥 쓰는 느낌이 강하게 들었다. 현재 3개월 가량의 온보딩 과정을 진행중이므로 남는 시간에 공식문서와 구글링을 통해 놓쳤던 개념들을 다시 한번 짚고 가야겠다.
이전에는 대부분 react 컴포넌트를 클래스형 컴포넌트로 개발을 진행했다. 하지만 class에 고질적인 이슈인 this 바인딩 때문에 많은 개발자가 클래스형 컴포넌트에 불편함을 호소 하였다. 자바스크립트 this는 다른 언어와 다르게 런타임에 결정되기 때문에 this 바인딩 이슈로인해 종종 에러를 이르킨다.
이러한 문제들을 해결하기 위해 react 14.0버전에 함수형 컴포넌트가 등장하였지만, 라이프 사이클관련 이슈들을 해결하지 못해 사용하지 못하다가 16.8버전에 hooks에 추가와 함께 공식문서에서도 클래스형 컴포넌트보다 함수형 컴포넌트 사용을 권장한다.
const [state, setState] = useState(initialState)
setState(newState)
// 함수적 갱신
const [bool, setBool] = useState(true)
setBool((prev) => !prev)
// bool = true
useEffcet(() => {
// mount or update 시 처리할 로직
return () => {
// unMount 시 처리할 로직
}
}, [의존성 데이터])
const [count, setConut] = useState(0)
useEffect(() => {
console.log(count) // conut값이 변경될 때마다 콘솔 출력
}, [count])
export const AppContext = createContext()
function App() {
const [count, _] = useState(50)
return (
<AppContext.Provider value={{count}}>
<Context />
</AppContext.Provider>
);
}
export default App;
import React, {useContext} from "react";
import {AppContext} from "./App";
function Context() {
const {count} = useContext(AppContext)
return <div>{count}</div>
}
export default Context
const [state, dispatch] = useReducer(reducer, initialState, init함수 : optional)
state : 현재상태
dispatch : action을 발생시키는 함수
reducer : state와 action을 받아 새로운 State를 반환하는 함수
initialState : 초기 값
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};
case 'reset':
return init(action.payload);
default:
throw new Error();
}
}
function Counter() {
const initialCount = 0
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
Count: {state.count}
<button
onClick={() => dispatch({ type: 'reset', payload: initialCount })}>
Reset
</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
export default Counter;
const memo = useCallback(doSomething(a,b), [a,b])
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
function App() {
const countRef = useRef(0)
const increaseRef = () => {
countRef.current = countRef.current + 1;
}
return (
<div>{countRef}</div>
<button onClick={increaseRef}>plus</button>
);
}
function App() {
const ref = useRef(null)
useEffect(() => {
ref.current.focus()
}, [])
return (
<input ref={ref} type={"text"} />
);
}
// Dom에 접근하여 input focus
useImperativeHandle은 ref를 사용할 때 부모 컴포넌트에 노출되는 인스턴스 값을 사용자화 (customizes)합니다.
useImperativeHandle는 forwardRef와 더불어 사용하세요
useImperativeHandle(ref, createHandle, [deps])
import React, {forwardRef, useRef} from "react";
function ParentComponent(){
const inputRef = useRef(null);
const onClick = () => {
if(inputRef. current){
console.log(inputRef.current)
}
}
return (
<>
<ChildCompo ref={inputRef} />
<button onClick={onClick}>확인</button>
</>
)
}
export default ParentComponent;
function ChildCompo(props,ref){
const [value, setValue] = useState(0)
useImperativeHandle(ref, () => ({
squared : value => setValue(value + value),
getValue : () => value,
}))
return (
<>
<input ref={ref} type={"text"}/>
</>
)
}
export default forwardRef(ChildCompo)
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
// 개발자 도구에서 확인을 위한 라벨 붙이기
useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}
custom hook rules
- 사용자 정의 hook은 이름이 use로 반드시 시작해야한다.
- React 함수 컴포넌트에서만 hook을 호출해야 한다.
- 최상위에서만 Hook을 호출해야 한다.
- 반복문, 조건문 혹은 중첨된 함수 내에서 Hook을 호출하면 안된다.
- 컴포넌트가 렌더링 될 때마다 항상 동일한 순서로 Hook이 호출되는 것이 보장되기 때문이다.
// 온보딩 과정 중 Echart resize control을 위한 element observer hook
import { useEffect, useRef, useState } from "react";
type elementType = {
current: HTMLDivElement | null;
};
const useElementObserver = (elRef: elementType) => {
const [size, setSize] = useState(0);
const observer = useRef(
new ResizeObserver((entries) => {
const { width } = entries[0].contentRect;
console.log(entries);
setSize(width);
})
);
useEffect(() => {
if (elRef.current) observer.current.observe(elRef.current);
return () => {
if (elRef.current) observer.current.unobserve(elRef.current);
};
}, [elRef, observer]);
return size;
};
export default useElementObserver;