React 中的高阶组件
React 中的高阶组件
React 中的高阶组件主要有两种形式:属性代理 和 反向继承。
属性代理(Props Proxy)
最简单的属性代理实现:
1 | // 无状态 |
可以发现,属性代理其实就是 一个函数接受一个 WrappedComponent 组件作为参数传入,并返回一个继承了 React.Component 组件的类,且在该类的 render() 方法中返回被传入的 WrappedComponent 组件。
那我们可以利用属性代理类型的高阶组件做一些什么呢?
因为属性代理类型的高阶组件返回的是一个标准的 React.Component 组件,所以在 React 标准组件中可以做什么,那在属性代理类型的高阶组件中就也可以做什么,比如:
- 操作 props
- 抽离 state
- 通过 ref 访问到组件实例
- 用其他元素包裹传入的组件 WrappedComponent
操作 props
为 WrappedComponent 添加新的属性:
1 | function HigherOrderComponent(WrappedComponent) { |
抽离 state
利用 props 和回调函数把 state 抽离出来:
1 | function withOnChange(WrappedComponent) { |
如何使用:
1 | const NameInput = props => (<input name="name" {...props.name} />); |
这样就将 input 转化成受控组件了。
通过 ref 访问到组件实例
有时会有需要访问 DOM element (使用第三方 DOM 操作库)的时候就会用到组件的 ref 属性。它只能声明在 Class 类型的组件上,而无法声明在函数(无状态)类型的组件上。
ref 的值可以是字符串(不推荐使用)也可以是一个回调函数,如果是回调函数的话,它的执行时机是:
- 组件被挂载后(componentDidMount),回调函数立即执行,回调函数的参数为该组件的实例。
- 组件被卸载(componentDidUnmount)或者原有的 ref 属性本身发生变化的时候,此时回调函数也会立即执行,且回调函数的参数为 null。
如何在 高阶组件 中获取到 WrappedComponent 组件的实例呢?答案就是可以通过 WrappedComponent 组件的 ref 属性,该属性会在组件 componentDidMount 的时候执行 ref 的回调函数并传入该组件的实例:
1 | function HigherOrderComponent(WrappedComponent) { |
注意:不能在无状态组件(函数类型组件)上使用 ref 属性,因为无状态组件没有实例。
用其他元素包裹传入的组件 WrappedComponent
给 WrappedComponent 组件包一层背景色为 #fafafa 的 div 元素:
1 | function withBackgroundColor(WrappedComponent) { |
反向继承(Inheritance Inversion)
最简单的反向继承实现:
1 | function HigherOrderComponent(WrappedComponent) { |
反向继承其实就是 一个函数接受一个 WrappedComponent 组件作为参数传入,并返回一个继承了该传入 WrappedComponent 组件的类,且在该类的 render() 方法中返回 super.render() 方法。
会发现其属性代理和反向继承的实现有些类似的地方,都是返回一个继承了某个父类的子类,只不过属性代理中继承的是 React.Component,反向继承中继承的是传入的组件 WrappedComponent。
反向继承可以用来做什么:
- 操作 state
- 渲染劫持(Render Highjacking)
操作 state
高阶组件中可以读取、编辑和删除 WrappedComponent 组件实例中的 state。甚至可以增加更多的 state 项,但是 非常不建议这么做 因为这可能会导致 state 难以维护及管理。
1 | function withLogging(WrappedComponent) { |
渲染劫持
之所以称之为 渲染劫持 是因为高阶组件控制着 WrappedComponent 组件的渲染输出,通过渲染劫持我们可以:
- 有条件地展示元素树(element tree)
- 操作由 render() 输出的 React 元素树
- 在任何由 render() 输出的 React 元素中操作 props
- 用其他元素包裹传入的组件 WrappedComponent (同 属性代理)
条件渲染
通过 props.isLoading 这个条件来判断渲染哪个组件。
1 | function withLoading(WrappedComponent) { |
修改由 render() 输出的 React 元素树
修改元素树:
1 | function HigherOrderComponent(WrappedComponent) { |