17. [React] - React State & Props

문도연·2022년 6월 9일
0

Chapter1. React State & Props
1-1. Props
1-2. State
1-3. 이벤트 처리
1-4. Controlled Component
1-5. React 데이터 흐름


  • state, props의 개념에 대해서 이해하고, 실제 프로젝트에 바르게 적용할 수 있다.
  • React 함수 컴포넌트(React Function Component)에서 state hook을 이용하여 state를 정의 및 변경할 수 있다.
  • React 컴포넌트(React Component)에 props를 전달할 수 있다.
  • 이벤트 핸들러 함수를 만들고 React에서 이용할 수 있다.
  • 실제 웹 애플리케이션의 컴포넌트를 보고 어떤 데이터가 state이고 props에 적합한지 판단할 수 있다.
  • 실제 웹 애플리케이션 개발 시 적합한 state와 props의 위치를 스스로 정할 수 있다.
  • React의 단방향 데이터 흐름(One-way data flow)에 대해 자신의 언어로 설명할 수 있다.
  • JSX 문법의 기본과 컴포넌트 기반 개발에 대해서 숙지한다.
  • React Router DOM으로 React에서 SPA(Single-Page Application)을 구현할 수 있다.
  • state hook을 이용하여, 컴포넌트에서 데이터를 변화시킬 수 있다.
  • props를 이용하여, 부모 컴포넌트의 데이터를 자식 컴포넌트로 전달할 수 있다.
  • 바람직한 컴포넌트 구조와 state와 props의 위치에 대해 고민한다.

React State & Props Intro

State

컴포넌트 사용 중 컴포넌트 내부에서 변할 수 있는 값

State vs Props

props는 외부로부터 전달받은 값
state는 내부에서 변화하는 값

State, Props 적절하게 구분하기

props 이름, 성별
state 나이, 현재 사는 곳, 취업여부, 결혼/연애 여부


1-1. Props

props의 특징

컴포넌트의 속성(property)을 의미함

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

부모 컴포넌트(상위 컴포넌트)로부터 전달받은 값

React 컴포넌트는 props를 함수의 전달인자(arguments)처럼 전달받아 이를 기반으로 화면에 어떻게 표시되는지를 기술하는 React 엘리먼트를 반환함

=>따라서, 컴포넌트가 최초 렌더링 될 때 화면에 출력하고자 하는 데이터를 담은 초깃값으로 사용할 수 있음

객체 형태

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

읽기 전용

외부로부터 전달받아 변하지 않는 값이며, 함부로 변경되서도 안됨
=> props는 함부로 변경될 수 없는 읽기 전용(read-only) 객체

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

props를 사용하는 방법

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

실습 - props를 사용하는 방법(1)

1) 부모 컴퍼넌트, 자식 컴퍼넌트를 만든다
2) 부모 컴퍼넌트 스코프 내에서 자식 컴퍼넌트 태그의 속성을 정의한다
3) 자식 컴퍼넌트 매개변수에 props 를 적어 속성을 전달받는다.
4) 전달받은 속성을 렌더링하기 위해 props.속성명을 태그의 콘텐트에 집어넣는다.

function Parent() {
  return (
      <div className="부모">
          <h1>나는 부모다</h1>
          <Child text={"나는 막내야"} />
          <Child text={"나는 둘째야"} />
      </div>
  );
};

export default Parent;
function Child(props) {
  return (
      <div className="자식">
          <p>{props.text}</p>
      </div>
  );
};

브라우저 출력화면

실습 - props를 사용하는 방법(2)

여는 태그와 닫는 태그의 사이에 value를 넣어 전달하는 방법
1) 부모 컴퍼넌트에서 태그 작성
2) 자식 컴퍼넌트에서 태그 사이에 {props.children} 넣기

function Parent() {
  return (
      <div className="부모">
          <h1>나는 부모다</h1>
          <Child>나는 맏이야</Child>
          <Child>나는 외동이야</Child>
      </div>
  );
};

export default Parent;
function Child(props) {
  return (
      <div className="자식">   
          <p>{props.children}</p>
      </div>
  );
};

