Part 1) useState 기본 개념 정리

Si Choi·2020년 11월 4일
5

React Hooks 시리즈

목록 보기
1/1

이 글의 코드 예시와 설명은 유튜버 개발자 Ben Awad님의 React Hooks 튜토리얼과 카카오 프렌즈의 이재승 개발자님의 실전 리액트 프로그래밍리액트 훅스 공식 문서 등을 주로 참조하여 작성하였습니다

제 부족한 블로그에 위 자료들을 인용할 수 있도록 허락해주신 Ben Awad님과 이재승 개발자님께 감사드린다는 말 전해드리며, 리액트 공부하시는 분들께 이재승 개발자님의 책 추천드리며 이재승 개발자님 인프런 강의도 있기 때문에 책이 어려우신 분들은 강의를 보시는 것도 추천드립니다.

(The code examples and explanations are referenced from Ben Awad's Youtube tutorial. Thanks Ben for allowing me to use your codes on my blog!)


useState 훅스 정리

1) useState의 기본 개념

  • useState 훅은 컴포넌트에 State을 추가할 때 쓰이는 Hooks임(활용 예제 1번)
  • useState 훅은 배열을 Return 하는데, 그 배열의 0번째 인덱스는 새로 추가할 state이고, 1번째 인덱스는 state 변경 함수임(활용예제 2번 참조)
  • 리액트는 state 변경 함수가 호출되면 해당 컴포넌트를 다시 그리며, 그 과정에서 child component도 함께 렌더링이 됨
  • 리액트는 가능하다면 상태값 변경을 Batch로 처리함(활용예제 4번 참조)

2) useState의 소스코드(타입 스크립트로 작성됨)

  • SetStateAction 타입은 S(initial state)라는 제너릭을 가지고 있고, S 타입의 Parameter(prevState)를 입력받아 S타입의 값을 리턴하는 함수임
  • Dispatch는 위 SetStateAction을 제너릭으로 받아 실행시키는 역할을 함
  • useState의 parameter type을 보면 S 타입의 state를 입력하거나 혹은 그를 return하는 함수를 입력할 수 있음
 // Unlike the class component setState, the updates are not allowed to be partial
type SetStateAction<S> = S | ((prevState: S) => S);

// Since action _can_ be undefined, dispatch may be called without any parameters.
type Dispatch<A> = (value: A) => void;

/*** initalState을 지정한 경우 ***/
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
//Type S나 그를 리턴하는 함수를 입력받아 배열을 리턴
// 해당 배열의 0번째 인덱스는 해당 state을, 1번째 인덱스는 state을 지정하는 함수를 리턴(하단의 2번 예제 참조할 것)                                                
          
/*** initalState을 지정하지 않은 경우 ***/
function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];

3) useState의 활용 예제

3-1) 일반적인 활용 예시(count, setCount의 Destructuring)

import React, { useState } from 'react';

const App = () => {
  const [count, setCount] = useState(10);  // count = initialValue 10

  return (
    <div>
      <button onClick={() => setCount(count + 1)}></button>
    </div>
  );
};

export default App;

3-2) useState 사용 시 count, setCount를 Destructuring 안 한 경우

import React, { useState } from 'react';

const App = () => {
  const _useState = useState(0),
  count = _useState[0],
  setCount = _useState[1];

  return (
    <div>
      <button onClick={() => setCount(count + 1)}></button>
    </div>
  );
};

export default App;

3-3) setCount에 update함수를 입력한 경우(race condition 혹은 중복 업데이트를 방지)

import React, { useState } from 'react';

const App = () => {
  const [count, setCount] = useState(10);
  return (
    <div>
    //currentCount는 parameter 명칭임... 아무렇게나 작성해도 됨
      <button onClick={() => setCount((currentCount) => currentCount + 1)}>
        click me
      </button>
      <div>{count}</div>
    </div>
  );
};

export default App;

3-4) Batch로 처리되는 리액트의 State

import React, { useState } from 'react';

const App = () => {
  const [count, setCount] = useState({ value: 0 });

  const onClick = () => {
    setCount({ value: count.value + 1 });
    setCount({ value: count.value + 1 });
  };

  console.log('render called');
  return (
    <div>
      <h2>{count.value}</h2>
      <button onClick={onClick}>증가</button>
    </div>
  );
};
  • onClick이 invoke 될 때 setCount는 2번이 아니라 1번만 실행이 됨

  • State 변경 함수가 비동기로 작동하기 때문이며, 리액트는 효율적으로 렌더링 하기 위해 여러 개의 상탯값 변경 요청을 배치로 처리함

  • 위 코드대로 작성해서 브라우저의 콘솔창을 보면, console.log('render called')가 2번이 아니라 1번만 실행이 된 것을 확인할 수 있음

  • 만일 리액트가 State 변경 함수를 동기로 처리한다면, 하나의 상태값 변경 함수가 호출 될 때마다 화면을 다시 그리기 때문에 성능 이슈가 발생할 수 있으며, 동기로 처리하지만 매번 화면을 다시 그리지 않는다면 UI 데이터와 화면 간의 불일치가 발생해서 혼라스러울 수 있기 때문에 State을 비동기/배치 방식으로 처리함

