웹 풀사이클 데브코스 TIL [Day 51] - 리액트 State와 Component

JaeKyung Hwang·2024년 2월 1일
0
post-thumbnail

2024.02.01(목)

🔄State

🧐State 왜 사용할까?

  • 컴포넌트 내의 일반 지역 변수를 사용했을 때 문제점
    1. 지역 변수는 rendering 간에 유지되지 ❌
      (컴포넌트를 rendering하면 scratch부터 render하기 때문에 지역 변수의 변화가 반영되지 않음)
    2. 지역 변수를 변경하더라도 rendering은 trigger되지 ❌
      (re-rendering 안됨)
  • 그래서 우리는 변경이 필요한 변수의 경우 Component의 memory와 같은 State를 사용!
    useState Hook은 다음 두 가지를 제공
    1. rendering간에 data가 유지되는 state variable
    2. 변수를 업데이트하고 리액트가 해당 컴포넌트를 다시 render하도록 trigger하는 state setter function

🪝useState Hook 사용법

import { useState } from 'react';

const [state, setState] = useState(initialState);
  • initialState에 저장하고 싶은 data의 초기값을 지정 (첫번째 rendering때 이 값이 반환되는 state 변수에 담기고 그 이후의 rendering에서는 무시됨)
  • useState는 stateful value와 이를 업데이트할 수 있는 function을 배열 형태로 반환

