[React] React State & Props

선정·2022년 6월 7일
0

Today I Learned

  • Props
  • State
  • 이벤트 처리
  • React 데이터 흐름

State & Props

  • props : 외부로부터 전달받은 값 (부모 컴포넌트에서 자식 컴포넌트로 전달되는 데이터)
  • state : (컴포넌트) 내부에서 변화하는 값

어떤 것이 Props 또는 State에 적합할까?
props (바꿀 수 없는 것) : 이름, 성별
state (변할 수 있는 것) : 나이, 현재 사는 곳, 취업 여부, 결혼/연애 여부

Toggle Switch 컴포넌트가 가지는 State는? (on/off 여부)

{ isOn: true }
{ isOn: false }

Counter 컴포넌트가 가지는 State는? (현재 숫자 값)

{ count: 0 }
{ count: 3 }
{ count: 6 }


Props


props 특징

  • 컴포넌트의 속성(property)
    props는 성별이나 이름처럼 변하지 않는 외부로부터 전달받은 값으로, 웹 애플리케이션에서 해당 컴포넌트가 가진 속성에 해당한다.

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

  • 객체 형태
    props로 어떤 타입의 값도 넣어 전달할 수 있도록 props는 객체형태를 가진다.

  • 읽기 전용
    props는 외부로부터 절달받아 변하지 않는 값이다. 그래서 props는 함부로 변경될 수 없는 읽기 전용 객체이다. 함부로 변경되지 않아야 하기 때문이다.

읽기 전용 객체가 아니라면 props를 전달받은 하위 컴포넌트 내에서 props를 직접 수정 시 props를 전달한 상위 컴포넌트의 값에 영향을 미칠 수 있다. 즉, 개발자가 의도하지 않은 side effect가 생기게 되고 이는 React의 단방향, 하향식 데이터 흐름 원칙에 위배된다.


props 사용법_(1)

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

function Child(props) { // <Parent> 컴포넌트에서 props 전달
  console.log(props)
  // {text: "I'm the eldest child"}
  // {text: "I'm the second child"}
  return (
    <div className="child">
      <p>{props.text}</p>
    </div>
  );
};


props 사용법_(2)

props를 전달하는 또 다른 방법으로, 자식 컴포넌트의 태그와 닫는 태그 사이에 value를 넣어 전달하고, 자식 컴포넌트에서 props.children을 이용하면 해당 value에 접근하여 사용할 수 있다.

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

function Child(props) {
  return (
    <div className="child">
      <p>{props.children}</p>
    </div>
  );
};

props 사용법 다시 비교하기

// 1️⃣ 속성에 값을 할당하여 전달하는 방법
const App = () => {
  const itemOne = "React를";
  const itemTwo = "배우고 있습니다.";

  return (
    <div className="App">
      <Learn text={itemOne + ' ' + itemTwo} />
    </div>
  );
};

const Learn = (props) => {
  return <div className="Learn">{props.text}</div>;
};


// 2️⃣ 태그와 닫는 태그 사이에 value를 넣어 전달하는 방법
const App = () => {
  const itemOne = "React를";
  const itemTwo = "배우고 있습니다.";

  return (
    <div className="App">
      <Learn>{itemOne + ' ' + itemTwo}</Learn>
    </div>
  );
};

const Learn = (props) => {
  return <div className="Learn">{props.children}</div>;
};



State

컴포넌트의 사용 중 컴포넌트 내부에서 변할 수 있는 값 (e.g. 쇼핑몰 장바구니 내에서 주문할 물건만 골라 체크하는 체크박스)

import React, { useState } from "react";

function CheckboxExample() {
  const [isChecked, setIsChecked] = useState(false);
  
  const handleChecked = (event) => {
    setIsChecked(event.target.checked);
  }
  
  return (
    <div className="App">
      <input type="checkbox" checked={isChecked} onChange={handleChecked} />
      <span>{isChecked ? "checked!!" : "Unchecked"}</span>
    </div>
  );
}

export default CheckboxExample;

useState 사용법

  1. useState 불러온다.
import { useState } from "react";
  1. useState를 컴포넌트 안에서 호출한다.
function checkboxExample() {
// 새로운 state 변수를 선언하고, 변수 이름은 isChecked로 한다.
  const [isChecked, setIsChecked] = useState(false);
  // const [state 저장 변수, state 갱신 함수] = useState(상태 초기 값);
}
  • isChecked : state를 저장하는 변수
  • setIsChecked : state isChecked를 변경하는 함수
  • useState : state hook
  • false : state 초깃값

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

