React高阶组件入门与常用用法

851 阅读6分钟

简介

React 高阶组件简单来说就是一个没有副作用的高阶纯函数,且该函数接受一个组件作为参数,并返回一个新的组件。正式由于他也算是高阶函数,所以也可以使用类似柯里化的方式传组件或者其他参数。常用于多个组件有共同方法的时候。

1.基础理解

假如现在有以下两个组件,分别是对当前用户进行说明

  • 1.用户简介
// UserProfiles.js
import * as React from 'react';
import axios from 'axios';
class UserProfiles extends React.Component {
  state = {
    username: ''
  }
  componentWillMount() {
    axios.get('/username').then(item => {
      this.setState({
        username: item.data.username
      })
    }).catch(() => {
      console.log('error')
    });// 获取用户名
  }
  render() {
    return (
      <div>{this.state.username} 是xxx院院士,中国高级工程师,巴拉巴拉</div>
    )
  }
}
export default UserProfiles;
  • 用户退出
// goodbueuser.js
import * as React from 'react';
import axios from 'axios';
class UserProfiles extends React.Component {
  state = {
    username: ''
  }
  componentWillMount() {
    axios.get('/username').then(item => {
      this.setState({
        username: item.data.username
      })
    }).catch(() => {
      console.log('error')
    });// 获取用户名
  }
  render() {
   return (
     <div>再见 {this.state.username}</div>
  )
 }
}
export default GoodbyeUser;

可以看到代码的冗余量。按照平时我们的编码习惯,冗余的代码我们会将它封装成一个函数。高阶组件就类似这么一个函数的概念。好,下面我们尝试用高阶组件来对这两个组件封装

//hoc.js
import * as React from 'react'
import axios from 'axios';
export interface Iprops {
  username: string;
}
export default <T extends React.Component<Iprops>>(Com: new (props: Iprops) => T) => {
  class NewComponent extends React.Component {
    state = {
      username: ''
    }
    componentWillMount() {
      axios.get('/username').then(item => {
        this.setState({
          username: item.data.username
        })
      }).catch(() => {
        console.log('error')
      });// 获取用户名
    }
    render() {
      return <Com username={this.state.username} />
    }
  }
  return NewComponent
}
}
// UserProfiles.js
import React, { Component } from 'react';
import wrapHoc, { Iprops } from 'hoc';
class UserProfiles extends Component<Iprops> {
  render() {
    return (
      <div>{this.props.username} 是xxx院院士,中国高级工程师,巴拉巴拉</div>
    )
  }
}
UserProfilesCom = wrapHoc(UserProfiles);
export default UserProfilesCom;
// goodbueuser.js
import * as React from 'react';
import wrapHoc, { Iprops } from 'hoc';
class GoodbyeUser extends React.Component<Iprops> {
  render() {
    return (
      <div>再见 {this.props.username}</div>
    )
  }
}
GoodbyeUserCom = wrapHoc(GoodbyeUser);
export default GoodbyeUserCom;

看到没有,高阶组件就是把username通过props传递给目标组件了。目标组件只管从props里面拿来用就好了。这样子就完成了一个基本的高阶组件了

2.更改props

可以对参数组件的props进行增删改查的操作。我们平时使用的时候更多的是增加的操作,比如说我们常用的 react-router-dom中的Withrouter()。上面的例子就是属于更改props

通过高阶组件,我们可以在参数组件中使用this.props.username来调用这个应用的用户名了,这就类似withrouter中使用this.props.match.params.xxxx,来获取路由参数一样。

3.抽象state

这种用法一般用于不可控组件。比如我们熟悉的input,在react中我们需要通过onChange事件将输入的值绑定到state上,然后通过触发render函数渲染UI才能体现出来。通过高阶组件,我们可以抽象一个state,将不可控组件转变成可控组件。

