컴포넌트의 상태 관리와 사이드 이펙트

</>·2021년 10월 23일
4

Get to Know React

목록 보기
3/8
post-thumbnail

목표

  • 컴포넌트의 상태와 관리에 대해 알아본다.
  • 컴포넌트의 사이드 이펙트에 대해 알아본다.

1. 컴포넌트의 상태 관리

1-1. 엘리먼트? 컴포넌트?

  • 엘리먼트(Element)는 화면에 표시할 내용의 가장 작은 단위이다.
  • 컴포넌트(Component)는 엘리먼트들의 집합이다.
  • React 공식 문서에서 엘리먼트는 리액트 앱의 가장 작은 단위이며 컴포넌트의 구성 요소라고 표현하고 있다.

1-2. 컴포넌트의 상태 관리(not use hooks)

  <body>
    <div id="root"></div>
    <script type="text/babel">
      const rootElement = document.getElementById("root");

      const App = () => {
        let keyword = "";

        const onChange = (e) => {
          keyword = e.target.value;
        };

        return (
          <>
            <input onChange={onChange} />
            <button>search</button>
            <p>searching... {keyword}</p>
          </>
        );
      };

      ReactDOM.render(<App />, rootElement);
    </script>
  </body>
  • 위 코드에서 onChange를 통해 값이 입력될 때마다 p태그의 내용을 바꾸는 코드이다. 하지만, input에 값을 넣어도 p 엘리먼트에 있는 keyword가 바뀌지 않는다. 이는 render를 하지 않기 때문이다.

notUseHooks


1-3. 컴포넌트의 상태 관리(use hooks)

  <body>
    <div id="root"></div>
    <script type="text/babel">
      const rootElement = document.getElementById("root");

      const App = () => {
        // let keyword;
        const [keyword, setKeyword] = React.useState("");

        const onChange = (e) => {
          // keyword = e.target.value;
          setKeyword(e.target.value);
        };

        return (
          <>
            <input onChange={onChange} />
            <button>search</button>
            <p>searching... {keyword}</p>
          </>
        );
      };

      ReactDOM.render(<App />, rootElement);
    </script>
  </body>
  • 이번엔 이전의 코드를 훅(useState)을 이용해서 상태를 변화시켰다. input에 값을 넣었을 때 p 엘리먼트에 있는 keyword가 바뀐것을 볼 수 있다. 이는 훅이 자동으로 렌더링 해주는 것을 알 수 있다.

useState

useState

  • useState는 클래스 컴포넌트의 this.state의 기능을 한다.
  • 일반적으로 일반 변수는 함수가 끝날 때 사라지지만 state 변수는 React에 의해 제거 되지 않는다.
  • useState의 인자로 초기값을 넘겨준다. 숫자, 문자뿐만 아니라 객체까지 초기값으로 설정할 수 있다.
  • useState가 반환하는 값은 state 변수와 해당 state 변수를 갱신 할 수 있는 함수 두 가지 쌍을 반환한다.
    const keywordState = React.useState("initialValue");
    console.log(keywordState[0]);
    console.log(keywordState[1]);
  • 위의 코드를 실행해 보면 다음과 같은 결과가 나오는데, state 변수에 초기값으로 설정했던 initialValue라는 문자열와 state 변수를 갱신할 수 있는 dispatchAction함수가 출력된 것을 볼 수 있다.

useState

  • 위의 코드는 useState 함수의 리턴 값을 통째로 가져와 배열의 인덱스로 값들을 불러 왔다. 하지만, 보통 이렇게 쓰지 않는다.

// 1. 구조 분해 할당 X
    const keywordState = React.useState("initialValue");
    const keyword = keywordState[0];
    const setKeyword = keywordState[1];

// 2. 구조 분해 할당 O
    const [keyword, setKeyword] = React.useState("");
  • 자바스크립트는 구조 분해 할당이란 것을 제공하는데 개별 변수로 한번에 담을 수 있다. 이는 코드가 더 깔끔해보이는 효과를 준다.

구조 분해 할당

  • 자바스크립트 문법으로 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JavaScript 표현식이다.
  • 자세한 내용은 Mozilla - 구조 분해 할당에서 확인할 수 있다.

1-4. 여러 개의 컴포넌트의 상태 관리(use hooks)

🤔 의문

  • 여러 개의 변수를 useState를 통해 관리하고 싶을 땐 어떻게 해야 할까?

