2018년 상반기 스터디 - react.js 3주-2

Posted by 서동우 on April 2, 2018

6장 프로퍼티, 상태, 컴포넌트 트리

  • 데이터를 더 잘 관리하고 애플리케이션 디버깅에 걸리는 시간을 줄이는 기법에 대해서 살펴본다.
  • 리액트 컴포넌트 안에서 데이터를 처리하는 방법에 대해서 살펴본다.

6.1 프로퍼티 검증

  • 자바스크립트는 타입 검증에 느슨한 언어
  • 변수 타입을 비효율적으로 다루면 애플리케이션을 디버깅할 때 오래 걸릴 수 있다.
  • 리액트에서는 타입을 지정하고 검증하는 방법을 제공
  • prop-types 패키지를 설치 후 사용 가능
import PropTypes from 'prop-types';

MyComponent.propTypes = {
    // 자바스크립트 기본 타입은 기본적으로 다 지원한다.
    optionalArray: PropTypes.array,
    optionalBool: PropTypes.bool,
    optionalFunc: PropTypes.func,
    optionalNumber: PropTypes.number,
    optionalObject: PropTypes.object,
    optionalString: PropTypes.string,
    optionalSymbol: PropTypes.symbol,

    // 랜더링 할 수 있는 모든 것(숫자, 문자, element, 배열)을 다 지원하는 타입
    optionalNode: PropTypes.node,

    // 리액트 element
    optionalElement: PropTypes.element,

    // 특정 클래스의 인스턴스인지 여부를 체크    
    optionalMessage: PropTypes.instanceOf(Message),

    // 특정한 값만 가능하도록 체크
    optionalEnum: PropTypes.oneOf(['News', 'Photos']),

    // 특정 타입만 가능하도록 체크
    optionalUnion: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.number,
        PropTypes.instanceOf(Message)
    ]),

    // 특정 타입의 배열만 가능하도록 체크
    optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

    // 특정 타입의 값만 가지고 있는 object 인지 체크
    optionalObjectOf: PropTypes.objectOf(PropTypes.number),

    // 해당 형태의 object인지 체크
    optionalObjectWithShape: PropTypes.shape({
        color: PropTypes.string,
        fontSize: PropTypes.number
    }),


    // 필수 값 체크
    requiredFunc: PropTypes.func.isRequired,

    // 필수 값만 체크. 어떤 값도 와도 됨
    requiredAny: PropTypes.any.isRequired,

    // 커스텀 검증
    customProp: function(props, propName, componentName) {
        if (!/matchme/.test(props[propName])) {
        return new Error(
            'Invalid prop `' + propName + '` supplied to' +
            ' `' + componentName + '`. Validation failed.'
        );
        }
    },

    // You can also supply a custom validator to `arrayOf` and `objectOf`.
    // It should return an Error object if the validation fails. The validator
    // will be called for each key in the array or object. The first two
    // arguments of the validator are the array or object itself, and the
    // current item's key.
    customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
        if (!/matchme/.test(propValue[key])) {
            return new Error(
                'Invalid prop `' + propFullName + '` supplied to' +
                ' `' + componentName + '`. Validation failed.'
            );
        }
    })
};

6.1.1. createClass로 프로퍼티 검증하기

  • Summary 클래스를 만든다.
import { Component } from 'react';

const Summary = class extends Component {
    constructor(props) {
        super(props);
    }
    
    render() {
        const { ingredients, steps, title  } = this.props;

        return (
            <div className="summary">
                <h1>{title}</h1>
                <p>
                    <span>재료 {ingredients.length} 종류 | </span>
                    <span> {steps.length}</span>
                </p>
            </div>
        );
    }
}

return Summary;
  • 프로퍼티 검증이 들어가 있지 않기 때문에 아래와 같은 경우 우리가 예상되는 결과 값을 생성하지 않는다.

// ingredients, steps 가 문자열이기 때문에 문자열의 길이 값이 결과로 나온다.
// 이 부분은 우리가 예상하지 못한 결과이고 이런 건 디버깅 하기가 너무 힘들다.
render(<Summary 
    title="땅콩버터와 젤리"
    ingredients="땅콩버터, 젤리, 식빵"
    steps="땅콩버터와 젤리를 넓게 바른 식빵 두 장을 바른 명이 안으로 가도록 겹친다." />, document.getElementById("react-container"));

// ingredients, steps 가 없는 경우 에러가 나온다.
// ingredients, steps 의 경우 배열 값이라는 가장 하에 length 값을 주었지만 식제 값은 null 값이기 때문에 null에는 length  속성 값이 없어서 에러가 발생
render(<Summary />, document.getElementById("react-container"));
  • 타입 검증을 추가하면 아래와 같다.
import React, { Component} from 'react';
import PropTypes from 'prop-types';

const Summary = class extends Component {
    render() {
        const { ingredients, steps, title  } = this.props;

        return (
            <div className="summary">
                <h1>{title}</h1>
                <p>
                    <span>재료 {ingredients.length} 종류 | </span>
                    <span> {steps.length}</span>
                </p>
            </div>
        );
    }

    static propTypes = {
        ingredients: PropTypes.array.isRequired,
        steps: PropTypes.array.isRequired,
        title: PropTypes.string
    }
}

