[ 2024.05.28 ] TIL 28일차

김선민·2024년 5월 28일

[ 본캠프 28일차 기록 ]

🖥️ 오늘 공부한 내용 🖥️

#useState란?

  • 함수형 또는 클래스형 컴포넌트 내에서 state(상태)를 관리 및 변경할 수 있게 해주는 React 훅이다.
  • React에서 제공하는 다양한 ReactHooks 중에 하나로, 함수형 또는 클래스형 컴포넌트에서 로컬의 데이터 상태를 관리할 수 있게 만들어 주는 기능이다.
  • 초기 입력될 상태 값을 인자로 받아서 상태 값과 해당 상태를 업데이트하는 함수를 쌍으로 반환하게 된다.
  • 함수는 stateless다.
    • 일반 변수는 함수가 끝날 때 사라지기 때문에 useState를 통해서 함수형 컴포넌트 내에서 상태를 관리할 수 있게 해주는 것이다.

# useState(initialState)

1. 컴포넌트의 최상위 레벨에서 useState를 호출하여 state 변수를 선언한다.

	import { useState } from 'react';
    
    function MyComponent(){
    	const [ age, setAge ] = useState(28);
        const [ name, setName ] = useState('Taylor');
        // ...
  • initialState : 초기 상태 값을 지정한다.
    • 함수를 전달하면 초기화할 때 초기화 함수를 호출하고 반환 값을 초기 상태로 저장한다.
  • 정리
    • state에 해당하는 값은 첫 렌더링 때 useState의 파라미터로 받은 initialState를 통해 매칭한다.
    • 이후 setState를 통해 다른 값을 주입하여 상태를 업데이트하고 리렌더링한다.

2. 구조 분해 할당를 사용하여 [something, setSomething]과 같은 state 변수 이름을 지정한다.

  • 구조 분해 할당이란 객체를 여러 변수로 "압축해제"할 수 있는 특수 구문이다.
  • 함수에 전달할 때는 전부가 아닌 특정 요소나 속성만 필요할 수 있기 때문에,
    구조분해할당이 더 편리할 때가 있다.

[ 배열 해제 ]

  • 다음은 배열이 변수로 구조 해제되는 방법에 대한 예이다.
    let arr = ["John", "Smith"]

    // 구조 분해 할당
    // 배열 해체 할당을 사용하여 배열의 각 요소를 추출하여 변수에 할당한다.
    // sets firstName = arr[0]
    // and surname = arr[1]
    let [firstName, surname] = arr;

    alert(firstName); // John
    alert(surname);  // Smith

[ 쉼표를 사용하여 요소 무시 ]

  • 배열의 원하지 않는 요소는 추가 쉼표를 통해 제거할 수 있다.
    // second element is not needed
    let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

    alert( title ); // Consul

[ 오른쪽의 모든 반복 가능 항목과 함께 작동 ]

  • 실제로 배열뿐만 아니라 모든 반복 가능 항목과 함께 사용할 수 있다.
  • 내부적으로 구조분해 할당은 올바른 값을 반복하여 작동하기 때문에 다음과 같이 작동한다.
    let [a, b, c] = "abc"; // ["a", "b", "c"]
    let [one, two, three] = new Set([1, 2, 3]);

[ 왼쪽에 있는 항목에 할당 ]

  • 왼쪽에 있는 "할당 가능 항목"을 사용할 수 있다.
  • 예를 들어 객체 속성은 다음과 같다.
    • 예시 1 ) 아래 항목들은 할당 연산자 왼쪽에 위치하고 있고, 여기에 값을 할당할 수 있는 상태이다.
    • 예시 1 ) 왼쪽에 있는 할당 가능 항목은 user.nameuser.surname이다.
        let user = {};
        
       // 예시 1
        [user.name, user.surname] = "John Smith".split(' ');
        // "John Smith" 문자열을 공백을 기준으로 나눈 배열을 생성하고, 
        	배열 해체 할당을 통해 각 요소를 객체 user와 surname 프로퍼티에 할당한다. 

        alert(user.name); // John
        alert(user.surname); // Smith

[ .entries()를 사용한 반복 ]

  • 객체의 키와 값을 반복하기 위해 구조 분해와 함께 사용할 수 있다.
    let user = {
      name: "John",
      age: 30
    };

   // `Object.entries(user)를 사용하여 `user` 객체의 각 프로퍼티를 키-값 쌍의 배열로 반환
   // 배열 해체 할당을 사용하여 현재 요소의 각각의 요소를 `key`와 `value 변수에 할당
    for (let [key, value] of Object.entries(user)) {
      alert(`${key}:${value}`); // name:John, then age:30
    }
  • 유사 코드 (Map 반복)
    let user = new Map();
    user.set("name", "John");
    user.set("age", "30");

   // Map iterates as [key, value] pairs, very convenient for destructuring
    for (let [key, value] of user) {
      alert(`${key}:${value}`); // name:John, then age:30
    }

