高阶组件
高阶组件是一个概念上很简单,却十分实用的东西,被大量的 React 相关的第三方库频繁使用。在前端业务开发当中,灵活使用高阶组件可以让你的代码更为优雅,灵活性和复用性也会更强。同时,了解高阶组件对我们理解各种 React 第三方库也有很大的帮助,例如 Redux 的 connect
和 Relay 的 createContainer
。
什么是高阶组件
A higher-order component is a function that takes a component and returns a new component. 高阶组件是一个函数,接收一个组件并返回一个新的组件。
const EnhancedComponent = highOrderComponent(WrappedComponent)
在 React 中,高阶组件是一种复用组件逻辑的技术。它是一个函数而非组件,就像一个组件将 props
转换为 UI
,高阶组件接收一个组件作为参数,返回一个新的组件。
进一步学习之前,我们先回顾一下 new
和构造函数之间的关系。
var Person = function(name, age) {
this.name = name;
this.age = age;
this.getName = function(){
return this.name;
}
}
function New(fn) {
var res = {};
if (fn.prototype !== null) {
res.__proto__ = fn.prototype;
}
var ret = fn.apply(res, Array.prototype.slice.call(arguments, 1));
if ((typeof ret === 'object || typeof ret === 'function) && ret !== null) {
return ret;
}
return res;
}
var p1 = New(Person, 'tom', 20);
console.log(p1.getName());
console.log(p1 instanceof Person); // true
在以上的例子中,我定义了一个本质上与普通函数没区别的构造函数,然后将该构造函数以参数形式传入 New 函数中。我在 New 函数中进行了一些的逻辑处理,让 New 函数的返回值为一个实例,正因为 New 的内部逻辑,让构造函数中的 this
能够指向返回的实例。
高阶组件的实例
同理,我来写一个简单的高阶组件。
import React, { Component } from 'react'
export default (WrappedComponent) => {
class NewComponent extends Component {
// 可以做很多自定义逻辑
render () {
return <WrappedComponent />
}
}
return NewComponent
}
上例简单构建了一个新的组件类 NewComponent
,然后把传入的 WrappedComponent
组件渲染出来。接下来我给 NewComponent
添加一些功能。
import React, { Component } from 'react'
export default (WrappedComponent, name) => {
class NewComponent extends Component {
constructor () {
super()
this.state = { data: null }
}
componentWillMount () {
let data = localStorage.getItem(name)
this.setState({ data })
}
render () {
return <WrappedComponent data={this.state.data} />
}
}
return NewComponent
}
增加功能之后,NewComponent
会根据第二个参数 name
在挂载阶段从 LocaleStorage 加载数据,并且添加到自己的 state.data
中,而渲染的时候将数据通过 props
传送给 WrappedComponent
中。
我们来复用这段代码,假设上述代码在 src/wrapLoadData.js
文件中。
import React, {Component} from 'react'
import wrapLoadData from './wrapLoadData'
class InputWithUserName extends Component {
render() {
return <input value={this.props.data} />
}
}
InputWithUserName = wrapLoadData(InputWithUserName, 'username')
export default InputWithUserName
假如 InputWithUserName
的功能需求是挂载的时候从 LocalStorage 里面加载 username
字段作为 <input />
的 value
值,现在有了 wrapLoadData
,我们可以很容易地做到这件事情。
根据 wrapLoadData
的代码我们可以知道,这个新的组件挂载的时候会先去 LocalStorage 加载数据,渲染的时候再通过 props.data
传给真正的 InputWithUserName
。
如果现在我们需要另外一个文本输入框组件,它也需要 LocalStorage 加载 ‘content’ 字段的数据。我们只需要定义一个新的 TextareaWithContent
:
import wrapLoadData from './wrapLoadData'
class TextareaWithContent extends Component {
render () {
return <textarea value={this.props.data} />
}
}
TextareaWithContent = wrapWoadData(TextareaWithContent, 'content')
export default TextareaWithContent
这样做起来会非常轻松,我们根本不需要重复写从 LocalStorage 加载数据字段的逻辑,直接用 wrapWithLoadData
包装一下就可以了。
总结
上述两个组件 InputWithValue
和 TextareaWithContent
的需求都有一个相同的逻辑,即挂载阶段从 LocalStorage 中加载特定的字段数据。
高阶组件的作用,其实就是为了组件之间的代码复用。组件可能有着某些相同的逻辑,把这些逻辑抽离出来,放到高阶组件中进行复用。高阶组件内部的包装组件和被包装组件之间通过 props
传递数据。