0%

Hooks

Hooks


기본 Hook


useState

1
const [state, setState] = useState(initialState);
  • useState() : statesetState 을 반환하는 함수

    • initialState : 초기값
      • 보통 null 이나 "" 등의 default value로 설정한다.
  • state : 상태 유지 (저장) 값

    • 최초 렌더링 시 반환된 stateinitialValue 이다.
  • setState : state 값을 갱신하는 함수

    1
    setState(newState);
    • state 값을 받으면 컴포넌트 리렌더링을 큐에 등록한다.
    • 리렌더링 시 useState 가 반환하는 첫번째 값 (인자) 은 갱신된 최신 state 이다.

Note

React guarantees that setState function identity is stable and won’t change on re-renders. This is why it’s safe to omit from the useEffect or useCallback dependency list.


Functional Updates

함수적 갱신


  1. setState 가 값을 갱신하는 과정을 직접 코드로 작성하면 다음과 같다.

    1
    2
    3
    4
    5
    const [state, setState] = useState({});
    setState(prevState => {
    // Object.assign would also work
    return {...prevState, ...updatedValues};
    });

    갱신된 객체를 자동으로 merge하는 useState 와 반대로, setStateinitialValue 를 유지한다.

    이전 state 를 이용해서 값을 갱신하는 경우, 이전 값을 받아 갱신된 값을 반환하는 함수를 setState 로 전달하면 된다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function Counter ({ initialCount }) {
    const [count, setCount] = useState(initialCount);
    return (
    <>
    Count: {count}

    // 초기화
    <button onClick={() => setCount(initialCount)}>Reset</button>

    // 이전 값을 이용해서 값을 갱신하는 경우
    // parameter value : 현재 값
    // return value : 갱신할 값
    <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
    <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
    </>
    );
    }

Note

Another option is useReducer, which is more suited for managing state objects that contain multiple sub-values.


  1. 업데이트 함수 (setState에 전달한 함수) 가 현재 상태와 정확히 동일한 값을 반환한다면 바로 뒤에 일어날 리렌더링은 완전히 건너뛰게 된다.

React에서 {} 를 쓰는 경우

  • JSX 내부에서는 객체를 받아옴
    • {{}} : 객체를 받아오는 것이 아니라 JSX 내부에서 생성할 때
  • JSX 외부에서는 일반 JS의 문법 중 function destructurizing을 실행

Lazy Initial State

지연 초기 state


initialState 인자는 초기 렌더링 시에 사용하는 state (값) 으로, 리렌더링 시 이 값은 무시된다.

만약 초기 state가 고비용 계산의 결과라면, 초기 렌더링 시에만 실행될 함수를 정의하여 전달하는 것이 효율적이다.

1
2
3
4
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});

Bailing Out of a State Update

state 갱신의 취소


앞서 설명한 “업데이트 함수 (setState에 전달한 함수) 가 현재 상태와 정확히 동일한 값을 반환한다면 바로 뒤에 일어날 리렌더링은 완전히 건너뛰게 된다.” 를 추가적으로 설명하자면,

State Hook을 현재 state와 동일한 값으로 갱신 (update) 할 경우, 리액트는 children or firing effects를 렌더링하는 과정을 건너뛰고 실행 (처리) 을 종료한다.


Note

React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree. If you’re doing expensive calculations while rendering, you can optimize them with useMemo.


useEffect

함수형 컴포넌트 내에서는 React’s render phase에 따라 Mutations, subscriptions, timers, logging, and other side effects 가 허용되지 않는다.

이를 위해 useEffect 를 사용할 수 있다.

1
useEffect(didUpdate);
  • useEffect() : 모든 렌더링이 완료된 후 / 어떤 값이 변경되었을 때에만 동작 ( didUpdate ) 을 수행
  • didUpdate : 특정 effect를 발생시키는 (명령형) 함수

Cleaning Up an Effect

effect 정리


