高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。
高阶组件就是一个没有副作用的纯函数。
不要改变原始组件,使用组合
例如:Redux的connect
方法
// This function takes a component... function withSubscription(WrappedComponent, selectData) { // ...and returns another component... return class extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = { data: selectData(DataSource, props) }; } componentDidMount() { // ... that takes care of the subscription... DataSource.addChangeListener(this.handleChange); } componentWillUnmount() { DataSource.removeChangeListener(this.handleChange); } handleChange() { this.setState({ data: selectData(DataSource, this.props) }); } render() { // ... and renders the wrapped component with the fresh data! // Notice that we pass through any additional props return <WrappedComponent data={this.state.data} {...this.props} />; } }; }
约定:贯穿传递不相关props属性给被包裹的组件
render() { // 过滤掉专用于这个阶组件的props属性, // 不应该被贯穿传递 const { extraProp, ...passThroughProps } = this.props; // 向被包裹的组件注入props属性,这些一般都是状态值或 // 实例方法 const injectedProp = someStateOrInstanceMethod; // 向被包裹的组件传递props属性 return ( <WrappedComponent injectedProp={injectedProp} {...passThroughProps} /> ); }
约定:最大化的组合性
// 不要这样做…… const EnhancedComponent = withRouter(connect(commentSelector)(WrappedComponent)) // ……你可以使用一个函数组合工具 // compose(f, g, h) 和 (...args) => f(g(h(...args)))是一样的 const enhance = compose( // 这些都是单独一个参数的高阶组件 withRouter, connect(commentSelector) ) const EnhancedComponent = enhance(WrappedComponent)
单独一个参数的高阶组件,类似 connect
函数返回的,签名是Component => Component
。输入和输出类型相同的函数确实是很容易组合在一起。
约定:包装显示名字以便于调试
function withSubscription(WrappedComponent) { class WithSubscription extends React.Component {/* ... */} WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`; return WithSubscription; } function getDisplayName(WrappedComponent) { return WrappedComponent.displayName || WrappedComponent.name || 'Component'; }
注意:
不要在render方法内使用高阶组件
原因: (差分算法,渲染性能问题)
render() { // 每一次渲染,都会创建一个新的EnhancedComponent版本 // EnhancedComponent1 !== EnhancedComponent2 const EnhancedComponent = enhance(MyComponent); // 那引起每一次都会使子对象树完全被卸载/重新加载 return <EnhancedComponent />; }
必须将静态方法做拷贝
function enhance(WrappedComponent) { class Enhance extends React.Component {/*...*/} // 必须得知道要拷贝的方法 :( Enhance.staticMethod = WrappedComponent.staticMethod; return Enhance; }
2)分别导出组件自身的静态方法。
// Instead of... MyComponent.someFunction = someFunction; export default MyComponent; // ...export the method separately... export { someFunction }; // ...and in the consuming module, import both import MyComponent, { someFunction } from './MyComponent.js';
Refs属性不能贯穿传递
如果你向一个由高阶组件创建的组件的元素添加ref应用,那么ref指向的是最外层容器组件实例的,而不是被包裹的组件。
现在我们提供一个名为 React.forwardRef
的 API 来解决这一问题(在 React 16.3 版本中)