본문 바로가기
Progamming/ReactJS

[벨로퍼트] props와 state

by 동그란 혜주 2019. 7. 11.

리액트 컴포넌트에서 다루는 데이터는 props와 state, 두 개로 나뉜다. 미리 요약해서 설명하자면,  props는 부모 컴포넌트가 자식 컴포넌트에게 주는 값이다. 자식 컴포넌트에서는 props를 받아오기만하고, 받아온 props를 직접 수정할 수는 없다. 반면에 state는 컴포넌트 내부에서 선언하며 내부에서 값을 직접 변경할 수 있다.

새 컴포넌트 만들기

MyName 이라는 새로운 컴포넌트를 만들어보자.

//MyName.js

import React, { Component } from 'react';

class MyName extends Component {
  render() {
    return (
      <div>
        안녕하세요! 제 이름은 <b>{this.props.name}</b> 입니다.
      </div>
    );
  }
}

export default MyName;

자신이 받아온 props 값은 this. 키워드를 통하여 조회할 수 있다. 위의 코드는 name이라는 props를 보여주도록 설정했다. 이제 이 컴포넌트를 사용해보자.

//App.js

import React, { Component } from 'react';
import MyName from './MyName';

class App extends Component {
  render() {
    return (
      <MyName name="리액트" />
    );
  }
}

export default App;

import를 통하여 MyName 컴포넌트를 불러오고, <MyName /> 태그를 통해 렌더링해보았다. props 값은 name="리액트"와 같이 태그의 속성을 설정하는 것 처럼 하면 된다.

 

defaultProps

실수로 props를 빼먹거나, 특정 상황에 props를 비워둬야 할 경우에 props의 기본 값을 설정해줄 수 있는데, 그 때 사용하는 것이 defaultProps 이다.

//MyName.js

import React, { Component } from 'react';

class MyName extends Component {
  static defaultProps = {
    name: '기본이름'
  }
  render() {
    return (
      <div>
        안녕하세요! 제 이름은 <b>{this.props.name}</b> 입니다.
      </div>
    );
  }
}

export default MyName;

이렇게 하면 부모 컴포넌트(여기에서는 App.js가 되겠지)에서 <MyName /> 과 같이 name값을 설정하지 않으면 MyName.js의 this.props.name이 가르키는 값은 defaultProps인 "기본이름"이 될 것이다. 

 

참고로, defaultProps는 아래와 같은 형태로도 설정할 수 있다.

//MyName.js

import React, { Component } from 'react';

class MyName extends Component {
  render() {
    return (
      <div>
        안녕하세요! 제 이름은 <b>{this.props.name}</b> 입니다.
      </div>
    );
  }
}

MyName.defaultProps = {
  name: '기본이름'
};

export default MyName;

곧 알아볼 함수형 컴포넌트에서 defaultProps를 설정할 때는 위의 방식으로 하면 된다.

 

함수형 컴포넌트

이렇게 단순히 props만 받아와서 보여주기만 하는 컴포넌트의 경우에는 더 간편한 문법인 함수형태로 작성할 수 있다. (내 컴퓨터 환경에서 react 프로젝트를 만들었을때 보여지는 초기 App.js가 함수형 컴포넌트임)

//MyName.js

import React from 'react';

const MyName = ({ name }) => {
  return (
    <div>
      안녕하세요! 제 이름은 {name} 입니다.
    </div>
  );
};

export default MyName;

위의 코드가 함수형태로 작성한 컴포넌트이다.

 

