簡體   English   中英

React.js,如何立即呈現用戶動作(在等待服務器傳播之前)

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

我正在構建一個購物清單 web 應用程序。 列表中的項目可以切換為“選中”或“未選中”。
我的數據流是這樣的:單擊項目復選框 --> 發送數據庫更新請求 --> 使用新的復選框狀態重新呈現列表數據。

如果我完全在反應本地組件 state 中處理復選框 state,則用戶操作更新非常快。

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

但是,如果我等待服務器傳播,更新會顯得很慢。

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

我的問題是:如果另一個客戶端更改數據庫數據,我如何使復選框動作顯示為瞬時(使用本地組件狀態),同時更新復選框 state。

這是我的嘗試(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;

我對如何在這里正確使用反應生命周期方法感到困惑。 我正在使用本地 state 來鏡像組件道具。 我嘗試使用 getDerivedStateFromProps() 來同步 state 和道具,但這不會使渲染更快。

您嘗試實現的效果稱為Optimistic UI ,這意味着當某些異步操作發生時,您正在偽造該操作成功的效果。

例如。 Facebook Like functionality 1. When you click Like on Facebook post the count is automatically increased by one (you) and then in the background ajax request is sent to API. 2. 當您收到 ajax 請求的響應時,您將知道它是成功還是失敗。 如果成功,那么您可以選擇什么都不做,但如果失敗,您可能希望減少之前添加的點贊數,並向用戶顯示一些錯誤(或不顯示)。

在你的情況下,你可以做同樣的事情,但如果你想要實時更新,你需要創建一些解決方案,如長輪詢web sockets .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;
    }

我相信問題在於,每次我嘗試 setState() 並重新渲染組件時,getDerivedStateFromProps 都會覆蓋本地state更改。 nextProps.item.checked.= prevState.itemChecked始終評估為 true,因為nextProps.item.checked綁定引用了以前的道具(道具沒有改變),但prevState.itemChecked綁定的值已經翻轉。

因此,這個 function 總是用之前的 prop state 覆蓋 state。

所以我需要添加prevState.prevPropCheck.= nextProps.item.checked來檢查道具確實發生了變化。

我不確定這是否是getDerivedStateFromProps()的預期用途,但 prevProps 參數似乎是我需要的!

如果您看到更優雅的解決方案,請告訴我

感謝@vedran 的幫助!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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