리액트 프로퍼티(props), 상태(state) 관리하기

    728x90
    반응형

    · props 프로퍼티

    프로퍼티는 상위 컴포넌트가 하위 컴포넌트에 값을 전달할 때 사용합니다.

    이것을 '단방향으로 데이터가 흐른다'라고 표현합니다.

    이 때 값을 받은 컴포넌트에서는 프로퍼티 값은 직접 수정할 수 없다는 특징이 있습니다. 

    프로퍼티를 받은 자식입작에서는 읽기 전용인 데이터이다.

     

    · state 상태

    props와 다르게 값을 바꿔야 하는 경우 사용합니다.

    '값을 저장하거나 변경할 수 있는 객체'로 보통 버튼을 클릭하거나 값을 입력하는 등의 이벤트와 함께 사용됩니다.

    컴포넌트 내부에서 선언하며, 내부에서 값을 변경할 수 있다.

    자신이 들고 있는 값이며 props와 비교한다면 쓰기전용이라고 볼 수 있다.


    주의점

    1. 생성자(constructor)에서 반드시 초기화해야 합니다

    2. state값을 실행할 때는 setState() 함수(상태 관리 함수)를 반드시 사용해야 합니다.

    3. setState() 함수는 비동기로 처리되며, setState() 코드 이후로 연결된 함수들의 실행이 완료된 시점에서 화면 동기화 과정을 거칩니다.


    위에서 언급했듯이 state에 저장되는 객체는 반드시 초기화해야 합니다.

    그렇지 않으면 내부 함수에서 state값에 접근할 수 없습니다.

    만약 마땅한 초깃값이 없다면 state에 빈 객체라도 넣어야 합니다. (this.state = {})

    그리고 state에 저장되는 객체의 값은 직접 변경하면 안됩니다.

    import React from 'react';
    
    class StateExample extends React.Component {
        constructor(props) {
            super(props);
            // state 정의
            this.state = {
                loading: true,
                formData: 'no data',
            }
            // 이후 콜백 함수를 다룰 때 bind(this)에 대해 자세히 이야기하겠습니다.
            this.handleData = this.handleData.bind(this);
            // 4초 후에 handleData 함수를 호출합니다.
            setTimeout(this.handleData, 4000);
        }
        handleData() {
            const data = 'new data';
            const {formData} = this.state;
            // state 변경
            this.setState({
                loading: false,
                formData: data + formData
            });
            // this.state.loading은 현재 true 입니다.
            console.log('loading값 ', this.state.loading);
            // 이후 호출될 render() 함수에서의 this.state.loading값 false 입니다.
        }
        render() {
            return (
                <div>
                    {/*state 데이터는 this.state로 접근 가능합니다.*/}
                    <span>로딩중: {String(this.state.loading)}</span>
                    <span>결과: {this.state.formData}</span>
                </div>
            );
        }
    }
    
    export default StateExample;

     

     

    1. state값은 setState() 함수로 변경합니다.

    state값을 직접 변경하면 안되는 이유는 render() 함수로 화면을 그려주는 시점은 리액트 엔진이 정하기 때문입니다.

    즉, state값을 직접 변경해도 render() 함수는 새로 호출되지 않습니다.

     

    하지만 setState() 함수를 호출하여 state값을 변경하면 리액트 엔진이 자동으로 render() 함수를 호출하므로 화면에 변경된 state값을 새롭게 출력할 수 있습니다.

     

    이는 setState() 함수는 리액트 컴포넌트의 생명주기와 깊이 연관되어있기 때문입니다.

    실제로 리액트 엔진은 setState() 함수로 state값을 변경하면 몇 단계의 검증 과정을 거쳐 render() 함수를 호출합니다.

     

     

    2. setState() 함수의 인자로 함수를 전달하면 이전 state값을 쉽게 읽을 수 있습니다.

    setState() 함수의 인자로 함수를 전달하면 이전 state값을 따로 읽는 과정을 생략할 수도 있습니다.

    handleData(data) {
    	this.setState(prevState => ({
        	loading: false,
            formData: data + prevState.formData
        }))
    }

     

     

    3. 클래스 인스턴스 변수와 forceUpdate() 함수로 state 관리하기

    그러나 꼭 setState()가 아니더라도 state를 관리 할 수 있습니다.

    setState() 함수를 사용하는 이유는 앞에서 언급했듯이 리액트 엔진이 state변경과 화면 동기화 과정을 처리해야하기 때문입니다.

    만약 출력 검증없이 함수가 호출될 때마다 새롭게 화면을 출력하고 싶다면 클래스 인스턴스 변수와

    화면을 강제로 출력해주는 forceUpdate() 함수를 사용하면 됩니다.

     

    하지만 이 방법은 리액트 성능에 제약이 있으므로 매번 새롭게 화면을 출력해야 되는 경우가 아니라면 가급적 사용하지 않기를 권합니다.

    import React, { Component } from "react";
    
    class ForceUpdateExample extends Component {
      constructor(props) {
        super(props);
        // status 정의
        this.loading = true;
        this.formData = "no data";
        // 이후 콜백 함수를 다룰 때 bind럴 선언하는 부분에 대해 다룹니다.
        this.handleData = this.handleData.bind(this);
        // 4초 후에 handleData를 호출합니다.
        setTimeout(this.handleData, 4000);
      }
      componentDidMount() {
        // 4초 후에 handleData를 호출합니다.
        // setTimeout(this.handleData, 4000);
      }
      handleData() {
        const data = "new Data";
        // state 변경
        this.loading = false;
        this.formData = `${data} ${this.formData}`;
        console.log(this.loading);
        this.forceUpdate();
      }
      render() {
        return (
          <div>
            {/* state 데이터는 this.state로 접근이 가능합니다. */}
            <span>로딩중: {String(this.loading)}</span>
            <span>결과: {this.formData}</span>
          </div>
        );
      }
    }
    
    export default ForceUpdateExample;
    

     

     

    4. setState() 함수는 객체의 깊숙한 곳 까지 확인하지 못한다.

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

    아래와 같이 전달한다고 해서 foobar값이 2로 바뀌지 않는다.

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

    위처럼 하면 foobar값이 2로 바뀌지 않고 기존의 foo객체 자체가 바뀌어버린다.

    {
      number: 0,
      foo: {
        // bar 값이 없다. foo 객체 자체가 바뀌었기 때문이다.
        foobar: 2
      }
    }

    위와 같은 상황에서는 아래처럼 작성해 줘야 한다.

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

    위 구문중 ...는 전개연산자이다. 기존의 객체안에 있는 내용을 해당 위치에 풀어준다는 의미다.

    그 다음에 설정하고 싶은 값을 넣어주면 해당 값을 덮어 쓰게 된다.

     

     

     

     

    · 애초에 왜 나누었을까?

    개발자들에게 명확한 관념 모델을 제공하기 위해서입니다.

    즉 논리적으로 이치에 맞는 사고모델을 제공한다는 것입니다.

     

    개발자들은 컴포넌트 간에는 무조건 props를 통해서만 데이터를 주고 받고

    props는 컴포넌트 안에서 변경되지 않습니다.

    한쪽방향, 그리고 자기 자신에 대해서만 고민하면 됩니다.

     

    => 변하는 데이터, 변하지 않는 데이터가 확실하니 개발자들의 혼동이 줄어든다.

         props는 고정, state는 변환이라는 것이 박혀있기 때문에

    즉, 지금 컴포넌트에서 필요한 값이 props인지 state인지 판단하고 어느 LifeCycle과 관련이 있는지 이 값을 어떻게 컴포넌트에 넘겨줄지만 생각하면 됩니다.

     

    퍼포먼스 측면으로도

    props와 state가 하나의 객체로 관리 되었다면, updateing을 결정하는 shouldComponentUpdate() 함수에서 

    둘다를 비교했어야 했지만 props와 state로 분리되어 있어서 O(keys(state))만큼만 비교하면 updateing을 결정할 수 있다.

    728x90
    반응형

    댓글