簡體   English   中英

在調用 setState 之前修改 React 組件狀態

[英]React Component State modified before setState called

我已閱讀並完全理解https://reactjs.org/docs/faq-state.html#what-is-the-difference-between-passing-an-object-or-a-function-in-setstate

問題是在調用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 對象。

您可以采取一些措施來防止這種情況發生:

  • 設置新狀態值時切勿使用賦值運算符 ( = ) ,例如,請勿使用=+=或此運算符的任何組合來設置狀態。

  • 永遠不要使用改變當前數組的數組函數 這包括poppushsplice等。您應該使用非變異函數,例如sliceconcatfilter等。


要解決此問題,您可以執行以下操作:

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對象,而不是在沒有直接操作的情況下創建它的新版本。 操縱發生在moveSnakeswitch和在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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM