[React] props와 state

CrackCo·2020년 10월 11일
0
post-thumbnail

본 시리즈는 같은 학원 수강생들과 함께하는 React 스터디 진행의 내용을 바탕으로 무작정 부딪치고 시간을 들여 얻은 지식을 공유하고자 한다.
학습 내용은 Do it! 리액트 프로그래밍 정석 교재를 기반으로 작성했다.

props(프로퍼티)


리액트의 프로퍼티는 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달할 때 사용하는 것으로 하위 컴포넌트에서는 프로퍼티를 수정할 수 없다.

프로퍼티 사용해보기

상위 컴포넌트에서 하위 컴포넌트로 프로퍼티를 전달하는 방법은 HTML 태그에 속성을 지정해주듯이 하위 컴포넌트로 key={value} 형식으로 전달한다. 빠른 이해를 위해 아래 간단한 예제를 보자.

우선 하위 컴포넌트를 작성해보자. 03폴더를 만들어서 ChildComponent.jsx 파일을 생성하여 컴포넌트를 작성했다.

하위 컴포넌트에서 프로퍼티를 전달받을 때는 this.props 키워드를 사용하여 데이터를 조회한다. 프로퍼티들을 props 객체로 전달 받으며 데이터들이 key: value 형식으로 존재한다. 그러므로 this.props.key를 사용하여 value를 출력할 수 있다.

// ChildComponent (하위 컴포넌트)
import React, { Component } from 'react';

class ChildComponent extends Component {
  render() {
    return (
      <div>
        {/* 전달 받을 프로퍼티는 message 지정*/}
        {this.props.message}
      </div>
    );
  }
}

export default ChildComponent;

그리고 실제로 렌더링이 이루어지는 root 컴포넌트인 App 컴포넌트에서 방금 작성한 ChildComponent를 불러와 프로퍼티를 전달하며 실행해보자.

// App 컴포넌트
import React from 'react';
// ChildComponent를 불러오는 import 구문
// 파일명 뒤의 확장자는 생략할 수 있다.
import ChildComponent from './03/ChildComponent';

function App() {
  return (
    <>
      {/* 하위 컴포넌트를 불러오며 프로퍼티를 지정해줌 */}
      {/* 프로퍼티: message, 값: "Hello, React" */}
      <ChildComponent message="Hello, React" />
    </>
  );
};

export default App;

🚨 하위 컴포넌트를 출력할 때는 HTML에서 요소를 작성할 때처럼 태그를 열어서 작성한다. 또한 JSX에서 변수를 사용할 때는 {}를 사용해야 한다.

❗ 위의 <></> 는 리액트에서 제공하는 프래그먼트 태그로 필요없는 <div> 등의 태그를 사용하지 않을 수 있다.

프로퍼티로 문자열뿐만 아니라 여러 데이터형의 데이터도 전달할 수 있다. 다른 데이터형도 전달해보자.

여러 데이터형 전달해보기

앞서 우리는 문자열을 프로퍼티로 전달했지만 프로퍼티는 문자열이 아닌 다른 형식의 데이터도 전달할 수 있다. Boolean, Number, Array, Object, Node, Fucntion 등 여러 데이터를 전달하는 아래 예제를 보자.

// 03 폴더의 ChildComponent.jsx
import React, { Component } from 'react';

class ChildComponent extends Component {
  render() {
    
    // props 객체를 구조 분해 할당
    const {
      boolValue,
      numValue,
      arrayValue,
      objValue,
      nodeValue,
      funcValue,
    } = this.props;

    // JSX에 변수 값을 사용할 때 꼭 {}를 사용
    return (
      <div>
        <div>불리언 값: {boolValue.toString()}</div>
        <div>숫자 값: {numValue}</div>
        <div>배열 값: {arrayValue}</div>
        <div>객체 값: {String(Object.entries(objValue))}</div>
        <div>노드 값: {nodeValue}</div>
        <div>함수 값: {funcValue.toString()}</div>
      </div>
    );
  }
}

export default ChildComponent;

우선 값을 전달 받을 하위 컴포넌트인 ChildComponent를 작성했다. 이전에는 프로퍼티를 하나만 전달 받았지만 다수를 전달 받을 수 있으며 하위 컴포넌트의 props 객체로 전달된다. 프로퍼티로 전달받을 데이터들은 위와 같이 객체 구조 분해 할당하여 사용할 수도 있다.

// App.js
import React from 'react';
import ChildComponent from './03/ChildComponent';

function App() {
  const boolValue = true;
  const numValue = 10;
  const arrayValue = [1, 2, 3, 4, 5];
  const objValue = {
    name: 'CrackCo',
    age: 20,
  };
  const nodeValue = <span>Hello, React</span>;
  const funcValue = () => {
    console.log('funcValue');
  };

  return (
    <span>
      {/* JSX로 변수 값을 전달할 때 꼭 {}를 사용 */}
      <ChildComponent
        boolValue={boolValue}
        numValue={numValue}
        arrayValue={arrayValue}
        objValue={objValue}
        nodeValue={nodeValue}
        funcValue={funcValue}
      />
    </span>
  );
}

export default App;

이 처럼 여러 형태의 데이터를 전달할 수 있다. 특히 함수를 전달하는 것은 매우 중요하므로 함수 또한 전달할 수 있다는 것을 기억하자.

그럼 HTML처럼 태그 사이의 데이터는 어떻게 전달할까? children 프로퍼티를 사용하여 전달할 수 있다.

children 프로퍼티

JSX에서 <ChildComponent>value</ChildComponent> 식으로 컴포넌트를 출력하는데 사이에있는 value도 프로퍼티로 전달된다.

// 03/ChildProperty.jsx
import React, { Component } from 'react';