함수형 컴포넌트와 클래스형 컴포넌트의 주요 차이점은 stateLifeCycle이 빠져있다는 점이다.(아마 함수형이 빠져있다는 말 같은데..) 그래서 컴포넌트 초기 마운트가 아주 미세하게 빠르고, 메모리 자원을 덜 사용한다. 미세한 차이라서 성능적으로 큰 차이는 없다.(보통은 그냥 다 클래스형으로 작성하는 것을 보니 클래스형으로 작성하는게 속 편하겠다.) -> (그런데 준우님이 최근에느 hook을 이용한 함수형 컴포넌트 형태로 바뀌고 있다고 하셨다.. 함수형이 좀 더 간결하고 쪼개서 관리하기 용이하다는 장점이 있다며.. 참고 링크 : https://velog.io/@velopert/react-hooks )

 

state

동적인 데이터를 다루기 위해서는 state를 사용해야한다. Counter라는 컴포넌트를 만들어보자.

//Counter.js

import React, { Component } from 'react';

class Counter extends Component {
  //class fields 문법 사용
  state = {
    number: 0
  }

  handleIncrease = () => {
    this.setState({
      number: this.state.number + 1
    });
  }

  handleDecrease = () => {
    this.setState({
      number: this.state.number - 1
    });
  }

  render() {
    return (
      <div>
        <h1>카운터</h1>
        <div>값: {this.state.number}</div>
        <button onClick={this.handleIncrease}>+</button>
        <button onClick={this.handleDecrease}>-</button>
      </div>
    );
  }
}

export default Counter;

 

state 정의

위에서부터 아래로 쭉 살펴보면, 우선 컴포넌트의 state를 정의할 때는 class fileds 문법을 사용해서 정의한다. 이 코드를 class fields 문법을 사용하지 않고 작성한다면 다음과 같다.

//Counter.js

import React, { Component } from 'react';

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = {
      number: 0
    }
  }

  ...

}

class fields를 사용하는 이유는 편의를 위함이다. 확실히 위와 같이 contructor에 넣는 것 보다는 아까 봤던 class fields를 사용한 방법이 간단해보인다. 위 코드의 constructor에서 super(props)를 호출한 이유는 컴포넌트를 만들게 되면서, Component를 상속했으며, 이렇게 constructor를 작성하게 되면 기존의 클래스 생성자를 덮어쓰게 된다. 그렇기 때문에 리액트 컴포넌트가 지니고있던 생성자를 super를 통해 미리 실행하고, 그 다음에 할 작업(state 설정)을 해주는 것이다. 

 

메소드 작성

위의 Conter.js의 11 - 21번째 줄을 살펴보자. 

  handleIncrease = () => {
    this.setState({
      number: this.state.number + 1
    });
  }

  handleDecrease = () => {
    this.setState({
      number: this.state.number - 1
    });
  }

화살표 함수 형태를 사용하여 컴포넌트에 메소드를 작성해 주었다.

 

setState

각 메소드에 들어있는 this.setState에 대해 알아보자. state에 있는 값을 바꾸기 위해서는, this.setState를 무조건 거쳐야한다. 리액트에서는 이 함수가 호출되면, 컴포넌트가 리렌더링 되도록 설계되어있다. setState는 객체로 전달되는 값만 업데이트를 해준다. 지금 state에는 number밖에 없지만, 만약에 다른 값이 있다고 가정해보자.

  state = {
    number: 0,
    foo: 'bar'
  }

그 후,

  this.setState({
    number: 1
  });

을 하게 된다면, foo는 그대로 남고 number 값만 업데이트 된다. setState는 객체의 깊숙한 곳 까지 확인하지 못한다. 예를 들어, state가 다음과 같이 설정되어있다고 가정한다면,

  state = {
    number: 0,
    foo: {
      bar: 0,
      foobar: 1
    }
  }

아래처럼 setState를 한다고 해서 foobar 값이 업데이트되지 않는다.

this.setState({
  foo: {
    foobar: 2
  }
})

이렇게 하게되면 그냥 기존의 foo 객체 자체가 바뀌어버린다.

{
  number: 0,
  foo: {
    foobar: 2
  }
}

위와 같은 상황에서는 다음과 같이 해주어야 한다.

this.setState({
  number: 0,
  foo: {
    ...this.state.foo,
    foobar: 2
  }
});

...은 자바스크립트의 전개연산자 이다. 기존의 객체 안에 있는 내용을 해당 위치에다가 풀어준다는 의미이다. 그 다음에, 우리가 설정하고 싶은 값을 또 넣어주면 해당 값을 덮어쓰게 된다.

 

