react在16.6推出了<Suspense>组件,看了官方文档和一些分析文章,发现以后react的开发方式将会发生重大改变
Suspense功能
声明方式等待任何内容(组件),包括数据,替代react-loadable做code spliting
Suspense 常见API
1.fallback:Suspense里面还有"任务"执行时展示的UI
2.children: 一般是异步组件(配合lazy包裹的异步组件),为什么说一般是,因为官方没提供稳定的api来将取数逻辑跟Suspense配合出延迟效果,但我们可以hack
异步加载组件使用lazy(<MyComponent />)就一定要Suspense包裹,看了一些分析Suspense源码的文章,了解到Suspense内部是利用componentDidCatch来捕获到lazy主动throw出来的Promise来做条件渲染,渲染fallback内容,待lazy内的Promise状态resolve,完整内容就渲染出来
// lazy内部也是返回了Promise对象
const AsyncCompnentA = lazy(() => import('../componentA'))
export default () => {
return (
<Suspense fallback={<Loading />}>
<AsyncCompnentA />
</Suspense>
)
}
简易理解中的Suspense源码
class Suspense extends React.Component {
state = {
promise: null
}
componentDidCatch(e) {
if(e instanceof Promise) {
this.setState({
promise: e,
}, () => {
e.then(() => {
promise: null
})
})
}
}
render() {
return this.state.promise ? this.props.fallback : this.props.children
}
}
不难理解,通过componentDidCatch捕获的异常改变自身state,然后凭借promise值做条件渲染
而官方没提供取数延迟,貌似暂时只开放给请求库(swr)而非大众调用者,但分析了一波源码,利用throw promise也是可以模拟的
const PromiseThrower = () => {
const getData = () => {
const getData = fetch()
getData.then(data => {
returnData = data
})
if (returnData === cache) {
throw getData
}
return returnData
}
return <>{getData()}</>
}
// useage
export default () => {
return (
<Suspense fallback={<Loading />}>
<PromiseThrower />
</Suspense>
)
}
以往取数组件逻辑
以往loading跟取数(网络请求)完全耦合,三目渲染、样板代码写到吐
const AsyncComponentC = () => {
const [loading, setLoading] = useState(false)
useEffect(() => {
setLoading(true)
fetch('xxx').then(() => {
setLoading(false)
})
}, [])
return loading ? <Loading /> : <RealComponent />
}
未来使用猜想
对于一些短命组件,实时性强且跟应用其它组件少联系的组件,我们大可将数据流范围从应用级(redux之类)收窄至组件本身state,这样让组件更加single,而我们自行封装的取数逻辑(hook)可以在axios,fetch甚至原生xhr上二次封装,在里面loading状态时throw个promise就可以在<Supense>里有延迟效果了
// usage
const AsyncComponentA = () => {
const { data } = useFetch('api1') // 自己封装取数逻辑
return (
<div>{data.whatthe}</div>
)
}
const AsyncComponentB = () => {
const { data } = useFetch('api2') // 自己封装取数逻辑
return (
<div>{data.whatthe}</div>
)
}
<Suspense fallback={<Loading />}>
<AsyncComponentA />
<AsyncComponentB />
</Suspense>
Suspense和改造过的取数hook能带来什么效果
1.loading逻辑和 UI 解耦
2.Suspense可以嵌套使用,声明方式时加载状态顺序可控
3.竞态更加可控
参考文献
1.https://zh-hans.reactjs.org/docs/concurrent-mode-suspense.html#what-if-i-dont-use-relay
2.https://juejin.im/post/5c7d4a785188251b921f4e26#heading-5