브라우저 출력화면

실습 - 다른 형태, 똑같은 기능

function Parent() {
  const one = "나는";
  const two = "셋째이고 막내이다";
  return (
      <div className="부모">
         <Moondoyeon text={one + ' ' + two}/>
      </div>
  );
};
const Moondoyeon = (props) => {
  return (
    <div className="Learn">
      <p>{props.text}</p>
    </div>
  );
};

브라우저 출력화면

퀴즈

props는 immutable한 데이터이며, 부모에서 자식으로 혹은 구성 요소 자체에서 데이터를 전달하는 데 사용됩니다.

클래스 컴포넌트뿐만 아니라 함수형 컴포넌트에서도 props를 사용할 수 있습니다.

ES6 class 문법으로도 컴포넌트를 만들 수 있고, 이를 클래스 컴포넌트라고 합니다. 이와 대조해서 함수 문법을 사용하여 만든 컴포넌트를 말 그대로 함수 컴포넌트라고 부릅니다. Hook이 나오기 전에는 state는 클래스 컴포넌트에만 다룰 수 있었으나, Hook이 나오면서 함수 컴포넌트도 state를 다룰 수 있게 되었습니다.

props 속성의 이름을 임의로 지정해 줄 수 있다.
props의 속성은 여러 개 지정할 수 있습니다.


Chapter1-2. State

State

컴포넌트 내부에서 변할 수 있는 값

카운터만들기 (Non-State)

=> 브라우저 화면상에서 작동 안함
증가버튼을 눌러도 1씩 증가하지 않고
콘솔에만 1씩 증가뜸

import './App.css';

function App() {
  let count = 0;
  const increase = ()=>{
    count = count + 1;
    console.log("conunt work?", count);
  }
  return (
    <main>
      <div>{count}</div>
      <button onClick={increase}>증가</button>
    </main>
  );
};

export default App;

출력

카운터만들기 (State)

import './App.css';
import {useState} from 'react';

function App() {
  const [count2, setCount2] = useState(0);
  const increase = ()=>{
    setCount2(count2+1)
    console.log("state work?", count2);
  }
  return (
    <main>
      <div>state:{count2}</div>
      <button onClick={increase}>증가</button>
    </main>
  );
};

export default App;

출력


setCount2함수는 비동기 함수여서 콘솔창의 count2는 한 박자 전의 값이 뜸

State hook, useState

함수 useState 사용법

1) 리액트로부터 useState 함수 불러와야함

app.js 최상단에 명령어 입력
=> import { useState } from "react";

2) useState 를 컴포넌트 안에서 호출해야 함

= "state" 라는 변수를 선언하는 것과 같음

일반적인 변수는 함수가 끝날 때 사라지지만, state 변수는 React에 의해 함수가 끝나도 사라지지 않음(저장됨)

state 변수의 이름은 아무 이름으로 지어도 됨
=> 아래 예시에서 state 변수 === isChecked

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

isChecked : state를 저장하는 변수
setIsChecked : state isChecked 를 변경하는 함수
useState : state hook
false : state 초깃값

잠깐, useState 수도코드를 적어보면

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

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

3) 이 state 변수에 저장된 값을 사용하려면 JSX 엘리먼트 안에 직접 불러서 사용

=> isChecked 가 boolean 값을 가지기 때문에 true or false 여부에 따라 다른 결과가 보이도록 삼항연산자를 사용

return (
   ...
   <span>{isChecked ? "Checked!!" : "Unchecked"}</span>
);

4) state 갱신하는 법

setIsChecked 함수를 호출해야 함

이번 예시의 경우, input JSX 엘리먼트의 값 변경에 따라서 isChecked 가 변경(true 또는 false)되어야 함.

즉, 브라우저에서 checked!!로 값이 변경되었다면, React의 isChecked도(false 에서 true로) 변경되어야 함

  • 사용자가 체크박스 값을 변경하면 onChange 이벤트가 이벤트 핸들러 함수인 handleChecked 를 호출하고, 이 함수가 setIsChecked 를 호출함

  • setIsChecked 가 호출되면 호출된 결과에 따라 isChecked 변수가 갱신되며, React는 새로운 isChecked 변수를 App 컴포넌트에 넘겨 새로 렌더링함

