简体   繁体   English

使用更新程序回调实际使用setState而不是在React JS中传递对象

[英]Real world usage of setState with an updater callback instead of passing an object in React JS

The React documentation says the following about setState : React文档说明了以下关于setState的内容

If you need to set the state based on the previous state, read about the updater argument below , If you need to set the state based on the previous state, read about the updater argument below

Beside the following sentence, which I don't understand: 除了以下句子,我不明白:

If mutable objects are being used and conditional rendering logic cannot be implemented in shouldComponentUpdate() , calling setState() only when the new state differs from the previous state will avoid unnecessary re-renders. 如果正在使用可变对象且无法在shouldComponentUpdate()中实现条件呈现逻辑,则仅当新状态与先前状态不同时调用setState()将避免不必要的重新呈现。

They say: 他们说:

The first argument is an updater function with the signature (state, props) => stateChange ... state is a reference to the component state at the time the change is being applied. 第一个参数是带有签名的updater函数(state,props)=> stateChange ... state是对应用更改时组件状态的引用。

And make an example: 举个例子:

this.setState((state, props) => {
  return {counter: state.counter + props.step};
});

Saying: 他说:

Both state and props received by the updater function are guaranteed to be up-to-date . 更新程序功能接收的状态道具保证是最新的 The output of the updater is shallowly merged with state. updater的输出与state轻微合并。

What do they mean by guaranteed to be up-to-date and what should we be aware of when deciding if we should use setState with an updater function (state, props) => stateChange or directly with an object as the first parameter? 在确定是否应该使用带有更新程序功能的setState (state, props) => stateChange或直接将对象作为第一个参数时,它们是什么意思保证是最新的 ?我们应该注意什么?

Let's assume a real world scenario. 让我们假设一个现实世界的场景。 Suppose we have a fancy chat application where: 假设我们有一个花哨的聊天应用程序,其中:

  1. The state of the chat is represented by this.state = { messages: [] } ; 聊天状态由this.state = { messages: [] } ;
  2. Previous messages are loaded making an AJAX request and are prepended to the messages currently in the state; 先前的消息被加载,发出一个AJAX请求,并被添加到当前状态的messages ;
  3. If other users (not the current user) send a message to the current user, new messages arrive to current user from a realtime WebSocket connection and are appended to the messages currently in the state; 如果其他用户(不是当前用户)向当前用户发送消息,则新消息从实时WebSocket连接到达当前用户,并附加到当前状态的messages中;
  4. If it is the current user the one who sends the message, the message is appended to the messages of the state as in point 3 as soon as the AJAX request fired when the message is sent completes; 如果当前用户是发送消息的用户,则当消息发送完成时,一旦发出AJAX请求,消息就会附加到状态messages中,如第3点所示;

Let's pretend this is our FancyChat component: 让我们假装这是我们的FancyChat组件:

import React from 'react'

export default class FancyChat extends React.Component {

    constructor(props) {
        super(props)

        this.state = {
            messages: []
        }

        this.API_URL = 'http://...'

        this.handleLoadPreviousChatMessages = this.handleLoadPreviousChatMessages.bind(this)
        this.handleNewMessageFromOtherUser = this.handleNewMessageFromOtherUser.bind(this)
        this.handleNewMessageFromCurrentUser = this.handleNewMessageFromCurrentUser.bind(this)
    }

    componentDidMount() {
        // Assume this is a valid WebSocket connection which lets you add hooks:
        this.webSocket = new FancyChatWebSocketConnection()
        this.webSocket.addHook('newMessageFromOtherUsers', this.handleNewMessageFromOtherUser)
    }

    handleLoadPreviousChatMessages() {
        // Assume `AJAX` lets you do AJAX requests to a server.
        AJAX(this.API_URL, {
            action: 'loadPreviousChatMessages',
            // Load a previous chunk of messages below the oldest message
            // which the client currently has or (`null`, initially) load the last chunk of messages.
            below_id: (this.state.messages && this.state.messages[0].id) || null
        }).then(json => {
            // Need to prepend messages to messages here.
            const messages = json.messages

            // Should we directly use an updater object:
            this.setState({ 
                messages: messages.concat(this.state.messages)
                    .sort(this.sortByTimestampComparator)
            })

            // Or an updater callback like below cause (though I do not understand it fully)
            // "Both state and props received by the updater function are guaranteed to be up-to-date."?
            this.setState((state, props) => {
                return {
                    messages: messages.concat(state.messages)
                        .sort(this.sortByTimestampComparator)
                }
            })

            // What if while the user is loading the previous messages, it also receives a new message
            // from the WebSocket channel?
        })
    }