Effect는 컴포넌트가 화면에서 제거될 때 정리되어야 하는 요소 (e.g. subscription, timer ID 등) 를 만들곤 한다. 이를 정리하는 함수를 useEffect 에 전달할 수 있다.

1
2
3
4
5
6
7
8
useEffect(() => {
// new subscription is created on every update
const subscription = props.source.subscribe();
return () => {
// Clean up the subscription
subscription.unsubscribe();
};
});
  • clean-up function (e.g. subscription.unsubscribe() )은 컴포넌트가 제거되기(=== 다음의 effect) 이전 에 실행되어 memory leaks를 방지한다.
  • 컴포넌트가 여러번 리렌더링 될 경우, 이전의 effect는 다음의 effect가 실행되기 이전 에 정리된다.

Timing of Effects

effect 타이밍


useEffect에 전달된 함수의 경우 레이아웃 배치과 그리기를 완료한 후, 지연된 이벤트가 발생하는 동안 실행된다. 이는 브라우저가 화면을 업데이트하는 과정을 방해하지 않기 때문에, 여러 side effects (e.g. setting up subscriptions and event handlers) 를 실행하기에 적합한 때이다.

useEffect 는 브라우저가 그려지기 이전까지는 대기하지만, 새로운 렌더링이 발생하기 이전에 실행되는 것을 보장한다. 리액트는 언제나 새로운 업데이트가 발생하기 이전에 이전의 렌더링 effect를 모두 제거한다.


useLayoutEffect

DOM 변경과 같이 사용자에게 effect가 보여지는 경우, visual inconsistency 방지를 위해 다음 execution (event) 이 실행되기 이전에 현재 화면이 렌더링이 됨과 동시에 effect가 (synchronously) 발생하여야 한다.


Conditionally Firing an Effect

조건부 effect 발생


Effect의 default behavior은 렌더링이 모두 완료된 이후 실행되는 것이다. 이는 해당 effect의 dependency 중 하나가 변경될 때마다 새로 실행됨을 의미한다.

앞선 예제를 통해서 자세히 보면,

1
2
3
4
5
6
7
8
useEffect(() => {
// new subscription is created on every update
const subscription = props.source.subscribe();
return () => {
// Clean up the subscription
subscription.unsubscribe();
};
});

subscription 은 매 업데이트마다 생성될 필요 없이, source prop 이 변할 때만 새로 생성되면 된다.

이와 같은 현상을 방지하기 위해, useEffect 의 2번째 argument로 effect가 의존하는 (depend on, dependency) 배열을 전달한다.

따라서 위 예제를 수정해보면,

1
2
3
4
5
6
7
8
9
10
11
useEffect(
// 전달하는 함수는 동일하되,
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
// 두번째 인자로 dependency array (depend on하는 배열) 을 전달한다.
[props.source],
);

이를 통해 subscriptionprops.source가 변경될 때에만 재생성된다.


만약 effect를 (mount 또는 unmount 시) 한 번만 수행하고 싶다면 두번째 인자로 빈 배열 ( [] ) 을 전달하면 된다. 이를 통해 effect는 컴포넌트 범위에서 가져온 값들 (e.g. props, state 등) 에 전혀 의존하지 않으므로 해당 값들이 변경되어도 다시 실행되지 않는다.

빈 배열 ( [] ) 은 effect 안에 있는 propsstate 가 항상 초기값을 가지게 됨을 의미한다. 이는 componentDidMountcomponentWillUnMount 의 기능과 같다.


두번째 인자로 전달하는 배열은 effect에 사용되는 컴포넌트 범위의 모든 값 (e.g. props, state 등) 들을 포함해야 한다. 이를 위반할 시 이전 렌더링에서 설정한 값을 참조하는 버그가 발생한다.

이렇게 전달된 dependency 배열은 effect 함수에 인자로 전달되지 않는다. 하지만 effect 함수 내에서 참조된 모든 값들이 해당 배열에 포함되어야 하므로, 전달 여부는 유의미하지 않다.


useContext

