본문 바로가기

Frontend study/Reactjs

리액트 - 5. Props와 State

  Props와 State

 리액트에서는 데이터를 다루는 데 정말 중요한 개념인 props와 state에 대해 살펴본다. 

 

 

1. Props

  props는 properties의 줄임말로 어떤 컴포넌트에게 데이터를 전달할 때 사용한다. 

  리액트는 단방향 데이터 흐름을 지향하므로 항상 부모 컴포넌트에서 자식 컴포넌트로의

  전달만을 허용한다. 따라서 자식 컴포넌트에선 props를 변경할 수 없고 부모 컴포넌트에서

  변경한 후 내려줘야 한다. 

  

  1) Props 사용해보기 

    props를 사용하는 방법은 부모 컴포넌트에서 자식 컴포넌트의 속성에 원하는 값을

    전달해주면된다.

 

 function Children( props ) {
   return <div>{ props.hello }</div>;
 }
 
 export default function Parent() {
   return (
     <div className="App">
       <div>즐거운 하루</div>
       <Child hello="안녕" />
     </div>
   );
 }

 

 부모 컴포넌트에서 hello 값을 전달하려면 위와 같이 자식 컴포넌트의 속성에 

 전달할 데이터를 key=value의 형태로 작성해주면 자식 컴포넌트에서 데이터를

 받아 출력할 수 있다. 

 

 props는 객체 형태로 전달되며 hello 값을 조회하고 싶다면 props.hello로 

 조회하면 된다. 즉 props.key 형태로 조회한다.

 

 props를 아래와 같이 콘솔에 찍어서 확인해보면 { hello: '안녕' } 가 출력되는 것을 

 확인할 수 있다.

 이처럼 props는 객체의 형태로 담겨 있다. 

 

 

 2) 여러 props 전달하기 및 비구조화 할당

   여러 props을 전달할 수도 있다. 이번에는 name이라는 props를 추가해서 

   출력해보자!

 

 function Child(props) {
   console.log(props);
   return (
     <div>
       {props.hello}! {props.name}
     </div>
   );
 }
 
 export default function Parent() {
   return (
     <div className="App">
       <div>즐거운 하루</div>
       <Child hello="안녕" name="리액트" />
     </div>
   );
 }
 

 

(출력 화면)

  

 여러 props를 조회할 때는 위와 같이 작성하는 것보다 자바스크립트의 비구조화 할당 문법을 사용하면 

 좀 더 깔끔하게 작성할 수 있다. 

  

 function Child({ hello, name }) {
   return (
     <div>
      {hello}! {name}
     </div>
   );
 }
 
 export default function Parent() {
   return (
     <div className="App">
       <div>즐거운 하루</div>
       <Child hello="안녕" name="리액트" />
     </div>
   );
 }
 

 

 

3) props 기본값 설정하기

 props를 지정하지 않을 때 기본값을 지정할 수 있다. 물론 함수 기본값 파라미터를 사용해서 

 할 수도 있지만 여기서는 리액트에서 제공하는 속성인  defaultProps을 사용해서 props의 

 기본값을 설정해보겠다. 

 

 사용방법은 컴포넌트 밖에 아래와 같이 작성해주면 된다. 

 

 import React from "react";
 
 function Child({ hello, name }) {
   console.log(hello);
   return (
     <div>
       {hello}! {name}
     </div>
   );
 }
 
 Child.defaultProps = {
   name"[이름 지정하지 않음]"
 };
 
 export default function Parent() {
   return (
     <div className="App">
       <div>즐거운 하루</div>
       <Child hello="안녕" />
     </div>
   );
 }
 

 

(출력 화면)

 

4) children 속성을 사용해 컴포넌트 사이의 담긴 데이터 보여주기

 children는 props를 통해 기본적으로 전달되는 속성이다. 이 children를 통해서 컴포넌트의 사이에 담긴 

 데이터를 조회할 수 있다. 보통 Wapper 컴포넌트(감싸서 무언가 전달하는 용도) 형태로 사용한다. 

 

 

 App.js

 import React from "react";
 import Wrapper from "./Wrapper";
 
 export default function App({ hello }) {
   console.log(hello);
   return (
     <div>
       <Wrapper>
         <h1>안녕</h1>
         <p>정말 아름다운 하루!</p>
       </Wrapper>
     </div>
   );
 }
 

 

Wrapper.js

 export default function Wrapper({ children }) {
   const headerStyles = {
     width: "200px",
     margin: "5px 10px",
     padding: "20px",
     border: "1px solid"
   };
 
   return <div style={headerStyles}>{children}</div>;
 }
 

 

  