    handleNewMessageFromOtherUser(data) {
        // `message` comes from other user thanks to the WebSocket connection.
        const { message } = data

        // Need to append message to messages here.
        // Should we directly use an updater object:
        this.setState({ 
            messages: this.state.messages.concat([message])
                // Assume `sentTimestamp` is a centralized Unix timestamp computed on the server.
                .sort(this.sortByTimestampComparator)
        })

        // Or an updater callback like below cause (though I do not understand it fully)
        // "Both state and props received by the updater function are guaranteed to be up-to-date."?
        this.setState((state, props) => {
            return {
                messages: state.messages.concat([message])
                    .sort(this.sortByTimestampComparator)
            }
        })
    }

    handleNewMessageFromCurrentUser(messageToSend) {
        AJAX(this.API_URL, {
            action: 'newMessageFromCurrentUser',
            message: messageToSend
        }).then(json => {
            // Need to append message to messages here (message has the server timestamp).
            const message = json.message

            // Should we directly use an updater object:
            this.setState({ 
                messages: this.state.messages.concat([message])
                    .sort(this.sortByTimestampComparator)
            })

            // Or an updater callback like below cause (though I do not understand it fully)
            // "Both state and props received by the updater function are guaranteed to be up-to-date."?
            this.setState((state, props) => {
                return {
                    messages: state.messages.concat([message])
                        .sort(this.sortByTimestampComparator)
                }
            })

            // What if while the current user is sending a message it also receives a new one from other users?
        })
    }

    sortByTimestampComparator(messageA, messageB) {
        return messageA.sentTimestamp - messageB.sentTimestamp
    }

    render() {
        const {
            messages
        } = this.state

        // Here, `messages` are somehow rendered together with an input field for the current user,
        // as well as the above event handlers are passed further down to the respective components.
        return (
            <div>
                {/* ... */}
            </div>
        )
    }

}

With so many asynchronous operations, how can I be really sure that this.state.messages will always be consistent with the data on the server and how would I use setState for each case? 有这么多的异步操作,我怎么能真正确定this.state.messages将始终与服务器上的数据保持一致,以及如何为每种情况使用setState What considerations I should make? 我应该考虑什么? Should I always use the updater function of setState (why?) or is safe to directly pass an object as the updater parameter (why?)? 我应该总是使用setStateupdater函数(为什么?)或者直接传递一个对象作为updater参数是安全的(为什么?)?

Thank you for the attention! 谢谢你的关注!

setState is only concerned with component state consistency, not server/client consistency. setState仅关注组件状态一致性,而不关心服务器/客户端一致性。 So setState makes no guarantees that the component state is consistent with anything else. 因此, setState不保证组件状态与其他任何东西一致。

The reason an updater function is provided, is because state updates are sometimes delayed, and don't occur immediately when setState is called. 提供更新程序功能的原因是因为状态更新有时会延迟,并且在调用setState时不会立即发生。 Therefore, without the updater function, you have essentially a race condition. 因此,如果没有更新程序功能,则基本上存在竞争条件。 For example: 例如:

  • your component begins with state = {counter: 0} 你的组件以state = {counter: 0}开头
  • you have a button that updates the counter when clicked in the following way: this.setState({counter: this.state.counter +1}) 你有一个按钮,当按照以下方式点击时更新计数器: this.setState({counter: this.state.counter +1})
  • the user clicks the button really fast, so that the state does not have time to be updated between clicks. 用户非常快速地单击按钮,因此状态没有时间在点击之间进行更新。
  • that means that the counter will only increase by one, instead of the expected 2 - assuming that counter was originally 0, both times the button is clicked, the call ends up being this.setState({counter: 0+1}) , setting the state to 1 both times. 这意味着计数器只会增加1而不是预期的2 - 假设计数器最初为0,两次按下按钮,调用最终都是this.setState({counter: 0+1}) ,设置状态为1次。