[ 변수 교환 트릭 ]

  • 구조 분해 할당을 사용하여 두 변수의 값을 바꾸는 잘 알려진 트릭이 있다.
    • 여기서는 두 변수의 임시 배열을 생성하고 즉시 교환된 순서로 구조를 해제한다.
    • 이런 식으로 두 개 이상의 변수를 교환할 수 있다
    let guest = "Jane";
    let admin = "Pete";

    // Let's swap the values: make guest=Pete, admin=Jane
    [guest, admin] = [admin, guest];

    alert(`${guest} ${admin}`); // Pete Jane (successfully swapped!)

[ 나머지(' ... ') ]

  • 일반적으로 배열이 왼쪽 목록보다 길면 "추가" 항목은 생략된다.
    • 예를 들어 아래에서는 두 항목만 선택되고, 나머지는 무시된다.
    let [name1, name2] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

    alert(name1); // Julius
    alert(name2); // Caesar
    // Further items aren't assigned anywhere
  • 아래에 나오는 모든 항목도 수집하려면 세 개의 점을 사용하여 "나머지"를 가져오는 매개변수를 하나 더 추가할 수 있다. (' ... ')
    let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

    // rest is an array of items, starting from the 3rd one
    alert(rest[0]); // Consul
    alert(rest[1]); // of the Roman Republic
    alert(rest.length); // 2

-> 값은 rest 나머지 배열 요소의 배열이다.

  • 대신에 다른 변수 이름을 사용할 수 있다.
  • 변수 이름 앞에 점 세 개가 있고, 구조 분해 할당에서 마지막에 오는지 확인해야한다.
    let [name1, name2, ...titles] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
    // now titles = ["Consul", "of the Roman Republic"]

3. 매개 변수 : 함수에 전달되는 이름이 있는 변수

  • initialState는 초기에 state를 설정할 값이다.
    • 값은 모든 데이터 타입이 허용되지만, 함수에 대해서는 특별한 동작이 있다.
    • 이 인자는 초기 렌더링 이후에는 무시된다.

- 함수를 `initialState`로 전달하면 이를 초기화 함수로 취급한다. - 이 함수는 순수해야 하고, 인자를 받지 않아야 하며 반드시 어떤 값을 반환해야 한다. - React는 컴포넌트를 초기화할 때 함수를 호출하고 그 반환값을 초기 `state`로 저장한다.

4. 반환값

  • useState는 정확히 두 개의 값을 가진 배열을 반환한다.
    1. 첫 번째 렌더링 중에는 전달한 initialState와 일치한다
    2. state를 다른 값으로 업데이트하고, 리렌더링을 촉발할 수 있는 set(설정자) 함수이다.

5. 주의사항

  • useState는 훅이므로 컴포넌트 최상위 레벨이나 직접 만든 훅에서만 호출 가능하다.

    • 반복문이나 조건문 안에서는 호출할 수 없다.
    • 필요한 경우 새 컴포넌트를 추출하고 state를 그 안으로 옮기면 호출 할 수 있다.

  • Strict Mode에서 React는 의도치 않은 불순물을 찾기 위해 초기화 함수를 두번 호출한다.

    • 호출 중 하나의 결과는 무시된다.
    • 이는 개발 환경 전용 동작이며 실제 환경에는 영향을 미치지 않는다.

# setSomething(nextState)과 같은 set 함수

