React State & Props

be kid·2022년 1월 12일
0

BEB - 웹 개발 과정

목록 보기
12/28

오늘은 React의 state와 props에 대해...

state : 컴포넌트 사용 중 컴포넌트 내부에서 변할 수 있는 값
props : 외부로부터 전달받은 값

Props의 특징

  • 컴포넌트의 속성(property)을 의미
    성별, 이름 등 변하지 않는 외부로부터 전달받은 값, 웹 어플리케이션에서 해당 컴포넌트가 가진 속성에 해당
  • 부모 컴포넌트(상위 컴포넌트)로부터 전달받은 값
    React 컴포넌트는 JavaScript 함수와 클래스로, props를 함수를 전달인자(arguments)처럼 전달받아 이를 기반으로 화면에 어떻게 표시되는지를 기술하는 React 엘리먼트를 반환함. 따라서, 컴포넌트가 최초 렌더링될 때에 화면에 출력하고자 하는 데이터를 담은 초기값으로 사용할 수 있음
  • 객체 형태
    props로 어떤 타입의 값도 넣어 전달할 수 있도록 객체의 형태를 가짐
  • 읽기 전용
    외부로부터 전달받아 변하지 않는 값. 함부로 변경될 수 없는 읽기 전용(read-only) 객체가 되었음.
    - 읽기 전용 객체가 아니라면 props를 전달받은 하위 컴포넌트 내에서 props를 직접 수정 시 props를 전달한 상위 컴포넌트의 값에 영향을 미칠 수 있게 됨. 즉, 개발자가 의도하지 않은 side effect가 생기게 되고 이는 React의 단방향, 하향식 데이터 흐름 원칙(React is all about one-way data flow down the component hierarchy)에 위배됨.

How to use props

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

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

props.children

props를 전달하는 또 다른 방법으로 여는 태그와 닫는 태그의 사이에 value를 넣어 전달할 수 있음.
이 경우 props.children 을 이용하면 해당 value에 접근하여 사용할 수 있음.

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

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

State

컴포넌트 내에서 변할 수 있는 값, 즉 상태는 React state로 다뤄야 한다.

State hook, useState

useState 사용법

React에서는 state를 다루는 방법 중 하나로 useState라는 특별한 함수를 제공함.

  • useState를 이용하기 위해서는 React로부터 useState를 불러와야 함. (import)
import { useState } from "react";
  • useState를 컴포넌트 안에서 호출해줌. useState를 호출한다는 것은 state라는 변수를 선언하는 것과 같으며, 이 변수의 이름은 아무 이름으로 지어도 됨. 일반적인 변수는 함수가 끝날 때 사라지지만, state변수는 React에 의해 함수가 끝나도 사라지지 않음.
  • 문법적으로 보면 아래 예시의 isChecked, setIsChecked는 useState의 리턴값을 구조 분해 할당한 변수
function CheckboxExample() {
	const [isChecked, setIsChecked] = useState(false);

	/*
	const stateHookArray = useState(false);
	const isChecked = stateHookArray[0];
	const setIsChecked = stateHookArray[1];
	*/
}
  • useState를 호출하면 배열을 반환하는데, 배열의 0번째 요소는 현재 state변수이고, 1번째 요소는 이 변수를 갱신할 수 있는 함수임. useState의 인자로 넘겨주는 값는 state의 초기값.
const [state 저장 변수, state 갱신 함수] = useState(상태 초기 값);
  • 이 state 변수에 저장된 값을 사용하려면 JSX 엘리먼트 안에 직접 불러서 사용하면 됨.
<span>{isChecked ? "Checked!!" : "Unchecked"}</span>

state 갱신하기

  • state를 갱신하려면 state변수를 갱신할 수 있는 함수는 setIsChecked를 호출해줌
  • checkbox예시의 경우, input[type=checkbox] JSX 엘리먼트의 값 변경에 따라서 isChecked가 변경되어야 함.
  • input[text=checkbox] 엘리먼트의 값이 변경되면 onChange 이벤트가 발생하고, 이벤트 핸들러 함수가 작동됨.
  • React도 마찬가지로, 사용자가 체크박스 값을 변경하면 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는 상태 변경 함수 호출로 변경해야 함. 강제로 변경을 시도하면 안 됨. 상태 변경 함수 사용은 React와 개발자의 약속. 강제로 변경을 시도하면, 리렌더링이 되지 않는다거나, state가 제대로 변경되지 않음.