An updater function fixes this, because the updates are applied in order: 更新程序功能修复此问题,因为更新按顺序应用:

  • your component begins with state = {counter: 0} 你的组件以state = {counter: 0}开头
  • you have a button that updates the counter when clicked in the following way: this.setState((currentState, props) => ({counter: currentState.counter + 1})) 你有一个按钮,当按照以下方式点击时更新计数器: this.setState((currentState, props) => ({counter: currentState.counter + 1}))
  • the user clicks the button really fast, so that the state does not have time to be updated between clicks. 用户非常快速地单击按钮,因此状态没有时间在点击之间进行更新。
  • unlike the other way, currentState.counter + 1 does not get evaluated immediately 与其他方式不同, currentState.counter + 1不会立即得到评估
  • the first updater function is called with the initial state {counter: 0} , and sets the state to {counter: 0+1} 使用初始状态{counter: 0}调用第一个updater函数,并将状态设置为{counter: 0+1}
  • the second updater function is called with the state {counter: 1} , and sets the state to {counter: 1+1} 使用状态{counter: 1}调用第二个updater函数,并将状态设置为{counter: 1+1}

Generally speaking, the updater function is the less error-prone way to change the state, and there is rarely a reason to not use it (although if you are setting a static value, you don't strictly need it). 一般来说,updater函数是更不容易出错的改变状态的方法,很少有理由不使用它(尽管如果你设置的是静态值,你并不严格需要它)。

What you care about, however, is that updates to the state don't cause improper data (duplicates and the like). 但是,您关心的是对状态的更新不会导致不正确的数据(重复等)。 In that case, I would take care that the updates are designed so that they are idempotent and work no matter the current state of the data. 在这种情况下,我会注意更新的设计使它们无论数据的当前状态如何都是幂等的并且可以工作。 For instance, instead of using an array to keep the collection of messages, use a map instead, and store each message by key or hash that is unique to that message, no matter where it came from (a millisecond timestamp may be unique enough). 例如,不是使用数组来保留消息集合,而是使用映射,并按照该消息所特有的键或散列存储每条消息,无论它来自何处(毫秒时间戳可能足够独特) 。 Then, when you get the same data from two locations, it will not cause duplicates. 然后,当您从两个位置获得相同的数据时,它不会导致重复。

I'm not an expert in React by any means and have only been doing it for two months only, but here's what I learned from my very first project in React, which was as simple as showing a random quote. 我无论如何都不是React的专家而且只做了两个月,但这是我从React的第一个项目中学到的东西,就像显示随机引用一样简单。

If you need to use the updated state right after you use setState, always use the updater function. 如果在使用setState后需要立即使用更新状态,请始终使用updater函数。 Let me give you an example. 让我给你举个例子。

// 
 handleClick = () => {
   //get a random color
   const newColor = this.selectRandomColor();
   //Set the state to this new color
   this.setState({color:newColor});
   //Change the background or some elements color to this new Color
   this.changeBackgroundColor();
}

I did this and what happened was that the color that was set to the body was always the previous color and not the current color in the state, because as you know, the setState is batched. 我做了这个,发生的事情是,设置到正文的颜色始终是以前的颜色而不是状态中的当前颜色,因为如您所知, setState是批处理的。 It happens when React thinks it's best to execute it. 当React认为最好执行它时会发生这种情况。 It's not executed immediately. 它没有立即执行。 So to solve this problem, all I have to do was pass this.changeColor as a second argument to setState. 所以要解决这个问题,我所要做的就是将this.changeColor作为setState的第二个参数传递。 Because that ensured that the color I applied was kept up to date with the current state. 因为这确保我应用的颜色与当前状态保持同步。

So to answer your question, in your case, since you're job is to display the message to the user as soon as a new message arrives, ie use the UPDATED STATE, always use the updater function and not the object. 所以要回答你的问题,在你的情况下,因为你的工作是在新消息到达时立即向用户显示消息,即使用UPDATED STATE,总是使用updater函数而不是对象。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM