데이터(State) 변경 → 리액트가 render()
함수를 호출 → UI 업데이트
컴포넌트 UI를 위한 데이터를 보관하는 오브젝트로, state
가 업데이트 되면 자동으로 render
함수를 호출해 준다. 이는 클래스 컴포넌트에서만 사용이 가능하다.
컴포넌트를 완전히 재사용 및 캡슐화하려면 props
을 통해 데이터를 받는 것이 아니라 컴포넌트 자체에 state
를 두어 state
가 변화됨에 따라 스스로 화면이 업데이트되도록 만들어야 한다.
state
는 props
와 유사하지만, 비공개이며 컴포넌트에 의해 완전히 제어된다.
React 16.8버전부터 함수형 컴포넌트에서도 리액트 훅을 이용해
state
를 사용할 수 있지만 여기서는 다루지 않을 거다.
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;
❗ 주의
state
에props
를 복사하면 안된다.constructor(props){ super(props); // 이렇게 하면 안됨 this.state={color: props.color}; }
이는 불필요한 작업(
this.props.color
를 직접 사용하면 됨)이며 버그를 발생시킨다(color props
의 값이 변해도state
에 반영되지 않음).따라서, props의 갱신을 의도적으로 무시해야 할 때만 이와 같은 패턴을 이용해야 한다.
state 안에 있는 값을 부모 컴포넌트에 전달해주려면, 부모 컴포넌트에서 메소드를 만들어 자식 컴포넌트에 전달한 다음 자식 내부에서 해당 메소드에 state값을 인자로 넣어 호출해주면 된다.
아래 예시들은 위에서 정의한 state
을 이용할 거다.
📍 예시__1
this.state.userAge++;
→ 이런식으로 하면 리액트는 state
가 업데이트 된 지 모르기 때문에 setState
을 사용해야 함
this.state.userAge++;
this.setState(this.state);
→ setState
을 해서 리액트에게 변경을 알리기는 했지만 직접 state
를 변경하는 것은 좋지 않다.
this.setState(this.state.userAge+1);
📍 예시__2
// (...)에 대해 모른다면 "➕Spread Syntax" 단락을 먼저 보고 오자.
const item ={...this.state.item};
item.count++;
this.setState({item});
// key와 value가 같은 이름이면 {item:item}을 위와 같이 생략 가능
→ 이 방법은 일반적인 Component
에서는 작동하나 memo
, Pure Component
에서는 렌더링이 일어나지 않는다.
그 이유에 대해서는 다음 포스팅 Pure Component와 memo 까지 보면 알 수 있을 거다.
this.setState({item:{...this.state.item, this.state.item.count+1}})
→ 이렇게 하면 새로운 참조값을 가진 오브젝트이면서 변경된 내용물을 가지므로 memo
와 PureComponent
에서도 잘 작동한다.
1. 직접 수정시 컴포넌트가 리렌더링 되지 않는다.
// Wrong
this.state.userName = 'Henry';
2. State의 업데이트는 비동기적일 수 있다.
this.props
와 this.state
가 비동기적으로 업데이트 될 수 있기 때문에 state
를 계산할 때 props
나 state
값에 의존해서는 안된다.
// Wrong
this.setState({
counter:this.state.userAge+this.props.increment,
});
만약 컴포넌트 내 state
값이 의존적일 경우 즉, state
를 업데이트 할 때 이전 state
나 props
값에 계산이 되어지는 경우라면 객체보다 함수를 인자로 사용 하는 setState()
를 사용해야 한다.
// 🌟Correct
this.setState((state,props)=>{
counter:state.counter+props.increment
});
🔍 setState 함수의 종류
- setState(updated)
→ 새로운 state 오브젝트를 인자로 전달- 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은 다음 포스팅에서 자세히 다룬다.
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 }