0522, 0530 props, state

이인우·2023년 5월 22일
0

1. Props, State

✔️ props

컴포넌트의 속성(property)를 의미하며, props는 변하지 않는 외부로부터 전달받은 값으로, 웹 애플리케이션에서 해당 컴포넌트가 가진 속성을 의미합니다. 즉, 부모 컴포넌트(상위 컴포넌트)로부터 전달받은 값입니다.

React 컴포넌트는 JavaScript 함수와 클래스로, props를 함수의 전달인자(arguments)처럼 전달받아 이를 기반으로 어떻게 표시되는지 기술하는 React 엘리먼트를 반환합니다. 컴포넌트가 최초 렌더링 될 떄 화면에 출력하고자 하는 데이터를 담은 초깃값으로 사용할 수 있습니다.

props는 객체 형태이며 props로 어떤 타입의 값도 넣어 전달할 수 있도록 합니다.
또한 props는 읽기 전용이며 성별, 이름처럼 외부로부터 전달받아 변하지 않는 값이고, 함부로 변결될 수 없는 읽기 전용 객체입니다.

  • props 사용법
  1. 하위 컴포넌트에 전달하고자 하는 값과 속성 정의
  2. props를 이용하여 정의된 값과 속성 전달
  3. 전달받은 props 렌더링
function Parent() {
  return (
    <div className="parent">
      <h1>I'm parent</h1>
      <Child text={"I'm the eldest child"} />
    </div>
  );
}

function Child(props) {
  // 함수에 인자를 전달하듯이 React 컴포넌트에 props 전달 (데이터 가져옴)
  return (
    <div className="child">
      <p>{props.text}</p>
    </div>
  );
}

전달받은 props는 객체의 형태이며 객체는 {key : value} 즉, <Parent /> 컴포넌트에서 정의한 {attribute : value} 의 형태를 띠게 됩니다. JavaScript에서 객체의 value에 접근할 때 dot notation을 사용하는 것처럼 똑같이 접근할 수 있습니다.

📝 props.children

props를 전달하는 방법 중 하나인 <> 와 </> 사이에 value 값을 집어넣어 전달하는 방법이 있습니다. 이 경우 props.children을 이용하여 접근합니다.

function Parent() {
  return (
    <div className="parent">
      <h1>I'm parent</h1>
      <Child text={"I'm the eldest child"} />
    </div>
  );
}

function Child(props) {
  // 함수에 인자를 전달하듯이 React 컴포넌트에 props 전달 (데이터 가져옴)
  return (
    <div className="child">
      <p>{props.children}</p>
    </div>
  );
}

✔️ state(상태)

state는 컴포넌트 내부에서 변할 수 있는 값이며, checkbox를 예시로 들었을 때 check 된 상태와 check 되지 않은 상태를 나타냅니다.

📝 useState

React에서 useState라는 함수를 통해 state를 다룹니다.

const [state 저장 함수, state 갱신 함수] = useState(상태 초기 값);

useState를 호출하면 배열을 반환하며, 0번째 요소는 현재 State 변수, 1번째 요소는 이 변수를 갱신할 수 있는 함수, 그리고 useState의 인자로 넘겨주는 값은 state의 초깃값입니다.

state를 갱신할 때 state 변수를 갱신할 수 있는 state 갱신 함수를 호출합니다.

React state는 상태 변경 함수 호출로 변경해야 하고, 강제로 변경을 시도하면 안되며 상태 변경 함수 사용은 React와 개발자의 약속이기에 꼭 지켜야 합니다. 이를 어길시 리렌더링이 되지 않거나, state가 제대로 변경되지 않습니다.

import {useState} from "react"; // React로부터 useState 불러오기

function CheckboxExample() {
    const [isChecked, setIsChecked] = useState(false);
    // useState를 컴포넌트 안에서 호출 즉, 변수를 선언하는 것과 같은 의미
    // [isChecked, setIsChecked] 리턴값을 구조분해 할당한 변수
    
    
    const handleChecked = (event) => {
        setIsChecked(event.target.checked);
    };

    return (
        <div className = "App">
        <input type="checkbox" checked={isChecked} onChange={handleChecked} />
        <span>{isChecked ? "Checked !" : "Unchecked"}</span>
        </div>
    )

    // onChange 이벤트가 이벤트 핸들러 함수 handleChecked 호출하며 이 함수가 setIsChecked를 호출하게 됩니다.
    // setIsChecked가 호출되면 호출된 결과에 따라 isChecked 변수가 갱신되며 React는 새로운 isChecked 변수를 CheckboxExample 컴포넌트에 넘겨 해당 컴포넌트를 다시 렌더링
};

✔️ React의 이벤트 처리 (이벤트 헨들링)

DOM의 이벤트 처리 방식과 유사하지만, React에서 이벤트는 소문자 대신 카멜 케이스를 사용하며, JSX를 사용하여 문자열이 아닌 함수로 이벤트 처리 함수를 전달합니다.

// HTML 이벤트 처리 방식
<button onclick = "handleEvent()">Event</button>

// React 이벤트 처리 방식
<button onClick = {handleEvent}>Event</button>

📝 onChange

<input>, <textarea>, <select>와 같은 폼(Form) 엘리먼트는 사용자의 입력값을 제어하는데 사용되며, React에서는 이 변경될 수 있는 값들을 컴포넌트의 state로 관리하고 업데이트 합니다. onChange 이벤트가 발생하면 e.target.value를 통해 이벤트 객체에 담겨있는 input 값을 읽을 수 있습니다.

function NameForm() {
    const [name, setName] = useState("");

    const handleChange = (e) => {
        setName(e.target.value);
        // onChange 이벤트 발생시 이벤트 객체 담겨있는 값 읽어오기
    }

    return (
        <div> 
        <input type="text" value={name} onChange={handleChange}></input>
        <h1>{name}</h1>
        </div>
    )
}

📝 onClick

사용자가 클릭했을 떄 발생하는 이벤트를 의미하고, button이나 a 태그를 통해 링크 이동 등과 같이 주로 사용자의 행동에 따라 애플리케이션이 반응해야 할때 사용되는 이벤트입니다.

onClick 이벤트에 함수를 전달할 떄는 함수를 호출하는 것이 아닌 리턴문 안에서 함수를 정의하거나 리턴문 외부에서 함수를 정의 후 이벤트에 함수 자체를 전달합니다.

import React, { useState } from "react";

function NameForm() {
  const [name, setName] = useState("");

  const handleChange = (e) => {
    setName(e.target.value);
  };

  // 함수 자체 전달
  // button onClick={() => alert(name)}>Button</button>

  const handleClick = () => {
    alert(name);
  };
  return (
    <div className="App">
      <h1>Event handler practice</h1>
      <input type="text" value={name} onChange={handleChange}></input>
      <button onClick={handleClick}>Button</button>
      <h3>{name}</h3>
    </div>
  );
}
export default NameForm;

✔️ React의 데이터 흐름

<출처 : 코드스테이츠 >

React의 개발 방식은 컴포넌트 단위로 시작합니다. 즉, 컴포넌트를 먼저 만들고 다시 페이지를 조립해 나가는 방식입니다. 이를 상향식(bottom-up)이라 표현하고 테스트가 쉽고 확장성이 좋다는 장점이 있습니다.

컴포넌트는 컴포넌트 바깥에서 props를 이용해 데이터를 마치 인자(arguments)나 속성(attribute)처럼 전달받을 수 있습니다. 즉, 데이터를 전달하는 주체는 부모 컴포넌트가 되고 이때 데이터는 하향식(top-down)입니다. props는 하위 컴포넌트로 내려갈 수 있지만, 상향식으로 전달할 수는 없습니다.

이를 단방향 데이터 흐름(one-way-data flow)라 하며, 컴포넌트는 props를 통해 전달받은 데이터가 어디서 왔는지 전혀 알지 못합니다.

모든 데이터를 상태로 둘 필요는 없고, 상태는 최소화 하는 것이 좋으며 상태가 많아질수록 애플리케이션은 복잡해집니다. 상태가 특정 컴포넌트에서만 유의미하다면 그 곳에만 두면 되니 어렵진 않으나, 만일 하나의 상태를 기반으로 두 컴포넌트가 영향을 받으면 이때는 공통 소유 컴포넌트를 찾아 그곳에 상태를 위치해야 합니다.

즉, 두 개의 자식 컴포넌트가 하나의 상태에 접근하고자 할 떄는 두 자식의 공통 컴포넌트에 상태가 위치해야 합니다.


✔️ state 끌어 올리기

종종 동일한 데이터의 변경사항을 여러 컴포넌트에 적용해야 할 때가 있는데 이 때 가까운 공통 조상 컴포넌트로 state를 끌어올리는 것이 좋습니다.

state를 끌어올리는 것은 버그를 찾고 격리하기 더 쉽게 만든다는 장점이 있으며, 어떤 state든 간에 특정 컴포넌트 안에서 존재하기 마련이고, 그 컴포넌트가 자신의 state를 스스로 변경할 수 있으므로 버그가 존재하는 범위가 크게 줄어듭니다. 또한 사용자의 입력을 거부하거나 변형하는 자체 로직을 구현할 수도 있습니다.