function ppHOC(WrappedComponent) {
  return class PP extends React.Component {
    constructor(props) {
      super(props)
      this.state = {
        name: ''
      }
      this.onNameChange = this.onNameChange.bind(this)
    }
    onNameChange(event) {
      this.setState({
        name: event.target.value
      })
    }
    render() {
      const newProps = {
        name: {
          value: this.state.name,
          onChange: this.onNameChange
        }
      }
      return <WrappedComponent {...this.props} {...newProps}/>
    }
  }
}
// 链接:https://www.jianshu.com/p/0aae7d4d9bc1

然后这样使用它:(这里作者使用高阶组件的修饰器用法)

@ppHOC
class Example extends React.Component {
  render() {
    return <input name="name" {...this.props.name}/>
  }
}

4.更多高阶函数用法

前面提到了,高阶组件就是一个高阶函数,那么我们使用很多关于高阶函数的用法。比如说柯里化

import React from 'react';
import axios from 'axios';
export interface Iprops {
  data: string;
}
const HocCom = (key: string) => <T extends React.Component<Iprops>>(Com: new (props: Iprops) => T) => {
  return class extends React.Component {
    componentWillMount() {
      axios.get('username/' + key).then(item => {
        this.setState({
          data: item.data.username
        });
      }).catch({
        console.log('error')
      })

    }
    render() {
      return <Com data={this.state.data} />
    }
  }
}
class Com extends React.Component<Iprops> {
  render() {
    return <div>{this.props.data}</div>
  }
}
export default HocCom('wenfei')(Com)

我们常见的这种柯里化的模式就是在antd的表单模块中,我们一般都会在模块的结尾写上

const WrappedNormalLoginForm = Form.create()(NormalLoginForm);

5.高阶组件使用注意事项

  • 不要在组件的render方法中使用高阶组件,尽量也不要在组件的其他生命周期方法中使用高阶组件
  • 如果需要使用被包装组件的静态方法,那么必须手动拷贝这些静态方法

6.高阶组件、组件继承的区别

高阶组件、组件组合、组件继承都可以用来扩充组件,赋予组件新的能力。 react的官网 (reactjs.org/docs/compos…) 简单的说明了一下组件组合和继承的形式区别,而且在文章末尾建议我们不要使用继承的方式来扩充组件。为什么呢?我认为有以下几点。

1.灵活性

对于小项目,组件复用率小的情况下,其实三种方式都合适。但是对于大型的组件复用率高的项目,继承就会出现和明显的短板。比如说一个简单的List组件,通过继承方式获得可以支持上/下拉加载的组件ListA。有一天,项目经理说需要讲这个功能改成下拉刷新和向下滚动加载,发现ListA有部分功能相似,然后通过ListA继承修改代码获得了下拉刷新和向下滚动加载的组件ListB。第二天,项目经理又说要将功能做成仅仅滚动加载(例子,平时见不到这种需求)。what???我是要在原List组件上继承呢?还是在ListB上来继承呢?假如还有很多个list需要这种需求呢??都用来继承?这显得组件特别膨胀而且不灵活,耦合程度高。其实我们可以封装对应list的不同功能的高阶组件或组件组合,比如说下拉刷新的,向下滚动加载的,等等...然后拿来即用,传递想要功能的参数即可。灵活性大大提高。

2.单一性

还是上面的例子,我们发现我们通过继承获得了List\ListA\ListB等等组件。我们封装组件,一般是它实现了这个项目最基础而且可用的某个功能,相同功能引用相同组件即可。而继承的方式会获得基于这个基础组件的不同功能的组件,而且这些组件做的是同样事情,仅仅在方式上的不同。高阶组件/组件组合则可以保持这种react组件单一的特性。

3.复杂度

写组件继承,1.书写的麻烦,每个继承组件需要额外开销许多import和export。2.继承的时候还需要知道原组件实现了哪些方法?如果使用了ts还可能出现更多各种各样的错误。3.初始化 state、声明其它方法时要小心避免污染父类的state与方法。

参考文章

www.jianshu.com/p/0aae7d4d9…

原文链接:tech.gtxlab.com/React-Hoc.h…


作者简介: 张栓,人和未来大数据前端工程师,专注于html/css/js的学习与开发。