1
const value = useContext(MyContext);
  • useContext() : ( React.createContext 를 통해 반환된 값인 ) context 객체에 **현재 context 값 (MyContext) **을 반환한다.
    • 가장 가까운 상위 컴포넌트 <MyContext.Provider> 가 업데이트 되면, useContext 는 해당 컴포넌트에 전달 (포함) 된 가장 최신의 context value 에 따라 화면을 리렌더링 한다.
  • MyContext : 현재 context 값 (객체) 으로, 트리 구조에서 해당 컴포넌트의 상위 컴포넌트이면서 가장 가까운 <MyContext.Provider>value prop 에 따라 결정된다.
    • useContext() 의 인자는 context 객체여야 한다.
      • Correct: useContext(MyContext)
      • Incorrect: useContext(MyContext.Consumer)
      • Incorrect: useContext(MyContext.Provider)

useContext 를 사용한 컴포넌트는 context 값이 변경될 때마다 리렌더링된다. 리렌더링 되는 컴포넌트가 비싸다면, momoization 을 이용해 효율성을 높일 수 있다.

상위 컴포넌트에서 React.memoshouldComponentUpdate 를 쓰더라도, useContext 를 사용한 컴포넌트부터 리렌더링이 진행 (시작) 된다.


useContext 는 context를 읽고 변경사항을 구독 (확인) 하는 것만 가능하기 때문에, 해당 context의 값을 전달하기 위해 트리 구조 내 상위 컴포넌트로서 <MyContext.Provider> 가 필요하다.


Putting it together with Context.Provider
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};

const ThemeContext = React.createContext(themes.light);

function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}

function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}

function ThemedButton() {
// useContext의 인자로서 ThemeContext 객체 전달
// theme = <ThemeContext.Provider>의 value인 {themes.dark} // 객체
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}



추가 Hooks


useReducer

useState() 의 대체재로서 사용한다. 여러 하위 값을 포함하거나 다음 state가 이전의 state 값에 영향을 받는 복잡한 state 로직을 가진 경우 선호된다.

컴포넌트의 state 업데이트 로직을 컴포넌트에서 분리하여 컴포넌트 바깥에서 작성하거나 다른 파일에서 작성한 후 불러와서 사용할 수도 있다.

또한 callback 대신 dispatch를 전달한 수 있어 deep update를 요구하는 컴포넌트의 경우 performance를 최적화할 수 있다.

What is deep update in React?

먼저, React에서 update란; useState() 를 사용하는 모든 리액트 컴포넌트에서 사용가능한 함수로, 컴포넌트 state가 변경되었을 때 리액트에게 이를 알림으로써 해당 state에 종속된 UI를 업데이트하기 위해 컴포넌트에게 리렌더링이 필요하다는 것을 전달한다.

따라서, deep updatedeep nested 객체 (many pieces of information과 fixed 스키마를 가진) 를 update 하는 것을 의미한다.


1
const [state, dispatch] = useReducer(reducer, initialArg, init);
  • useReducer() : reducer 를 받아 현재 state와 짝지어진 dispatch 함수를 반환한다.

  • reducer : 현재 stateaction 객체를 파라미터로 받아와서 ( (state, action) ) 새로운 state를 반환하는 함수

    • (state, action) => newState 의 구조를 가진다.

      1
      2
      3
      4
      5
      function reducer(state, action) {
      // 새로운 state를 만드는 로직
      // const nextState = ...
      return nextState;
      }
    • 반환하는 해당 state는 컴포넌트가 지닐 새로운 state가 된다.

    • action : 업데이트를 위한 정보를 가지고 있다.

      • 주로 type 값을 지닌 객체 형태로 사용한다.

        type 의 경우, 대문자 를 사용하거나 _ 로 시작하는 관습이 있다.

  • state : 컴포넌트에서 사용할 수 있는 상태

  • dispatch : action 을 (인자로 받아) 발생시키는 함수

    1
    dispatch({ type: 'INCREMENT' })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const initialState = {count: 0};

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, initialState);
return (
<>
Count: {state.count}

// dispatch의 인자로 action (type) 전달
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}