이벤트객체 파헤치기

console.dir(event) // SyntheticBaseEvent 

event = PointerEvent

event.target = input

event.target.checked  === input.checked = true 또는 false 

event.target.__reactProps$n34dqkzfq4o.onChange.name = handleChecked

코드 동작 순서 (확실치않음 ㅠㅎㅠ)

유저가 input 네모박스체크
-> input onChange 이벤트 발생
-> 이벤트핸들러 함수 호출
-> setIsChecked 함수 호출
-> state 변수인 isChecked 값이 false 에서 true로 변경됨(false가 초기값이었지)
-> true 값이 span태그에 반영됨
-> checked!! 가 렌더링됨

import './App.css';
import {useState} from 'react';

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

  const handleChecked = (event) => {
    console.dir(event);
    setIsChecked(event.target.checked);
  };
  return (
    <div className="App">
      <input type="checkbox" checked={isChecked} onChange={handleChecked} />
      <span>{isChecked ? "Checked!!" : "Unchecked"}</span>
    </div>
  );
}

export default App;

state hook 사용 시 주의점

React 컴포넌트는 컴포넌트의 state(상태)가 변경될 때마다 새롭게 호출되고, 리렌더링 됨

React state는 상태 변경 함수를 사용(호출)해서 변경해야 합니다. 강제로 변경을 시도하면 안 됩니다.

퀴즈

컴포넌트에서 변화가 필요한 데이터를 state로 정해서 사용합니다.

state가 변경되면 컴포넌트가 갱신되면서 사용자가 해당 내용을 확인할 수 있습니다.

React 16.8 버전에 Hook이 추가되면서 클래스 컴포넌트를 작성하지 않아도 state를 사용할 수 있게 되었습니다.

state는 하위 컴포넌트에서도 존재할 수 있습니다.


Chapter1-3. 이벤트 처리

React의 이벤트 처리 방식은 DOM의 이벤트 처리 방식과 유사하나 몇 가지 문법 차이가 있음

  • React 에서 이벤트는 소문자 대신 카멜 케이스(camelCase) 를 사용
  • JSX를 사용하여 문자열이 아닌 함수로 이벤트 핸들러를 전달함

HTML에서 이벤트 처리 방식

<button onclick="handleEvent()">Event</button>

React의 이벤트 처리 방식

<button onClick={handleEvent}>Event</button>

자주 사용되는 이벤트 [onChange, onClick]

onChange

입력값이 바뀌면 발생하는 이벤트
리액트에서는 변경될 수 있는 입력값을 컴포넌트의 state 로 관리하고 업데이트 함

변경될 수 있는 입력값

ex) 폼(Form) 엘리먼트 : <input>, <textarea>, <select>

실습

import './App.css';
import {useState} from 'react';

function App() {
  const [name, setName] = useState('아무개');
  const handleChange = (e) => {
    setName(e.target.value);
    console.log(e.target.value);
  }
  
  return (
    <div>
      <input type="text" value={name} onChange={handleChange}></input>
      <h1>{name}</h1>
    </div>
  );
}

export default App;

작동순서

사용자가 input에 '도연띠'를 입력한다
=> onChange 이벤트 발생 (input 텍스트가 바뀔때마다 발생하는 이벤트)

=> 이벤트핸들러인 handleChange작동

=> setName 함수가 변수 name(state) 값을 업데이트함 : 아무개 에서 도연띠 로

=> 이벤트 객체에 담긴 input.value 값 바뀜

출력화면

이벤트 발생 전

이벤트 발생 후

onClick

사용자가 클릭이라는 행동을 했을 때 발생하는 이벤트
ex) <a> tag 를 통한 링크이동

즉, 주로 사용자의 행동에 따라 애플리케이션이 반응해야 할 때 자주 사용하는 이벤트

