React | State & Props

SURI·2021년 12월 4일
0

1. 개념


1.1 State & Props

state

  • 살면서 변할 수 잆는 값
  • 컴포넌트의 사용 중 컴포넌트 내부에서 변할 수 있는 값(컴포넌트 안에서 지지고 볶고)

props

  • 외부로부터 전달받은 값
  • 변할 수 없는 값. immutable

내가 좋아하는 웹사이트에서 데이터거 state/props 중 어떤 걸로 관리가 되어야 하는지 체크해보는 것도 좋다.

1.2 Props

해당 컴포넌트의 속성이다.

  • 부모 컴포넌트로부터 전달받은 값으로, 객체 형태이다.
  • 리액트 컴포넌트는 JS 함수와 클래스이다. Props를 인자로 전달받아, 화면에 어떻게 출력되지를 기술하는 리액트 엘리먼트를 반환한다.
  • 수정이 불가능한 읽기전용이다.

Props 사용하기

  1. 하위 컴포넌트에 전달하고자 하는 값과 속성을 정의한다.
    • HTML과 비슷하나, 리액트에서는 전달하고자 하는 값을 중괄호로 감싸준다.
    • 속성의 이름은 자유롭게 정할 수 있다.
  2. props를 이용해 정의된 값과 속성을 전달한다.
    • 하위 컴포넌트에 props라는 인자를 전달하면 위에 정의한 데이터를 객체 형태로 가져오게 된다.
  3. props jsx 안에 불러와서 렌더링
    • props는 객체이기 때문에 닷 노테이션을 이용해 value에 접근할 수 있다.
function Parent() {
  return (
    <div className="parent">
      <h1>I'm the parent</h1>
      <Child name={"suri"} text={"I'm the eldest child"} />
    // 그냥 문자열인 경우 중괄호를 생략해도 된다.
      <Child>You can do it!</Child>
    </div>
  );
}

function Child(props) {
  console.log("props : ", props);
  return (
    <div className="child">
      <h1>{props.name}</h1>
      <p>{props.text}</p>
	  <p>{props.children}></p>
    </div>
  );
}

export default Parent;
  • Parent 컴포넌트의 코드를 읽어나가다가 Child 컴포넌트를 만나면 그 안에 넣어준 속성대로 Chlid 조각들의 값이 변경되면서 계속 출력되는구나! 아하!
  • Child 컴포넌트의 얼굴을 바꿔나가면서 쓸 수 있겠군! 싶다!