1. set 함수

  • useState가 반환하는 set 함수를 사용하면 state를 다른 값으로 업데이트하고 리렌더링을 할 수 있다.
  • 다음 state를 직접 전달하거나, 이전 state로부터 계산하여 다음 state를 도출하는 함수를 전달할 수도 있다.
  const [name, setName] = useState('Edward');

  function handleClick() {
    setName('Taylor');
    setAge(a => a + 1);
   	
    // ...

2. 매개 변수 : 함수에 전달되는 이름이 있는 변수

  • nextState는 state가 될 값이다.
  • 값은 모든 데이터 타입이 허용되지만, 함수에 대해서는 특별한 동작이 있다.

  • 함수를 nextState로 전달하면 업데이터 함수로 취급한다.
    • 이 함수는 순수해야 하고, 대기 중인 state를 유일한 인수로 사용해야 하며, 다음 state를 반환해야 한다.
  • React는 업데이터 함수를 대기열에 넣고 컴포넌트를 리렌더링한다.
    • 다음 렌더링 중에 React는 대기열에 있는 모든 업데이터를 이전 state에 적용하여 다음 state를 계산한다.

3. 매개 변수 추가 설명

  1. set 함수는 다음 렌더링에 대한 state 변수만 업데이트한다.
    set 함수를 호출한 후에도 state 변수에는 여전히 호출 전 화면에 있던 이전 값이 담겨 있다.

  2. 사용자가 제공한 새로운 값이 Object.is에 의해 현재 state와 동일하다고 판정되면, React는 컴포넌트와 그 자식들을 리렌더링하지 않는다.

  3. 모든 이벤트 핸들러가 실행되고 set 함수를 호출한 후에 화면을 업데이트한다.
    이렇게 하면 단일 이벤트 중에 여러 번 리렌더링 되는 것을 방지할 수 있다.

  4. 렌더링 도중 set 함수를 호출하는 것은 현재 렌더링 중인 컴포넌트 내에서만 허용된다.

4. 주의사항

  • set 함수는 다음 렌더링에 대한 state 변수만 업데이트한다.
    • set 함수를 호출한 후에도 state 변수에는 여전히 호출 전 화면에 있던 이전 값이 담겨 있다.
  • 사용자가 제공한 새로운 값이 Object.js에 의해 현재 state와 동일하다면,
    • React는 컴포넌트와 그 자식들을 렌더링 하지 않는다.
    • 간혹 React가 자식을 건너뛰기 전에 컴포넌트를 호출해야할 수도 있지만 코드에는 영향이 없다.
  • React는 state 업데이트를 일괄처리한다.
    • 모든 이벤트 핸들러가 실행되고 set 함수를 호출한 후에 화면을 업데이트한다.
      • 이렇게 하면 단일 이벤트 중에 여러 번 리렌더링 되는 것을 방지할 수 있다.
    • 드물지만 DOM에 접근하기 위해 React가 화면을 더 일찍 업데이트하도록 강제해야 하는 경우,
      flushSync를 사용할 수 있다.

# 사용법 (Usage)

1. 컴포넌트에 state 추가하기

  • 컴포넌트 최상위 레벨에서 useState를 호출하여 하나 이상의 state 변수를 선언한다
  import { useState } from 'react';

  function MyComponent() {
    const [age, setAge] = useState(42);
    const [name, setName] = useState('Taylor');
    // ...
  1. 구조 분해 할당을 사용하여 [something, setSomething]과 같은 state 변수의 이름을 지정하는 것이 관례이다.
  2. useState는 정확히 두 개의 항목이 있는 배열을 반환합니다.
    • 이 state 변수의 현재 state로, 처음에 제공한 초기 state로 설정된다
    • 상호작용에 반응하여 다른 값으로 변경할 수 있는 set 함수이다.

화면의 내용을 업데이트하려면 다음 state로 set 함수를 호출한다 :

  function handleClick() {
    setName('Robin');
  }

useState 예시 코드

1. Counter(number)

  import { useState } from 'react';

  export default function Counter() {
    const [count, setCount] = useState(0);

    function handleClick() {
      setCount(count + 1);
    }

    return (
      <button onClick={handleClick}>
        You pressed me {count} times
      </button>
    );
  }
  • count state 변수는 숫자를 받는다. 버튼을 클릭하면 숫자가 증가한다.

2. Text field(string)

  import { useState } from 'react';

  export default function MyInput() {
    const [text, setText] = useState('hello');

    function handleChange(e) {
      setText(e.target.value);
    }

    return (
      <>
        <input value={text} onChange={handleChange} />
        <p>You typed: {text}</p>
        <button onClick={() => setText('hello')}>
          Reset
        </button>
      </>
    );
  }
  • 예제에서 `text state 변수는 문자열을 받는다
  • input에 타이핑하면 handleChange는 input DOM 요소에서 최신 input 값을 읽고,
    setText를 호출하여 state를 업데이트한다

3. CheckBox(boolean)

  import { useState } from 'react';

  export default function MyCheckbox() {
    const [liked, setLiked] = useState(true);

    function handleChange(e) {
      setLiked(e.target.checked);
    }

    return (
      <>
        <label>
          <input
            type="checkbox"
            checked={liked}
            onChange={handleChange}
          />
          I liked this
        </label>
        <p>You {liked ? 'liked' : 'did not like'} this.</p>
      </>
    );
  }
  • 예제에서 liked state 변수는 boolean을 받는다.
  • input을 클릭하면 setLiked는 체크박스가 선택되어 있는 여부에 따라 liked state 변수를 업데이트 한다.
  • liked 변수는 체크박스 아래의 텍스트를 렌더링하는 데 사용된다.

4. Form (two variables)

  import { useState } from 'react';

  export default function Form() {
    const [name, setName] = useState('Taylor');
    const [age, setAge] = useState(42);

    return (
      <>
        <input
          value={name}
          onChange={e => setName(e.target.value)}
        />
        <button onClick={() => setAge(age + 1)}>
          Increment age
        </button>
        <p>Hello, {name}. You are {age}.</p>
      </>
    );
  }
  • 동일한 컴포넌트에 두개 이상의 state 변수를 선언할 수 있다
  • 각 state 변수는 완전히 독립적이다.

이전 state를 기반으로 state 업데이트하기

1. 기본 내용

A. age가 42라고 가정했을 때, 이 핸들러는 setAge(age + 1)를 세번 호출한다.

    function handleClick() {
      setAge(age + 1); // setAge(42 + 1)
      setAge(age + 1); // setAge(42 + 1)
      setAge(age + 1); // setAge(42 + 1)
    }
  • 그러나 클릭해보면 age는 45가 아니라 43이 된다
    • 이는 set 함수를 호출해도 이미 실행중인 코드에서 age state 변수가 업데이트되지 않기 때문이다.
    • 따라서 각 setAge(age + 1) 호출은 setAge(43) 이 된다.

B. 이 문제를 해결하려면 다음 state 대신 setAge에 업데이터 함수를 전달할 수 있다.

    function handleClick() {
      setAge(a => a + 1); // setAge(42 => 43)
      setAge(a => a + 1); // setAge(43 => 44)
      setAge(a => a + 1); // setAge(44 => 45)
    }
  • 여기서 a => a + 1 은 업데이트 함수이다.
    • 이 함수는 대기중인 state를 가져와서 다음 state를 계산한다.
  • React는 업데이트 함수를 큐에 넣는다.
    • 그러면 다음 렌더링 중에 동일한 순서로 호출한다.
  • 대기 중인 다른 업데이트가 없으므로, React는 결국 45를 현재 state로 저장한다.
  • 관례상 대기 중인 state 인수의 이름을 age의 a와 같이 state 변수 이름의 첫 글자로 지정하는 것이 일반적이다.
    • 그러나 prevAge 또는 더 명확하다고 생각하는 다른 이름으로 지정해도 된다.
  • React는 개발 환경에서 순수한지 확인하기 위해 업데이터를 두 번 호출할 수 있다.

객체 및 배열 state 업데이트

1. 기본 내용

A. state에는 객체와 배열도 넣을 수 있다.

  • React에서 state는 읽 전용으로 간주되므로 기존 객체를 변이하지 않고, 교체를 해야 한다.
    // 🚩 Don't mutate an object in state like this:
    // 🚩 state 안에 있는 객체를 다음과 같이 변이하지 마세요: 
    form.firstName = 'Taylor';

B. 대신 새로운 객체를 생성하여 전체 객체를 교체하세요:

    // ✅ Replace state with a new object
    // ✅ 새로운 객체로 state 교체합니다
    setForm({
      ...form,
      firstName: 'Taylor'
    });

초기 state 다시 생성하지 않기

1. 기본 내용

A. React 초기 state를 한번 저장하고 다음 렌더링부터는 이를 무시한다.

    function TodoList() {
      const [todos, setTodos] = useState(createInitialTodos());
      // ...
  • createInitialTodos() 의 결과는 초기 렌더링에만 사용되지만, 여전히 모든 렌더링에서 이 함수를 호출하게 된다.

    • 이는 큰 배열을 생성하거나 값비싼 계산을 수행하는 경우 낭비가 될 수 있다.
  • 이 문제를 해결하려면, 대신 이를 useState에 초기화 함수로 전달해야 한다.

  function TodoList() {
    const [todos, setTodos] = useState(createInitialTodos);
    // ...
  • 함수를 호출한 결과인 createInitialTodos() 가 아니라 함수 자체인 createInitialTodos() 를 전달하고 있다.

    • 함수를 useState에 전달하면 React는 초기화 중에만 함수를 호출한다.
  • 개발 환경에서는 React가 초기화 함수가 순수한지 확인하기 위해 초기화 함수를 두번 호출할 수 있다.













profile
웹 프론트엔드

0개의 댓글