React - State와 setState (+Spread Syntax)

Noma·2021년 4월 12일
0
post-custom-banner

1. 리액트 동작 원리

데이터(State) 변경 → 리액트가 render()함수를 호출 → UI 업데이트

2. State

컴포넌트 UI를 위한 데이터를 보관하는 오브젝트로, state가 업데이트 되면 자동으로 render함수를 호출해 준다. 이는 클래스 컴포넌트에서만 사용이 가능하다.

컴포넌트를 완전히 재사용 및 캡슐화하려면 props을 통해 데이터를 받는 것이 아니라 컴포넌트 자체에 state를 두어 state가 변화됨에 따라 스스로 화면이 업데이트되도록 만들어야 한다.

stateprops와 유사하지만, 비공개이며 컴포넌트에 의해 완전히 제어된다.

React 16.8버전부터 함수형 컴포넌트에서도 리액트 훅을 이용해 state를 사용할 수 있지만 여기서는 다루지 않을 거다.

  1. rcc + tab key를 이용해서 만든 클래스 컴포넌트에 state 추가하기
import React, { Component } from 'react';

class Cart extends Component {
    state={
        userName:'merry',
        userAge:24,
        item:{
          name:'lemon',
          count:2
        }
      };
    render() {
        return (
            <div>
            	<h1>{this.state.item.count}</h1>
                <button>{this.state.item.name}</button>
            </div>
        );
    }
}
export default Cart;

2. constructor 이용해서 state 정의하기

class Cart extends React.Component{
  constructor(props){
      super(props);
      this.state={
        userName:'merry',
        userAge:24,
        item:{
          name:'lemon',
          count:2
        }
      };
  }
  render() {
      return(
      		<div>
            	<h1>{this.state.item.count}</h1>
                <button>{this.state.item.name}</button>
            </div>
      );
  }
}
export default Cart;

❗ 주의
stateprops를 복사하면 안된다.

constructor(props){
super(props);
// 이렇게 하면 안됨
this.state={color: props.color};
}

이는 불필요한 작업(this.props.color를 직접 사용하면 됨)이며 버그를 발생시킨다(color props의 값이 변해도 state에 반영되지 않음).

따라서, props의 갱신을 의도적으로 무시해야 할 때만 이와 같은 패턴을 이용해야 한다.

➕ 부모 컴포넌트에 자식 state 전달하기

state 안에 있는 값을 부모 컴포넌트에 전달해주려면, 부모 컴포넌트에서 메소드를 만들어 자식 컴포넌트에 전달한 다음 자식 내부에서 해당 메소드에 state값을 인자로 넣어 호출해주면 된다.

3. State 업데이트 하기 - setState

아래 예시들은 위에서 정의한 state을 이용할 거다.

📍 예시__1

  • wrong
this.state.userAge++;

→ 이런식으로 하면 리액트는 state가 업데이트 된 지 모르기 때문에 setState을 사용해야 함

this.state.userAge++;
this.setState(this.state);

setState을 해서 리액트에게 변경을 알리기는 했지만 직접 state를 변경하는 것은 좋지 않다.

  • 🌟correct
this.setState(this.state.userAge+1);

📍 예시__2

  • wrong
// (...)에 대해 모른다면 "➕Spread Syntax" 단락을 먼저 보고 오자.
const item ={...this.state.item};
item.count++;
this.setState({item}); 
// key와 value가 같은 이름이면 {item:item}을 위와 같이 생략 가능

→ 이 방법은 일반적인 Component에서는 작동하나 memo, Pure Component에서는 렌더링이 일어나지 않는다.

그 이유에 대해서는 다음 포스팅 Pure Component와 memo 까지 보면 알 수 있을 거다.

  • 🌟correct
this.setState({item:{...this.state.item, this.state.item.count+1}})

→ 이렇게 하면 새로운 참조값을 가진 오브젝트이면서 변경된 내용물을 가지므로 memoPureComponent에서도 잘 작동한다.