각 고유한 state들에 대해 해당 state를 “소유”하는 컴포넌트를 선택하게 됩니다. 이 원칙은 ”단일 진실 공급원“이라고도 합니다. 이는 모든 state가 한 곳에 있다는 뜻이 아니라, 각 state마다 해당 정보를 소유하는 특정 컴포넌트가 있다는 뜻입니다. 컴포넌트 간에 공유하는 state를 복제하는 대신 공통으로 공유하는 부모로 끌어올려서 필요한 자식에게 전달합니다.

⭐ 끌어올리는 과정

1. 자식 컴포넌트에서 state 제거

2. 공통 부모에 하드 코딩된 데이터 전달

3. 공통 부모에 state 추가

import { useState } from "react";

export default function Accordion() {
  // state를 끌어올리려면 조정하려는 두 자식 컴포넌트의 가장 가까운 공통 부모 컴포넌트 찾기 Accordion , 두 패널 위에 있고 props를 제어 o

  // Accordion 컴포넌트가 두 패널 모두에 하드 코딩된 isActive 값(예: true)을 전달

  const [activeIndex, setActiveIndex] = useState(0)
  // 공통 부모에 state 추가
  // activeIndex가 0 이면 첫번째 패널이 활성화된 것이고, 1 이면 두 번째 패널이 활성화된 것
  // 각 Panel에서 “Show” 버튼을 클릭하면 Accordion의 활성화된 인덱스를 변경 


  return (
    <>
      <h2>Almaty, Kazakhstan</h2>
      <Panel
        title="About"
        isActive={activeIndex === 0}
        onShow={() => setActiveIndex(0)}
      >
        With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city.
      </Panel>
      <Panel
        title="Etymology"
        isActive={activeIndex === 1}
        onShow={() => setActiveIndex(1)}
      >
        The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple.
      </Panel>
    </>
  );
}

function Panel({ title, children, isActive }) {
  //  Panel의 부모 컴포넌트는 isActive를 prop으로 전달하여 제어 o
  //  자식 컴포넌트에서 state 제거
  return (
    <section className="panel">
      <h3>{title}</h3>
      {isActive ? (
        <p>{children}</p>
      ) : (
        <button onClick={() => setIsActive(true)}>Show</button>
      )}
    </section>
  );
}

<ChildComponent> 는 마치 고차 함수가 인자로 받은 함수를 실행하듯, props로 전달받은 함수를 컴포넌트 내에서 실행할 수 있게 됩니다.

function ChildComponent({ handleButtonClick }) {
  const handleClick = () => {
    handleClick();
  };

  return <button onClick={handleClick}>값 변경</button>;
}

✔️ state 로직 reducer로 추출하기

많은 state 업데이트가 여러 이벤트 핸들러에 분산되어 있는 컴포넌트는 과부하가 걸릴 수 있으며, 이 경우 컴포넌트 외부의 모든 state 로직을 reducer라는 단일 함수로 통합할 수 있습니다. 이벤트 핸들러는 사용자 “액션”만 지정하기 때문에 간결해집니다.

import { useReducer } from 'react';

export default function TaskApp() {
  const [tasks, dispatch] = useReducer(
    tasksReducer,
    initialTasks
  );

  // ... 생략

✔️ context로 데이터 깊숙이 전달하기

일반적으로 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달할 때 props를 사용합니다. 하지만 여러 컴포넌트에 prop을 전잘하거나 여러 컴포넌트에 동일한 데이터가 필요한 경우 props 전달이 불편해질 수 있는데, 이때 context를 사용하여 부모 컴포넌트가 그 아래 트리의 모든 컴포넌트에서 데이터를 사용할 수 있습니다.

✔️ state 구조 선택 (최적화 된 구조)

1. 관련 state의 그룹화 : 항상 두 개 이상의 state 변수를 동시에 업데이트하는 경우 단일 state로 병합하기 !

state 변수가 객체인 경우 다른 필드를 명시적으로 복사하지 않고는 그 안의 한 필드만 업데이트할 수는 없습니다 !

2. state의 모순 피하기 : 여러 state 조각이 서로 모순되거나 "불일치" 할 수 있는 방식으로 state 구성시 에러 발생 여지가 생기므로 피하기!

3. 불필요한 state 피하기 : 렌더링 중 컴포넌트의 props나 기존 state 변수에서 일부 정보를 계산할 수 있다면 해당 정보를 해당 컴포넌트의 state에 넣지 x

4. state 중복 피하기 : 동일한 데이터가 여러 state 변수 간 or 중첩된 객체 내에 중복되면 동기화 state를 유지하기 어려움

5. 깊게 중첩된 state 피하기 : 깊게 계층화 된 state는 업데이트하기 어려움, 가능하면 state를 평평한 방식으로 구성하는 것이 좋음

profile
안녕하세요 ! 프론트엔드 개발자 취업을 목표로 하는 이인우 입니다!

0개의 댓글