App.js 에서 Wrapper 컴포넌트 사이에 h1, p 태그를 담았다. 이것을 전달받기 위해서는 

Wrapper 컴포넌트의 props로 전달되는 children을 랜더링 해주면 된다. 

(반드시 컴포넌트 사이에 넣은 값을 조회할 때는 children을 넣어줘야 한다.)

 

(출력 화면)

 

2. State

 state는 컴포넌트가 자체적으로 가진, 변경될 수 있는 값을 의미한다.  props와는 달리 state는 

 컴포넌트 내부에서 업데이트할 수 있다. 

 

 리액트 16.8 이전 버전에서는 오직 클래스 컴포넌트에서만 state를 통해 상태를 관리(추가, 삭제

 등등)할 수 있었는데 16.8 이후 버전에서 Hooks의 등장으로 함수 컴포넌트에서도 state를 통해

 상태를 관리할 수 있게 되었다. 

 

 

 1) state 사용 및 변경하기 

  useState 라는 hook을 통해 상태를 관리할 수 있다. 

 

  사용하는 방법

  useState의 인자로 초기값을 넣어 준다. 이때 초기값의 형태는 자유롭게 작성해도 된다. 

  이 useState 함수는 실행될 때 배열을 리턴한다. 

 

  첫번째 요소는 현재 상태이고, 두번째 요소는 상태를 변경해주는(setter) 함수를 담은

  배열을 리턴해준다. 

 

  이 배열로 리턴된 값들을 비구조할당으로 변수에 받아서 사용하면 된다. 변수를 받을 때

  변수명은 상태에 관련한 이름으로 자유롭게 지어주면된다. 

 

SayHello.js

 function SayHello() {
   const [msg, setMsg] = useState('');
   const onClickEnter = e => setMsg('환영합니다.');
   const onClickLeave = e => setMsg('다음에 또 오세요');
   return (
     <div className="App">
       <button className="btn" onClick={onClickEnter}>
         입장
       </button>
       <button className="btn" onClick={onClickLeave}>
         퇴장
       </button>
       <h1>{msg}</h1>
     </div>
   );
 }
 
 export default SayHello;

 

style.css

 .App {
   width: fit-content;
   height: 250px;
   font-size: 18px;
   margin: 1rem 2.3rem;
   padding: 2rem 1.5rem;
   border: 1px solid gray;
   border-radius: 3px;
 }
 
 h1 {
   margin-top: 1rem;
   text-align: center;
 }
 
 .btn {
   display: inline-block;
   margin-top: 2rem;
   width: 150px;
   height: 30px;
   background: #9db8f0;
   font-size: 20px;
   color: #f3ede5;
   cursor: pointer;
 }
 
 .btn:active {
   position: relative;
   top: -1px;
 }

 

결과 화면

 

2) useState 여러번 사용해보기 

여러 상태를 관리할 때 useState 를 여러번 사용해도 상관없다. 

위 예제에서 입장했으면 색상을 초록색으로 퇴장했으면 핑크색으로

변경해보자! 

 

color라는 상태를 추가하고 입장 버튼을 누르면 초록색으로 

퇴장 버튼을 누르면 핑크색으로 상태를 변경해주면된다.  

 

SayHello.js 수정

 function SayHello() {
   const [msg, setMsg] = useState('');
   const [color, setColor] = useState({
     color: '#000',
   });
 
   const onClickEnter = e => {
     setMsg('환영합니다.');
     setColor(preColor => ({
       ...preColor,
       color: '#008000',
     }));
   };
   const onClickLeave = e => {
     setMsg('다음에 또 오세요');
     setColor(preColor => ({
       ...preColor,
 
       color: '#ffc0cb',
     }));
   };
   return (
     <div className="App">
       <button className="btn" onClick={onClickEnter}>
         입장
       </button>
       <button className="btn" onClick={onClickLeave}>
         퇴장
       </button>
       <h1 style={color}>{msg}</h1>
     </div>
   );
 }
 
 export default SayHello;

 

결과 보기

See the Pen Untitled by leedong2897 (@leedong2897) on CodePen.

 

 

  

 

3) 간단한 카운터 예제 만들기 

 버튼을 누르면 숫자가 증가하고, 감소하는 간단한 예제를 만들어보자 

 

 💡 증가, 감소를 보여줄 숫자(state)와 증가, 감소를 처리할 이벤트 함수가 있으면 된다. 

  

 (Count.js) 

 import React, { useState } from "react";
 import "./count.css";
 
 export default function Count(props) {
   const [count, setCount] = useState(0);
 
   return (
     <div className="count__container">
       <div>{count}</div>
       <button>증가</button>
       <button>감소</button>
     </div>
   );
 }
 

 