4. State의 특성

1. 직접 수정시 컴포넌트가 리렌더링 되지 않는다.

// Wrong
this.state.userName = 'Henry';

2. State의 업데이트는 비동기적일 수 있다.

this.propsthis.state가 비동기적으로 업데이트 될 수 있기 때문에 state를 계산할 때 propsstate값에 의존해서는 안된다.

// Wrong
this.setState({
	counter:this.state.userAge+this.props.increment,
});

만약 컴포넌트 내 state 값이 의존적일 경우 즉, state를 업데이트 할 때 이전 stateprops값에 계산이 되어지는 경우라면 객체보다 함수를 인자로 사용 하는 setState()를 사용해야 한다.

// 🌟Correct
this.setState((state,props)=>{
	counter:state.counter+props.increment
});

🔍 setState 함수의 종류

  1. setState(updated)
    → 새로운 state 오브젝트를 인자로 전달
  2. setState(prevState => newState)
    → 이전 state나 props를 받아 새로운 state를 리턴하는 함수(일반 함수, 화살표 함수O)를 인자로 전달

3. State의 업데이트는 병합된다.

constructor(props){
	super(props);
  	this.state={
    	posts:[],
      	comments:[]
    }
}

아래처럼 별도의 setState()호출로 변수들을 독립적으로 업데이트 할 수 있다.

componentDidMount(){
	fetchPosts().then(response=>{
    	this.setState({
        	posts:reponse.posts
        });
    });
  	fetchComments().then(response=>{
    	this.setState({
        	comments: response.comments
        });
    });
}

얕은 병합(shallow comparison) 이 이루어지기 때문에 this.setState({comments})this.state.posts에 영향을 주진 않지만 this.state.comments는 완전히 대체된다.

Shallow Comparison은 다음 포스팅에서 자세히 다룬다.


➕ Spread Syntax

1. 배열 리터럴에서의 spread
배열의 참조값은 새로운 값을 가지고 배열 안의 값이 오브젝트이면 그 안의 값들은 오브젝트의 참조값을 복사해오고, 일반 숫자나 문자열이면 값 자체를 복사해온다.

const arr = [1, 2, 3];
const arr2 = [...arr]; // [1, 2, 3]
arr2.push(4); // arr2 -> [1, 2, 3, 4], arr = [1, 2, 3]
// 값 자체를 복사해왔기 때문에 arr은 영향을 받지 않는다.
------------------------------------------------
let arr1 = [0, 1, 2];
const arr2 = [3, 4, 5];
arr1 = [...arr1, ...arr2]; // [0, 1, 2, 3, 4, 5]
-----------------------------------------------
const arr=[{name:'merry', age:24}];
const arr2=[...arr];
arr2[0].age++;
// arr2[0].age와 arr[0].age 모두 25임 (영향 O)

한쪽(arr2)에서 어느 오브젝트의 age를 변경해도 둘다(arr, arr2) 변경된 내용을 볼 수 있다.

하지만 둘은 서로 다른 배열 오브젝트이기 때문에, 한쪽(arr2)에서 배열에 새로운 아이템을 삭제 및 추가하면 다른쪽(arr2)에서는 이를 확인할 수 없다.

2. 객체 리터럴에서의 spread

객체는 새로운 참조값을 가지는 새로운 오브젝트를 생성하고, 그 안으로 제공된 객체가 소유한 열거형 프로퍼티들을 복사해온다.

const obj1 = { foo: 'bar', x: 42 };
const obj2 = { foo: 'baz', y: 13 };

const clonedObj = { ...obj1 };
// Object { foo: "bar", x: 42 }
clonedObj.x++;
//clonedObj.x는 43, obj1.x는 42
// 프로퍼티를 변경해도 원본 프로퍼티에 영향을 주지 않는다.

const mergedObj = { ...obj1, ...obj2 };
// Object { foo: "baz", x: 42, y: 13 }

📚 reference

profile
오히려 좋아
post-custom-banner

0개의 댓글