const App = () => {
  const itemOne = "React를";
  const itemTwo = "배우고 있습니다.";

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

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

export default App;
  • 위의 코드와 다른 점은 위에 속성과 값을 정의할 때, 위에 변수를 선언해주었다.
  • 이벤트 핸들러는 props로 전달할 수 있다.
 const props = {
    name: "walli"
  };
  
  return <Hello {...props} />;
  • 이런식으로 전달 할 수 있다. 객체를 변수에 할당하고, spread 연산자를 활용했다.흠! 하지만 중괄호 안에 키 : 값으로 전달할 때는 안 되었다.
const obj = {
  name : "suri",
  age : 20,
};

return <Hello test={obj} />;

const Hello (props) => {
	return <div>{props.test.name}</div>
}
  • 객체를 어떻게 전달해줄 수 있을지 궁금했는데 여러 방식이 있다.
    • 속성 없이 {...obj} 이렇게 전달하고 props.keyname
    • attr={obj}로 전달해주고 props.attr.keyname
    • attr={obj}로 전달해주고 인자를 {attr} 구조 분해 할당으로 받은 뒤에 attr.keyname 이렇게 바로 사용하는 것 (스프린트 내용)
    • {keyname : value} 이런식으로는 직접 넣는 건 안 된다.

  • 하위 컴포넌트는 데이터를 구성하는 틀이 되고, 데이터는 상위 컴포넌트가 보내주었다. 사실 이 자체도 객체를 요소로 하는 배열에서 map 메소드를 활용해서 써주면 일일이 적지 않아도 될 것이다. 흠!

1.3 State

애플리케이션의 상태이다.

  • 컴포넌트 내부에서 변할 수 있는 값이다.
    • Toggle Switch, Counter, Checkbox
  • React State로 다룬다.

useState 사용하기

리액트에서 state를 다루는 방법 중 하나로 제공하는 특별한 함수이다. 이를 통해, 함수 컴포넌트도 state 를 다룰 수 있게 되었다.

  1. 리액트로부터 useState를 불러온다. import { useState } from "react";

  2. 컴포넌트 안에서 useState를 호출한다.

    • state라는 변수를 선언하는 것과 같다.
    • 변수의 이름은 아무 이름으로 지어도 된다.
    • state 변수는 리액트에 의해 함수가 끝나도 사라지지 않는다.
    • useState 함수를 호출하면 배열을 반환한다.
      • 배열의 0번째 요소는 state 변수이고, 1번째 요소는 이 변수를 갱신할 수 있는 함수이다.
      • useState 인자는 state의 초기값이다.
const [isChcked, setIsChecked] = useState(false)
// useState의 리턴값을 구조 분해 할당한 변수이다.
// const [state 저장 변수, state 갱신 함수] = useState(상태 초기 값);

===비교===
 
const stateHookArray = useState(false); 
const isChecked = stateHookArray[0];
const setIsChecked = stateHookArray[1];
  1. state 변수에 저장된 값을 사용하려면 JSX 엘리먼트 안에 불러서 사용한다.
<span>{isChecked ? "Checked" : "unChecked"}</span>
  1. state 변수를 갱신하려면 변수 갱신 함수를 호출한다.
    • 사용자에 의해 이벤트가 발생하면 이벤트 핸들러 함수를 호출하고, 이 함수가 변수 갱신 함수를 호출하게 된다.
    • 그에 따라 state 변수가 갱신되고, 리액트는 갱신된 변수를 컴포넌트에 넘겨 컴포넌트를 Rerendering 한다.
    • 변수 갱신 함수에 넣어주는 인자를 state 변수에 재할당 해준다고 생각하고 있었는데, 그게 아니라 초기값을 새롭게 바꾸고 리렌더링해주는 거구나!
    • 주소값이 같으면 state가 변경을 인지하지 못한다. unshift와 state를 함께 써주면 안 되는 이유?
function CheckboxExample() {
  const [isChecked, setIsChecked] = useState(false);

  const handleChecked = (event) => {
    setIsChecked(event.target.checked); // 이벤트 객체에 변경된 checked 값을 불러와 넣어준다?
  };

  return (
    <div className="App">
      <input type="checkbox" checked={isChecked} onChange={handleChecked} />
      <span>{isChecked ? "Checked!!" : "Unchecked"}</span>
    </div>
  );
}
  1. 주의사항
    • React 컴포넌트는 state가 변경되면 새롭게 호출되고, 리렌더링된다. 즉, 화면이 업데이트 된다.
      • checkbox를 누를 때마다, 컴포넌트 첫 줄에 console.log("rerendered?")가 계속 찍히는 걸 확인할 수 있었다.
    • React State는 상태 변경 함수 호출로 변경해야한다. 강제 변경은 안 된다.
    • 여러개의 state를 선언하는 것도 가능하다.

function ExampleWithManyStates() {
  // 여러 개의 state를 선언할 수 있습니다!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
  • useState의 초기값으로 이렇게 배열을 전달할 수도 있다는 생각을 왜 못했을까? 스프린트에 적용할 수 있는 생각이다.

1.4 이벤트 처리

  • React에서 이벤트는 카멜 케이스를 사용한다.
  • JSX를 사용하여 문자열이 아닌 함수 그 자체로 이벤트 핸들러를 전달한다.
<button onclick="eventHandler()">Event</button>
===비교===
<button onClick="eventHandler">Event</button>

<button onClick={() => handleClick("나는 개발자이다.")}>Click ME!</button>

onChange

폼 엘리먼트(input textarea select)는 사용자의 입력값을 받는다. 리액트에서는 이러한 변경될 수 있는 입력값을 컴포넌트의 state로 관리하고 업데이트한다.

  • onChange 이벤트(input의 텍스트 변화)가 발생하면 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 태그를 통한 링크 이동 등에 자주 사용되는 이벤트이다.

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

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

  return (
    <div>
      <input type="text" value={name} onChange={handleChange}></input>
      <h1>{name}</h1>
      <button onClick={() => alert(name)}>Click Me!?</button>
	// alert 창을 띄우는 버튼을 추가했다.                       
    </div>
  );
}
  • 여기에서 주의할 점은 onClick={alert(name)} 이렇게 해주면 안 된다!
    • 이벤트 핸들러는 함수여야 한다. 함수를 호출한 실행값을 전달하면, 버튼을 클릭할 때가 아니라 컴포넌트가 렌더링될 때 실행이 되어 버린다.
      • 리턴문 안에서 함수를 정의하거나, 외부에서 함수를 정의후 이벤트에 함수 자체를 전달해야 한다.

흠, 정리하면 이런 내용인 것 같다.

이벤트가 발생하면 이벤트 핸들러를 호출하고 그 안에서 변수 갱신 함수를 호출하면, 인자로 변화된 이벤트 객체의 값을 가져와서 state 저장 변수에 넣어준다. state 저장 변수의 값은 이벤트+이벤트핸들러에 의해 계속 바뀔 수 있는 값이다.

<Select>

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

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

  const members = ["som", "ming", "viho", "yoojin", "tanso"];
  const options = members.map((member) => {
    return <option value={member}>{member}</option>;
  });

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

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

Pop up

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

  const togglePopup = (event) => {
    let isOpen = event.target.className === 'open'
    isOpen ? setShowPopup(true) : setShowPopup(false)
  };

  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>
  );
}
  • 이건 위의 예제들보다 이벤트 핸들러에서 변수 갱신 함수에 인자로 넣어주는 게 복잡했다. 이미 있는 값을 가지고 오는 게 아니라, 조건에 따라 값을 직접 넣어줘야했다. 그래도 구현을 하긴 했다.

