[英]React Component State modified before setState called
問題是在調用setState
之前,我仍然遇到狀態正在被修改的實例。 這是一個學習活動,所以請不要推薦像 immutablejs 這樣的庫。
游戲蛇是我正在制作的。 目前使用step
法逐幀查看。
render
這樣做(網格只是循環狀態來構建網格):
render() {
return (
<div>
<Grid state={this.state}>
<button onClick={ this.step }>Step Frame</button>
</div>
);
}
step
方法如下所示:
step = () => this.setState(state => {return this.moveSnakeHandler(state)});
其中調用:
moveSnakeHandler = (prevState : AppState) => {//This returns to setState
console.log(this.state.snake[0], this.state.snake[1]);
//this shows state having x: 25, y: 15} {x: 25, y: 14}
const snake = moveSnake(prevState.snake, prevState.direction);
console.log(...this.state.snake);
//this shows state having {x: 25, y: 15} why is state modified?
return snake;
}
蛇的實際移動(這是在組件類之外):
const moveSnake = (prevSnake, direction) => {
//store what the current head is to change direction and placement
const head : {x: number, y: number} = prevSnake[0];
//Tail is the full snake my pop/not below to grow
const tail : Array<{x: number, y: number}> = prevSnake;
switch (direction) {
case "up":
head.y -= 1;
break;
case "down":
head.y += 1;
break;
case "left":
head.x -= 1;
break;
case "right":
head.x += 1;
break;
default:
break;
}
//remove the last element of the new tail so I do not grow
tail.pop();
//toggle pop on isApple when ready
//put the new head and tail together same size as before with everything shifted one!
const snake = [head].concat(tail)
//return the object that will be updated.
return {snake:snake};
}
我的問題是為什么在調用setState
之前 state 會發生變化?
我可以用下面的移動蛇解決所有這些問題,但這似乎沒有必要......
const head : {x: number, y: number} = JSON.parse(JSON.stringify(prevSnake[0]));
const tail : Array<{x: number, y: number}> = JSON.parse(JSON.stringify(prevSnake));
您正在直接改變狀態。 我已將邏輯從moveSnake
移到moveSnakeHandler
函數中:
const moveSnakeHandler = (prevState) => {
console.log(this.state.snake[0], this.state.snake[1])
//this shows state having x: 25, y: 15} {x: 25, y: 14}
// head is the object contained in state - do not mutate this
const head = prevState.snake[0]
// tail is the array saved in state - do not mutate this
const tail = prevState.snake
// here we mutate head - this is bad
switch (direction) {
case 'up':
head.y -= 1
break
case 'down':
head.y += 1
break
case 'left':
head.x -= 1
break
case 'right':
head.x += 1
break
default:
break
}
// here we mutate tail - this is bad
tail.pop()
console.log(...this.state.snake)
//this shows state having {x: 25, y: 15}
// we mutated state, so that is why state is modified
return { snake: [head].concat(tail) }
}
現在你看到問題了嗎?
你永遠不應該改變狀態,你應該只調用setState
。 即使您使用的是setState
函數:
step = () => this.setState(state => {return this.moveSnakeHandler(state)});
您傳遞給moveStateHandler
的值state
是一個對象,因此您無法修改其屬性,因為這意味着您正在修改 state 對象。
您可以采取一些措施來防止這種情況發生:
設置新狀態值時切勿使用賦值運算符 ( =
) ,例如,請勿使用=
或+=
或此運算符的任何組合來設置狀態。
永遠不要使用改變當前數組的數組函數。 這包括pop
、 push
、 splice
等。您應該使用非變異函數,例如slice
、 concat
、 filter
等。
要解決此問題,您可以執行以下操作:
const moveSnakeHandler = (prevState: AppState) => {
const { snake, direction } = prevState
const newSnakeHead = getNewHead(snake[0], direction)
// We use slice which returns a new array instead of mutating the existing one
// slice(0, -1) returns a new array without the last element
// We create a new array with the new snake head
const newSnake = [newSnakeHead, ...snake.slice(0, -1)]
// Return a new object for the new state
return { ...prevState, snake: newSnake }
}
const getNewHead = (head: { x: number; y: number }, direction: string) => {
// We always return a new object, never mutate the existing head
switch (direction) {
case 'up':
return { ...head, y: head.y - 1 }
case 'down':
return { ...head, y: head.y + 1 }
case 'left':
return { ...head, x: head.x - 1 }
case 'right':
return { ...head, x: head.x + 1 }
default:
return head
}
}
您的狀態在setState
調用之前被更新,因為您直接修改了snake
對象,而不是在沒有直接操作的情況下創建它的新版本。 操縱發生在moveSnake
在switch
和在tail.pop()
正如您所指出的,您可以通過使用JSON.parse(JSON.stringify())
制作snake
的深層副本來解決此問題,這是一種方法。 另一種方法是使用擴展運算符 ( ...
) 和.slice
數組方法創建一條新snake
。
例如:
const moveSnake = (prevSnake, direction) => {
const prevHead : {x: number, y: number} = prevSnake[0];
let newHead : {x: number, y: number};
switch (direction) {
case "up":
newHead = {x: prevHead.x, y: prevHead.y - 1};
break;
case "down":
newHead = {x: prevHead.x, y: prevHead.y + 1};
break;
case "left":
newHead = {x: prevHead.x - 1, y: prevHead.y};
break;
case "right":
newHead = {x: prevHead.x + 1, y: prevHead.y};
break;
default:
newHead = {x: prevHead.x, y: prevHead.y}; // or newHead = {...prevHead};
}
// newTail is prevSnake without the last element so I do not grow
const newTail = prevSnake.slice(0, -1);
//put the new head and tail together same size as before with everything shifted one!
const newSnake = [newHead].concat(newTail);
//return the object that will be updated.
return {snake: newSnake};
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.