(count.css)

 .count__container {
   width: 130px;
   margin: 20px 30px;
   padding: 15px;
   border: 2px solid gray;
   text-align: center;
 }
 
 .count__container div {
   margin-bottom: 15px;
   text-align: center;
   font-size: 20px;
 }
 
 .count__container button {
   margin: 0 3px;
 }

 

(출력 화면)

 

증가 버튼 또는 감소 버튼을 누를 때 숫자가 증가 또는 감소하도록 하기 위해서는 

위에서 다룬 useState 함수를 사용해서 숫자(count)를 보여줄 상태를 관리해준다. 

 

 const [count, setCount] = useState(0);  

 

이와 같이 useState의 인자로 count의 초기값을 설정해준다.

 

다음으로 할 것은 setCount(setter 함수)로 count를 변경하는 로직을

작성해주면된다. 

 

아래와 같이 onIncrement,  onDecrement 함수를 통해 

변경하는 로직을 구현해주면 된다. 

 

(count.js)

 import React, { useState } from "react";
 import "./count.css";
 
 export default function Count(props) {
   const [count, setCount] = useState(0);
 
   const onIncrement = (e) => {
     setCount((preState) => preState + 1);
   };
 
   const onDecrement = (e) => {
     setCount((preState) => preState - 1);
   };
 
   return (
     <div className="count__container">
       <div>{count}</div>
       <button onClick={onIncrement}>증가</button>
       <button onClick={onDecrement}>감소</button>
     </div>
   );
 }
 

 

 

(출력 화면)

4) state를 사용할 때 주의사항 

 컴포넌트에서 state를 사용할 때 state를 직접 변경하지 말고 useState를 통해 전달 받은 

 setter 함수를 사용해야한다. 

 

 객체 또는 배열를 담은 state를 변경할 때  직접 변경하는 것은 특히 주의해야한다. 

 리액트는 객체 또는 배열를 직접 변경하게 되면 변경되었는지 알 방법이 없다. 

 

 리액트는 이전 상태와 업데이트된 상태를 비교해서 변경된 경우 리랜더링한다. 

 근데 직접 객체 또는 배열을 변경하게되면 결과는 변경되었을 지라도 리액트 입장에서는 

 동일한 객체 또는 배열로 인식하기 때문에 어떠한 처리도 하지 않는다. 

 

 따라서 객체 또는 배열을 변경할 때는 사본을 만들어서 그 사본에 값을 업데이트 한후, 

 그 사본의 상태를 setter 함수를 통해 업데이트해야한다. 

 이렇게 사본을 만들어서 기존 배열과 객체를 직접 변겨되지 않도록 하는 것을 불변성이라 

 부른다.  즉, 리액트는 불변성을 지켜서 상태를 업데이트해야한다. 

 

 

 객체 또는 배열에 대한 사본 만들기 

 객체 또는 배열에 대한 사본을 만드는 방법은 아래와 같다. 

 

◎ 객체 사본 만들기 

 const userInfo = { name'Mike', age: 22 }; //{name: 'Mike', age: 22} 
 const updateUserInfo = {... userInfo, name'Jenney'}; //{name: 'Jenney', age: 22}

 

 객체에 대한 사본을 만드는 방법은 spread 연산자(...)를 사용해서 처리한다. 

 

 

◎ 배열 사본 만들기 

 const users = [
    {
       id: 1,
       name'신짱구',
       age: 5,
       gender: '남'
    },
    {
       id: 2,
       name'안철수',
       age: 5,
       gender: '남'
    },
    {
       id: 3,
       name'김지연',
       age: 6,
       gender: '여'
    },
 ]
 
 // 항목 추가
 const addUsers = users.concat({ id: 4name'김유리', age: 5, gender: '여' })
 console.log(addUsers)
 
 // 항목 수정
 const updateUsers = users.map(user => (user.id === 2) ? { ...user, name'김철수' } : user)
 console.log(updateUsers)
 
 // 항목 삭제
 const deleteUsers = users.filter(user => user.id !== 3);
 console.log(deleteUsers)
 

 

 

배열의 사본을 만들때는 배열의 내장 함수(새로운 배열을 리턴해주는 메서드들만)를 주로 사용한다. 

 

배열 항목 추가하는 할 때도 spread 연산자를 사용할 수 있다. 

 const addUsers = [...users, { id: 4name'김유리', age: 5, gender: '여' }]

 

끝.