深入理解React 高阶组件
1. 基本概念高阶组件是React 中一个很重要且较复杂的概念,高阶组件在很多第三方库(如Redux)中都被经常使用,即使你开发的是普通的业务项目,用好高阶组件也能显著提高你的代码质量。 高阶组件的定义是类比于高阶函数的定义。高阶函数接收函数作为参数,并且返回值也是一个函数。类似的,高阶组件接收React组件作为参数,并且返回一个新的React组件。高阶组件本质上也是一个函数,并不是一个组件,这一点一定要注意。 2. 应用场景为什么React引入高阶组件的概念?它到底有何威力?让我们先通过一个简单的例子说明一下。 假设我有一个组件,需要从LocalStorage中获取数据,然后渲染出来。于是我们可以这样写组件代码: import React,{ Component } from 'react' class MyComponent extends Component { componentWillMount() { let data = localStorage.getItem('data'); this.setState({data}); } render() { return <div>{this.state.data}</div> } } 代码很简单,但当我有其他组件也需要从LocalStorage中获取同样的数据展示出来时,我需要在每个组件都重复 import React,{ Component } from 'react' function withPersistentData(WrappedComponent) { return class extends Component { componentWillMount() { let data = localStorage.getItem('data'); this.setState({data}); } render() { // 通过{...this.props} 把传递给当前组件的属性继续传递给被包装的组件WrappedComponent return <WrappedComponent data={this.state.data} {...this.props} /> } } } class MyComponent2 extends Component { render() { return <div>{this.props.data}</div> } } const MyComponentWithPersistentData = withPersistentData(MyComponent2)
通过这个例子,可以看出高阶组件的主要功能是封装并分离组件的通用逻辑,让通用逻辑在组件间更好地被复用。高阶组件的这种实现方式,本质上是一个装饰者设计模式。 高阶组件的参数并非只能是一个组件,它还可以接收其他参数。例如,组件MyComponent3需要从LocalStorage中获取key为name的数据,而不是上面例子中写死的key为data的数据, 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} 把传递给当前组件的属性继续传递给被包装的组件WrappedComponent return <WrappedComponent data={this.state.data} {...this.props} /> } } } class MyComponent2 extends Component { render() { return <div>{this.props.data}</div> } //省略其他逻辑... } class MyComponent3 extends Component { render() { return <div>{this.props.data}</div> } //省略其他逻辑... } const MyComponent2WithPersistentData = withPersistentData(MyComponent2,'data'); const MyComponent3WithPersistentData = withPersistentData(MyComponent3,'name'); 新版本的 3. 进阶用法高阶组件最常见的函数签名形式是这样的:
用这种形式改写 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} 把传递给当前组件的属性继续传递给被包装的组件WrappedComponent return <WrappedComponent data={this.state.data} {...this.props} /> } } } class MyComponent2 extends Component { render() { return <div>{this.props.data}</div> } //省略其他逻辑... } class MyComponent3 extends Component { render() { return <div>{this.props.data}</div> } //省略其他逻辑... } const MyComponent2WithPersistentData = withPersistentData('data')(MyComponent2); const MyComponent3WithPersistentData = withPersistentData('name')(MyComponent3); 实际上,此时的 connect([mapStateToProps],[mapDispatchToProps],[mergeProps],[options]) 这个函数会将一个React组件连接到Redux 的 store。在连接的过程中,connect通过函数参数 例如,我们把组件ComponentA连接到Redux上的写法类似于: const ConnectedComponentA = connect(componentASelector,componentAActions)(ComponentA); 我们可以把它拆分来看: // connect 是一个函数,返回值enhance也是一个函数 const enhance = connect(componentASelector,componentAActions); // enhance是一个高阶组件 const ConnectedComponentA = enhance(ComponentA); 当多个函数的输出和它的输入类型相同时,这些函数是很容易组合到一起使用的。例如,有f,g,h三个高阶组件,都只接受一个组件作为参数,于是我们可以很方便的嵌套使用它们: 例如我们将connect和另一个打印日志的高阶组件 const ConnectedComponentA = connect(componentASelector)(withLog(ComponentA)); 这里我们定义一个工具函数: const enhance = compose( connect(componentASelector),withLog ); const ConnectedComponentA = enhance(ComponentA); 像Redux等很多第三方库都提供了 4.与父组件区别有些同学可能会觉得高阶组件有些类似父组件的使用。例如,我们完全可以把高阶组件中的逻辑放到一个父组件中去执行,执行完成的结果再传递给子组件。从逻辑的执行流程上来看,高阶组件确实和父组件比较相像,但是高阶组件强调的是逻辑的抽象。高阶组件是一个函数,函数关注的是逻辑;父组件是一个组件,组件主要关注的是UI/DOM。如果逻辑是与DOM直接相关的,那么这部分逻辑适合放到父组件中实现;如果逻辑是与DOM不直接相关的,那么这部分逻辑适合使用高阶组件抽象,如数据校验、请求发送等。 5. 注意事项1)不要在组件的render方法中使用高阶组件,尽量也不要在组件的其他生命周期方法中使用高阶组件。因为高阶组件每次都会返回一个新的组件,在render中使用会导致每次渲染出来的组件都不相等( 2)如果需要使用被包装组件的静态方法,那么必须手动拷贝这些静态方法。因为高阶组件返回的新组件,是不包含被包装组件的静态方法。hoist-non-react-statics可以帮助我们方便的拷贝组件所有的自定义静态方法。有兴趣的同学可以自行了解。 3)Refs不会被传递给被包装组件。尽管在定义高阶组件时,我们会把所有的属性都传递给被包装组件,但是 function FocusInput({ inputRef,...rest }) { return <input ref={inputRef} {...rest} />; } //enhance 是一个高阶组件 const EnhanceInput = enhance(FocusInput); // 在一个组件的render方法中... return (<EnhanceInput inputRef={(input) => { this.input = input } }>) // 让FocusInput自动获取焦点 this.input.focus(); (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |