React JS开发团队在几个月前宣布了一些激动人心的更改-React将获得“并发模式”。 本质上,这将允许React同时执行多个UI渲染。 当然,JavaScript是单线程的,真正的并发只是一种幻想,但是新功能将使Web应用程序(以及一旦响应React Native的本机应用程序成为本机应用程序)比现在需要的精力和自定义代码更加灵敏从开发人员那里来实现这一目标。
在React的实验版本中现在可以使用并发模式,因此让我们深入研究一下如何使用闪亮的新API。
在这篇文章中,我将向您展示如何通过useTransition挂钩使用Suspense API。 还有另一个钩子useDeferredValue ,它具有稍微不同但同样重要的目的,我将在后续文章中介绍。
但是现有的Suspense功能如何?
您会注意到,自v16.6起, Suspense API就已经存在于React中。 实际上,这是在React实验版本中扩展为执行更多操作的同一API。 在React 16.6中,Suspense仅可用于一种用途:使用React.lazy()代码拆分和延迟加载组件
新方法-获取时渲染
这已经通过演讲和博客进行了很多讨论,并且已经在官方文档中进行了讨论,因此我将在其简要说明中进行介绍-Concurrent React允许我们实现“获取时渲染”模式,该模式将组件渲染为填充它们所需的数据同时获取。 React会在没有可用数据的情况下尽可能多地渲染,并在数据可用时立即渲染需要获取的数据的组件。 在这段时间内,这些组件被称为“已暂停”。
普遍使用的现有方法是“先获取然后渲染”,即先渲染所有需要渲染的数据,然后再渲染组件;然后“先渲染然后获取”,渲染组件,然后组件本身获取填充其子元素所需的数据。 这两种方法都较慢,并且具有许多需要解决方法的缺点。
设置
为此,我使用了一个基本的React应用程序,该应用程序是我通过Webpack和Babel手动配置的( 单击此处可查看我编写的操作指南 ),唯一的区别是正在运行:
npm i react@experimental react-dom@experimental --save
要获得实验版本,而不是安装react和react-dom的发行版本。
这也应该与用create-react-app React应用程序一起工作,方法是用它们的实验版本替换react和react-dom 。
选择并发模式
由于并发模式改变了React从根本上处理组件的方式,因此您需要将index.js的ReactDOM.render()行更改为:
ReactDOM.createRoot( document .getElementById( 'root' )).render( < App /> );
这将在您的应用程序中启用并发模式。
我还设置了App.js来渲染一个名为Data的组件,并在其中进行演示。
import React from 'react' ;
import Data from './Data' ;
const App = () => {
return (
< div >
<p>React Concurrent Mode testing</p>
<Data />
</ div >
);
}
export default App;
演示
现在我们创建Data.js
import React, { useState, useTransition, Suspense } from 'react' ;
import DataDisplay from './DataDisplay' ;
import { dataFetcher } from './api' ;
const initialData = { read : () => { return { foo : "initial" } } };
const Data = () => {
const [data, setData] = useState(initialData);
const [count, setCount] = useState( 0 );
const [startDataTransition, isDataPending] = useTransition({ timeoutMs : 2000 });
const fetchNewData = () => {
startDataTransition( () => {
setData(dataFetcher())
})
}
return (
< div >
<Suspense fallback={<p>Loading...</p>}>
<DataDisplay data={data} />
<button disabled={isDataPending} onClick={() => fetchNewData()}>Click me to begin data fetch</button>
</Suspense>
<p>Counter: {count}</p>
<button onClick={() => { setCount(count + 1); }}> Click me to check if the app is still responsive</button>
</div>
)
}
export default Data;
分解如下:
dataFetcher是一个返回“特殊”对象的函数,该对象使React知道在呈现依赖于此状态的组件时可以获取设置为该对象的状态。 如果数据尚未完成提取,则将这些组件“挂起”。 我们将在最后探讨如何创建“特殊对象”。
initialData显示数据加载完成后dataFetcher返回的对象的格式。 它具有read功能,该功能可将所需的数据返回给对象。 理想情况下, initialData应该为最后加载的数据实现某种缓存功能,但是这里我们只使用{ foo : "initial" } 。
当状态被更新/获取导致组件挂起时,必须使用useTransition挂钩进行更新。 这个钩子返回一对值-一个函数,该函数采用一个在其中设置状态的回调函数,以及一个布尔值,该布尔值让我们知道过渡发生的时间。
传递给useTransition的参数是一个对象,该对象告诉React挂起组件之前要等待多长时间。 要理解它,请这样考虑:在屏幕上有一些数据,并且正在获取一些新数据来替换它。 我们希望在获取新数据时显示一个微调框,但是用户可以在显示该微调框之前一秒钟甚至半秒钟看到旧数据。 在此对象中提到了此延迟。
这在需要显示陈旧数据直到需要加载新数据之前非常有用,并且还可以防止微调器在快速数据获取操作中出现几分之一秒(导致抖动)。
让我们仔细看看Suspense本身:
<Suspense fallback={<p>Loading...< /p>}>
<DataDisplay data={data} / >
< button disabled = {isDataPending} onClick = {() => fetchNewData()}>Click me to begin data fetch </ button >
< /Suspense>
任何应该挂起的组件都包装在Suspense组件内。 在其fallback支持中,当其中的组件正在等待数据时,我们传递应该显示的组件。 通常,它是某种微调器或加载指示器,以可视方式向用户指示发生了什么事情,因此,它看起来好像页面没有响应单击一样。
在这里,我使用了isDataPending布尔值来在获取数据时禁用按钮,从而防止用户多次按下按钮并发送多个请求-我们从模式中获得了isDataPending 。
谈论页面保持响应-它确实如此。 页面上的所有JavaScript都将继续运行,而组件将被挂起并且正在获取数据。 计数器和增加它的按钮可以用来确认这一点。
DataDisplay是一个简单的组件,它接收数据并调用其读取函数并显示结果。
import React, { memo } from 'react' ;
const DataDisplay = ( { data } ) => {
return (
< h3 > {data.read().foo} </ h3 >
)
}
export default memo(DataDisplay);
memo在此处用于防止该组件在其父项重新渲染时重新渲染,并且对于并发模式正常工作至关重要。
最后,我们来看一下dataFetcher和api.js的其他内容
export const dataFetcher = ( params ) => {
return wrapPromise(fetchData(params))
}
const wrapPromise = ( promise ) => {
let status = "pending" ;
let result;
let suspender = promise.then(
r => {
status = "success" ;
result = r;
},
e => {
status = "error" ;
result = e;
}
);
return {
read() {
if (status === "pending" ) {
throw suspender;
} else if (status === "error" ) {
throw result;
} else if (status === "success" ) {
return result;
}
}
};
}
const fetchData = ( params ) => {
// In a real situation, use params to fetch the data required.
return new Promise ( resolve => {
setTimeout( () => {
resolve({
foo : 'bar'
})
}, 3000 );
});
}
如您所见, dataFetcher只是返回wrapPromise(fetchData()) ,而fetchData()是一个对数据进行实际请求的函数。 在实际情况下,您将在fetchData()使用fetch()并将params传递给它,或者从其他位置加载数据。 在这里,我使用setTimout返回一个Promise对象,该对象在返回{ foo : 'bar' }之前有意引入了3秒的延迟。
wrapPromise负责使事物与React集成,如果您以前使用过Promises,则应该简单明了。 如果获取成功,则返回结果;如果未获取成功,则返回错误;如果操作尚未完成,则返回“挂起”状态的Promise 。
所有这些加在一起导致:
最初显示的数据为“初始”。 然后,我单击按钮开始数据获取。 根据我们的配置,该按钮会立即被禁用,并且2秒钟内没有任何反应。 然后该组件挂起,显示后备“ Loading ...”。 然后,最终数据获取完成,并且组件更新为显示“ bar”。 在整个过程中,剩余的应用程序(在此处由计数器显示)保持活动状态。
最后, Suspense可以在没有useTransition钩子的情况下工作,但useTransition是所需数据不属于状态。
结论
这里值得一api.js是, api.js奇怪的特定功能并不是太重要,因为预计将来许多流行的数据提取库都将支持它们。 React也建议不要在生产中使用并发模式,因为它仍然是“实验性的”。
但是,预计很快利用并发模式将成为绕过UI中冗长操作的实际方法,从而允许创建用户体验,这些体验在处理能力和网络连接速度截然不同的许多设备上保持一致。
- 您可以在此处找到演示中使用的所有代码。
- 您可以在Twitter和LinkedIn上找到我。
From: https://hackernoon.com/concurrent-react-using-suspense-and-usetransition-to-build-better-ux-cman2cdd
1302

被折叠的 条评论
为什么被折叠?



