React states, props

왕지호·2022년 11월 29일
0

오늘은 React Staes, Props에 대해 알아보자!

State

  • 살면서 변할 수 있는 값
  • 나이, etc...
  • 컴포넌트의 사용 중 컴포넌트 내부에서 변할 수 있는 값

Props

  • 변하지 않는 값

Props vs. State

  • props는 외부로부터 전달받은 값
  • state는 내부에서 변화하는 값(컴포넌트 안에서, 외부의 영향 받지 않음)

흠... 그럼 어떤것이 props 또는 state에 적합할까?

props

  • 이름
  • 성별

state

  • 나이
  • 현재 사는 곳
  • 취업 여부
  • 결혼/ 연애 여부

만약 Toggle Switch나 counter가 가지는 state는 무엇이 있을까?

Toggle Switch

  • on/off 여부
  • { isOn: true }
  • { isOn: false}

Counter

  • 현재 숫자 값
  • { count: 0 }
  • { count: 3 }
  • { count: 6 }

그럼 props의 특징을 알아보자!

props의 특징

props는 컴포넌트의 속성(property)을 의미한다.

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

또한, 부모 컴포넌트(상위 컴포넌트)로부터 전달받은 값이다.

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

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

또한, props는 읽기 전용이다. props는 성별이나 이름처럼 외부로부터 전달받아 변하지 않는 값이다. 그래서 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

props를 사용하는 방법은 아래와 같이 3단계 순서로 나눌 수 있다.

1. 하위 컴포넌트에 전달하고자 하는 값(data)과 속성을 정의한다.
2. props를 이용하여 정의된 값과 속성을 전달한다.
3. 전달받은 props를 렌더링한다.

위 단계에 맞추어 props를 사용하기 위해 우선 <Parent><Child> 라는 컴포넌트를 선언하고, <Parent> 컴포넌트 안에 <Child> 컴포넌트를 작성해보자

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

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

그럼 컴포넌트를 만들었으니 이제 전달하고자 하는 속성을 정의해 보자. HTML에서 속성과 값을 할당하는 방법과 같음. 아래의 코드에서는 <a> 요소의 href 속성에 "www.codestates.com" 라는 값을 준다.

<a href="www.codestates.com">Click me to visit Code States</a>

React에서 속성 및 값을 할당하는 방법도 이와 유사하다. 다만, 전달하고자 하는 값을 중괄호 {}를 이용하여 감싸주면 된다.

<Child attribute={value} />

위 방법을 이용하여 text라는 속성을 선언하고, 이 속성에 "I'm the eldest child"라는 문자열 값을 할당하여 <Child> 컴포넌트에 전달해 보자!

<Child text={"I'm the eldest child"} />

자, 이제 <Parent> 컴포넌트에서 전달한 "I'm the eldest child"라는 문자열을 <Child> 컴포넌트에서 받아 보자. 방법은 간단하다. 함수에 인자를 전달하듯이 React 컴포넌트에 props를 전달하면 되고, 이 props가 필요한 모든 데이터를 가지고 오게된다.

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

props를 전달받았으니, 마지막으로 이 props를 렌더링해 보자! props를 렌더링하려면 JSX 안에 직접 불러서 사용하면 된다.

다만, props는 객체라고 하였고, 이 객체의 { key : value }<Parent> 컴포넌트에서 정의한 { attribute : value }의 형태를 띠게 된다. 따라서 JavaScript에서 객체의 value에 접근할 때 dot notation을 사용하는 것과 동일하게 props의 value 또한 dot notation으로 접근할 수 있다. 아래와 같이 props.text를 JSX에 중괄호와 함께 작성하면 잘 작동한다.

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>);
};

Action item

App 컴포넌트에 있는 itemOneitemTwoLearn 컴포넌트에 props로 전달하여, "React를 배우고 있습니다"라는 문자열이 rendering 되도록 코드를 완성해보자.

import "./styles.css";

const App = () => {
  const itemOne = "React를";
  const itemTwo = "배우고 있습니다.";

  return (
    <div className="App">
      {/* Learn 컴포넌트에 itemOne 과 itemTwo 를
      props 로 전달하세요 */}
      <Learn />
    </div>
  );
};

const Learn = (props) => {
  // 전달받은 props 를 아래 <div> tag 사이에 사용하여
  // "React를 배우고 있습니다" 라는 문장이 렌더링되도록 컴포넌트를 완성하세요
  return <div className="Learn"></div>;
};

export default App;

그럼 이제 state를 알아보자!

애플리케이션의 "상태"

state는 Toggle Switch나 Counter처럼 컴포넌트 내부에서 변할 수 있는 값이다.

실제 애플리케이션에서는 무엇이 "상태"라고 할 수 있을까?

쇼핑몰 장바구니를 예로 들어보자. 사용자는 구매할 물건과 당장은 구매하지 않을 물건을 체크박스에 체크하여 구분 짓는다. 이를 장바구니 내에서의 상태로 구분해 본다면 check 된 상태check 되지 않은 상태로 나눌 수 있다.

체크박스를 코드로 구현해 보면 아래와 같다.

아래 예시에서는 단순히 체크된 상태에 따라 보이는 글씨가 달라지지만, 이를 쇼핑몰에 적용하면 체크 여부에 따라 구매할 물건의 개수나 구매 금액이 변경되고, 이에 따라 사용자의 화면 또한 달라지게 된다.

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


그럼 state는 어떤 방식으로 사용해야 할까?

State hook, useState

useState 사용법

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

이 함수의 사용 방법과 작동 방식을 위의 체크박스 예로 들어 살펴보자

  • useState 를 이용하기 위해서는 React로부터 useState 를 불러와야 한다. import 키워드로 useState 를 불러오자.

    import { useState } from "react";
  • 이후 useState 를 컴포넌트 안에서 호출해 준다. useState 를 호출한다는 것은 "state" 라는 변수를 선언하는 것과 같으며, 이 변수의 이름은 아무 이름으로 지어도 된다. 일반적인 변수는 함수가 끝날 때 사라지지만, state 변수는 React에 의해 함수가 끝나도 사라지지 않는다.

  • 문법적으로 보면 아래 예시의 isChecked, setIsCheckeduseState 의 리턴값을 구조 분해 할당한 변수이다.

    function CheckboxExample() {
    // 새로운 state 변수를 선언하고, 여기서는 이것을 isChecked 라 부르겠습니다.
      const [isChecked, setIsChecked] = useState(false);
    }
    function CheckboxExample() {
      // 1번 코드를 풀어쓰면
      const [isChecked, setIsChecked] = useState(false); // 1번
    
      //...
    
      // 2번 코드와 같습니다.
      const stateHookArray = useState(false); // 2번
      const isChecked = stateHookArray[0];
      const setIsChecked = stateHookArray[1];
    }
  • useState를 호출하면 배열을 반환하는데, 배열의 0번째 요소는 현재 state 변수이고, 1번째 요소는 이 변수를 갱신할 수 있는 함수이다.

  • useState의 인자로 넘겨주는 값은 state의 초깃값

const [state 저장 변수, state 갱신 함수] = useState(상태 초기 값);
function CheckboxExample() {
  const [isChecked, setIsChecked] = useState(false);
  // const [state 저장 변수, state 갱신 함수] = useState(state 초깃값);
  • isChecked : state를 저장하는 변수
  • setIsChecked : state isChecked 를 변경하는 함수
  • useState : state hook
  • false : state 초깃값
  • 이 state 변수에 저장된 값을 사용하려면 JSX 엘리먼트 안에 직접 불러서 사용하면 된다. 여기서는 isChecked가 boolean 값을 가지기 때문에 trueor false여부에 따라 다른 결과가 보이도록 삼항연산자를 사용한다.
<span>{isChecked ? "Checked!!" : "Unchecked"}</span>

state 갱신하기

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

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

  • 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>);
}

흠.. 그렇다면 state의 주의점은 없을까?

주의점

React 컴포넌트는 state가 변경되면 새롭게 호출되고, 리렌더링된다.

즉, 컴포넌트의 상태가 변경될 때마다 새롭게 호출되고, 리렌더링 된다.

React state는 상태 변경 함수 호출로 변경해야 한다.

