ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Kent C. dodds - Epic React] React hooks(1) - Lazy initial state / useRef
    Frontend 2020. 10. 25. 23:57

     

    정말 오랜만에 글을 쓰려고 블로그를 켰더니 너무 낯설다.

    회사 일이 바쁘다는 핑계로 살짝 멀리했었는데 블로그야 미안...

     

    회사 슬랙에 Kent C. doddsEpic React 강의에 관심있는 사람 있으면 같이 공동구매해서 듣자는 제안이 올라왔는데 

    쿨하게 회사에서 지원을 해줘서 공짜로 들을 수 있게 되었다. 🎉

     

    강의 챕터는 요렇게 구성되어있다. 

    넘나 알찬 구성 🥰

     

    • React Fundamentals
    • React Hooks
    • Advanced React Hooks
    • Advanced React Patterns
    • React Performance
    • Testing React Apps
    • React Suspense
    • Build an Epic React App
    • Epic React Expert Interviews

     

    오늘은 React Hooks 파트를 절반 정도 들었는데,

    오늘 들었던 내용 중에 내가 처음 알게 된 것, 재밌게 본 것 한 두가지를 정리해보려고 한다.

     


    Lazy initial state

    React 공식 문서에 useState 설명 밑에 쬐끄만한 소제목으로 적혀있는 내용인데 이런 기능이 있는 줄 오늘 강의보고 처음 알았다. 🙈

    (물론 initialState 값을 넣을 때 엄청나게 복잡한 계산을 해야하는 경우가 거의 없어서 알았어도 많이 썼을 것 같지는 않지만 어쨌든!)

     

    만약에 내가 어떤 값들을 localStorage에 저장해서 새로고침을 해도 내가 입력한 값들이 그대로 화면에 보여지도록 한다고 생각해보자.

     

    function Name() {
      const [name, setName] = React.useState(
        window.localStorage.getItem('name') || ''
      );
      
      React.useEffect(() => {
        window.localStorage.setItem('name', name);
      }, []);
      
      const handleChange = (event) => {
        setName(event.target.value);
      }
      
      return (
        <>
          <input value={name} onChange={handleChange} />
          {name ? <p>Hello, {name}</p> : 'Please type your name'}
        </>
      );
    }

     

    대충 위와 같은 컴포넌트가 만들어질거다.

     

    여기서 name을 state로 관리하는데 초기 name 값을

    localStorage에 저장된 name 값이 있으면 그 값으로 지정하고,

    값이 없는 경우에는 빈 string을 입력한다고 해보자.

     

    그러면 매 번 Name 컴포넌트가 실행되면 로컬 스토리지에서 값을 조회한 후 가져오는 로직을 수행하게 된다.

    그런데 문제는 사실 초기 State로 가져오는 값은, 이 컴포넌트를 최초로 렌더링할 때 딱 한 번만 가져오면 된다.

    이 후에 유저가 input에 어떤 값을 입력하게 되면 더 이상 초기값은 화면에 보여질 필요가 없어진다.

     

    그러나 그냥 위와 같이 초기 state값을 설정하면 이 컴포넌트가 re-render가 될 때마다

    쓸데없이 로컬 스토리지에서 name값을 탐색을 하게 되어 성능에 안좋은 영향을 줄 수 있다.

     

    이런 문제를 해결하는 방법은 아주 간단한데 바로 리액트의 useState hook의 초기 state로

    value가 아니라 value를 리턴하는 function을 전달하는 것이다.

     

    function Name() {
      const [name, setName] = React.useState(() => {
        const valueInStorage = window.localStorage.getItem('name');
        
        return valueInStorage || '';
      });
      
      React.useEffect(() => {
        window.localStorage.setItem('name', name);
      }, []);
      
      const handleChange = (event) => {
        setName(event.target.value);
      }
      
      return (
        <>
          <input value={name} onChange={handleChange} />
          {name ? <p>Hello, {name}</p> : 'Please type your name'}
        </>
      );
    }

     

    이제 리액트는 컴포넌트가 최초로 렌더링될 때 딱 한 번만 useState 인자로 전달받은 함수를 호출하여 초기 값을 세팅할 것이다!

     

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

    (^ 공식 문서에 있는 예제)

     


    Bailing out of a state update

    요거는 강의에 나온 내용은 아니지만... 공식 문서에서 lazy initial state 부분을 찾아서 읽다가 같이 읽은 내용인데

    만약에 State hook을 기존 state와 동일한 값으로 업데이트한다면

    리액트는 하위 자식들을 렌더하거나 effects를 호출하지 않고 그냥 실행을 종료한다고 한다.

     

    그리고 리액트는 Object.is와 동일한 알고리즘으로 값을 비교한다는 말이 나오는데

    이 참에 Object.is가 어떤 알고리즘으로 비교하는지 정리하자면...

     

    Object.is(value1, value2)

    Object.is는 2개의 인자를 받아서 비교를 한 후에, 값이 같으면 true 같지 않으면 false를 return한다.

    그리고 다음과 같은 경우에 두 값이 같다고 판단한다.

     

      1) 두 값이 undefined일 때

      2) 두 값이 null일 때

      3) 두 값이 둘 다 true나 false일 때

      4) 두 값이 같은 길이의 같은 글자를 같은 순서로 사용한 string일 때

      5) 두 값이 같은 reference를 가진 object일 때

      6) 두 값이 같은 숫자일 때 (둘 다 +0 / -0 / NaN / 혹은 0과 NaN이 아닌 같은 값의 숫자)

     

    얼핏보면 strict equality operator인 ===과 똑같은 방식이라고 생각할 수 있지만 약간 다른 점이 있다.

    • ===는 -0과 +0을 같은 값이라고 여기지만 Object.is는 아니다.
    • ===는 NaN과 NaN을 다른 값이라고 여기지만 Object.is는 같은 값이라고 여긴다.

     


    useRef - Is there something like instance variables?

    useRef를 배울 때 거의 DOM을 reference하기 위해서 사용한다는 식으로 배웠던 것 같다.

    React는 가상 DOM을 사용하니까 실제 DOM을 조작하기 위해서 useRef 기능으로 DOM의 reference를 저장해서 사용하는 식이다.

     

    그런데 사실 useRef hook은 DOM reference로 사용하기 위한 것만은 아니다.

    오늘 강의에서 다양하게 useRef를 활용한 예제 코드들을 보고 이참에 정리해보고자 공식 문서 내용을 가져왔다.

     

    ref 객체는 current라는 property로 이루어져 있는데, 이 current property는 mutable하며 어떤 value든지 가지고 있을 수 있다.

    조금 더 자세히 설명하자면 useRef는 current라는 property를 가지고 있는 plain object를 생성한다.

     

    useRef()를 사용하는 것과 {current: ...}라는 객체를 직접 만들어 쓰는 것의 차이는

    useRef는 우리에게 매 렌더링 시 항상 같은 reference의 객체를 제공해준다는 점이다.

     

    아래 예시처럼 useEffect hook 안에서도 current에 값을 자유롭게 할당할 수 있다.

    function Timer() {
      const intervalRef = useRef();
    
      useEffect(() => {
        const id = setInterval(() => {
          // ...
        });
        intervalRef.current = id;
        return () => {
          clearInterval(intervalRef.current);
        };
      });
    
      // ...
    }

    (^ 공식 문서에 있는 예제)

     

    만약 위와 같이 useEffect hook안에서 interval을 세팅하고 clear한다면 ref를 굳이 사용할 필요가 없다.

    그냥 useEffect hook안의 local variable인 id를 사용하면 되니까...!

     

    그런데 만약에 useEffect 바깥에 있는 이벤트 핸들러에서

    useEffect 안에 선언된 interval을 clear해주고 싶다면? ref가 아주 유용해진다.

      // ...
      function handleCancelClick() {
        clearInterval(intervalRef.current);
      }
      // ...

     

    반드시 알고 있어야하는 점은 useRef는 current property의 값이 변경되더라도 컴포넌트를 re-render 시키지 않는다!

    만약에 값이 변경될 때마다 특정 코드를 실행시키고 싶다면 callback ref (주로 DOM node의 reference를 저장하기 위해 사용한다)라는 것을 활용해야 한다.

    반응형

    COMMENT