React.js,如何即时呈现用户操作(在等待服务器传播之前)

React.js, how to render users action instantaneously (before waiting for server propagation)

我正在构建一个购物清单网络应用程序。列表中的项目可以切换 'checked' 或 'unchecked'.
我的数据流是这样的:单击项目复选框 --> 发送数据库更新请求 --> 使用新的复选框状态重新呈现列表数据。

如果我完全在 React 本地组件状态中处理复选框状态,则用户操作更新非常快。

快速演示:https://youtu.be/kPcTNCErPAo

但是,如果我等待服务器传播,更新速度会很慢。

慢速演示:https://youtu.be/Fy2XDUYuYKc

我的问题是:如何让复选框动作立即显示(使用本地组件状态),同时在另一个客户端更改数据库数据时更新复选框状态。

这是我的尝试(React 组件):

import React from 'react';
import ConfirmModal from '../ConfirmModal/ConfirmModal';
import {GrEdit} from 'react-icons/gr';
import {AiFillDelete, AiFillCheckCircle} from 'react-icons/ai';
import {MdRadioButtonUnchecked} from 'react-icons/md';
import './ListItem.scss';

class ListItem extends React.Component{
    constructor(props){
        super(props);

        this.state = {
            confirmOpen: false,
            checkPending: false,
            itemChecked: props.item.checked,
        };
    }

    static getDerivedStateFromProps(nextProps, prevState){
        if(nextProps.item.checked != prevState.itemChecked){
            return ({itemChecked: nextProps.item.checked})
        }
        return null;
    }

    render(){
        return (
            <div className={`listItemWrapper${this.state.itemChecked ? ' checked': ''} `}>
                {this.state.confirmOpen ? 
                    <ConfirmModal 
                        triggerClose={() => this.setState({confirmOpen: false})}
                        message={`Do you want to delete: ${this.props.item.content}?`}
                        confirm={() => {
                            this.clickDelete();
                            this.setState({confirmOpen: false});
                        }}
                    /> : null
                }

                <div className="listItem">
                    <div className='listContent'>
                        { this.state.itemChecked ?
                            <strike>{this.props.item.content}</strike>
                            : this.props.item.content
                        }
                        <div className={`editBtn`}>
                            <GrEdit onClick={() => {
                                let {content, category, _id} = this.props.item;
                                this.props.edit({content, category, _id});
                            }}
                            />
                        </div>
                    </div>
                </div>
                <div className={`listToolsWrapper`}>
                    <div className = "listTools">
                        <div onClick={() => this.setState({confirmOpen: true})} className={`tool`}><AiFillDelete className="listItemToolIcon deleteIcon"/></div>
                        <div onClick={() => !this.state.checkPending ? this.clickCheck() : null} className={`tool`}>
                            {this.state.itemChecked ? <AiFillCheckCircle className="listItemToolIcon checkIcon"/> : <MdRadioButtonUnchecked className="listItemToolIcon checkIcon"/>}
                        </div>
                    </div>
                    <div className = "listInfo">
                        <div className ="itemDate">
                            {this.props.item.date}
                            {this.props.item.edited ? <p className="edited">(edited)</p> : null}
                        </div>
                    </div>
                </div>
            </div>
        );
    }

    async clickCheck(){
        this.setState(prevState => ({checkPending: true, itemChecked: !prevState.itemChecked}));
        await fetch(`/api/list/check/${this.props.item._id}`,{method: 'POST'});
        this.setState({checkPending: false});
        //fetch updated list
        this.props.fetchNewList();
    }

    async clickDelete(){
        await fetch(`/api/list/${this.props.item._id}`,{method: 'DELETE'});
        //fetch updated list
        this.props.fetchNewList();
    }
}

export default ListItem;

我对如何在这里正确使用 React 生命周期方法感到困惑。我正在使用本地状态来镜像组件道具。我尝试使用 getDerivedStateFromProps() 来同步状态和道具,但这并不能使渲染速度更快。

您尝试实现的效果称为 Optimistic UI,这意味着当某些异步操作发生时,您正在伪造该操作成功的效果。

例如。 Facebook 点赞功能 1. 当您在 Facebook 上点击赞 post 时,计数会自动加一(您),然后在后台 ajax 请求发送到 API。 2. 当您收到 ajax 请求的响应时,您将知道它是成功还是失败。如果它成功了,那么你可以选择什么都不做,但如果失败了,你可能会想要减少你之前添加的点赞数,并向用户显示一些错误(或不显示)。

在你的情况下,你可以做同样的事情,但如果你想要实时更新,你将需要创建一些解决方案,如 long polling网络套接字.co

此外,getDerivedStateFromProps 你提供的看起来不错。

这就是最终对我有用的——尽管它不是最优雅的解决方案

我将 getDerivedStateFromProps 更改为:

static getDerivedStateFromProps(nextProps, prevState){
        // Ensure that prop has actually changed before overwriting local state
        if(nextProps.item.checked != prevState.itemChecked && 
            prevState.prevPropCheck != nextProps.item.checked){
            return {itemChecked: nextProps.item.checked}
        }
        return null;
    }

我认为问题是 getDerivedStateFromProps 每次我尝试 setState() 并重新渲染组件时都会覆盖本地状态更改。 nextProps.item.checked != prevState.itemChecked 总是评估为真,因为 nextProps.item.checked 绑定引用了以前的道具(道具没有改变),但 prevState.itemChecked 绑定的值已经翻转。

因此,此函数总是用先前的 prop 状态覆盖状态。

所以我需要添加 prevState.prevPropCheck != nextProps.item.checked 来检查道具是否确实发生了变化。

我不确定这是否是 getDerivedStateFromProps() 的预期用途,但 prevProps 参数似乎正是我所需要的!

如果你看到更优雅的解决方案请告诉我

感谢@vedran 的帮助!