3-5) useState으로 여러 개의 상태값 관리하기

  • useState로 여러 개의 상태 값을 Object 형식으로 관리할 수 있음
  • useState의 state 변경 함수는 setState와는 달리 1개의 state만 개별적으로 수정할 수 없으며, 1개의 state만 개별적으로 수정하려면 아래와 같이 ...currentStatecount: currentState.count + 1과 같이 변경할 state을 덮어주는 형식으로 진행해야 함
  • 여러 개의 state을 object로 관리할 때는 useReducer 훅을 사용하는 것을 권장함
const App = () => {
  const [{ count, count2 }, setCount] = useState({ count: 10, count2: 20 });

  const onClick = () => {
    setCount((currentState) => ({
      ...currentState,
      count: currentState.count + 1,
    }));
  };
  
  return (
    <div>
      <button onClick={onClick}>증가</button>
      <div>count 1: {count}</div>
      <div>count 2: {count2}</div>
    </div>
  );
};

3-6) useState는 각각의 state마다 사용할 수 있으며, 호출 순서는 코드 순서대로 임

const App = () => {
  const [count, setCount] = useState(10);
  const [count2, setCount2] = useState(20);

  const onClick = () => {
    setCount(() => count + 1);
    setCount2(() => count2 + 1);
  };

  return (
    <div>
      <button onClick={onClick}>증가</button>
      <div>count 1: {count}</div>
      <div>count 2: {count2}</div>
    </div>
  );
};

4) useState으로 Custom Hooks 만들기

useState의 가장 큰 장점은 custom hooks를 만들 수 있다는 점인데요..
다음은 useState으로 custom hooks를 만드는 예시입니다.

4-1) Custom Hook 만들기(useForm)

  • 다음과 같이 useState을 이용하여 input들을 입력받고, input을 수정할 때마다 해당 state의 값을 변경하는 custom hook을 만들 수 있음
  • 아래의 경우 initialValues와 value는 object 형식으로 입력 됨
import { useState } from 'react';

export const useForm = (initialValues) => {
  const [values, setValues] = useState(initialValues);

  return [
    values,
    (e) => {
      setValues({
        ...values,
        [e.target.name]: e.target.name,
      });
    },
  ];
};

4-2) Custom Hook 활용하기

  • useForm을 useForm 훅스에서 가져옴
  • useForm에 email과 password를 가진 객체를 initial state으로 입력
  • email이나 password를 input에 입력할 때마다 수정 됨
import React from 'react';
import { useForm } from './useForm';

const App = () => {
  const [values, handleChange] = useForm({ email: '', password: '' });

  return (
    <div>
      <input name='email' value={values.email} onChange={handleChange} />
      <input
        type='password'
        name='password'
        value={values.password}
        onChange={handleChange}
      />
    </div>
  );
};

export default App;

5) State 변경이 배치로 처리되지 않는 경우

  • 리액트는 내부에서 관리하는 이벤트 처리 함수에 대해서만 상태값 변경을 배치로 처리
  • 만일 리액트 외부에서 관리되는 이벤트 처리 함수가 설정될 경우, state 변경은 배치로 처리되지 않음
import React, { useEffect, useState } from 'react';

const App = () => {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const onClick = () => {
      setCount((prev) => prev + 1);
      setCount((prev) => prev + 1);
    };

    window.addEventListener('click', onClick);
    return () => window.removeEventListener('click', onClick);
  });
  console.log('render called');

  return <div>{count}</div>;
};

  • 위 예시의 경우, 이벤트 처리 함수를 window 객체에 추가함으로서 리액트 내부에서 구동이 되지 않기 때문에 window 화면을 클릭할 경우 setCountconsole.log('render called')가 2번 호출 됨.

  • 만일 리액트 외부에서 관리되는 이벤트 처리 함수에도 State 변경을 배치로 처리하고자 한다면, unstable_batchedUpdate 함수를 이용하면 됨

  • 대신에 unsateble_batchedUpdate을 많이 이용하는 것은 권장되지 않음

  • 아래를 보면 setCount는 2번 실행이 되기는 했지만, console.log('render called')는 1번 밖에 실행이 안 된 것을 확인할 수 있음.

const App = () => {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const onClick = () => {
      ReactDOM.unstable_batchedUpdates(() => {
        setCount((prev) => prev + 1);
        setCount((prev) => prev + 1);
      });
    };

    window.addEventListener('click', onClick);
    return () => window.removeEventListener('click', onClick);
  });
  console.log('render called');

  return <div>{count}</div>;
};

profile
함께 성장하는 개발자가 되겠습니다!

0개의 댓글