하나의 객체로 관리하기

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

export default function App() {
  // 👇 useState 두 번 사용!
  // const [name, setName] = useState("");
  // const [phone, setPhone] = useState("");

  // const handleNameInputChange = (e) => {
  //   // console.log(e.target.value)
  //   setName(e.target.value);
  // };
  // const handlePhoneInputChange = (e) => {
  //       // console.log(e.target.value)
  //   setPhone(e.target.value);
  // };

  // 👇 하나의 객체로 관리하기!
  const [userInfo, setUserInfo] = useState({
    name: "",
    phone: ""
  });

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setUserInfo({
      ...userInfo,
      [name]: value
    });
  };

  return (
    <div className="App">
      <input
        name="name"
        placeholder={"이름을 입력해주세요"}
        onChange={handleInputChange}
      />
      <br />
      <input
        name="phone"
        placeholder={"전화번호를 입력해주세요"}
        onChange={handleInputChange}
      />
      <br />
      <button>제출</button>
      <div>
        <div>{`이름: ${userInfo.name}`}</div>
        <div>{`휴대전화: ${userInfo.phone}`}</div>
      </div>
    </div>
  );
}
  • 아 여기에서 e.target을 구조분해할당했을 때.. e.target에서 name과 value라는 속성 값을 가져와서 여기에 담는 것 같다!
      

1.5 Controlled Component

리액트가 state를 통제할 수 있는 컴포넌트이다. input에 값 입력 시, state도 그 때 그 때 바뀌는 방식으로 통제할 수 있다. 리액트는 상태에 해당하는 데이터를 따로 관리하고 싶어한다. e.g. 트윗 전송 폼 컴포넌트

1.6 데이터의 흐름

  1. 페이지를 만들기 이전에, 컴포넌트 계층 구조로 나누는 것이 가장 먼저 해야할 일이다.

  2. 데이터는 위에서 아래로 흐른다. (하향식)

    • 컴포넌트 바깥에서 props를 이용해 데이터를 인자 혹은 속성처럼 전달받을 수 있다.
    • 데이터를 전달하는 주체는 부모 컴포넌트다.
  3. 리액트는 단방향 데이터 흐름을 따른다.

  4. 어떤 데이터를 상태로 두어야 할까? 3가지 질문을 통해 답을 찾자.

    • 부모로부터 props를 통해 전달되나?
    • 시간이 지나도 변하지 않나?
    • 컴포넌트 안의 다른 state나 props를 가지고 계산 가능한가?
  5. 상태 데이터는 최소화하는 것이 바람직하다. 그렇지 않으면 앱이 복잡해진다.

  6. 하나의 상태를 기반으로 두 컴포넌트가 영향을 받는다면, 공통 소유 컴포넌트를 찾아 그 곳에 상태를 위치시킨다.

    • e.g. '전체 트윗 목록' 상태는 그에 영향을 받는 개별 트윗 위에 위치시킨다.

2. 에러로그


props 대신 {속성이름} 이렇게 전달해주면 객체를 전달할 수도 있다!

 const App = () => {
 const itemOne = {name : 'suri', age : 26};

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

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

export default App;
  • props라고 바꾸니까 안 된다. 이게 왜 될까? 스프린트에서도 사실 tweet={el} 객체인 el을 전달하고 props대신 {tweet} 이렇게 했을 때 되는 게 좀 의아했었는데 말이다.

3. 질문


Q. input 태그 안에 checked나 value 속성을 꼭 넣어야 하나? 넣지 않아도 구현은 되는 것 같다.

Q. 화살표 함수에서 return이 생략되는 거 조금 헷갈린다. return을 넣어주나 빼주나 그대로 실행이 된다. 흐음.

Q. 두 가지 방법 모두 arrow function 을 사용하여 함수를 정의하여야 해당 컴포넌트가 가진 state에 함수들이 접근할 수 있습니다. 화살표 함수가 아니어도 되던데..

Q. 객체의 형태로 값을 전달하고 싶을 때? 는 어떻게 하나?

Q. select 태그 안에서 option을 입력할 때, map 메소드를 활용해서 배열로 전달해도 가능한가보다. 원래 html태그에서는 그런 건 없다.

Q. const { name, value } = e.target; 객체로 데이터 관리하기에서 e.target을 구조분해 했을 때, name과 value에 각각 무엇이 담기는지? name은 value일 테고, value는 이벤트에 의해 바뀐 값이 되어야 하지 않나..

profile
Every step to become a better version of me 🚶‍♂️ 블로그 이사중

0개의 댓글

관련 채용 정보