Table of Contents

1、概述

我们可以通过 props 将函数传递给子组件。

如果这个函数中,返回的是 JSX,那么该函数就是一个正常的 React 组件定义。例如在一个列表组件中,我们可以通过 renderItem 属性来传递一个函数,该函数返回一个 JSX 元素,该元素就是列表中的一个子元素。

list.tsx
1
<List
2
renderItem={(item) => {
3
return <ListItem>{item.name}</ListItem>
4
}}
5
/>

此时,这种传递,我们就可用术语 render props 来描述。render props 是一种在 React 组件之间,使用值为函数的 prop 共享代码的简单技术。具有 render props 的组件接受一个返回 React 元素的函数,并在组件内部通过调用此函数来实现自己的渲染逻辑。

code.ts
1
<DataProvider render={data => (
2
<h1>Hello {data.target}</h1>
3
)}/>

对于新手朋友来说,这个概念理解起来有点困难,我们下面通过更多的例子来说明它的使用场景。

2、render props 使用时机

render props 的合理使用时机非常难把控。因此这篇文章的案例值得多看几遍。

我们需要特别注意的是,此时传入的是一个函数,该函数返回的是一个 JSX 元素,因此该函数就是一个 React 组件。

那么在组件内部,我们就可以通过 props.renderItem 来获取该函数,然后调用该函数,获取返回的 JSX 元素,并将其渲染出来。

list.tsx
props.renderItem({ name: 'Hello' })

由此我们需要考虑的时,什么情况下,使用 render props 是合适的。注意以下几个点

  1. 传入的函数组件,需要接收一个参数,
  2. 该参数为封装组件内部的状态,由内部的活动告知,在外部无法直接获得
  3. 通常当我们需要外部组件共享内部状态时,使用 render props 是合适的

3、Mouse 组件

我们通常使用 render props 来实现包含状态逻辑片段的复用。或者有的地方称为横切关注点(Cross-Cutting Concerns)

下面来一起实现一个简单的 Mouse 组件来说明这个问题。

我有一个需求,是可以随时获得到鼠标的位置信息。但是,我就试图通过一种方式,将获取鼠标位置信息的能力,通过 React 组件封装起来可以复用

预览

那么我们应该怎么做呢?

方法很简单

首先,我们定义一个 Mouse 组件,该组件会监听鼠标的位置,并在组件内部维护鼠标的位置信息。当外部有函数组件传入时,我们调用该函数,并将其传递给外部组件。

mouse.tsx
1
import { useState, useEffect } from 'react';
2
3
interface Position {
4
x: number,
5
y: number
6
}
7
8
interface MouseProps {
9
render: (position: Position) => React.ReactNode
10
}
11
12
export default function Mouse(props: MouseProps) {
13
const [position, setPosition] = useState({ x: 0, y: 0 })
14
15
useEffect(() => {
16
const handleMouseMove = (event: MouseEvent) => {
17
setPosition({ x: event.clientX, y: event.clientY })
18
}
19
20
window.addEventListener('mousemove', handleMouseMove)
21
22
return () => {
23
window.removeEventListener('mousemove', handleMouseMove)
24
}
25
}, [])
26
27
return props.render(position)
28
}

这样,我们就把获取鼠标位置信息的能力,提取到了一个 Mouse 组件中。当我们想要使用时,只需要将一个函数组件作为 prop 传递给 Mouse 组件即可。

完整代码如下

预览
index.tsx
mouse.tsx
1
import Mouse from './mouse'
2
3
export default function App() {
4
return (
5
<div className='flex items-center gap-4'>
6
<Mouse render={({ x, y }) => (
7
<div>
8
<h1>Mouse Position:</h1>
9
<p>X: {x}</p>
10
<p>Y: {y}</p>
11
</div>
12
)} />
13
</div>
14
)
15
}
16

4、总结

在过去 class 的组件开发年代,render props 是重要的状态封装手段。是必须掌握的一个高阶技能。随着 React Hooks 的兴起,render props 的应用场景逐渐被 Hooks 取代。

render props 被取代的主要原因,是因为我们想要封装的仅仅是包含状态的逻辑片段,但 render props 必须被处理成一个 React 组件。这会严重增加 Fiber Tree 结构的复杂性,从而让更新 diff 的工作量变得更大。

不过在许多项目的封装中,依然有一大批使用 render props 的代码存在。因此,他可能不再是我们实现新功能的首选,但是认识并理解它,依然是一件非常有必要的事情。在后续的学习中,我们会进一步学习 React Hooks 的知识以在代码封装中取代 render props 的地位。

专栏首页
到顶
专栏目录