前言
在react中组件只有挂载和卸载两种状态,而在vue中还存在第三种状态失活。这种状态在日常业务开发中是很有用的,比如tabs切换时,我们想保持住切换至后台的tab的组件状态,做法一般来说有两种,第一种即保持组件挂载,而后通过css的方式控制DOM的隐藏,这种方式的缺点是当react应用更新时,隐藏组件仍会参与IDFF;第二种是获取当前tab的状态,然后控制组件jsx的返回,这种方式会将tab的显隐状态耦合进组件。下面我将介绍我是如何利用offscreen component解决该问题的。
前置知识
提起Suspense想必大家都不陌生,它是在react16.6中引入的新组件。它可以配合着lazy来做组件的懒加载,在RSC中也可以用于数据加载。当组件或者数据没有准备好的时候,它将展示fallback,下面是一个例子。
import { lazy, Suspense } from 'react';
const LazyComponent = lazy(() => import('./lazy-compoent'));
function App() {
return (
<Suspense fallback={<span>loading</span>}>
<LazyComponent />
</Suspense>
)
}
在LazyComponent加载完毕之前,它将展示loading fallback。 我们思考几个问题
- react是如何切换fallback和component的呢
- 我们能否控制Suspense的状态切换呢
- 切换至后台后组件的状态会保持吗
带着以上三个问题,我们请求我们本期内容的主角@ivliu/react-offscreen
安装
以pnpm为例
pnpm add @ivliu/offscreen
基本使用
codesandbox.io/s/lucid-mar… 代码如下
import { useState } from 'react';
import { Offscreen } from '@ivliu/react-offscreen';
function Child1() {
const [count, setCount] = useState(0);
return (
<button type="button" onClick={() => setCount(count + 1)}>
{count}
</button>
)
}
function Child2() {
const [count, setCount] = useState(0);
return (
<button type="button" onClick={() => setCount(count + 1)}>
{count}
</button>
)
}
function App() {
const [bool, setBool] = useState(false);
return (
<>
<Offscreen mode={bool === true ? 'visible' : 'hidden'}>
<Child1 />
</Offscreen>
<Offscreen mode={bool === false ? 'visible' : 'hidden'}>
<Child2 />
</Offscreen>
</>
)
}
例子很简单,当bool为true时展示Child1,为false时展示Child2。 它使用很简单,我们可以把它看做content-visibility的react版本,mode值为visible时即组件激活,为hidden时即组件失活。
实现原理
回到第一节我们思考的三个问题,我来回答一下
- react是通过子组件是否throw promise来控制fallback和component的切换
- 我们可以控制Suspense的状态切换
- 切换至后台的组件仍然可以保持其状态
我们看一下它的简单实现
import { useRef, Suspense } from 'react';
function Repeater(props) => {
// props
const { mode, children } = props;
// refs
const resolveRef = useRef();
// destroy promise
if (resolveRef.current) {
resolveRef.current();
resolveRef.current = void 0;
}
if (mode === 'hidden') {
throw new Promise((resolve) => (resolveRef.current = resolve));
}
return <>{children}</>;
};
function Offscreen(props) {
// props
const { mode, children } = props;
return (
<Suspense>
<Repeater mode={mode}>{children}</Repeater>
</Suspense>
)
}
这其实就是它的完整实现了,之所以称之简单实现是因为它的实现太简单了。 我简单解释下,Offscreen组件接受mode和children属性,它返回了Suspense,fallback不做设置,将mode和children属性传递给Repeater组件;Repeater组件接受到mode和children属性后,判断mode是否为hidden,如果为hidden则抛出一个promise,否则正常返回jsx。当Repeater组件throw promise的时候,Suspense会捕捉到该promise,然后展示fallback,但是因为我们没有设置,因此展示为null,当正常返回jsx时则正常渲染,从而实现对Suspense状态的控制。
总结
以上就是对@ivliu/react-offscreen的全部介绍了,它的使用和实现都很简单,大家如果遇到需要的场景可以拿来使用。 如果大家感兴趣的话我可以再写一篇与市面上其他类似组件的对比。
github + npm
希望大家可以不吝小星星,更希望可以踊跃pr