Open minhuaF opened 3 years ago
“Suspense就是让组件‘等待’某个异步操作,直到该异步操作结束即可渲染。”
在代码的实际应用有两种情况:
Suspense正式发布是于版本v16.6.0
到目前v17.0.2,Suspense的异步请求数据也还没有正式发布,还在试验阶段。
所以,目前只能用Suspense来进行资源异步加载;(没有Suspense之前都是怎么做的组件异步记载呢?)
hooks
Suspense
直接上代码,会比较清晰;
const sleep = (time) => { return new Promise((resolve) => { setTimeout(() => { resolve({ message: 'success', time: time }) }, time) }) } const Father = () => { const [loading, setLoading] = useState(true); useEffect(() => { console.log('Father 开始请求...') sleep(5000).then(() => { console.log('Father 结束请求...') setLoading(false) }) }, []); if(loading) { return (<div>Father loading ...</div>) }; return ( <div> {console.log('Father 开始渲染...')} This is Father! <Child/> </div> ) } const Child = () => { const [loading, setLoading] = useState(true); useEffect(() => { console.log('Child 开始请求...') sleep(2000).then(() => { console.log('Child 结束请求...') setLoading(false) }) }, []); if(loading) { return (<div>Child loading ...</div>) }; return ( <div> {console.log('Child 开始渲染...')} This is Child </div> ) } ReactDOM.render( <Father />, document.getElementById('root'));
上面代码在控制台的输出如下:
从上面的代码和执行过程来看,Child组件的请求是在Father组件请求完成之后才发出,这并不是期望的效果,期待的效果是能同时发出请求,减少用户等待的时间。
参考官网的示例重写上面的实现
const warpPromise = (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 fetchProfileData = () => { let fatherPromse = fatherHander(); let childPromse = childHander(); return { father: warpPromise(fatherPromse), child: warpPromise(childPromse), } } const fatherHander = () => { console.log('Father 开始请求...') return new Promise(resolve => { setTimeout(() => { console.log('Father 结束请求...') resolve({ msg: 'This is Father' }) }, 5000) }) } const childHander = () => { console.log('Child 开始请求...') return new Promise(resolve => { setTimeout(() => { console.log('Child 结束请求...') resolve({ msg: 'This is Child' }) }, 2000) }) } const resource = fetchProfileData(); const FatherPage = () => { return ( <Suspense fallback={<div>Father loading ...</div>}> <Father /> </Suspense> ) } const Father = () => { const data = resource.father.read(); return ( <div> {console.log('Father 开始渲染...')} {data.msg} <Suspense fallback={<div>Child loading ...</div>}> <Child /> </Suspense> </div> ) } const Child = () => { const data = resource.child.read(); return ( <div> {console.log('Child 开始渲染...')} {data.msg} </div> ) } ReactDOM.createRoot(document.getElementById('root')).render(<Father />)
上面代码执行的结果(Father是5s,Child是2s)
如果改下Father和Child接口请求的延迟:Father 是1s,Child是2s,结果如下
细品下上面的区别(不会截gif图...)
结论就是:
在React v16.6.0 版本中,新增了React.lazy函数,能让你像渲染常规组件一样处理动态引用的组件,配置webpack的code Splitting,实现只有当组件被加载时,对应的资源才会被导入,从而达到懒加载的效果。
React.lazy
React.lazy 不能单独使用,需要配合Suspense组件一起使用,不然react会报错。
LazyLoadComponent.js
import React from "react"; const LazyLoadComponent = () => { return ( <div>This is the LazyLoadComponent</div> ) } export default LazyLoadComponent;
SuspenseLazyLoadDemo.js
import React, { Suspense, useState, lazy } from 'react'; const LazyLoadComponent = lazy(() => import(/* webpackChunkName: "LazyLoadComponent" */ './LazyLoadComponent')); /** * 点击按钮时改变状态,加载对应的组件 */ export default function SuspenseLazyLoadDemo() { const [showChildren, setshowChildren] = useState(false); const showChildrenHandler = () => { if (!showChildren) { setshowChildren(true) } } return ( <div> <div onClick={showChildrenHandler}>Click me can load child...</div> { showChildren && <Suspense fallback={<div>loading....</div>}> <LazyLoadComponent /> </Suspense> } </div> ) }
运行上方代码,从控制台中可以看到LazyLoadComponent组件被单独出来一个chunk。
LazyLoadComponent
达到了资源异步加载的效果
拆分的代码会异步加载,可以有效地减少首屏包的体积。在实际操作中,发现fallback中添加对应组件的背景或者骨架图或者布局占位,能达到较好的用户体验,而且状态也不需要开发手动去控制。 拆分的代码会异步加载,可以有效地减少首屏包的体积。在实际操作中,发现fallback中添加对应组件的背景或者骨架图或者布局占位,能达到较好的用户体验,而且状态也不需要开发手动去控制。
fallback
(TODO: 待补充......)
精读《Suspense 改变开发方式》 React Suspense for Data(分析数据“瀑布”问题) 用于数据获取的 Suspense
Suspense 是什么
“Suspense就是让组件‘等待’某个异步操作,直到该异步操作结束即可渲染。”
在代码的实际应用有两种情况:
Suspense正式发布是于版本v16.6.0
到目前v17.0.2,Suspense的异步请求数据也还没有正式发布,还在试验阶段。
所以,目前只能用Suspense来进行资源异步加载;(没有Suspense之前都是怎么做的组件异步记载呢?)
为什么要用Suspense?
hooks
开发时,如果嵌套的组件各自都有接口请求,有可能会造成"瀑布问题",用Suspense
能避免此问题发生;解释“瀑布问题”
直接上代码,会比较清晰;
上面代码在控制台的输出如下:
从上面的代码和执行过程来看,Child组件的请求是在Father组件请求完成之后才发出,这并不是期望的效果,期待的效果是能同时发出请求,减少用户等待的时间。
Suspense 用法示例
使用Suspense异步请求数据
参考官网的示例重写上面的实现
上面代码执行的结果(Father是5s,Child是2s)
如果改下Father和Child接口请求的延迟:Father 是1s,Child是2s,结果如下
细品下上面的区别(不会截gif图...)
结论就是:
使用Suspense进行代码拆分
使用Suspense进行代码拆分
在React v16.6.0 版本中,新增了
React.lazy
函数,能让你像渲染常规组件一样处理动态引用的组件,配置webpack的code Splitting,实现只有当组件被加载时,对应的资源才会被导入,从而达到懒加载的效果。React.lazy 不能单独使用,需要配合Suspense组件一起使用,不然react会报错。
LazyLoadComponent.js
SuspenseLazyLoadDemo.js
运行上方代码,从控制台中可以看到
LazyLoadComponent
组件被单独出来一个chunk。达到了资源异步加载的效果
拆分的代码会异步加载,可以有效地减少首屏包的体积。在实际操作中,发现
fallback
中添加对应组件的背景或者骨架图或者布局占位,能达到较好的用户体验,而且状态也不需要开发手动去控制。 拆分的代码会异步加载,可以有效地减少首屏包的体积。在实际操作中,发现fallback
中添加对应组件的背景或者骨架图或者布局占位,能达到较好的用户体验,而且状态也不需要开发手动去控制。Suspense核心源码分析
异步请求资源
(TODO: 待补充......)
异步请求数据
(TODO: 待补充......)
参考资料
精读《Suspense 改变开发方式》 React Suspense for Data(分析数据“瀑布”问题) 用于数据获取的 Suspense