该系列文章由IMWeb团队成员howenhuo翻译并首发于 imweb.io。 点击阅读原文即可查看更多精彩文章。
原文链接:How To Master Advanced React Design Patterns: Render Props (https://itnext.io/using-advanced-design-patterns-to-create-flexible-and-reusable-react-components-part-3-render-d7517dfe72bc)
在本系列的第1部分中,我们探讨了如何使用复合组件和静态类属性来构建可读可重用的 Stepper
组件。我们看到这种设计模式有一些局限性,因为它不是很灵活; 组件需要是父组件的直接子组件,否则 props
传递会中断。
在第2部分中,我们使用新的 Context API
为第1部分的限制提供了优雅且可读的解决方案。可这种设计模式的问题在于它需要一些初始设置才能工作,并且我们的组件不能放在另一个应用程序中。
在本部分中,我们将探讨一种设计模式,该模式可以解决到目前为止我们已经确定的所有问题。 它被称为:render props
。
这种设计模式起初可能有点令人头疼(还记得我们在第2部分中使用的 context consumer
函数吗?)并且为了真正掌握它是如何工作的,我们需要深入了解顶级 React API
以及我们编写的 JSX 代码如何转换为 javascript。接下来,让我们使用一个非常简单的示例,并逐步了解幕后发生的事情。
JSX 是由 Facebook 工程师设计的 JavaScript语法扩展。我们使用它与 React 来描述 UI 应该是什么样子(有点像模板语言),同时它具有 JavaScript的全部功能。无论何时使用 JSX 编写任何组件,Babel
都会将其编译为 React.createElement()
调用。
我们来看一个非常简单的例子:
上面的两个例子产生相同的结果,父组件简单地转换为 React.createElement()
调用,类型是我们的 Parent
组件,没有属性,也没有子项。
当我们添加子组件时,请注意它本身如何转换为 React.createElement()
调用,上图这种格式创建了我们的 React 组件树。
这里要理解的关键是 Babel 将 Parent
的所有属性编译为一个 props 的 javascript对象; 因为它是纯粹的 javascript对象,所以我们可以传递任何我们想要的东西,例如函数。
在上面的例子中,我们不传递 'string',而是传递了一个返回 'string' 的函数 。当调用该函数时,我们会得到完全相同的结果。
那么上面的例子到底发生了什么呢? 在最初的例子中,我们只是向下传递 'string',将其放在 'div' 中并进行渲染。 然而,在下一个例子中,我们将它作为函数传递并将其放在 'div' 中,但这次是调用函数来实现完全相同的结果。
为什么这很重要? 传统上我们将放在父组件中的子组件通过 props.children
渲染出来。
这里要理解的关键是,我们除了设计组件去渲染一个子项,我们还能通过渲染 props中函数
来实现完全相同的结果:
所以,在这个设计模式中,我们渲染 props中函数
而不是子项。 更进一步的想象,我们还能用函数做些什么? 我们可以在调用它们时传递参数:
我们花点时间来消化刚刚发生的事情。 我们传递了一个像以前一样的函数,但不总是返回 'string',而是返回我们在调用它时传入的参数!
等一下,这不是我们在第1部分遇到的问题吗? 为了解决它,我们必须克隆并遍历每个元素,然后传递所需的 props。
现在使用 Render Props 设计模式,我们可以将 props 传递给子组件。
我们可以根据需要命名 props。 因此,不使用 'example',改用更合适的东西:
如果您已经使用过 react router
,这可能看起来非常熟悉。 当您需要将 props
传递给 route
时,您需要使用 render
方法。
这就是 render props
。 我们不直接渲染组件,而是调用 render
并传入我们想要的任何参数。
让我们回到 Stepper
组件,看看如何利用这种设计模式(我已经删除了所有 context 样板并将 state 添加回 stepper 组件)。
这次不是添加 this.props.children
而是添加 this.props.render(stage,HandleClick)
。 我们不再需要向 stepper 组件添加任何子项,我们需要做的就是在 render 中返回相同的标记。
这实现了什么?很棒,现在树中的每个组件都可以访问所有 props。 它本质上给了我们与 context API
相同的 props 曝露,我们不必手动将 props 传递给每个子项。 这种对组件设计的简单调整解决了我们之前提到的所有问题。
然而,使用这种设计模式时要权衡一点,那就是代码的可读性略低于之前。还记得我们在本系列前面看到的奇怪函数吗,那个要在 Context.consumer
组件中添加的函数。
这对我来说很可读; 让我们想想发生了什么。我们只是添加与子项相同效果的函数来代替添加 render 函数。
让我们尝试与之前使用的示例组件对比一下:
左侧,我们像以前一样将函数添加到 render prop。 当 Babel 编译时,该函数被添加到 React.createElement
第二个参数:props。
右侧,我们将函数添加为子项,当编译时被添加到 React.createElement
第三个参数:children。
如何在创建组件时访问该子项函数?
以类似于调用 render prop
的方式,我们可以调用 props.children
(子项是一个函数)并传入我们所需的参数,这不但得到与之前相同的结果,还提高了可读性。
就这样,我们设计出一个高度灵活和极易阅读的组件。用户拥有重新排列子组件的自主权,同时不用担心是否可以访问到它们需要的 props。 最终,它是可重用的,我们可以将它直接放在任何其他应用程序中,无需预先进行任何设置,它都完美地工作。
源码见:
https://codesandbox.io/embed/6xmrjo7xn?referrer=https://itnext.io/media/b1a39a8c067cf87b6a0f1bce3ae8545e?postId=d7517dfe72bc