// isChecked, setIsChecked는 useState의 리턴값을 구조분해 할당한 변수이다.
// 구조분해 할당된 위의 변수를 풀어쓰면 아래와 같다.
function checkboxExample() {
  const stateHookArray = useState(false);
  const isChecked = stateHookArray[0];
  const setIschecked = stateHookArray[1];

useState를 호출한다는 것은 "state"라는 변수를 선언하는 것과 같다.
일반적인 변수는 함수가 끝날 때 사라지지만, state 변수는 React에 의해 함수가 끝나도 사라지지 않는다.


이 state 변수에 저장된 값을 사용하려면 JSX 엘리먼트 안에 직접 불러서 사용하면 된다. 여기서는 isChecked가 boolean 값을 가지기 때문에 true or false 여부에 따른 결과가 보이도록 삼항연산자를 사용한다.

<span>{isChecked ? "Checked!!" : "unchecked!!"}</span>

state를 갱신하려면 state 변수를 갱신할 수 있는 함수인 setIsChecked를 호출한다.

이번 예시의 경우, input type="checkbox" JSX 엘리먼트와 값 변경에 따라서 isChecked가 변경되어야 한다. 브라우저에서 checked로 값이 변경되었다면 React의 isChecked도 변경되어야 한다.

input 엘리먼트의 값이 변경되면 onChange 이벤트가 발생하고, 이벤트 핸들러 함수가 작동된다. 사용자가 체크박스 값을 변경하면 onChange 이벤트가 이벤트 핸들러 함수인 handleChecked를 호출하고, 이 함수가 setIsChecked를 호출한다.
setIsChecked가 호출되면 호출된 결과에 따라 isChecked 변수가 갱신되며, React는 새로운 isChecked 변수를 CheckboxExample 컴포넌트에 넘겨 해당 컴포넌트를 다시 렌더링한다.

function CheckboxExample() {
  const [isChecked, setIsChecked] = useState(false);

  const handleChecked = (event) => {
    setIsChecked(event.target.checked);
  };

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

주의점

  • React 컴포넌트는 state가 변경되면 새롭게 호출되고, 리렌더링된다.
  • React state는 상태 변경 함수 호출로 변경해야 한다. 강제로 변경을 시도하면 안된다. 강제로 변경을 시도하면, 리렌더링이 되지 않는다거나, state가 제대로 변경되지 않는다.
    e.g.) state.push(1);, state[1] = 2;, state = "wrong state";


이벤트 처리

React 이벤트와 DOM 이벤트 처리 방식의 문법에 차이가 있다. React에서 이벤트는 소문자 대신 카멜 케이스를 사용한다. (ex. onClick)
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 값을 읽어올 수 있다. 컴포넌트 Return문 안의 input 태그에 value와 onChange를 넣어준다. onChange는 input의 텍스트가 바뀔 때마다 발생하는 이벤트이다. 이벤트가 발생하면 handleChange 함수가 작동하며, 이벤트 객체에 담김 input 값을 setState를 통해 새로운 state로 갱신한다.

  1. 함수 자체를 전달하기
function NameForm() {
  const [name, setName] = useState("");
  
  const handleChance = (e) => {
  setName(e.targe.value);
  }
  
  return (
    <div>
      <input type="text" value={name} onChange={handleChange}></input>
    </div>
  );
};
  1. 함수 정의하기
function NameForm() {
  const [name, setName] = useState("");

  return (
    <div>
      <input type="text" value={name} onChange={(e) => setName(e.target.value)}></input>
      <h1>{name}</h1>
    </div>
  );
};

onClick

버튼이나 <a> 같은 엘리먼트와 같이 클릭을 통해 애플리케이션이 반응해야 할 때 자주 사용하는 이벤트이다.

function NameForm() {
  const [name, setName] = useState("");
  
  const handleChange = (e) => {
    setName(e.target.value);
  }
  
  return (
    <div>
      <input type="text" value={name} onChange={handleChange}></input>
      <button onClick={alert(name)}>Button</button>
      <h1>{name}</h1>
    </div>
  );
};

위와 같이 onClick 이벤트에 alert(name)를 바로 호출하면 컴포넌트가 렌더링 될 때 함수 자체가 아닌 함수 호출의 결과가 onClick에 적용된다. 때문에 버튼을 클릭할 때가 아닌, 컴포넌트가 렌더링 될 때에 alert가 실행되고 따라서 그 결과인 undefined가 onClick에 적용되어 클릭했을 때 아무런 결과도 일어나지 않는다. 따라서 onClick 이벤트에 함수를 전달할 때는 함수를 호출하는 것이 아니라 아래와 같이 리턴문 안에서 함수를 정의하거나 리턴문 외부에서 함수를 정의 후 이벤트에 함수 자체를 전달해야 한다.
단, 두 가지 방법 모두 arrow function을 사용하여 함수를 정의하여야 해당 컴포넌트가 가진 state에 함수들이 접근할 수 있다.

  1. 함수 정의하기
function NameForm() {
  const [name, setName] = useState("");
  
  const handleChange = (e) => {
    setName(e.target.value);
  }
  
  return (
    <div>
      <input type="text" value={name} onChange={handleChange}></input>
      <button onClick={() => alert(name)}>Button</button>
      <h1>{name}</h1>
    </div>
  );
};
  1. 함수 자체를 전달하기
function NameForm() {
  const [name, setName] = useState("");
  
  const handleChange = (e) => {
    setName(e.target.value);
  }
  
  const handleClick = () => {
    alert(name);
  };
  
  return (
    <div>
      <input type="text" value={name} onChange={handleChange}></input>
      <button onClick={handleClick}>Button</button>
      <h1>{name}</h1>
    </div>
  );
};


Action item 1 : <select>

selct tag는 사용자가 drop down 목록을 열어 그 중 한 가지 옵션을 선택하면, 선택된 옵션이 state 변수에 갱신된다.

import React, { useState } from "react";

function SelectExample() {
  const [choice, setChoice] = useState("apple");

  const fruits = ["apple", "orange", "pineapple", "strawberry", "grape"];
  const options = fruits.map((fruit) => {
    return <option value={fruit}>{fruit}</option>;
  });

  const handleFruit = (event) => {
    setChoice(event.target.value);
  };

  return (
    <div className="App">
      <select onChange={handleFruit}>{options}</select>
      <h3>You choose "{choice}"</h3>
    </div>
  );
}

export default SelectExample;

Action item 2 : Pop up

import React, { useState } from "react";

function App() {
  const [showPopup, setShowPopup] = useState(false);

  const togglePopup = (event) => {
    setShowPopup(event.target.value);
  };

  return (
    <div className="App">
      <h1>Fix me to open Pop Up</h1>
      <button className="open" value="false" onClick={togglePopup}>
        Open me
      </button>
      {showPopup ? (
        <div className="popup">
          <div className="popup_inner">
            <h2>Success!</h2>
            <button className="close" onClick={togglePopup}>
              Close me
            </button>
          </div>
        </div>
      ) : null}
    </div>
  );
}

export default App;


React 데이터 흐름

React의 개발 방식의 가장 큰 특징은 페이지 단위가 아닌, 컴포넌트 단위로 시작한다는 것이다. 앱의 디자인이 완성되면 먼저 컴포넌트를 찾아 이를 컴포넌트 계층 구조로 먼저 나눈다. 이때, 단일 책임 원칙에 따라 하나의 컴포넌트는 한 가지 일만 해야 한다. 그리고 페이지를 만들기 이전에 컴포넌트를 먼저 만들고 조립한다. 즉, 상향식(bottom-up)으로 앱을 만든다. 이러한 방식은 테스트가 쉽고 확장성이 좋다.

컴포넌트는 컴포넌트 바깥에서 props를 이용해 데이터를 전달 받을 수 있다. 데이터를 전달하는 주체는 부모 컴포넌트가 된다. 즉, 데이터 흐름은 하향식(top-down)이며 단방향 데이터 흐름(one-way data flow)를 따른다.

상태가 특정 컴포넌트에서만 유의미하다면, 특정 컴포넌트에만 두면 되니까 크게 어렵지 않지만, 만일 하나의 상태를 기반으로 두 컴포넌트가 영향을 받는다면 이때에는 공통 소유 컴포넌트를 찾아 그곳에 상태를 위치해야 한다. 즉, 두 개의 자식 컴포넌트가 하나의 상태에 접근하고자 할 때는 두 자식의 공통 부모 컴포넌트에 상태를 위치해야 한다.


import React, { useState } from "react";

function FirstChild(props) {
  return <div>fitst-child: {props.name}</div>
}

function SecondChild(props) {
    return <div>second-child: {props.name}</div>
}

function Parents() {
  const [name, setName] = useState("이름");
  
  return (
    <>
      <FirstChild name={name} />
      <SecondChild name={name} />
      <button onClick={() => setName("")}>clear</button>
    </>
  )
}
profile
starter

0개의 댓글