export default Summary;
  • 위와 같이 처리할 경우에 ingredients와 steps가 배열이 아니기 때문에 콘솔 창에 경고가 표시된다.
  • 경고는 표시되지만 실제 프로그램은 실행된다.

6.1.2 디폴트 프로퍼티

  • 값을 무조건 지정할 수 있도록 위와 같이 isRequired 를 설정하는 것도 좋은 방법이지만 디폴트 값을 지정하는 방법도 존재한다.
import React, { Component} from 'react';
import PropTypes from 'prop-types';

const Summary = class extends Component {
    render() {
        const { ingredients, steps, title  } = this.props;

        return (
            <div className="summary">
                <h1>{title}</h1>
                <p>
                    <span>재료 {ingredients.length} 종류 | </span>
                    <span> {steps.length}</span>
                </p>
            </div>
        );
    }

    static propTypes = {
        ingredients: PropTypes.array,
        steps: PropTypes.array,
        title: PropTypes.string
    }

    // 이런 식으로 디폴트 설정이 가능
    static defaultProps = {
        ingredients: [],
        steps: [],
        title: ''
    }
    
}

export default Summary;

6.1.3. 커스텀 프로퍼티 검증

  • 필수 값이나 타입 검증만으로 검증이 되지 않을 때 커스텀 프로퍼티 검증을 사용
  • 특정 값의 범위를 지정한다던가, 문자열 안에 꼭 들어가야 하는 부분 문자열 검사 등등
  • 함수로 구현
  • 요구 사항을 만족하지 않는 경우 error를 반환, 요구 사항을 만족하면 null 을 반환
  • props, propName 2개의 아규먼트를 가진다.
propTypes: {
    title: (props, propName) => 
        (typeof props[propName] !== 'string') ? 
        new Error('제목은 문자열이어야 합니다.') 
        : props[propName].length > 20 ? 
        new Error('제목은 20자 이내야야 합니다.') 
        : null;
}

6.2 참조

  • 짧게 ref 라고 씀
  • 리액트 컴포넌트가 자식 엘리먼트와 상호작용할 때 필요한 기능
  • 사용자 입력을 받는 UI 엘리먼트와 상호작용할 때 참조를 가장 자주 사용
  • 이 책에서는 refs를 사용하고 있는데 곧 refs는 제거 될 것이라고 하니 해당 방법을 말고 callback 방법을 이용하자.

6.2.1 역방향 데이터 흐름

  • 리액트 컴포넌트에서 데이터를 수집하는 일반적인 방법으로 사용
  • 컴포넌트가 데이터를 돌려줄 때 사용할 수 있는 콜백 함수를 컨포넌트의 프로퍼티로 설정하는 것
  • 함수는 컴포넌트에 프로퍼티로 넘기고 컴포너트는 데이터를 그 함수의 인자로 돌려주기 때문에 역방향 데이터 흐름이라고 한다.

6.3 리액트 상태 관리

  • 기본적으로 프로퍼티 값은 한번 렌더링 되면 변경되지 않는다.
  • state는 컴포넌트 안에서 바뀌는 데이터를 관리하기 위해 리액트가 기본적으로 제공하는 기능
  • 상태가 변경되면(setState) UI는 자동으로 상태를 반영해 새로 렌더링
  • 사용자는 애플리케이션과 상호 작용을 함
  • 자바 객체 형태로 표현

소스링크

6.3.1 컴포넌트 상태 소개

6.3.2 프로퍼티로부터 상태 초기화하기

6.4 컴포넌트 트리 안의 상태

  • 모든 컴포넌트에 상태 값을 가질 수 있지만 그렇게 할 경우 상태 변경 시 그와 연관된 컴포넌트를 들을 찾아서 다 변경해줘야 하는 문제가 발생할 수 있음
  • 상태 값을 최소화 하고 프로퍼티 값으로 데이터를 주고 받는 것을 추천
  • 보통은 루트 컴포넌트에 상태 값을 두고 프로퍼티를 이용해서 트리 아랏방향으로 전달하고, 양방향 함수 바인딩을 활용해서 트리 아래쪽에서 수집한 데이터를 위로 올릴 수 있음
  • 표현 컴포넌트 : 애플리케이션에서 화면에 표현하는 것만 관심을 가지고 있는 컴포넌트, 보통 상태가 없는 함수형 컴포넌트로 구성하는 것이 좋다.
  • 애플리케이션이 커지면 프로퍼티 전달이나 양방향 데이터 바인딩이 너무 복잡해지고 소스 코딩이 많아지는 문제가 발생할 수 있음
  • 위 문제를 해결하기 위해서 flux나 redux 같은 것을 사용

참고자료

  • 러닝 리액트
  • https://reactjs.org/docs/typechecking-with-proptypes.html
  • https://reactjs.org/docs/refs-and-the-dom.html