Vue 的 Keep-Alive 组件是用于缓存组件的高阶组件,可以有效地提高应用性能。它能够使组件在切换时仍能保留原有的状态信息,并且有专门的生命周期方便去做额外的处理。该组件在很多场景非常有用,比如:
· tabs 缓存页面
· 分步表单
· 路由缓存
在 Vue 中,通过 KeepAlive 包裹内的组件会自动缓存下来, 其中只能有一个直接子组件。
<KeepAlive>
// <component 语法相当于 React的{showA ? <A /> : <B />}
<component :is="showA ? 'A' : 'B'">
</KeepAlive>
可惜的是 React 官方目前并没有对外正式提供的 KeepAlive 组件,但是我们可以参考 Vue 的使用方式与 API 设计,实现一套 React 版本的 KeepAlive。
下文将为大家详细介绍三种不同的实现方式。
Style 隐藏法
Style 隐藏法是最简单方便的方式,直接使用 display: none 来代替组件的销毁。
封装一个 StyleKeepAlive 组件,传入的 showComponentName 属性表示当前要展示的组件名,同时 children 组件都需要定义下组件名 name。
const StyleKeepAlive: React.FC<any> = ({children, showComponentName}) => {
return (
<>
{React.Children.map(children, (child) => (
<div
style={
{
display: child.props.name === showComponentName ? "block" : "none",
}}
>
{child}
</div>
))}
</>
);
}
// 使用
<StyleKeepAlive showComponentName={counterName}>
<Counter name="A" />
<Counter name="B" />
</StyleKeepAlive>
假如就这样写,勉强能实现要求,但会带来以下问题:
· 第一次挂载时每个子组件都会渲染一遍
· 父组件 render ,会导致子组件 render ,即使该组件目前是隐藏状态
· 对实际 dom 结构具有侵入式,如会为每个子组件包一层 div 用来控制 display 样式
我们研究下antd的Tabs 组件,其 TabPane 也是通过 display 来控制显隐的, 动态设置.ant-tabs-tabpane-hidden 类来切换。
可是它并没有一次性就把所有 TabPane 渲染出来,active 过一次后再通过类名来做控制显隐,且切换 tab后,除了第一次挂载会 render ,后续切换 tab 都不会 rerender 。
为了实现与 Tabs 一样的效果,我们稍加改造 StyleKeepAlive 组件, 对传入的 children 包裹一层 ShouldRender 组件,该组件实现初次挂载时只渲染当前激活的子组件, 且只有在组件激活时才会进行 rerender 。
const ShouldRender = ({ children, visible }: any) => {
// 是否已经挂载
const renderedRef = useRef(false);
// 缓存子组件,避免不必要的渲染
const childRef = useRef();
if (visible) {
renderedRef.current = true;
childRef.current = children();
}
if (!renderedRef.current) return null;
return (
<div
style={
{
display: visible ? "block" : "none",
}}
>
{childRef.current}
</div>
);
};
const StyleKeepAlive: React.FC<any> = ({children, showComponentName}) => {
return (
<>
{React.Children.map(children, (child) => {
const visible = child.props.name === showComponentName;
return (
<ShouldRender visible={visible}>
{() => child}
</ShouldRender>
);
})}
</>