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 的帮助!
我正在构建一个购物清单网络应用程序。列表中的项目可以切换 '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 的帮助!