可能你已经听过 React 中的一些设计模式,比如 Hoc(高阶组件),Render Props Pattern(渲染道具模式),Compound Pattern(组合组件),那我为什么要写这篇文章来重新描述它们呢。

因为与 Custom Hooks(自定义钩子)不同的是,我认为这些模式的好处可能并不那么显而易见。有时候你可能会想,即使你不使用某个模式,也能做到相同的效果。所以我会以开源社区的代码为例,来理解这些模式的必要性。

渲染道具模式

我们来看一看一些常见的虚拟列表实现的用户侧 Case。

React Native中的 FlatList

<FlatList
  renderItem={({item}) => <Item data={item} />}
/>

react-virtualized

import {List} from 'react-virtualized';

const list = [
  'Brian Vaughn',
];

function rowRenderer({
  key,
  index, 
  isScrolling, 
  isVisible, 
  style, 
  return (
    <div key={key} style={style}>
      {list[index]}
    </div>
  );
}

<List
  width={300}
  height={300}
  rowCount={list.length}
  rowHeight={20}
  rowRenderer={rowRenderer}
/>

以及 react-window, 也是差不多的代码,这里就不列举了。

我们很“偶然”地发现,这些虚拟列表库都不约而同地使用了 Render Props Pattern ,为什么呢?

试想一下,我们不用这个模式来实现 FlatList 可能是怎么样的。

<FlatList>
  <FlatListHeader>...</FlatListHeader>
  <FlatListItem>...</FlatListItem>
  <FlatListFooter>...</FlatListFooter>
</FlatList>

一个非常”React”的方式是使用声明式的语法,直接传递 Children

很优雅,但是这种方式存在一个明显的性能问题,就是所有items都会被一次性创建,即使不在视图中。这违背了虚拟列表的初衷。

所以,当你需要懒加载的时候,直接传递 Children 是行不通的。因为这个时候列表渲染的控制权在”FlatList”这个容器,而不是使用者(我们)。

但其实也有一个折衷的方案,我们可以将 Children 作为函数来传入,这样也是可以实现懒加载的。

image.png

但这样做的缺点是,不如 Render Prop 来的灵活,比如 FlatList 很容易传递多个 Render Prop

<FlatList
  renderItem={({item}) => <Item data={item} />}
  renderScrollComponent={(props) => <View {...props} style={styles.list} />}
/>

总结: 当你需要懒加载的时候,想一想 Render Prop 模式。