参数传递
高阶组件的参数并非只能是一个组件,它还可以接收其他参数。例如,6.1节的示例是从 LocalStorage 中获取 key 为 data 的数据,当需要获取的数据的 key 不确定时,withPersistentData 这个高阶组件就不满足需求了。我们可以让它接收一个额外的参数来决定从 LocalStorage 中获取哪个数据:
import React, { Component } from 'react'
function withPersistentData(WrappedComponent, key) {
return class extends Component {
componentWillMount() {
let data = localStorage.getItem(key);
this.setState({data});
}
render() {
// 通过 {...this.props} 把传递给当前组件的属性继续传递给被包装的组件
return <WrappedComponent data={this.state.data} {...this.props} />
}
}
}
class MyComponent extends Component {
render() {
return <div>{this.props.data}</div>
}
}
// 获取 key='data' 的数据
const MyComponent1WithPersistentData = withPersistentData(MyComponent, 'data');
// 获取 key='name' 的数据
const MyComponent2WithPersistentData = withPersistentData(MyComponent, 'name');
新版本的 withPersistentData 满足获取不同 key 值的需求。但实际情况中,我们很少使用这种方式传递参数,而是采用更加灵活、更具通用性的函数形式:
HOC(...params)(WrappedComponent)
HOC(…params) 的返回值是一个高阶组件,高阶组件需要的参数是先传递给 HOC 函数的。用这种形式改写 withPersistentData 如下(注意:这种形式的高阶组件使用箭头函数定义更为简洁):
import React, { Component } from 'react'
function withPersistentData = (key) => (WrappedComponent) => {
return class extends Component {
componentWillMount() {
let data = localStorage.getItem(key);
this.setState({data});
}
render() {
// 通过 {...this.props} 把传递给当前组件的属性继续传递给被包装的组件
return <WrappedComponent data={this.state.data} {...this.props} />
}
}
}
class MyComponent extends Component {
render() {
return <div>{this.props.data}</div>
}
}
// 获取 key='data' 的数据
const MyComponent1WithPersistentData = withPersistentData('data')(MyComponent);
// 获取 key='name' 的数据
const MyComponent2WithPersistentData = withPersistentData('name')(MyComponent);
实际上,这种形式的高阶组件大量出现在第三方库中,例如 react-redux 中的 connect 函数就是一个典型的例子。connect 的简化定义如下:
connect(mapStateToProps, mapDispatchToProps)(WrappedComponent)
这个函数会将一个 React 组件连接到 Redux 的 store 上,在连接的过程中,connect 通过函数参数 mapStateToProps 从全局 store 中取出当前组件需要的 state,并把 state 转化成当前组件的 props;同时通过函数参数 mapDispatchToProps 把当前组件用到的 Redux 的 action creators 以 props 的方式传递给当前组件。connect 并不会修改传递进去的组件的定义,而是会返回一个新的组件。
connect 的参数 mapStateToProps、mapDispatchToProps 是函数类型,说明高阶组件的参数也可以是函数类型。 |
例如,把组件 ComponentA 连接到 Redux 上的写法类似于:
const ConnectedComponentA = connect(mapStateToProps, mapDispatchToProps)(ComponentA);
我们可以把它拆分来看:
// connect 是一个函数,返回值 enhance 也是一个函数
const enhance = connect(mapStateToProps, mapDispatchToProps);
// enhance 是一个高阶组件
const ConnectedComponentA = enhance(ComponentA);
这种形式的高阶组件非常容易组合起来使用,因为当多个函数的输出和它的输入类型相同时,这些函数很容易组合到一起使用。例如,有 f、g、h 三个高阶组件,都只接收一个组件作为参数,于是我们可以很方便地嵌套使用它们:f( g(h(WrappedComponent) ) )。这里有一个例外,即最内层的高阶组件h可以有多个参数,但其他高阶组件必须只能接收一个参数,只有这样才能保证内层的函数返回值和外层的函数参数数量一致(都只有 1 个)。
例如,将 connect 和另一个打印日志的高阶组件 withLog()(注意,withLog() 的执行结果才是真正的高阶组件)联合使用:
// connect的参数是可选参数,这里省略了 mapDispatchToProps 参数
const ConnectedComponentA = connect(mapStateToProps)(withLog()(ComponentA));
我们还可以定义一个工具函数 compose(…funcs):
function compose(...funcs) {
if(funcs.length === 0) {
return arg => arg
}
if(funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a,b) => (...args) => a(b(args)));
}
调用 compose(f, g, h) 等价于 (…args) ⇒ f(g(h(…args)))。用 compose 函数可以把高阶组件嵌套的写法打平:
const enhance = compose(
connect(mapStateToProps),
withLog()
);
const ConnectedComponentA = enhance(ComponentA);
像 Redux 等很多第三方库都提供了 compose 的实现,compose 结合高阶组件使用可以显著提高代码的可读性和逻辑的清晰度。