onClick 이벤트에 함수를 전달하는 방법 2가지

1) 리턴문 안에서 함수를 정의

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

2) 리턴문 외부에서 함수를 정의하고 함수 자체를 전달

  const handleClick = () => {
    alert(name);
  };
  
  return (
    <div>

      <button onClick={handleClick}>버튼</button>

    </div>
  );

함수를 바로 호출하면 안됨. 위처럼 이벤트에 함수 자체를 전달해야 함

단, 두 가지 방법 모두 arrow function 을 사용하여 함수를 정의해야 함
-> 그래야 해당 컴포넌트가 가진 state에 함수들이 접근할 수 있음

State 실습1 select

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

import './App.css';
import {useState} from 'react';

function App() {
  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) => {
    //TODO : select tag 가 정상적으로 작동하도록 코드를 완성하세요.
    setChoice(event.target.value);
  };

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

export default App;

동작순서

사용자가 드롭다운 목록 중 딸기를 선택하면 (디폴트: 사과)
=> handleFruit 이벤트핸들러가 동작
=> 이벤트.타겟.벨류 === 딸기 가 setChoice 인자로 전달됨
=> setChoice 함수로 인해 state인 choice가 애플에서 딸기로 업뎃됨
=> 업뎃된 choice값(딸기)이 h3태그 값에 반영되고 렌더링 됨

options = fruits.map() 은 배열 fruits의 각 원소에 대한 옵션태그를 만드는 역할을 수행

출력화면


State 실습2 pop up

state를 통해 Pop up의 open / close 를 관리할 수 있음

import './App.css';
import {useState} from 'react';

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

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

  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;

// 방법 2
const togglePopup = () => {
    if(showPopup === false) {
      setShowPopup(true);
    } else {
      setShowPopup(false);
    }
  };

Chapter1-4. Controlled Component

트위터 사례

트윗은 받는 사람이 팔로워로 정해져 있음
보내는 사람의 경우, 누가 작성하는가에 따라 변경될 수 있음

따라서, 트윗의 경우
state는 보내는 사람(username)과 보낼 내용(tweet)

이렇게 React가 state를 통제할 수 있는 컴포넌트를 Controlled Component 라고 함
React에서는 상태에 해당하는 데이터를 state로 따로 관리하고 싶어함

어떻게 React가 state를 통제할 수 있음?
input과 textarea value 값 입력 시, state도 그때그때 바뀌면(onChange) 됨

실습

import './App.css';
import {useState} from 'react';

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

export default App;

동작순서

1) state가 username 인 경우

사용자가 input태그 입력창에 '문도연'을 쓴다
=> onchange 이벤트 발생
=> setUsername 함수 인자로 이벤트.타겟.벨류인 '문도연'이 전달됨 (여기서 타겟은 input)
=> 전달된 '문도연'값으로 state가 업데이트 됨
=> div태그 값이 문도연이 되고 렌더링되어 화면에 나타남

2) state가 msg인 경우

사용자가 textarea 입력창에 '트윗이 재밋는감?'을 쓴다
=> onchange 이벤트 발생
=> setMsg 함수 인자로 이벤트.타겟.벨류인 '트윗이 재밋는감?'이 전달됨 (여기서 타겟은 textarea)
=> 전달된 '트윗이 재밋는감?'값으로 msg(state)가 업데이트 됨
=> div태그 값이 '트윗이 재밋는감?'으로 렌더링되어 화면에 나타남

출력화면


Chapter1-5. React 데이터 흐름

React 개발방식의 가장 큰 특징: 상향식(bottom-up)

컴포넌트 먼저 만들고나서 페이지 조립
즉, 상향식으로 앱을 만드는 것임
=> 테스트하기 쉽고 확장성이 좋음

따라서, 기획자나 UX디자이너로부터 앱 디자인을 받고나서 내가 가장 먼저 할일은

  • 이를 컴포넌트 계층 구조로 나누기(와이어프레임, 트리구조 등)
  • 하나의 컴포넌트는 한 가지 일만 한다! (단일 책임 원칙)

