state = {
isOpenModal: true,
fruitList: [],
};
onClickButton(e, ele) {
let newFruitList = [];
// 빨간색 과일
if(ele.name === "red") {
newFruitList = [{
fruitName: "apple",
fruitColor: "red",
fruitPrice: "12000",
},{
fruitName: "strawberry",
fruitColor: "red",
fruitPrice: "18000",
}];
} else { // 노락색 과일
newFruitList = [{
fruitName: "banana",
fruitColor: "yellow",
fruitPrice: "7000",
},{
fruitName: "mango",
fruitColor: "yellow",
fruitPrice: "15000",
}];
}
}
onOpenModal() {
this.setState({...this.state, isOpenModal: true});
}
- 기능 설명 -
버튼이 두 개 있다.
모달을 여는 버튼이다.
두 개 중 하나는 빨간 과일의 목록을 보는 모달이다.
다른 버튼은 노란 색깔의 과일을 보는 모달이다.
버튼을 클릭하면 onClickButton() 함수가 실행된다.
어떤 버튼을 눌렀는지를 판단해서 과일 목록을 바꾼다.
빨간 버튼을 눌렀으면 과일 목록을 빨간 과일로 바꾸고,
노란 버튼을 눌렀으면 과일 목록을 노란 색깔 목록으로 바꾼다.
모달의 띄우고 과일 목록을 보여준다.
이 과일 목록은 state 에서 들고 있다.
과일 목록이 나오는 모달을 열고 닫는 toggle 값도 state 에 있다.
따라서 버튼을 클릭하면 하는 일이 두 가지다.
첫째는 과일 목록을 바꾸는 것이고,
둘째는 모달을 여는 함수를 실행하는 것이다.
- 문제 상황 -
과일 목록이 바뀌지 않았다.
과일 목록은 처음에 선언해줄 때 넣어줬던 그 초기값이 그대로 들어 있었다.
빈 배열로 초기화해줬으면 빈 배열이, 하나짜리 배열이면 그 하나만 들어 있고 그 뒤로는 setState() 로 바뀌지 않았다.
버튼을 누르면 state 안에 있는 fruitList 가 바뀌어야 하는데 작동하지 않았다.
신기한 건 버튼을 눌렀을 때 모달은 떴다.
즉, onClickButton() 함수도 실행된다는 것이고,
onClickButton() 함수 안의 onOpenModal() 함수도 실행된다는 뜻이다.
중간에 에러가 나는 것도 아니다.
그런데 그 사이에 있는 setState() 구문만 먹히지가 않았다.
중간에서 setState() 로 fruitList 를 바꿔주고 있었다.
이 부분이 먹통인 것이다.
간혹 setState() 가 비동기로 작동해서
변경되기 이전 값이 들어가 있는 경우도 종종 있다.
그러나 모달을 띄운 후에 console.log() 로 state 를 찍어 봐도 변함이 없었다.
즉 렌더링이 문제가 아니라 값 자체가 바뀌지를 않았다.
그래도 혹시나 해서 updater 함수를 써 보았지만 역시나 해결되지 않았다.
setState(()=>{return {fruitList: newFruitList}}); // 작동하지 않음
- 문제 원인 -
문제 원인은 이것 저것 해보다가 우연히 발견했다.
어떤 두 가지 요소가 겹치면서 이러한 문제가 발생했다.
하나는 setState() 가 비동기라는 것이고
다른 하나는 JavaScript 문법인 전개 연산자(spread operator) 이다.
> setState()는 비동기로 작동한다
첫째로 React.js 에서는 setState() 가 비동기라는 점을 이해해야 한다.
일단 onClickButton() 함수 안에서는 리스트를 만들어 setState() 를 실행한다.
그러나 이 setState() 결과는 바로 반영되지 않는다.
왜냐하면 setState() 는 비동기로 작동하기 때문이다.
만약 setState() 함수가 동기로 작동한다면 어떨지 생각해 보자.
setState() 가 연쇄적으로 여러 번 실행되는 상황도 있을 것이다.
React.js 에서는 state 가 바뀌면 render 를 다시 실행하는데,
따라서 setState() 가 실행될 때마다 렌더링이 계속 일어나는 상황이 발생한다.
이는 매우 비효율적인 작동 방식이다.
그래서 React.js 에서는 setState() 를 비동기로 작동하도록 만들었다.
setState() 함수가 여러 번 실행되더라도 변경 사항을 차곡차곡 모아뒀다가
렌더링은 마지막에 한 번만 실행할 수 있도록 했다.
훨씬 효율적인 작동 방식이다.
(렌더 패스는 여러 번 돌 수도 있다.)
<렌더링 기본 개념 정리 - 렌더링/렌더/커밋/가상DOM>
이와 같은 이유로 onClickButton() 함수 안에서 실행했던 setState() 는 당장 실행되지 않는다.
대신 함수를 이어서 계속 실행하다가 onOpenModal() 까지도 실행한다.
이번에는 onOpenModal() 안을 들여다보자.
onOpenModal() 안에서도 setState() 를 실행하고 있다.
> JavaScript 전개 연산자(spread operator)
여기서 둘째로 전개 연산자(spread operator) 도 알아야 한다.
const newObj = {...myObj, myKey: "myValue"};
점 세 개(...)를 이용해 이런 구문을 쓸 수 있다.
그냥 JavaScript 문법이다.
{...myObj, myKey: "myValue"} 하면 myObj 안에 있는 내용물을 복사해오겠다는 뜻이다.
거기에 더해 myKey 에는 "myValue" 를 넣는다.
만약 key 가 겹친다면 새로 넣어주는 값으로 대체한다.
자세히 보면 이 문법은 setState() 를 호출할 때 자주 쓰인다.
보통 아래처럼 쓴다.
this.setState({...this.state, isOpenModal: true});
그러면 this.state 에 있던 값은 그대로 넣고,
isOpenModal 이라는 값만 true 로 바꾸겠다는 뜻이다.
이 두 요소를 이해했다면 이제 왜 작동하지 않았는지 알 수 있다.
setState() 는 비동기이다.
따라서 당장 적용되지 않고 한 번에 모아서 반영한다.
우리가 버튼을 클릭했을 때 setState() 는 총 두 번 실행된다.
onClickButton() 에서 한 번, onOpenModal() 에서 한 번.
그 중에 onOpenModal() 이 나중에 실행됐으므로 이전 사항을 덮어쓸 것이다.
fruitList 를 앞에서 setState() 로 아무리 바꾸어도 state 값이 바뀌지 않았던 이유는
그 뒤에서 실행되는 setState() 가 전개 연사자를 사용하고 있어서 다른 값들을 덮어쓰고 있었기 때문이었다.
첫 번째 setState() 에서는 state 의 fruitList 를 다른 배열로 바꿨다.
하지만 state 에는 아직 반영되지는 않았으므로 fruitList 는 바뀌기 이전의 값이다.
이 상태에서 두 번째 setState() 를 실행한다.
{...this.state, isOpenModal: true}
이러면 this.state 값들을 그대로 복사해서 넣되, isOpenModal 의 값만 true 로 바꾸겠다는 뜻이다.
문제는 이때 fruitList 의 값은 바뀌기 이전의 값이라는 점이다.
onClickButton() 에서 기껏 바꿔놨던 fruitList 는
나중에 실행되는 onOpenModal() 에서 다시 이전 값으로 덮어쓰고 있었다.
이래서 fruitList 를 아무리 바꾸어도 바뀌지 않았던 것이다.
- 해결 -
setState() 함수를 호출할 때 전개 연산자를 뺴고 실행했다.
setState({isOpenModal: true});
이렇게 한다고 해서 나머지 값들이 사라지고 state 에 isOpenModal 값만 남는 것은 아니다.
굳이 ...this.state 를 넣어주지 않아도 setState() 함수는 알아서 새로 바뀌는 값만 수정하고 나머지는 그대로 둔다.
이렇게 하니 잘 작동했다.
fruitList 도 값이 바뀌고 isOpenModal 도 바뀌었다.
모달도 잘 열리고 과일 목록도 잘 바뀌어서 나왔다.
그동안 setState() 를 쓸 때 관습적으로 전개 연산자를 써 왔다.
그런데 이러한 덮어쓰기 문제점이 있음에도 왜 이렇게 쓰는지 이유를 모르겠다.
나중에 시간이 난다면 찾아봐야겠다.
- 221230-setState_overwrite
'React' 카테고리의 다른 글
[React] 렌더링 기본 개념 정리 - 렌더링/렌더/커밋/가상DOM (0) | 2023.02.06 |
---|---|
[React] store 에 있는 객체를 view 에 바인딩하여 출력할 때 주의할 점 - setProps (0) | 2023.01.30 |
[React] onChange 값이 한 박자씩 늦게 들어갈 때 / observable 변수 렌더링 뒤늦게 반영될 때 (0) | 2023.01.02 |
[React] + dataGrid 트리 구조 테이블에서 합계 구하는 법 (feat. 재귀 함수) (0) | 2022.12.12 |
[React] 주요 LifeCycle 함수들 호출 시점 / 활용 방법 / 주의할 점 정리 (0) | 2022.12.05 |