Ryu.log

[ React-Tutorial-04 ] props와 state 본문

Prev-content

[ React-Tutorial-04 ] props와 state

류뚝딱 2018. 5. 25. 16:35


01. 새 Component 만들기

새로운 Component를 한번 만들어보자.  src  디렉토리에 새로운 Component인  MyName.js  이라는 파일을 생성 해보자.
// 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를 보여주도록 설정해 두었다. 그럼 이제 이 Component를 사용해 보자.


App.js를 열고 아래와같이 작성해보자.

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

class App extends Component {
  render() {
    return (
      <MyName name="이수근" />
    );
  }
}

export default App;

import를 통하여 Component를 불러오고 랜더링을 한다. 이렇게 Component를 만들고 나면, 일반 테그를 작성 하듯이 작성해주면 된다.

그리고 props 값은  name="이수근"  이런식으로 테그에 속성(attrbute 값)을 설정해주는 것 처럼 해주면 된다.


작성을 완료 한 뒤, 브라우저 확인을 해보면 아래와 같은 결과가 나온다.






02. defaultProps

가끔씩 실수로 props 를 빼먹을 때가 있다. 혹은, 특정 상황에 props를 일부로 비워야 할 때가 있을 수도 있다,
그런 경우에, props의 기본값을 설정해 줄 수 있는데, 그것이 바로 defaultProps 이다.
// MyName.js
import React, { Component } from 'react';

class MyName extends Component {
  static defaultProps = {
    name: '1577'
  }
  render() {
    return (
      <div>
        안녕하소! <b>{this.props.name}</b> 입니다~
      </div>
    );
  }
}

export default MyName;

이렇게 MyName. Component의 코드를 고쳐 놓은 뒤, App.js에서  <MyName />  이런식으로 뒤에 name값을 생략해버리면 "1577"이 default 값으로 들어가게 될것이다.


defaultProps는 아래와 같은 형태로도 설정 가능하다.

import React, { Component } from 'react';

class MyName extends Component {
  render() {
    return (
      <div>
        안녕하소! <b>{this.props.name}</b> 입니다~
      </div>
    );
  }
}

MyName.defaultProps = {
  name: '1577'
};

export default MyName;

바로 다음에 알아볼 함수형 Component에서 defaultProps를 설정할땐 위 방식으로 하면 된다.





03. 함수형 Component

이렇게 단순히 props만 받아와서 보여주기만 하는 Component의 경우엔 더 간편한 문법으로 작성할 수 있는 방법이 있다.
바로, 함수형태로 작성하는 것인데,  MyName  컴포넌트를 다시 작성해보자.
import React from 'react';

const MyName = ({ name }) => {
  return (
    <div>
      안녕하소! {name} 입니다~
    </div>
  );
};

export default MyName;

훨씬 간단하다. 함수형 Component와 클래스형 Component의 주요 차이점은, 조만간 진행할 state와 LifeCycle 이 빠져있다는 점이다.

그래서 Component 초기 마운트가 아주 미세하게 빠르고, 메모리 자원을 덜 사용한다. 미세한 차이이니, Component를 무수히 많이 렌더링 하게 되는게 아니라면

성능 이슈는 없다.




04. state

동적인 데이터를 다룰 땐 어떨까? 바로 state를 사용한다. 새로운 Component를 만들어 보자.
 src  디렉토리에 새로운 Component인  Counter.js  이라는 파일을 생성하여 아래와 같이 작성 해보자.

import React, { Component } from 'react'; class Counter extends Component { state = { number: 0 } handleIncrease = () => { this.setState({ number: this.state.number + 1 }); } handleDecrease = () => { this.setState({ number: this.state.number - 1 }); } render() { return ( <div> <h1>Counter</h1> <div>Result: {this.state.number}</div> <button onClick={this.handleIncrease}>+</button> <button onClick={this.handleDecrease}>-</button> </div> ); } } export default Counter;

위에서부터 아래로 쭉 살펴보면 우선 Component의 state를 정의할 때는 class fields 문법을 사용해서 정의한다.

만약 class fields 문법을 사용하지 않는다면 아래와 같이 사용한다.

import React, { Component } from 'react';

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

  ...

 
}

class fields 를 사용하는 이유는 편의를 위함이다. 확실히 constructor에 넣어서 사용하는 것보다 편해보인다.


위 코드의 constructor 에서 super(props)를 호출 한 이유는, Component를 만들게 되면서, React Component를 상속했으며, 

constructor를 작성하게 되면 기존( React Component ) 클래스의 생성자를 덮어쓰게 된다. 

그렇기에, React Component가 지니고 있던 생성자를 super를 통하여 미리 실행하고,