Note

리액트는 리렌더링이 되더라도 dispatch 함수의 identity 는 동일하게 유지하기 때문에, useEffect 나 useCallback 의 dependency list에 포함하지 않는다.


Specifying the Initial State

초기 state의 구체화


useReducer() 의 state를 초기화하는 방법에는 2가지가 있다.

  1. 가장 간단한 방법으로는, initial state를 2번째 인자로 전달하는 방법이 있다.
1
2
3
4
const [state, dispatch] = useReducer(
reducer,
{count: initialCount}
);

Lazy Initialization

초기화 지연


  1. initial state를 lazily 생성하는 방법으로, init 함수를 3번째 인자로 전달하여 initial state를 init(initialArg) 로 설정할 수 있다.
    • initial state 를 계산하는 로직을 reducer 밖에 정의하여 action 에 따른 state 재설정 시 유용하게 사용할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 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};

// 재설정 시 init 함수 호출
case 'reset':
return init(action.payload);
default:
throw new Error();
}
}

function Counter({initialCount}) {

// 3개의 인자 전달 (reducer, initial_value, initialization_function)
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
Count: {state.count}

// 재설정 시 action 객체에 {type, payload} 전달
<button
onClick={() => dispatch({type: 'reset', payload: initialCount})}>
Reset
</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}

payload

전송되는 데이터


Bailing Out of a Dispatch

dispatch의 회피


앞서 useState() 에서 설명한 것과 같이, Reducer Hook ( useReducer() ) 이 현재 state과 동일한 값을 반환할 경우, 자식을 렌더링하거나 effect를 실행하지 않고 처리 (i.e. 렌더링, 이벤트) 를 종료한다.

Object.is() comparison 알고리즘을 이용

처리를 종료하기 전에 해당 컴포넌트를 다시 렌더링해야 하는 경우가 존재하지만, 그 이상으로 깊게 트리를 탐색하지 않으므로 문제가 되지는 않는다. 만약 렌더링에 비싼 계산 과정 이 포함된다면, useMemo() 를 사용할 수 있다.


useCallback

memoized callback 을 반환한다.


memoization

  1. 비싼 함수의 호출 결과를 저장하고 같은 input이 발생할 경우 저장된 (캐시) 값을 반환하여 프로그램의 수행 속도를 증가시키는 최적화 (optimization) 기술

  2. (some sort of) 캐시를 이용하여 (비싼) 함수를 부르는 횟수를 제한하는 것

    • 특정 input에 대한 캐시값이 없다면, original 함수가 한번 호출되어 (캐시) map에 결과값이 추가된다.
    • 캐시를 지우지 않는 한 결과는 캐시 map에서 반환된다.

👉 useCallback hook 은 다른 기능을 한다.

