본문 바로가기

코드스테이츠 SEB FE 41기/Section 별 내용 정리

section2/unit6/[React] React State & Props(9/29)

반응형

블로깅 주제

  •  React State & Props

1. 지금 현재, 당신의 기분이나 느낌을 표현해 주세요.

  • 오늘 배운 내용은 헷갈리는게 많았다... ㅠㅠ 어제까지는 쉬운 편에 속했던 것 같다. 오늘 과제도 잘 마무리하고 싶다. 처음 배우는 건 늘 어렵다. 차차 익숙해지겠지? :)

2. 오늘 무엇을 학습한 내용 중 지금 떠올릴 수 있는 단어를 모두 나열해 주세요.

  •  React State & Props

3. 2에서 작성한 단어를 가지고, 오늘의 학습 내용을 설명해 보세요.

- Props: 컴포넌트의 속성(property)

▶ Props의 정의

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

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

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

· props는 읽기 전용이다. props는 성별이나 이름처럼 외부로부터 전달받아 변하지 않는 값이다. 그래서 props는 함부로 변경될 수 없는 읽기 전용(read-only) 객체이다. 함부로 변경되지 않아야 하기 때문이다.

 

▶ Props의 사용법

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

 

1. 전달하고자 하는 값을 중괄호 {}를 이용하여 감싸준다.

<자식컴포넌트 이름 attribute={value} />

 

예시)

import "./styles.css";

function Parent() {
  return (
    <div className="parent">
      <h1>I'm the parent</h1>
      <Child text={"I'm the eldest child"} />
      {/* Child 컴포넌트에 또 다른 문자열을 props 로 전달해 보세요 */}
      <Child text2={"I'm the second child"} />
      <Child />
    </div>
  );
}

function Child(props) {
  // console 을 열어 props 의 형태를 직접 확인하세요.
  console.log("props : ", props);
  return (
    <div className="child">
      <p>{props.text}</p>
      <p>{props.text2}</p>
    </div>
  );
}

export default Parent;

2. 여는 태그와 닫는 태그의 사이에 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>
  );
};

 

예시)

import "./styles.css";

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

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

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

export default App;


- States

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

 

▶ State hook, useState

▷ useState 사용법

useState 를 이용하기 위해서는 React로부터 useState 를 불러와야 한다.

import { useState } from "react";

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

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(상태 초기 값);
  • isChecked : state를 저장하는 변수
  • setIsChecked : state isChecked 를 변경하는 함수
  • useState : state hook
  • false : state 초깃값

 

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

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

 

 state 갱신하기

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

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

사용자가 체크박스 값을 변경하면 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 hook 사용 시 주의점

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

체크박스를 누를 때마다 콘솔에 "rerendered?" 가 찍힌다. 즉, 컴포넌트의 상태가 변경될 때마다 새롭게 호출되고, 리렌더링 된다.

React state는 상태 변경 함수 호출로 변경해야 한다. 강제로 변경을 시도하면 안된다.

강제로 변경을 시도하면, 리렌더링이 되지 않는다거나, state가 제대로 변경되지 않는다.


- 이벤트 처리

React 에서 이벤트는 소문자 대신 카멜 케이스(camelCase) 를 사용한다.

JSX를 사용하여 문자열이 아닌 함수로 이벤트 처리 함수(이벤트 핸들러; Event handler)를 전달한다.

<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 이벤트에 함수를 전달할 때는 함수를 호출하는 것이 아니라 아래와 같이 리턴문 안에서 함수를 정의하거나 리턴문 외부에서 함수를 정의 후 이벤트에 함수 자체를 전달해야 한다. 단, 두 가지 방법 모두 arrow function 을 사용하여 함수를 정의하여야 해당 컴포넌트가 가진 state에 함수들이 접근할 수 있다.

 

// 1. 함수 정의하기

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

// 2. 함수 자체를 전달하기

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

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

 

예시)

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

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

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

  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>
      //or
      <button onClick={() => alert(name)}>Button</button> 
      <h3>{name}</h3>
    </div>
  );
}
export default NameForm;

Action item 1 : <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) => {
    //TODO : select tag 가 정상적으로 작동하도록 코드를 완성하세요.
    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";
import "./styles.css";

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>
      {/* 버튼을 클릭했을 때 Pop up 의 open/close 가 작동하도록
          button tag를 완성하세요. */}
      <button className="open" onClick={togglePopup} value="click">
        Open me
      </button>
      {showPopup ? ( // truthy한 값이 들어가면 팝업창 보이게, falsy하면 팝업창 안보이게
        <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)
    }
  };

방법3

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

 


- controlled component

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

 

- React 데이터 흐름

React의 개발 방식의 가장 큰 특징은 페이지 단위가 아닌, 컴포넌트 단위로 시작한다는 점이다. 그림과 같이 앱의 프로토타입을 전달받았다면 먼저 컴포넌트를 찾아보자.

프로토타입에서 그림과 같이 컴포넌트를 찾아냈다. 이렇게 먼저 컴포넌트를 만들고, 다시 페이지를 조립해나간다.

즉, 상향식(bottom-up)으로 앱을 만든다. 이 방식은 테스트가 쉽고 확장성이 좋다. 

 

이를 트리 구조로 나타내면 다음 그림과 같다.

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

데이터를 전달하는 주체는 부모 컴포넌트가 된다. 이는 데이터 흐름이 하향식(top-down) 임을 의미한다.

애플리케이션에서 필요한 데이터가 무엇인지 먼저 정의해보자. 다음과 같은 데이터를 생각해 볼 수 있다.

  • 전체 트윗 목록
  • 사용자가 작성 중인 새로운 트윗 내용

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

어떤 데이터를 상태로 두어야 하는지는 다음 세 가지 질문을 통해 판단해 보자.

  •  부모로부터 props를 통해 전달됩니까? 그러면 확실히 state가 아니다.
  •  시간이 지나도 변하지 않나요? 그러면 확실히 state가 아니다.
  •  컴포넌트 안의 다른 state나 props를 가지고 계산 가능한가? 그렇다면 state가 아니다.

두 개의 자식 컴포넌트가 하나의 상태에 접근하고자 할 때는 두 자식의 공통 부모 컴포넌트에 상태를 위치해야 한다.

"전체 트윗 목록" 상태는 어디에 위치하는 것이 좋을까?

전체 트윗 목록은 Tweets에서 필요로 하는 데이터이고, SingleTweet 컴포넌트들은 모두 전체 트윗 목록에 의존한다. 따라서 전체 트윗 목록 상태는 Tweets에 위치해둔다.

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


- 과제 React Twittler State & Props

반응형