useState는 state별로 독립적으로 관리하기 때문에 2개 이상의 변수를 저장한다면 useState를 그만큼 호출해주면 된다.


  <body>
    <div id="root"></div>
    <script type="text/babel">
      const rootElement = document.getElementById("root");

      const App = () => {
        const [keyword, setKeyword] = React.useState("");
        const [typing, setTyping] = React.useState(false);
        const [result, setResult] = React.useState("");

        const onChange = (e) => {
          setKeyword(e.target.value);
          setTyping(true);
        };

        const onClick = () => {
          setTyping(false);
          setResult(`find search result of ${keyword}`);
        };

        const onKeyPress = (event) => {
          if (event.key === "Enter" && event.keyCode === 0) {
            onClick();
          }
        };

        return (
          <>
            <input onChange={onChange} onKeyPress={onKeyPress} />
            <button onClick={onClick}>search</button>
            <p>{typing ? `searching... ${keyword}` : result}</p>
          </>
        );
      };

      ReactDOM.render(<App />, rootElement);
    </script>
  </body>

total

  • keyword를 state 변수로 관리하는 것처럼 typing, result도 state 변수로 독립적으로 관리할 수 있다.
  • onChange 함수에서는 keyword와 typing state 변수를 바꾸고 onClick 함수에서는 typing과 result state 변수를 바꾼다.
  • onKeyPress 함수는 event 객체의 key 값이 Enter이고 keyCode 값이 0 즉, 엔터를 치면 검색이 되도록 하는 역할을 한다.

[참고] window 객체의 localStorage

localStorage

  • localStorage를 사용하면 Document의 Storage 객체에 접근할 수 있다. 저장한 데이터는 브라우저 세션 간에 공유된다.
  • 저장해야할 데이터가 별로 중요하지 않거나, 유실되도 무방할 데이터 즉, 서버 단에 데이터를 저장하는 것이 낭비라고 판단되면 사용하는 것이 좋다.
    <script type="text/babel">
      const rootElement = document.getElementById("root");

      const App = () => {
        const [keyword, setKeyword] = React.useState(
          window.localStorage.getItem("keyword")
        );
        const [typing, setTyping] = React.useState(false);
        const [result, setResult] = React.useState("");

        const onChange = (e) => {
          window.localStorage.setItem("keyword", e.target.value);
          setKeyword(e.target.value);
          setTyping(true);
        };

        // 생략

        return (
          <>
            <input
              onChange={onChange}
              onKeyPress={onKeyPress}
              value={keyword}
            />
            <button onClick={onClick}>search</button>
            <p>{typing ? `searching... ${keyword}` : result}</p>
          </>
        );
      };

      ReactDOM.render(<App />, rootElement);
    </script>
  • 위 코드는 keyword state 변수의 값이 바뀔 때마다 localStorage에 저장한다.
  • setItem 함수를 통해 localStorage에 저장하는데 첫 번째 인자는 key값, 두 번째 인자는 key에 해당하는 value 값을 넣어주면 된다.
  • setItem을 통해 저장된 키:쌍은 getItem을 통해 가져올 수 있다. 인자에 key 값을 넣어주면 value 값을 가져올 수 있다.
  • localStorage는 개발자 도구 > Application > LocalStorage 에서 확인할 수 있다.

    // initialState
    const [keyword, setKeyword] = React.useState(
      window.localStorage.getItem("keyword")
    );

💡 주의

  • useState를 선언하는 부분에서 브라우저 내에 localStorage를 읽고 쓰는(I/O) 작업은 언제든 딜레이가 발생할 수 있기 때문에 초기값을 불러오지 못하는 문제가 발생할 수 있다.

그렇다면 딜레이 문제를 방지하려면 어떻게 해야할까?

    // lazy initialState
    const [keyword, setKeyword] = React.useState(() => {
       return window.localStorage.getItem("keyword");
    });
  • 위 코드를 보면 useState에 직접 변수를 인자로 넘겨주지 않고 화살표 함수를 인자로 넘겨 주었다.
  • 이렇게 되면 초기값을 읽어 오는데 시간이 좀 걸리더라도 함수가 결과 값을 반환하면 초기 값을 불러올 수 있다.
  • 이렇게 변수 대신 함수를 넘겨 초기화 작업을 지연시킬 수 있는데, 이를 지연 초기화(lazy initialization)라고 한다.