🌈 REFERENCE를 꼭 읽어보세염 (:->

useCallback() 을 이용하는 이유는, callback 함수인 cbmemoized 버전을 받기 위해서이다.

컴포넌트가 처음으로 렌더링될 때, 새로운 cb 가 생성되고 똑같은 cb 함수가 useCallback() 에 의해 반환된다.


위 예시에서 memoizedCb() 가 호출되면, original cb 콜백함수가 호출되어 결과적으로 original cb 함수를 호출한다.

컴포넌트가 리렌더링 (2번째) 되면, 새로운 cb 가 생성된다. 하지만 useCallback() 에 전달된 dependency가 변하지 않았기 때문에, 생성된 cb는 버려지고 memoizedCb는 첫번째 렌더링 때 생성된 cb를 유지한다. 결국 이 때의 memoizedCb 호출은 이전과 같이 기존의 (old) cb 를 호출하여 original cb 함수를 호출하게 된다.

dependency가 변하지 않는 이상, 모든 렌더링은 위와 같은 과정을 반복하여 사용하지 않는 콜백함수를 생성하고 기존의, memoized 콜백을 호출하여 결국 original cb 함수를 호출한다.


dependency 가 변한 경우, memoizedCb 도 변한다.

이전의 memoizedCb 가 첫번째 cb를 호출하는 과정과 비슷하게, 이 때의 memoizedCb 또한 dependency 가 변한 시점부터 첫번째 cb 를 호출한다.


callback

특정 함수 (콜백함수) 를 다른 함수에 전달하고 이를 호출하는 특정 이벤트 (e.g. a query finished or an error occurs) 가 발생하면 콜백함수를 발생시키는 것

  • 여러 조각의 코드의 소통을 정의하여, 콜백함수를 호출하는 모듈이 결과를 사용하는 것이 아니라 이벤트가 발생했을 때 호출을 받는 함수에 콜백함수를 전달한다.
  • 여기서 중요한 점은, one piece of code가 콜백함수를 호출하면 다른 (조각의) 코드가 이 호출을 받는다는 것이다. 여기서 어떠한 호출 코드도 잃으면 (생략되면) 안 되기 때문에, 콜백함수를 memoizing하여 모듈 간 소통 line을 끊는 방법이 탄생한 것이다.

결과적으로 useCallback() 이 하는 것은 똑같은 callback 의 횟수를 제한하는 것이다. 결과값들은 캐시에서 반환되지 않고, dependency 가 변한 시점으로부터 첫번째의 cb 와 같은 memoizedCb 를 반환한다.


1
2
3
4
5
6
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
  • useCallback() : inline callback 함수와 dependencies 배열을 전달
    • memoized version of the callback을 반환하며, dependencies가 변했을 때만 memoized version 도 변경된다.
    • useCallback(fn, deps) === useMemo(() => fn, deps)

불필요한 렌더링을 방지하기 위해 reference의 동일성에 의존하는 (동일성에만 영향을 받는) (최적화된) 자식 컴포넌트에 콜백함수를 전달할 때 유용하다.

앞선 useEffect와 마찬가지로, dependencies 배열은 콜백함수에 인자로 전달되지 않는다.

하지만 정의 자체가, “콜백함수에서 참조하는 모든 값들은 dependencies 배열에 포함되어야 한다” 이다.


useMemo

memoized 값을 반환한다.

useMemo vs. useCallback

useCallback() : 함수 반환

useMemo() : 값 반환


1
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  • useMemo() : create 함수dependencies 배열 을 전달
    • dependency 가 변할 때에만 memoized 값을 다시 계산한다. 이를 통해 비싼 계산 과정이 매 렌더링마다 발생하는 것을 방지한다.
    • 2번째 인자 (dependencies 배열 로서) 에 빈 배열 ( [] ) 이 전달되면, 매 렌더링 마다 새로운 값이 계산된다.

useMemo() 에 전달된 create 함수렌더링 동안 실행된다.

보통 side effect는 useMemo 가 아니라 useEffect 에서 발생한다. 따라서 렌더링 동안은 다른 행동을 하지 않아야 한다. (당연함 -.-)


❗️useMemo() 는 성능 최적화에만 사용하고, 큰 의미적 기능 (semantic guarantee) 을 담지 않아야 한다. 따라서 useMemo() 를 사용하지 않고도 동작할 수 있도록 코드를 작성하고, 성능 최적화를 위해 useMemo() 를 추가하도록 한다.

앞선 useEffect, useCallback 과 마찬가지로, depedencies 배열 은 콜백함수에 인자로 전달되지 않는다.


useRef

JavaScript를 이용해 특정 DOM 을 선택해야 하는 경우 getElementById, querySelector 와 같은 DOM Selector 함수 를 사용한다.

리액트를 사용할 때도 특정 DOM 을 선택해야 하는 상황이 발생하는데, 이 때 ref 를 사용한다.

  • 함수형 컴포넌트에서 ref 를 사용 할 때에는 useRef 라는 Hook 함수 를 사용한다.
  • 클래스형 컴포넌트에서는 콜백 함수를 사용하거나 React.createRef 라는 함수를 사용한다.

1
const refContainer = useRef(initialValue);
  • useRef() : (생성된 ref 객체의) .current 프로퍼티 (속성) 가 전달된 인자 (e.g. initialValue) 로 초기화된, 변경 가능한 ref 객체를 반환한다.
    • 변경가능한 값을 넣을 수 있는 .current 프로퍼티를 갖고 있는 “박스” 와 같다.
    • 해당 객체는 컴포넌트의 full lifetime 동안 지속된다.

useRef()DOM에 접근하는 방법으로 많이 사용된다.

useRef() 를 사용하여

  1. Ref 객체를 만들고,
  2. 이 객체를 우리가 선택하고 싶은 DOM (노드) 에 ref 값으로 설정한다.
  3. 그러면, Ref 객체의 .current 값은 우리가 원하는 DOM 을 가르키게 된다.

1
<div ref={refContainer} />

refContainer.current 프로퍼티를 해당 ref 객체 ( refContainer ) 를 ref 로 설정한 DOM 노드로 설정하고, 노드가 변경될 때마다 그에 맞게 .current 프로퍼티를 조정한다.

하지만 useRef().current 에 변경 사항이 존재하더라도 리렌더링 하지 않기 때문에, 이에 대해 notify 하지 않는다. 만약 리액트가 DOM 노드에 ref 객체를 연결하거나 제거할 때 notify 하고 싶다면, callback ref 를 이용해야 한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function TextInput_With_FocusButton() {
// inputEl : ref 객체 (null로 초기화됨)
const inputEl = useRef(null);

// button을 클릭하면 inputEl.current 인 input node에 focus() 를 적용한다.
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
// inputEl ref 객체를 ref로 설정하여 inputEl.current 프로퍼티를 input node 로 설정한다.
<input ref={inputEl} type="text" />

// onClick 시 onButtonClick 함수를 실행하고 // 👉 goto onButtonClick comment
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}

HTMLElement.focus()

특정 element 에 (focus를 맞출 수 있을 때) focus를 맞추는 함수로, 해당 element는 키보드 등의 이벤트를 받는 default element로 정의된다.




useRef() 는 ref 속성 외에도, 클래스가 instance field (인스턴스 변수) 를 가지고 있는 것처럼 ref 객체에 변경 가능한 어떠한 값도 담을 수 있다는 장점이 있다.

이는 useRef()plain JavaScript 객체 를 생성하기 때문인데, 직접 {current: ...} 로 객체를 생성하는 것과 유일한 차이점은 useRef() 는 매 렌더링마다 똑같은 ref 객체를 제공 (반환) 한다는 것이다.

class field (클래스 변수) vs. instance field (인스턴스 변수)

  • 클래스 변수 : 여러 인스턴스 (서로 다른 객체) 간에 공유해야 하는 값을 바인딩
  • 인스턴스 변수 : 각 인스턴스 (객체) 마다 가지고 있는 고유한 값

파이썬은 인스턴스를 통해 접근한 이름 (변수) 이 인스턴스의 name_space (name들을 정의한 공간) 에 없을 경우, 그 다음으로 클래스의 name_space에서 찾아본다.

참고로 클래스 변수에 접근할 때에는 클래스 이름을 사용하여 바로 변수 (값) 에 접근할 수 있다.


useImperativeHandle

❗️ref를 사용하는 imperative code 는 되도록이면 피하는 것이 좋다. ):-<

