React高阶组件应用

15. 三月 2017 React 0

higher-order 这个词业界老司机基本上都知道,higher-order-function(高阶函数)在函数式编程中是一种概念。描述的是一种函数:这种函数接受函数作为参数,或者输出一个参数。 常见的有mapsortreduce

higher-order-component (高阶组件HOC)类似于高阶函数,它接受一个React组件作为参数,返回一个新的React组件。

通俗点讲就是:当React组件呗包裹时(wrapped),高阶组件会返回一个加强的React组件。  高阶组件让我们的代码更具有复用性、逻辑性和抽象特征。它可以对render方法作劫持,也可以控制props和state

实现高阶组件有两种方式:

  • 属性代理(props proxy)。高阶组件通过被包裹的React组件来操作props
  • 反向继承(inheritance inversion)。 高阶组件继承于被包裹的React组件。

1、属性代理

属性代理是最常见的高阶组件的实现方式:

最重要的部分是render中返回来传入的WrappedComponent 的React组件。这样我们可以通过高阶组件来传递、修改props,这种方法即为属性代理。

当我们使用MyContainer这个高阶组件就会变得很容易:

这样组件就可以一层层的作为参数被调用,原始组价就具备了高阶组件对它的修饰。保持了单个组件封装性的同时还保留了易用性。当然我们也可以通过decorator 来转换:

decorator是ES7的新特性,这里不多说,有兴趣可以看一下这里

简单的替换为作用在类上的decorator,既接受需要装饰的类为参数,返回一个新的内部类。这与高阶组件的定义完全一致。因此,可以认为作用在类上的decorator语法糖简化了高阶组件的调用。

从功能上来讲,高阶组件能做到控制props通过refs使用引用抽象state使用其他元素包裹wrappedComponent

  • 控制props
    你可以读取、添加、编辑、删除传给 WrappedComponent 的 props。但是要小心操作重要的props,尽可能对高阶组件的props作新的命名以防止混淆
  • 通过refs使用引用
    在高阶组件中,我们可以接受refs使用WrappedComponent的组件。
    你可以通过引用ref)访问到 thisWrappedComponent 的实例),但为了得到引用,WrappedComponent 还需要一个初始渲染,意味着你需要在 HOC 的 render 方法中返回 WrappedComponent 元素,让 React 开始它的一致化处理,你就可以得到 WrappedComponent 的实例的引用。

 

Ref 的回调函数会在 WrappedComponent 渲染时执行,你就可以得到 WrappedComponent 的引用。这可以用来读取/添加实例的 props ,调用实例的方法。

  • 抽象state
    我们可以通过传入 props 和回调函数把 state 提取出来
    抽象一个input组件来说明

    可以这样使用

    这样我们就得到一个受控input组件。
  • 使用其他元素包裹wrappedComponent这么做是为了布局或者样式处理等目的,我们可以在wrappedComponent 外包裹其他元素或者组件

2、反向继承

另一种实现高阶组件的方式成为反向继承。先看一个简单的实现:

高阶组件返回的组件继承于wrappedComponent,因为被动的继承了WrappedComponent,所以所有的调用都会反向。这也是这种方法的由来。

在反向继承中高阶组价可以通过this来访问到wrappedComponent,意味着它可以访问到 state、props、组件生命周期方法和 render 方法。但是它不能保证完整的子组件树被解析。

反向继承可以做什么?

  • 渲染劫持(Render Highjacking)
    之所以被称为渲染劫持是因为 HOC 控制着 WrappedComponent 的渲染输出,可以用它做各种各样的事。
    通过渲染劫持你可以:

    • 在由 render输出的任何 React 元素中读取、添加、编辑、删除 props
    • 读取和修改由 render 输出的 React 元素树
    • 有条件地渲染元素树
    • 把样式包裹进元素树(就像在 Props Proxy 中的那样)

就像刚才说的,反向继承不能保证完整的子组件呗解析,这以为这将限制渲染劫持的功能。使用渲染劫持你可以完全操作 WrappedComponent 的 render 方法返回的元素树。但是如果元素树包括一个函数类型的 React 组件,你就不能操作它的子组件了。(被 React 的一致化处理推迟到了真正渲染到屏幕时)

例1:条件渲染。当 this.props.loggedIn 为 true 时,这个 HOC 会完全渲染 WrappedComponent 的渲染结果。(假设 HOC 接收到了 loggedIn 这个 prop)

例2:修改由 render 方法输出的 React 组件树。

在这个例子中,如果 WrappedComponent 的输出在最顶层有一个 input,那么就把它的 value 设为 “may the force be with you”

你可以在这里做各种各样的事,你可以遍历整个元素树,然后修改元素树中任何元素的 props。这也正是样式处理库 Radium 所用的方法

  • 操作 state

HOC 可以读取、编辑和删除 WrappedComponent 实例的 state,如果你需要,你也可以给它添加更多的 state。记住,这会搞乱 WrappedComponent 的 state,导致你可能会破坏某些东西。要限制 HOC 读取或添加 state,添加 state 时应该放在单独的命名空间里,而不是和 WrappedComponent 的 state 混在一起。

这里 HOC 用其他元素包裹着 WrappedComponent,还输出了 WrappedComponent 实例的 props 和 state

3、组件命名

当包裹了一个高阶组件时,我们失去了原始的wrappedComponent 的displayName,而组件名字方便我们开发和调试的重要属性。

通常会用 WrappedComponent 的名字加上一些 前缀作为 HOC 的名字。下面的代码来自 React-Redux:

getDisplayName 函数:

我们可以使用recompose库,它已经帮我们实现了相应的方法。

4、组件参数

有的时候我们调用高级组件的时候组要传入一些参数。我们可以这么做:

例子:Props Proxy 模式 的 HOC 最简参数使用方法。关键在于 HOCFactoryFactory 函数。

你可以这样用:

这是利用了函数式编程了特性。

 

参考阅读:https://zhuanlan.zhihu.com/p/24776678#!

书籍:《深入React技术栈》


发表评论

电子邮件地址不会被公开。