💡 state 변수는 구조분해할당을 이용해 [something, setSomething]으로 이름을 짓는 naming convention이 존재

  • 반환받은 set 함수는 setSomething(nextState)와 같이 같이 사용
    • set 함수는 반환값은 없고 인자로 next state를 바로 전달(“just replacing it”)할 수도 있고, 이전 state에서 값을 계산하는 updater function을 전달(“do something with the state value”)할 수 있음

      import { useState } from 'react';
      
      function MyComponent() {
        const [age, setAge] = useState(42);
        const [name, setName] = useState('Taylor');
      
      	function handleClick() {
      	  setName('Swift');      // 다음 값을 직접 전달
      	  setAge(a => a + 1);    // updater function 전달
      	  // ...
    • updater function을 전달하는 경우에는 보류 상태(pending state)를 유일한 인수로하고 next state를 반환하는 순수함수를 사용해야 함

      💡 updater function의 인수(argument)명에 대한 naming convention

      • 해당 state variable의 첫 글자들로 짓기
        setEnabled(e => !e);
        setLastName(ln => ln.reverse());
        setFriendCount(fc => fc * 2);
      • 좀 더 자세한 코드를 선호하거나 일반적인 경우에는 state variable 이름 전체를 그대로 사용할 수 있음
        setEnabled(enabled => !enabled);
        setEnabled(prevEnabled => !prevEnabled);    // prefix 사용
  • ⭐Array나 Object와 같은 객체 타입을 state variable로 만들었을 때에는 spread 문법으로 객체의 사본을 만들어 수정해야 하고 이 사본을 set 함수에 전달해야 함
    • React는 내부적으로 Object.is 비교를 통해 next state가 previous state와 같은지 비교하고 같은 경우 update를 무시함 🔗
      • Object.is로 객체를 비교하면 참조값만을 비교하기 때문에 state variable을 직접 수정하고 이를 set 함수에 전달하더라도 React는 변경 사항을 알 수 없고 update가 되지 않음
      • React에서 State는 객체 타입일지라도 자바스크립트의 원시 타입들처럼 immutable한 읽기 전용(read-only)으로 간주해야 함!
      • 불변성(immutability)이 지켜져야 next state와 previous state를 비교할 수 있고 React의 Virtual DOM에서 컴포넌트를 업데이트할 지 판단 가능
    • Arrays in State Updating Arrays in State – React
      avoid (mutates the array)prefer (returns a new array)
      addingpushunshiftconcat[...arr] spread syntax (example)
      removingpopshiftsplicefilterslice (example)
      replacingsplicearr[i] = ... assignmentmap (example)
      sortingreversesortcopy the array first (example)
    • Objects in State
      Updating Objects in State – React

      💡 참고로 spread 문법은 1 level depth의 요소들만 복사하는 shallow copy가 수행되기 때문에 깊이가 깊은 객체들은 spread 문법을 여러 번 사용하여 deep copy를 수행해야 함 🔗

      const [person, setPerson] = useState({
        name: 'Niki de Saint Phalle',
        artwork: {
          title: 'Blue Nana',
          city: 'Hamburg',
          image: 'https://i.imgur.com/Sd1AgUOm.jpg',
        }
      });
      // Updating a nested object
      setPerson({
        ...person, // Copy other fields
        artwork: { // but replace the artwork
          ...person.artwork, // with the same one
          city: 'New Delhi' // but in New Delhi!
        }
      });

🎨리액트의 Rendering 방법

“Rendering” means that React is calling your component, which is a function.

  • 해당 함수에서 반환하는 JSX는 rendering되는 시점의 snapshot과 같음!

    • React가 Component를 rendering하는 과정
    • rendering 시점의 값으로 state를 업데이트하게 됨

    • 예시
      import { useState } from 'react';
      
      export default function Counter() {
        const [number, setNumber] = useState(0);
      
        return (
          <>
            <h1>{number}</h1>
            <button onClick={() => {
              setNumber(number + 1);
              setNumber(number + 1);
              setNumber(number + 1);
            }}>+3</button>
          </>
        )
      }
      • number라는 stateful value는 rendering 시점에서 0으로 고정(fixed)되어 있기 때문에 +3이 아닌 +1로 처리가 됨
        <button onClick={() => {
          setNumber(0 + 1);
          setNumber(0 + 1);
          setNumber(0 + 1);
        }}>+3</button>
  • 리액트는 효율적인 rendering을 위해 state 업데이트를 이벤트 핸들러의 실행이 완료된 후에 일괄 처리함(batching)

    • 리액트는 event handler의 다른 모든 코드가 실행되고 updater function이 처리될 수 있도록 queue에 넣어두고 해당 component를 re-rendering함
    • 다음 rendering 과정에서 queue에 있는 모든 updater function들을 순서대로 이전 state에 적용해서 다음 state를 계산
    • 예시
      import { useState } from 'react';
      
      export default function Counter() {
        const [number, setNumber] = useState(0);
      
      	// 다음 세 번의 setNumber를 각각 처리하지 않고 queue에 모아서 일괄 처리 -> 버튼을 누르면 3씩 증가
        return (
          <>
            <h1>{number}</h1>
            <button onClick={() => {
              setNumber(n => n + 1);
              setNumber(n => n + 1);
              setNumber(n => n + 1);
      				alert(number);
            }}>+3</button>
          </>
        )
      }
      • 버튼을 클릭하면 3개의 setNumber는 queue로 들어가고 alert가 가장 먼저 실행됨
        • 이때 number는 아직 업데이트되지 않았기 때문에 값은 초기값인 0
      • 그 후에 rendering 과정에서 queue에 있는 setNumber들을 일괄 처리해서 3으로 증가하게 됨

🪄Component

A JavaScript function that you can sprinkle with markup

  • open source community에 있는 component들을 사용할 수 있음
  • 사용하는 목적과 방법이 함수와 거의 흡사 → component는 함수에서 JSX를 return하고 Tag처럼 사용하여 호출
    • lowercase로 사용하면 React는 HTML tag로 인식: <section>
    • 대문자로 시작하면 React는 component로 인식해서 해당 component를 호출: <Profile /> → component Profile 호출
  • component는 모듈화를 할 수 있다는 장점이 있지만 component를 많이 사용할 수록 state 변수를 공유하기가 불편하다는 단점이 있음
    • 물론 함수 간에도 데이터를 기본적으로 공유할 수 없기 때문에 매개변수를 이용하여 전달하는데 컴포넌트에서도 이와 비슷한 props라는 기능을 제공
  • 꼭 필요한 경우에만 컴포넌트로 분리하자
profile
이것저것 관심 많은 개발자👩‍💻

0개의 댓글