ref를 사용할 때 부모 컴포넌트에 노출되는 인스턴스 값 (변수) 을 커스터마이징 한다.

즉, forwarding된 ref를 replace할 수 있는 기능을 제공한다.


1
useImperativeHandle(ref, createHandle, [deps])
  • useImperativeHandle() : ref, 콜백함수 (createHandle), dependnecies 배열 을 전달

    • createHandle() : 현재 컴포넌트의 값을 부모 컴포넌트가 접근할 수 있도록 하는 콜백함수
  • forwardRef() 와 함께 사용되어야 한다.

    forwardRef()

    (말 그대로) reference를 전달해주는 기능을 하는 함수


1
2
3
4
5
6
7
8
9
10
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

<FancyInput ref={inputRef} /> 를 수행 (렌더링) 하는 부모 컴포넌트는 inputRef.current.focus() 를 호출할 수 있다.


useLayoutEffect

useEffect 와 동일한 기능을 DOM 변경 시 동시에 (synchronously) 실행하는 함수로, DOM 에서 레이아웃을 읽음과 동시에 리렌더링을 해야할 때 사용한다.

useLayoutEffect 내부에 정의된 예정된 업데이트 또한 브라우저가 레이아웃을 그리기 이전에, 읽음과 동시에 발생한다.