setState에 객체 대신 함수를 전달하기

기존의 값을 참고하며 setState를 사용하여 값을 업데이트하게 될 때, 조금 더 나은 문법으로도 할 수 있다. 기존에 작성했던 코드는 아래와 같다.

this.setState({
  number: this.state.number + 1
});

큰 문제는 아니지만, 굳이 또 this.state를 조회해야하는데, 다음과 같이 하면 조금 더 멋진 문법으로 작성이 가능하다.

this.setState(
  (state) => ({
    number: state.number
  })
);

setState에 updater 함수를 만들어서 전달해주었다. 여기서 조금 더 나아가면 이렇게 작성할 수 있다.

this.setState(
  ({ number }) => ({
    number: number + 1
  })
);

보면 (state)가 ({ number })가 된 것을 볼 수 있다. 이건 비구조화 할당이라는 문법이다. 이 문법은 다음과 같이도 사용할 수 있다.

const { number } = this.state;
this.setState({
  number: number + 1
})

기존에 작성했던 함수를 각각 다른 방식으로 구현할 수도 있다. (handleIncrease 메소드가 보기에 예쁘긴 함)

  handleIncrease = () => {
    const { number } = this.state;
    this.setState({
      number: number + 1
    });
  }

  handleDecrease = () => {
    this.setState(
      ({ number }) => ({
        number: number - 1
      })
    );
  }

 

이벤트 설정

render 함수에서 이벤트 설정을 한 부분을 확인해보자.

  render() {
    return (
      <div>
        <h1>카운터</h1>
        <div>값: {this.state.number}</div>
        <button onClick={this.handleIncrease}>+</button>
        <button onClick={this.handleDecrease}>-</button>
      </div>
    );
  }

버튼이 클릭되면 아까 만든 함수가 각각 호출되도록 설정해주었다.

 

리액트에서 이벤트 함수를 설정할 때 html과 다음과 같은 사항에서 차이가 있다.

- 이벤트 이름을 설정할 때 camelCase로 설정해주어야 한다. html에서의 onclick은 리액트에서 onClick으로, onchange는 onChange 이런식으로 !

- 이벤트에 전달해주는 값은 함수여야 한다. 만약에 onClick = { this.handleIncrease() } 이런식으로 하게 된다면, 렌더링을 할 때마다 해당 함수가 호출이 된다. 그렇게 되면 정말 큰 일이 발생한다. 렌더링 -> 함수 호출 -> setState -> 렌더링 -> 함수 호출 -> 무한 반복.. 그러니까 꼭 주의해야한다. this.handleIncrease()가 아니라 this.handleIncrease !! 전달하는 값은 함수 호출이 아니라 함수 자체 !!


 

누구든지 하는 리액트 4편: props 와 state | VELOPERT.LOG

이 튜토리얼은 10편으로 이뤄진 시리즈입니다. 이전 / 다음 편을 확인하시려면 목차를 확인하세요. 리액트 컴포넌트에서 다루는 데이터는 두개로 나뉩니다. 바로 props 와 state 인데요, 미리 요약하여 설명드리자면 props 는 부모 컴포넌트가 자식 컴포넌트에게 주는 값입니다. 자식 컴포넌트에서는 props 를 받아오기만하고, 받아온 props 를 직접 수정 할 수 는 없습니다. 반면에 state 는 컴포넌트 내부에서 선언하며 내부에서 값을 변경 할

velopert.com

 

'Progamming > ReactJS' 카테고리의 다른 글

[벨로퍼트] input 상태 관리하기  (0) 2019.07.12
[벨로퍼트] LifeCycleAPI  (0) 2019.07.12
[벨로퍼트] JSX  (0) 2019.07.05
[벨로퍼트] 리액트 프로젝트 시작하기  (0) 2019.07.05
[벨로퍼트] 리액트는 무엇인가  (0) 2019.07.05

댓글