데이터는 위에서 아래로 흐른다: 하향식(top-down)

컴포넌트는 컴포넌트 바깥에서 props를 이용해 데이터를 마치 인자(arguments) 혹은 속성(attributes)처럼 전달받을 수 있음

데이터를 전달하는 주체는 부모 컴포넌트임

  • 데이터 흐름이 하향식이다
  • 리액트는 단방향 데이터 흐름(one-way data flow)을 따른다

컴포넌트는 props를 통해 전달받은 데이터가 어디서 왔는지 전혀 알지 못합니다. => 무슨말?

앱에서 필요한 데이터가 뭔지 정의하기

모든 데이터를 상태로 둘 필요는 없음
상태는 최소화하는 것이 가장 좋음. 상태가 많아질수록 애플리케이션은 복잡해지기 때문

  • 부모로부터 props를 통해 전달됩니까? 그러면 확실히 state가 아닙니다.
  • 시간이 지나도 변하지 않나요? 그러면 확실히 state가 아닙니다.
  • 컴포넌트 안의 다른 state나 props를 가지고 계산 가능한가요? 그렇다면 state가 아닙니다.
트위터 앱이 갖고 있는 상태(state)
전체 트윗 목록
사용자가 작성 중인 새로운 트윗 내용

상태 위치 정하기

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

상태가 특정 컴포넌트에서만 유의미하다면, 특정 컴포넌트에만 두면 됨
=== 상태가 하나의 컴포넌트에만 영향을 준다면 그 컴포넌트에만 두면 괜찮음

문제는

하나의 상태를 기반으로 두 컴포넌트가 영향을 받는 경우임

=> 즉, 두 개의 자식 컴포넌트가 하나의 상태에 접근하고자 하는 경우
=> 이때는 공통 소유 컴포넌트(두 자식의 부모 컴퍼넌트)를 찾아 그곳에 상태를 위치시켜야 함

부모자식
TweetssingleTweet

트위터 예시에서 전체 트윗 목록 상태는 Tweets에 위치시키면 됨.

=> 전체 트윗 목록은 Tweets에서 필요로 하는 데이터임

=> 개별 트윗을 출력하기 위해서 필요하기 때문임

=> SingleTweet 컴포넌트들도 모두 전체 트윗 목록에 의존

=> 그럼 SingleTweet들의 공통 부모 컴포넌트인 Tweets가 전체 트윗 목록 을 관리하면 되겟네~

퀴즈

하나의 상태를 기반으로 두 컴포넌트가 영향을 받으면, 두 컴포넌트 상위에 상태를 공유하는 컴포넌트가 존재해야 한다.

React는 props를 이용해 하위 컴포넌트로 데이터를 전달할 수 있다.

props는 하위 컴포넌트로 내려갈 수 있지만, 상향식으로 전달할 수는 없습니다.

React의 데이터의 흐름은 하향식(top-down) 이다.

=>React는 데이터를 전달하는 주체가 부모 컴포넌트이므로 props를 이용해 하위 컴포넌트로 데이터를 전달할 수 있습니다. 그러므로 하향식입니다.


[과제] React Twittler State & Props

*유저 parkhacker의 Twittler에서의 트윗 전송 기능 구현하기

*트윗 추가 및 추가된 트윗 조회 기능 구현

*이 기능 구현을 위해서 어떤 컴포넌트에 어떤 state가 필요하고, 이 state를 어떻게 변화시킬 것인지 고민해야 함

  • JSX 문법의 기본과 컴포넌트 기반 개발에 대해서 숙지한다.
  • React Router DOM으로 React에서 SPA(Single-Page - Application)을 구현할 수 있다.
  • state hook을 이용하여, 컴포넌트에서 데이터를 변화시킬 수 있다.
  • props를 이용하여, 부모 컴포넌트의 데이터를 자식 컴포넌트로 전달할 수 있다.
  • 바람직한 컴포넌트 구조와 state와 props의 위치에 대해 고민한다.
profile
중요한건 꺾이지 않는 마음이 맞는 것 같습니다

0개의 댓글