그 다음에 우리가 작업할(state 설정)을 해주는 것이다.


그렇다면, class fields와 constructor 둘 다 사용하게 되면, 어떤부분이 더욱 늦게 설정 될까?

class fields가 먼저 실행되고, 그 뒤에 constructor에서 설정된 것들이 나오게 된다.





05. 메소드 작성

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

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

Component에 메소드를 작성해 주었다. Component에서 메소드는 아래와 같은 형식으로도 작성 될 수 있다.

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

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

이렇게 하면, 나중에 버튼에서 Click Event가 발생 했을 때, this 가 undefind로 나타나서 제대로 처리되지 않게 된다.

이는 함수가 버튼의 Click Event로 전달이 되는 과정에서 "this"와의 연결이 끊겨버리기 때문인데, 이를 고쳐주려면 constructor 에서

  constructor(props) {
    super(props);
    this.handleIncrease = this.handleIncrease.bind(this);
    this.handleDecrease = this.handleDecrease.bind(this);
  }

위와 같이 해주거나, 이전에 작성한 코드처럼 아에 arrow function 형태로 하면 this가 풀리는 것에 대해 걱정할게 없다.





06. setState

이제 각 메소드에 들어있는  this.setState  에 대해 알아보자. state에 있는 값을 바꾸기 위해서는, this.setState를 무조건 거쳐야 한다.
React에서는, 이 함수가 호출되면 Component가 리렌더링 되도록 설계되어 있다.

이 function에 대해 좀 더 알아보자면 setState는 객체로 전달되는 값만 업데이트를 해준다.
지금은 state에 number 값 밖에 없지만 만약 아래와 같이 다른 값이 있다고 가정해 보면,
state = {
    number: 0,
    foo: 'bar'
  }

this.setState({ number:1 });을 하게 되면, foo 는 그대로 남고, number 값만 업데이트 된다.

setState는 객체의 깊숙한 곳까지 확인하지 못한다. 예를 들어서, state가 다음과 같이 설정되어있다고 가정한다면,

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

아래와 같이 한다고 해서 foobar 값이 업데이트 되지 않는다.

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

그 대신에 위와같은 상황에서는 아래와 같이 해주어야 한다.

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

...은 JavaScript의 전개연산자 이다. 기존 객체안에 있는 내용을 해당 위치에다가 풀어준다는 의미이다. 그 뒤에, 설정하고 싶은 값을 또넣게 되면 해당값을 덮어쓰게 된다.





07. 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;

결국 코드를 조금 덜 작성하고 싶다면 이렇게도 할 수 있다.

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

마음에 드는 코드 중 아무거나 사용하면 된다.

그렇다면 기존에 작성했던 함수를 각각 다른 방식으로 구현해보자.

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

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





08. 이벤트 설정

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

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

버튼이 클릭되면 준비한 메소드 함수가 각각 호출되도록 설정해주었다. 기존에 JavaScript로 비슷한 작업을 해봤다면 아래 코드가 익숙 할 것이다.

<button onclick="alert('hello');">Click Me</button>

html에서는 onclick 속성에 클릭되면 실행  할 JavaScript를 문자열 형태로 넣어준다. 반면에 우리가 작성한 코드를 다시보면

<button onClick={this.handleIncrease}>+</button>

여기서 정말 주의 해야 할 것은, React 에서 이벤트 함수를 설정할 때 html과 아래와 같은 사항이 다르다.


  • 이벤트 이름을 설정 할 때 camelCase 로 설정해 주어야 한다. onclick은 onClick, onmousedown은 onMouseDown, onchange는 onChange 이런식으로 말이다.
  • 이벤트에 전달해주는 값은 함수여야 한다. 만약에  onClick={this.handleIncrease()}  이런식으로 하게 된다면, 렌더링을 할 때마다 해당 함수가 호출이 된다.
    그렇게 된다면 정말 큰일이 발생한다. 렌더링 > 함수호출 > setstate > 렌더링 > 함수호출 > 무한반복... 이렇게 되어버리는 것이다.

그래서 꼭 주의해야 한다. 렌더링 함수에서 이벤트를 설정할 때 우리가 만든 메소드를 호출하면 안된다.


모든 설명이 끝났고 Counter Component를 App.js에 불러와서 랜더링해보면

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

class App extends Component {
  render() {
    return (
      <fragment>
        <MyName name="이수근"/>
        <Counter />
      </fragment>
    );
  }
}

export default App;


아래에 있는 버튼들을 눌러보면 숫자가 바뀐다!



이 글은 Velopert님의 블로그에서 React 포스팅을보며 실습하며 공부한 자료입니다.

Comments