이벤트 처리

React의 이벤트 처리(이벤트 핸들링 Event handling) 방식은 DOM의 이벤트 처리 방식과 유사하지만 몇 가지 문법 차이가 있음

  • React에서 이벤트는 소문자 대신 카멜 케이스(camelCase)를 사용함
  • JSX를 사용하여 문자열이 아닌 함수로 이벤트 처리 함수(이벤트 핸들러 Event handler)를 전달함
// 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);
  }

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

onClick

사용자가 클릭이라는 행동을 했을 때 발생하는 이벤트. 버튼이나 a tag를 통한 링크 이동 등과 같이 주로 사용자의 행동에 따라 애플리케이션이 반응해야 할 때 자주 사용하는 이벤트.

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 (함수는 리턴값이 없을 때 undefined를 반환함)가 onClick에 적용되어 클릭했을 때 아무런 결과도 일어나지 않음. 따라서 onClick 이벤트에 함수를 전달할 때는 함수를 호출하는 것이 아니라 리턴문 안에서 함수를 정의하거나 리턴문 외부에서 함수를 정의 후 이벤트에 함수 자체를 전달해야함.
단, 두 가지 방법 모두 arrow function을 사용하여 함수를 정의하여야 해당 컴포넌트가 가진 state에 함수들이 접근할 수 있음.

select

import React, { useState } from "react";
import "./styles.css";

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>;
  });
  console.log(choice);
  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;

pop up

import React, { useState } from "react";
import "./styles.css";

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

  const togglePopup = () => {
    setShowPopup(!showPopup);
  };

  return (
    <div className="App">
      <h1>Fix me to open Pop Up</h1>
      <button className="open" 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;

Controlled Component

React에서는 상태에 해당하는 데이터를 state로 따로 관리하고 싶어함.
React가 state를 통제할 수 있는 컴포넌트를 Controlled Component라고 함.
https://reactwithhooks.netlify.app/docs/forms.html

어떻게 React가 state를 통제할 수 있는가? → input에 값 입력 시, state도 그때그때 바뀌면(onChage)됨.


React 데이터 흐름

컴포넌트

  • React의 개발 방식의 가장 큰 특징은 페이지 단위가 아닌, 컴포넌트 단위로 시작한다는 점.
    앱의 프로토타입을 보고 컴포넌트를 찾아내 우선 만들고, 페이지를 조립해나감.
  • 즉, 상향식(bottom-up)으로 앱을 만듬.
    이것의 가장 큰 장점은 테스트가 쉽고 확장성이 좋음.
    → 페이지를 만들기 이전에, 컴포넌트를 먼저 만들고 조립

데이터의 흐름

  • 데이터를 어디에 둘지 결정.
    컴포넌트는 컴포넌트 바깥에서 props를 이용해 데이터를 마치 인자(arguments)혹은 속성(attributes)처럼 전달받을 수 있음. 즉 데이터를 전달하는 주체는 부모 컴포넌트가 됨. 이는 데이터 흐름이 하향식(top-down) 임을 의미함. 매우 중요한 원칙.
  • 단방향 데이터 흐름(one-way data flow)이라는 키워드가 React를 대표하는 설명 중 하나일 정도.
    또한 컴포넌트는 props를 통해 전달받은 데이터가 어디서 왔는지 전혀 알지 못함.

데이터 정의

  • 애플리케이션에서 필요한 데이터가 무엇인지 먼저 정의
  • 모든 데이터를 상태로 둘 필요는 없음. 상태는 최소화하는 것이 가장 좋음. 상태가 많아질수록 애플리케이션은 복잡해지기 때문.
  • 어떤 데이터를 상태로 둘 것인가
    • 부모로부터 props를 통해 전달되는가? 그러면 확실히 state가 아님
    • 시간이 지나도 변하지 않는가? 그러면 확실히 state가 아님
    • 컴포넌트 안의 다른 state나 props를 가지고 계산 가능한가? 그렇다면 state가 아님

상태 위치 정하기

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

→ 데이터를 다룰 때는 컴포넌트들 간의 상호 관계와 데이터의 역할, 데이터의 흐름을 고려해 위치를 설정해야 함


profile
개쩌는 개발자가 되고 싶다 !

0개의 댓글