지연 초기화(lazy initialization)

  • 지연 초기화는 오직 state가 처음 만들어 질 때만 실행된다. 이 후에 다시 리렌더링이 된다면, 이 함수의 실행은 무시된다.
  • React 공식 문서에서는 초기 state가 고비용 계산의 결과라면 함수를 쓰는 것을 권장한다.

🤔 의문

  • 그렇다면 모든 초기 값들을 지연 초기화(lazy initialization)으로 처리하면 되지 않을까?
  • 비록, 함수가 지연 초기화로 인해 한 번만 호출되지만, 함수를 만드는 비용이 보통 변수를 생성하거나 단순히 값을 넘기는 비용보다는 크다. 따라서, 모든 초기 값들을 지연 초기화로 처리하는 것은 과한 최적화의 예이다.
  • localStroge, map, filter 등과 같이 고비용 계산이 들어가는 것은 지연 초기화로 처리하는 것이 좋다.

2. 컴포넌트의 사이드 이펙트

2-1. React에서의 사이드 이펙트(Side Effect)란?

  • 사이드 이펙트란 사전적 의미로 원래의 목적과 다르게 다른 효과가 발생하는 것을 말한다.
  • 리액트에서의 사이드 이펙트란 변경이나 효과가 일어날 때 다른 효과가 발생할 수 있도록 설정할 수 있는 것이다.
  • 이전의 코드에서 input에 값을 입력할 때 뿐만 아니라 버튼을 누를 때도 콘솔에 render가 찍히는 것을 볼 수 있다.
 
  • 이렇게 되면 버튼을 누를 때도 localStorage에 저장되는 비효율적인 사이드 이펙트 효과가 발생하게 된다.

🤔 의문

  • 버튼을 눌렀을 때 말고 input에 값을 입력하여 keyword state 변수의 상태 값만 바뀔 때에만 localStorage에 값을 저장하려면 어떻게 해야할까?

React에서는 이러한 상황을 방지할 수 있도록 useEffect라는 훅(Hook)을 제공한다.


2-2. useEffect

    const App = () => {
    console.log("render");
    const [keyword, setKeyword] = React.useState(() => {
      return window.localStorage.getItem("keyword");
    });
    const [typing, setTyping] = React.useState(false);
    const [result, setResult] = React.useState("");

    React.useEffect(() => {
      console.log("effect");
      window.localStorage.setItem("keyword", keyword);
    }, [keyword]);

	// 생략
 
  • 위 코드는 useEffect를 사용해서 keyword state 변수의 상태 값이 변할 때만 localStorage에 저장하게 된다.

useEffect

  • userEffect를 사용해서 리액트에게 컴포넌트가 렌더링 이후에 어떤 일을 수행해야 하는 지를 말할 수 있다. 그러면 리액트는 우리가 넘긴 함수를 기억했다가(effect) DOM 업데이트를 수행한 이 후에 불러낸다.
  • userEffect의 첫 번째 인자로는 어떤 일을 수행해야 하는지 말하는 함수를 넣어주고 두 번째 인자로는 사이드 이펙트를 일으키고 싶은 대상을 배열로 넣어주면 된다. 이 배열을 의존성 배열(dependency array)이라고 부른다.
  • 위 코드에서는 의존성 배열에 keyword값을 넘겨줌으로써 keyword의 상태가 바뀔 때마다 사이드 이펙트를 발생시키는 함수를 실행하는 것이다.
  • 만약 의존성 배열이 없다면 useEffect를 사용하지 않았을 때와 같다. 인풋에 값을 입력해도 버튼을 눌러도 함수가 실행될 것이다. 즉, 모든 변화에 다 반응한다.
  • 의존성 배열에 빈 배열 값을 준다면 어떤 행동을 해도 처음 컴포넌트가 그려지는 그 순간 즉, 처음에만 effect가 실행된다.

의존성 배열

  • 의존성 배열이 없다면?   모든 state 변수의 변화가 일어날 때마다 실행된다.
  • 의존성 배열이 빈 값이면?   처음 렌더링 됐을 때 한 번만 실행된다.
  • 의존성 배열에 값이 있으면?   그 state 변수들이 변할 때마다 실행된다.


참고

profile
개발자가 되고 싶은 개발자

0개의 댓글