강제로 변경을 시도하면 안 된다!!
상태 변경 함수 사용은 React와 개발자의 약속이기 때문에 지켜줘야 한다. 강제로 변경을 시도하면, 리렌더링이 되지 않는다거나, state가 제대로 변경되지 않는다.`

예시 : `state.push(1);`, `state[1] = 2;`, `state = 'wrong state';


그럼 이제 react의 이벤트 처리에 대해 알아보자!!

이벤트 처리

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 값을 읽어올 수 있다.

컴포넌트 return 문 안의 input 태그에 valueonChange 를 넣어주었다. onChangeinput 의 텍스트가 바뀔 때마다 발생하는 이벤트이다.

이벤트가 발생하면 handleChange 함수가 작동하며, 이벤트 객체에 담긴 input 값을 setState 를 통해 새로운 state 로 갱신한다.

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

onClick 이벤트는 말 그대로 사용자가 클릭이라는 행동을 하였을 때 발생하는 이벤트이다.

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

그럼 위의 onChange 예시에 버튼을 추가하여 버튼 클릭 시 input tag 에 입력한 이름이 alert을 통해 알림 창이 팝업 되도록 코드를 추가해 보자!

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이벤트에 함수를 전달할 때는 함수를 호출하는 것이 아니라 아래와 같이 리턴문 안에서 함수를 정의하거나 리턴문 외부에서 함수를 정의 후 이벤트에 함수 자체를 전달해야 한다!

// 함수 정의하기

return (
  <div>
	...
    <button onClick={() => alert(name)}>Button</button>
	...
  </div>);
};

// 함수 자체를 전달하기

const handleClick = () => {
  alert(name);
};

return (
  <div>
      ...
    <button onClick={handleClick}>Button</button>
      ...
  </div>);
};


그럼 이제 react의 action 아이템들을 살펴보자!

Action item 1 : <select>

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

useState 가 어떠한 상태를 갱신하도록 설정되어 있는지 확인한 뒤, 주석에 따라 select tag 가 정상적으로 작동하도록 코드를 완성해보면 아래와 같다.


Action item 2 : Pop up

Pop up 역시 Pop up 의 open 과 close 를 state 를 통해 관리할 수 있다.

아래 예제 또한 useState 를 확인하여 버튼 클릭에 따라 하단의 이미지와 같이 Pop up 이 open/close 되도록 코드를 완성해봤다.

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

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

  const togglePopup = () => {
    // Pop up 의 open/close 상태에 따라
    // 현재 state 가 업데이트 되도록 함수를 완성하세요.
    if (showPopup === false) {
      setShowPopup(true);
    } else setShowPopup(false);
  };

  return (
    <div className="App">
      <h1>Fix me to open Pop Up</h1>
      {/* 버튼을 클릭했을 때 Pop up 의 open/close 가 작동하도록
          button tag를 완성하세요. */}
      <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;


그럼 이제 마지막으로 state를 통제할 수 있는 컴포넌트를 알아보자!

Controlled Component

Controlled Component

  • React가 state를 통제할 수 있는 컴포넌트

React에서는 이렇게 상태에 해당하는 데이터를 state로 따로 관리하고 싶어한다.

그럼 어떻게 React가 state를 통제할 수 있을까?

input에 값 입력 시, state도 그때그때 바뀌면(onChange) 되고, 이 변경된 state와 input의 value 또한 같게 작성해야 한다.

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

export default function App() {
  const [username, setUsername] = useState("");
  const [msg, setMsg] = useState("");

  return (
    <div className="App">
      <div>{username}</div>
      <input
        type="text"
        value={username}
        onChange={(event) => setUsername(event.target.value)}
        placeholder="여기는 인풋입니다."
        className="tweetForm__input--username"
      ></input>
      <div>{msg}</div>
      {/* TODO : 위 input과 같이 입력에 따라서 msg state가 변할 수 있게 
      아래 textarea를 변경하세요. */}
      <textarea
        placeholder="여기는 텍스트 영역입니다."
        className="tweetForm__input--message"
        onChange={(event) => {
          setMsg(event.target.value);
        }}
        value={msg}
      ></textarea>
    </div>
  );
}

profile
개발 공부하는 코린이!

0개의 댓글