class ChildProperty extends Component {
  render() {
    return (
      {/* props 객체의 children으로 태그 사이의 값을 전달 받을 수 있다. */}
      <div>{this.props.children}</div>
    );
  }
}

export default ChildProperty;

하위 컴포넌트에서 children 프로퍼티를 전달 받는다고 작성했다. 그럼 이제 상위 컴포넌트를 작성해보자.

import React from 'react';
import ChildProperty from './03/ChildProperty';

function App() {
  // Hello, React를 태그 사이에 입력하여 프로퍼티 전달
  return <ChildProperty>Hello, React</ChildProperty>;
}

export default App;

HTML을 사용하여 태그에 text를 작성하는 것처럼 컴포넌트 태그 사이에 Hello, React라는 문자열을 작성하여 프로퍼티를 전달할 수 있다.

그럼 전달받지 않고 컴포넌트 자체의 값을 사용하는 방법도 있지 않을까?

state


리액트 컴포넌트 안에서 필요한 데이터를 변경하거나 가공할 땐 state를 사용하여 수행한다. state는 프로퍼티와 달리 값을 저장하거나 변경할 수 있는 객체로 보통 이벤트와 함께 사용된다.

state 사용해보기

state를 초기화할 때는 해당 컴포넌트의 state에 정의한다. 정의된 state의 값을 변경할 때는 직접적으로 변경하는 것이 아닌 해당 컴포넌트의 setState() 함수를 사용하여 변경해야 화면이 변경되므로 꼭 setState()를 기억하자.

그럼 간단하게 setTimeout() 함수를 사용하여 처음에는 로딩중이라는 문자열을 출력하다가 4초 후에 문자열이 로딩 완료로 변경되는 StateExample 컴포넌트를 작성해보자.

// 03/StateExample.jsx
import React, { Component } from 'react';

class StateExample extends Component {
  // 생성자
  constructor(props) {
    super(props);

    this.state = {
      loading: true,
    };

    this.handleData = this.handleData.bind(this);

    // 4초 뒤에 handleData() 수행
    setTimeout(this.handleData, 4000);
  }

  // 클래스의 함수
  handleData() {
    // state 변경
    this.setState({
      loading: false,
    });
  }

  render() {
    // state의 loading을 구조 분해 할당
    const { loading } = this.state;

    return <div>{loading ? '로딩중' : '로딩 완료'}</div>;
  }
}

export default StateExample;
// App.js
import React from 'react';
import StateExample from './03/StateExample';

const App = () => {
  return (
    <>
      <StateExample />
    </>
  );
};

export default App;

우선 constructor 키워드를 사용하여 생성자 부분을 작성하였다. 해당 컴포넌트가 생성되면 state를 초기화하고 handleData() 함수의 thisbind() 함수를 사용하여 해당 컴포넌트를 항상 가리키도록 설정하였고 setTimeout() 함수를 통해 4초 뒤에 handleData() 함수가 수행되도록 작성하였다. 그 외에 사용할 함수를 정의하고 렌더링하는 부분을 정의하였는데 렌더링 정보를 반환하기 전, state를 구조 분해 할당을 하여 this.state 부분을 생략할 수 있도록 작성하였다.

🚨 state를 변경할 때는 항상 setState() 함수를 사용하여 변경해야 한다. setState() 함수가 수행되어야 render() 함수가 자동으로 다시 호출된다.

state와 이벤트 함께 사용하기

앞에서 state는 이벤트와도 함께 많이 사용한다고 했다. 이벤트와 state를 함께 사용하는 예제를 다뤄보자. 물론 state를 조작할 때 이벤트 안에서도 setState() 함수를 사용하는 것은 꼭 기억하자.

버튼을 클릭하면 숫자가 증가되는 Counter 컴포넌트를 작성하여 출력해보자.

// 03 폴더의 Counter.jsx
import React, { Component } from 'react';

class Counter extends Component {
  // constructor(props) {
  //   super(props);

  //   this.state = {
  //     count: 0,
  //   };

  //   this.increaseCount = this.increaseCount.bind(this);
  // }

  // 생성자를 생략하고 state를 초기화할 수 있음
  state = {
    count: 0,
  };

  increaseCount() {
    // setState()에 함수를 인자로 받을 수 있음
    // 인자는 state가 들어가며 구조 분해할 수 있음
    this.setState(({ count }) => ({
      count: count + 1,
    }));
  }

  render() {
    // state의 count 구조 분해 할당
    const { count } = this.state;

    // bind() 함수를 통해 this 범위 고정하고 변수 지정
    const increaseCount = this.increaseCount.bind(this);

    return (
      <div>
        <div>현재 카운트: {count}</div>
        <button onClick={increaseCount}>카운트 증가</button>
      </div>
    );
  }
}

export default Counter;
// App.js
import React from 'react';
import Counter from './03/Counter';

const App = () => {
  return (
    <>
      <Counter />
    </>
  );
};

export default App;

먼저 이전에 작성했던 constructor() 생성자를 생략하고 state에 바로 초기화할 수 있다. 또한 생성자를 생략했으므로 render() 함수에 구조 분해 할당이나 this의 범위 지정을 수행하여 긴 코드를 간략한 이름의 변수에 할당했다.

state의 count를 div 태그에서 출력하도록 작성하여 현재 상태를 화면에서 확인할 수 있도록 작성하였다.

increaseCounter() 함수는 수행되면 setState() 함수를 통해 state의 count 값을 기존 count 값에 1 증가시켜 저장하도록 정의하였고 button 태그에 onClick 속성에 넘겨주어 클릭 시 수행되도록 하였다.

카운트 증가 버튼은 클릭하면 현재 카운트의 값이 1씩 증가되는 모습을 확인할 수 있다.

profile
개발이 좋아서 개발자가 됐다.

0개의 댓글