화면 업데이트를 차단하지 않아도 되는 경우에는 useEffect 를 사용하는 것이 좋다.


클래스 컴포넌트에서 코드를 옮길 때 useLayoutEffect()componentDidMountcomponentDidUpdate 와 같은 단계에서 발생한다. 하지만, 먼저 useEffect 를 시도해보고 에러가 발생할 때 useLayoutEffect 를 사용하는 것이 좋다.

SSR (서버 사이드 렌더링) 의 경우, useLayoutEffectuseEffect 는 JavaScript 가 모두 다운되기 전까지는 실행되지 않는다. SSR 컴포넌트가 useLayoutEffect 를 포함한 경우,

  • 첫 렌더링 시 해당 컴포넌트가 필요하지 않다면 useEffect 로 로직을 옮기고,
  • useLayoutEffect 가 실행되기 전까지 HTML이 망가져 보인다면 클라이언트 렌더링이 완료될 때까지 해당 컴포넌트를 보여주는 것을 딜레이시킨다.

서버 렌더링 HTML에서 레이아웃 effect를 필요로 하는 컴포넌트를 제외하기 위해서,

  • showCild && <Child /> 를 이용하여 해당 컴포넌트를 조건적으로 렌더링을 하고,
  • useEffect(() => { setShowChild(true); }, []) 를 이용하여 보이는 것을 미룬다 (defer).

이를 통해 HTML이 hydration (useLayoutEffect, useEffect 실행) 이전에 망가져 보이는 것을 방지한다.


useDebugValue

1
useDebugValue(value)

React DevTools 에서 커스텀 Hooks의 label (value) 을 보여준다.


1
2
3
4
5
6
7
8
9
10
11
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);

// ...

// Show a label in DevTools next to this Hook
// e.g. "FriendStatus: Online"
useDebugValue(isOnline ? 'Online' : 'Offline');

return isOnline;
}

모든 커스텀 Hook에 debug value를 추가하는 것은 좋지 않다.

shared libraries에 속하는 커스텀 Hooks에 유용하게 사용될 수 있다.


Defer Formatting Debug Values

디버그 값 포맷팅 지연하기


디스플레이 (되는) 값을 포매팅하는 것은 고비용 연산일 수 있고, Hook이 감지되지 않은 경우 사실상 포매팅은 불필요하다. 따라서, useDebugValue 의 (optional한) 2번째 인자로 포매팅 함수 를 전달하여 Hook이 감지되었을 때만 해당 함수를 호출하여 포매팅할 수 있다.


1
useDebugValue(value, value => value.formatting_function());
  • useDebugValue() : valueoptional formatting 함수 를 전달
    • optional formatting function : debug value 를 인자로 전달하고 formatted display value 를 반환한다.

1
useDebugValue(date, date => date.toDateString());

2번째 인자로 fomatting function 을 전달하여 Date value 를 반환하는 커스텀 Hook이 toDateString()불필요하게 호출하는 것을 방지한다.




Reference

https://ko.reactjs.org/docs/hooks-reference.html

https://reactjs.org/docs/hooks-reference.html