千家信息网

React深入分析从Mixin到HOC再到Hook

发表于:2026-11-02 作者:千家信息网编辑
千家信息网最后更新 2026年11月02日,这篇文章主要介绍"React深入分析从Mixin到HOC再到Hook",在日常操作中,相信很多人在React深入分析从Mixin到HOC再到Hook问题上存在疑惑,小编查阅了各式资料,整理出简单好用的
千家信息网最后更新 2026年11月02日React深入分析从Mixin到HOC再到Hook

这篇文章主要介绍"React深入分析从Mixin到HOC再到Hook",在日常操作中,相信很多人在React深入分析从Mixin到HOC再到Hook问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"React深入分析从Mixin到HOC再到Hook"的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

Mixin设计模式

Mixin(混入)是一种通过扩展收集功能的方式,它本质上是将一个对象的属性拷贝到另一个对象上面去,不过你可以拷贝任意多个对象的任意个方法到一个新对象上去,这是继承所不能实现的。它的出现主要就是为了解决代码复用问题。

很多开源库提供了Mixin的实现,如Underscore的_.extend方法、JQuery的extend方法。

使用_.extend方法实现代码复用:

var LogMixin = {    actionLog: function() {      console.log('action...');    },    requestLog: function() {      console.log('request...');    },  };  function User() {  /*..*/  }  function Goods() {  /*..*/ }  _.extend(User.prototype, LogMixin);  _.extend(Goods.prototype, LogMixin);  var user = new User();  var good = new Goods();  user.actionLog();  good.requestLog();

我们可以尝试手动写一个简单的Mixin方法:

function setMixin(target, mixin) {    if (arguments[2]) {      for (var i = 2, len = arguments.length; i < len; i++) {        target.prototype[arguments[i]] = mixin.prototype[arguments[i]];      }    }    else {      for (var methodName in mixin.prototype) {        if (!Object.hasOwnProperty(target.prototype, methodName)) {          target.prototype[methodName] = mixin.prototype[methodName];        }      }    }  }  setMixin(User,LogMixin,'actionLog');  setMixin(Goods,LogMixin,'requestLog');

您可以使用setMixin方法将任意对象的任意方法扩展到目标对象上。

React中应用Mixin

React也提供了Mixin的实现,如果完全不同的组件有相似的功能,我们可以引入来实现代码复用,当然只有在使用createClass来创建React组件时才可以使用,因为在React组件的es6写法中它已经被废弃掉了。

例如下面的例子,很多组件或页面都需要记录用户行为,性能指标等。如果我们在每个组件都引入写日志的逻辑,会产生大量重复代码,通过Mixin我们可以解决这一问题:

var LogMixin = {    log: function() {      console.log('log');    },    componentDidMount: function() {      console.log('in');    },    componentWillUnmount: function() {      console.log('out');    }  }; var User = React.createClass({    mixins: [LogMixin],    render: function() {      return (
...
) } }); var Goods = React.createClass({ mixins: [LogMixin], render: function() { return (
...
) } });

Mixin带来的危害

React官方文档在Mixins Considered Harmful一文中提到了Mixin带来了危害:

  • Mixin 可能会相互依赖,相互耦合,不利于代码维护

  • 不同的 Mixin 中的方法可能会相互冲突

  • Mixin非常多时,组件是可以感知到的,甚至还要为其做相关处理,这样会给代码造成滚雪球式的复杂性

React现在已经不再推荐使用Mixin来解决代码复用问题,因为Mixin带来的危害比他产生的价值还要巨大,并且React全面推荐使用高阶组件来替代它。另外,高阶组件还能实现更多其他更强大的功能,在学习高阶组件之前,我们先来看一个设计模式。

装饰模式

装饰者(decorator)模式能够在不改变对象自身的基础上,在程序运行期间给对像动态的添加职责。与继承相比,装饰者是一种更轻便灵活的做法。

高阶组件(HOC)

高阶组件可以看作React对装饰模式的一种实现,高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。

高阶组件(HOC)是React中的高级技术,用来重用组件逻辑。但高阶组件本身并不是React API。它只是一种模式,这种模式是由React自身的组合性质必然产生的。

function visible(WrappedComponent) {    return class extends Component {      render() {        const { visible, ...props } = this.props;        if (visible === false) return null;        return ;      }    }  }

上面的代码就是一个HOC的简单应用,函数接收一个组件作为参数,并返回一个新组件,新组建可以接收一个visible props,根据visible的值来判断是否渲染Visible。

下面我们从以下几方面来具体探索HOC。

HOC的实现方式

属性代理

函数返回一个我们自己定义的组件,然后在render中返回要包裹的组件,这样我们就可以代理所有传入的props,并且决定如何渲染,实际上 ,这种方式生成的高阶组件就是原组件的父组件,上面的函数visible就是一个HOC属性代理的实现方式。

function proxyHOC(WrappedComponent) {    return class extends Component {      render() {        return ;      }    }  }

对比原生组件增强的项:

  • 可操作所有传入的props

  • 可操作组件的生命周期

  • 可操作组件的static方法

  • 获取refs

反向继承

返回一个组件,继承原组件,在render中调用原组件的render。由于继承了原组件,能通过this访问到原组件的生命周期、props、state、render等,相比属性代理它能操作更多的属性。

function inheritHOC(WrappedComponent) {    return class extends WrappedComponent {      render() {        return super.render();      }    }  }

对比原生组件增强的项:

  • 可操作所有传入的props

  • 可操作组件的生命周期

  • 可操作组件的static方法

  • 获取refs

  • 可操作state

  • 可以渲染劫持

HOC可以实现什么功能

组合渲染

可使用任何其他组件和原组件进行组合渲染,达到样式、布局复用等效果。

通过属性代理实现:

function stylHOC(WrappedComponent) {    return class extends Component {      render() {        return (
{this.props.title}
); } } }

通过反向继承实现:

function styleHOC(WrappedComponent) {    return class extends WrappedComponent {      render() {        return 
{this.props.title}
{super.render()}
} } }

条件渲染

根据特定的属性决定原组件是否渲染。

通过属性代理实现:

function visibleHOC(WrappedComponent) {    return class extends Component {      render() {        if (this.props.visible === false) return null;        return ;      }    }  }

通过反向继承实现:

function visibleHOC(WrappedComponent) {    return class extends WrappedComponent {      render() {        if (this.props.visible === false) {          return null        } else {          return super.render()        }      }    }  }

操作props

可以对传入组件的props进行增加、修改、删除或者根据特定的props进行特殊的操作。

通过属性代理实现:

function proxyHOC(WrappedComponent) {    return class extends Component {      render() {        const newProps = {          ...this.props,          user: 'ConardLi'        }        return ;      }    }  }

获取refs

高阶组件中可获取原组件的ref,通过ref获取组件实力,如下面的代码,当程序初始化完成后调用原组件的log方法。(不知道refs怎么用,请?Refs & DOM)

通过属性代理实现:

function refHOC(WrappedComponent) {    return class extends Component {      componentDidMount() {        this.wapperRef.log()      }      render() {        return  { this.wapperRef = ref }} />;      }    }  }

这里注意:调用高阶组件的时候并不能获取到原组件的真实ref,需要手动进行传递,具体请看传递refs

状态管理

将原组件的状态提取到HOC中进行管理,如下面的代码,我们将Input的value提取到HOC中进行管理,使它变成受控组件,同时不影响它使用onChange方法进行一些其他操作。基于这种方式,我们可以实现一个简单的双向绑定,具体请看双向绑定。

通过属性代理实现:

function proxyHoc(WrappedComponent) {    return class extends Component {      constructor(props) {        super(props);        this.state = { value: '' };      }      onChange = (event) => {        const { onChange } = this.props;        this.setState({          value: event.target.value,        }, () => {          if(typeof onChange ==='function'){            onChange(event);          }        })      }      render() {        const newProps = {          value: this.state.value,          onChange: this.onChange,        }        return ;      }    }  }  class HOC extends Component {    render() {      return     }  }  export default proxyHoc(HOC);

操作state

上面的例子通过属性代理利用HOC的state对原组件进行了一定的增强,但并不能直接控制原组件的state,而通过反向继承,我们可以直接操作原组件的state。但是并不推荐直接修改或添加原组件的state,因为这样有可能和组件内部的操作构成冲突。

通过反向继承实现:

function debugHOC(WrappedComponent) {    return class extends WrappedComponent {      render() {        console.log('props', this.props);        console.log('state', this.state);        return (          
{super.render()}
) } } }

上面的HOC在render中将props和state打印出来,可以用作调试阶段,当然你可以在里面写更多的调试代码。想象一下,只需要在我们想要调试的组件上加上@debug就可以对该组件进行调试,而不需要在每次调试的时候写很多冗余代码。(如果你还不知道怎么使用HOC,请?如何使用HOC)

渲染劫持

高阶组件可以在render函数中做非常多的操作,从而控制原组件的渲染输出。只要改变了原组件的渲染,我们都将它称之为一种渲染劫持。

实际上,上面的组合渲染和条件渲染都是渲染劫持的一种,通过反向继承,不仅可以实现以上两点,还可直接增强由原组件render函数产生的React元素。

通过反向继承实现:

function hijackHOC(WrappedComponent) {    return class extends WrappedComponent {      render() {        const tree = super.render();        let newProps = {};        if (tree && tree.type === 'input') {          newProps = { value: '渲染被劫持了' };        }        const props = Object.assign({}, tree.props, newProps);        const newTree = React.cloneElement(tree, props, tree.props.children);        return newTree;      }    }  }

注意上面的说明我用的是增强而不是更改。render函数内实际上是调用React.creatElement产生的React元素:

虽然我们能拿到它,但是我们不能直接修改它里面的属性,我们通过getOwnPropertyDescriptors函数来打印下它的配置项:

可以发现,所有的writable属性均被配置为了false,即所有属性是不可变的。(对这些配置项有疑问,请?defineProperty)

不能直接修改,我们可以借助cloneElement方法来在原组件的基础上增强一个新组件:

React.cloneElement()克隆并返回一个新的React元素,使用 element 作为起点。生成的元素将会拥有原始元素props与新props的浅合并。新的子级会替换现有的子级。来自原始元素的 key 和 ref 将会保留。

React.cloneElement() 几乎相当于:

{children}

如何使用HOC

上面的示例代码都写的是如何声明一个HOC,HOC实际上是一个函数,所以我们将要增强的组件作为参数调用HOC函数,得到增强后的组件。

class myComponent extends Component {    render() {      return (原组件)    }  }  export default inheritHOC(myComponent);

compose

在实际应用中,一个组件可能被多个HOC增强,我们使用的是被所有的HOC增强后的组件,借用一张装饰模式的图来说明,可能更容易理解:

假设现在我们有logger,visible,style等多个HOC,现在要同时增强一个Input组件:

logger(visible(style(Input)))

这种代码非常的难以阅读,我们可以手动封装一个简单的函数组合工具,将写法改写如下:

const compose = (...fns) => fns.reduce((f, g) => (...args) => g(f(...args)));  compose(logger,visible,style)(Input);

compose函数返回一个所有函数组合后的函数,compose(f, g, h) 和 (...args) => f(g(h(...args)))是一样的。

很多第三方库都提供了类似compose的函数,例如lodash.flowRight,Redux提供的combineReducers函数等。

Decorators

我们还可以借助ES7为我们提供的Decorators来让我们的写法变的更加优雅:

@logger  @visible  @style  class Input extends Component {    // ...  }

Decorators是ES7的一个提案,还没有被标准化,但目前Babel转码器已经支持,我们需要提前配置babel-plugin-transform-decorators-legacy:

"plugins": ["transform-decorators-legacy"]

还可以结合上面的compose函数使用:

const hoc = compose(logger, visible, style);  @hoc  class Input extends Component {    // ...  }

HOC的实际应用

下面是一些我在生产环境中实际对HOC的实际应用场景,由于文章篇幅原因,代码经过很多简化,如有问题欢迎在评论区指出:

日志打点

实际上这属于一类最常见的应用,多个组件拥有类似的逻辑,我们要对重复的逻辑进行复用,官方文档中CommentList的示例也是解决了代码复用问题,写的很详细,有兴趣可以?使用高阶组件(HOC)解决横切关注点。

某些页面需要记录用户行为,性能指标等等,通过高阶组件做这些事情可以省去很多重复代码。

function logHoc(WrappedComponent) {    return class extends Component {      componentWillMount() {        this.start = Date.now();      }      componentDidMount() {        this.end = Date.now();        console.log(`${WrappedComponent.dispalyName} 渲染时间:${this.end - this.start} ms`);        console.log(`${user}进入${WrappedComponent.dispalyName}`);      }      componentWillUnmount() {        console.log(`${user}退出${WrappedComponent.dispalyName}`);      }      render() {        return       }    }  }

可用、权限控制

function auth(WrappedComponent) {    return class extends Component {      render() {        const { visible, auth, display = null, ...props } = this.props;        if (visible === false || (auth && authList.indexOf(auth) === -1)) {          return display        }        return ;      }    }  }

authList是我们在进入程序时向后端请求的所有权限列表,当组件所需要的权限不列表中,或者设置的visible是false,我们将其显示为传入的组件样式,或者null。我们可以将任何需要进行权限校验的组件应用HOC:

@auth  class Input extends Component {  ...  }  @auth  class Button extends Component {  ...  }    添加用户

双向绑定

在vue中,绑定一个变量后可实现双向数据绑定,即表单中的值改变后绑定的变量也会自动改变。而React中没有做这样的处理,在默认情况下,表单元素都是非受控组件。给表单元素绑定一个状态后,往往需要手动书写onChange方法来将其改写为受控组件,在表单元素非常多的情况下这些重复操作是非常痛苦的。

我们可以借助高阶组件来实现一个简单的双向绑定,代码略长,可以结合下面的思维导图进行理解。

首先我们自定义一个Form组件,该组件用于包裹所有需要包裹的表单组件,通过contex向子组件暴露两个属性:

  • model:当前Form管控的所有数据,由表单name和value组成,如{name:'ConardLi',pwd:'123'}。model可由外部传入,也可自行管控。

  • changeModel:改变model中某个name的值。

class Form extends Component {    static childContextTypes = {      model: PropTypes.object,      changeModel: PropTypes.func    }    constructor(props, context) {      super(props, context);      this.state = {        model: props.model || {}      };    }    componentWillReceiveProps(nextProps) {      if (nextProps.model) {        this.setState({          model: nextProps.model        })      }    }    changeModel = (name, value) => {      this.setState({        model: { ...this.state.model, [name]: value }      })    }    getChildContext() {      return {        changeModel: this.changeModel,        model: this.props.model || this.state.model      };    }    onSubmit = () => {      console.log(this.state.model);    }    render() {      return 
{this.props.children}
} }

下面定义用于双向绑定的HOC,其代理了表单的onChange属性和value属性:

  • 发生onChange事件时调用上层Form的changeModel方法来改变context中的model。

  • 在渲染时将value改为从context中取出的值。

function proxyHoc(WrappedComponent) {    return class extends Component {      static contextTypes = {        model: PropTypes.object,        changeModel: PropTypes.func      }      onChange = (event) => {        const { changeModel } = this.context;        const { onChange } = this.props;        const { v_model } = this.props;        changeModel(v_model, event.target.value);        if(typeof onChange === 'function'){onChange(event);}      }      render() {        const { model } = this.context;        const { v_model } = this.props;        return ;      }    }  }  @proxyHoc  class Input extends Component {    render() {      return     }  }

上面的代码只是简略的一部分,除了input,我们还可以将HOC应用在select等其他表单组件,甚至还可以将上面的HOC兼容到span、table等展示组件,这样做可以大大简化代码,让我们省去了很多状态管理的工作,使用如下:

export default class extends Component {    render() {      return (        
) } }

表单校验

基于上面的双向绑定的例子,我们再来一个表单验证器,表单验证器可以包含验证函数以及提示信息,当验证不通过时,展示错误信息:

function validateHoc(WrappedComponent) {    return class extends Component {      constructor(props) {        super(props);        this.state = { error: '' }      }      onChange = (event) => {        const { validator } = this.props;        if (validator && typeof validator.func === 'function') {          if (!validator.func(event.target.value)) {            this.setState({ error: validator.msg })          } else {            this.setState({ error: '' })          }        }      }      render() {        return 
{this.state.error || ''}
} } }
const validatorName = {    func: (val) => val && !isNaN(val),    msg: '请输入数字'  }  const validatorPwd = {    func: (val) => val && val.length > 6,    msg: '密码必须大于6位'  }    

当然,还可以在Form提交的时候判断所有验证器是否通过,验证器也可以设置为数组等等,由于文章篇幅原因,代码被简化了很多,有兴趣的同学可以自己实现。

Redux的connect

redux中的connect,其实就是一个HOC,下面就是一个简化版的connect实现:

export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {    class Connect extends Component {      static contextTypes = {        store: PropTypes.object      }      constructor () {        super()        this.state = {          allProps: {}        }      }      componentWillMount () {        const { store } = this.context        this._updateProps()        store.subscribe(() => this._updateProps())      }      _updateProps () {        const { store } = this.context        let stateProps = mapStateToProps ? mapStateToProps(store.getState(), this.props): {}         let dispatchProps = mapDispatchToProps? mapDispatchToProps(store.dispatch, this.props) : {}         this.setState({          allProps: {            ...stateProps,            ...dispatchProps,            ...this.props          }        })      }      render () {        return       }    }    return Connect  }

代码非常清晰,connect函数其实就做了一件事,将mapStateToProps和mapDispatchToProps分别解构后传给原组件,这样我们在原组件内就可以直接用props获取state以及dispatch函数了。

使用HOC的注意事项

告诫—静态属性拷贝

当我们应用HOC去增强另一个组件时,我们实际使用的组件已经不是原组件了,所以我们拿不到原组件的任何静态属性,我们可以在HOC的结尾手动拷贝他们:

function proxyHOC(WrappedComponent) {    class HOCComponent extends Component {      render() {        return ;      }    }    HOCComponent.staticMethod = WrappedComponent.staticMethod;    // ...     return HOCComponent;  }

如果原组件有非常多的静态属性,这个过程是非常痛苦的,而且你需要去了解需要增强的所有组件的静态属性是什么,我们可以使用hoist-non-react-statics来帮助我们解决这个问题,它可以自动帮我们拷贝所有非React的静态方法,使用方式如下:

import hoistNonReactStatic from 'hoist-non-react-statics';  function proxyHOC(WrappedComponent) {    class HOCComponent extends Component {      render() {        return ;      }    }    hoistNonReactStatic(HOCComponent,WrappedComponent);    return HOCComponent;  }

告诫—传递refs

使用高阶组件后,获取到的ref实际上是最外层的容器组件,而非原组件,但是很多情况下我们需要用到原组件的ref。

高阶组件并不能像透传props那样将refs透传,我们可以用一个回调函数来完成ref的传递:

function hoc(WrappedComponent) {    return class extends Component {      getWrappedRef = () => this.wrappedRef;      render() {        return  { this.wrappedRef = ref }} {...this.props} />;      }    }  }  @hoc  class Input extends Component {    render() { return  }  }  class App extends Component {    render() {      return (         { this.inpitRef = ref.getWrappedRef() }} >      );    }  }

React 16.3版本提供了一个forwardRef API来帮助我们进行refs传递,这样我们在高阶组件上获取的ref就是原组件的ref了,而不需要再手动传递,如果你的React版本大于16.3,可以使用下面的方式:

function hoc(WrappedComponent) {    class HOC extends Component {      render() {        const { forwardedRef, ...props } = this.props;        return ;      }    }    return React.forwardRef((props, ref) => {      return ;    });  }

告诫—不要在render方法内使用高阶组件

React Diff算法的原则是:

  • 使用组件标识确定是卸载还是更新组件

  • 如果组件的和前一次渲染时标识是相同的,递归更新子组件

  • 如果标识不同卸载组件重新挂载新组件

每次调用高阶组件生成的都是是一个全新的组件,组件的唯一标识响应的也会改变,如果在render方法调用了高阶组件,这会导致组件每次都会被卸载后重新挂载。

约定-不要改变原始组件

官方文档对高阶组件的说明:

高阶组件就是一个没有副作用的纯函数。

我们再来看看纯函数的定义:

如果函数的调用参数相同,则永远返回相同的结果。它不依赖于程序执行期间函数外部任何状态或数据的变化,必须只依赖于其输入参数。

该函数不会产生任何可观察的副作用,例如网络请求,输入和输出设备或数据突变。

如果我们在高阶组件对原组件进行了修改,例如下面的代码:

InputComponent.prototype.componentWillReceiveProps = function(nextProps) { ... }

这样就破坏了我们对高阶组件的约定,同时也改变了使用高阶组件的初衷:我们使用高阶组件是为了增强而非改变原组件。

约定-透传不相关的props

使用高阶组件,我们可以代理所有的props,但往往特定的HOC只会用到其中的一个或几个props。我们需要把其他不相关的props透传给原组件,如下面的代码:

function visible(WrappedComponent) {    return class extends Component {      render() {        const { visible, ...props } = this.props;        if (visible === false) return null;        return ;      }    }  }

我们只使用visible属性来控制组件的显示可隐藏,把其他props透传下去。

约定-displayName

在使用React Developer Tools进行调试时,如果我们使用了HOC,调试界面可能变得非常难以阅读,如下面的代码:

@visible  class Show extends Component {    render() {      return 

我是一个标签

} } @visible class Title extends Component { render() { return

我是一个标题

} }

为了方便调试,我们可以手动为HOC指定一个displayName,官方推荐使用HOCName(WrappedComponentName):

static displayName = `Visible(${WrappedComponent.displayName})`

这个约定帮助确保高阶组件***程度的灵活性和可重用性。

使用HOC的动机

回顾下上文提到的 Mixin 带来的风险:

  • Mixin 可能会相互依赖,相互耦合,不利于代码维护

  • 不同的 Mixin 中的方法可能会相互冲突

  • Mixin非常多时,组件是可以感知到的,甚至还要为其做相关处理,这样会给代码造成滚雪球式的复杂性

而HOC的出现可以解决这些问题:

  • 高阶组件就是一个没有副作用的纯函数,各个高阶组件不会互相依赖耦合

  • 高阶组件也有可能造成冲突,但我们可以在遵守约定的情况下避免这些行为

  • 高阶组件并不关心数据使用的方式和原因,而被包裹的组件也不关心数据来自何处。高阶组件的增加不会为原组件增加负担

HOC的缺陷

  • HOC需要在原组件上进行包裹或者嵌套,如果大量使用HOC,将会产生非常多的嵌套,这让调试变得非常困难。

  • HOC可以劫持props,在不遵守约定的情况下也可能造成冲突。

Hooks

Hooks是React v16.7.0-alpha中加入的新特性。它可以让你在class以外使用state和其他React特性。

使用Hooks,你可以在将含有state的逻辑从组件中抽象出来,这将可以让这些逻辑容易被测试。同时,Hooks可以帮助你在不重写组件结构的情况下复用这些逻辑。所以,它也可以作为一种实现状态逻辑复用的方案。

阅读下面的章节使用Hook的动机你可以发现,它可以同时解决Mixin和HOC带来的问题。

官方提供的Hooks

State Hook

我们要使用class组件实现一个计数器功能,我们可能会这样写:

export default class Count extends Component {    constructor(props) {      super(props);      this.state = { count: 0 }    }    render() {      return (        

You clicked {this.state.count} times

) } }

通过useState,我们使用函数式组件也能实现这样的功能:

export default function HookTest() {    const [count, setCount] = useState(0);    return (      

You clicked {count} times

); }

useState是一个钩子,他可以为函数式组件增加一些状态,并且提供改变这些状态的函数,同时它接收一个参数,这个参数作为状态的默认值。

Effect Hook

Effect Hook 可以让你在函数组件中执行一些具有 side effect(副作用)的操作

参数

useEffect方法接收传入两个参数:

  • 1.回调函数:在第组件一次render和之后的每次update后运行,React保证在DOM已经更新完成之后才会运行回调。

  • 2.状态依赖(数组):当配置了状态依赖项后,只有检测到配置的状态变化时,才会调用回调函数。

useEffect(() => {     // 只要组件render后就会执行   });   useEffect(() => {     // 只有count改变时才会执行   },[count]);

回调返回值

useEffect的***个参数可以返回一个函数,当页面渲染了下一次更新的结果后,执行下一次useEffect之前,会调用这个函数。这个函数常常用来对上一次调用useEffect进行清理。

export default function HookTest() {    const [count, setCount] = useState(0);    useEffect(() => {      console.log('执行...', count);      return () => {        console.log('清理...', count);      }    }, [count]);    return (      

You clicked {count} times

); }

执行上面的代码,并点击几次按钮,会得到下面的结果:

注意,如果加上浏览器渲染的情况,结果应该是这样的:

页面渲染...1   执行... 1   页面渲染...2   清理... 1   执行... 2   页面渲染...3   清理... 2   执行... 3   页面渲染...4   清理... 3   执行... 4

那么为什么在浏览器渲染完后,再执行清理的方法还能找到上次的state呢?原因很简单,我们在useEffect中返回的是一个函数,这形成了一个闭包,这能保证我们上一次执行函数存储的变量不被销毁和污染。

你可以尝试下面的代码可能更好理解。

var flag = 1;      var clean;      function effect(flag) {        return function () {          console.log(flag);        }      }      clean = effect(flag);      flag = 2;      clean();      clean = effect(flag);      flag = 3;      clean();      clean = effect(flag);      // 执行结果      effect... 1      clean... 1      effect... 2      clean... 2      effect... 3

模拟componentDidMount

componentDidMount等价于useEffect的回调仅在页面初始化完成后执行一次,当useEffect的第二个参数传入一个空数组时可以实现这个效果。

function useDidMount(callback) {    useEffect(callback, []);  }

官方不推荐上面这种写法,因为这有可能导致一些错误。

模拟componentWillUnmount

function useUnMount(callback) {    useEffect(() => callback, []);  }

不像 componentDidMount 或者 componentDidUpdate,useEffect 中使用的 effect 并不会阻滞浏览器渲染页面。这让你的 app 看起来更加流畅。

ref Hook

使用useRef Hook,你可以轻松的获取到dom的ref。

export default function Input() {    const inputEl = useRef(null);    const onButtonClick = () => {      inputEl.current.focus();    };    return (      
); }

注意useRef()并不仅仅可以用来当作获取ref使用,使用useRef产生的ref的current属性是可变的,这意味着你可以用它来保存一个任意值。

模拟componentDidUpdate

componentDidUpdate就相当于除去***次调用的useEffect,我们可以借助useRef生成一个标识,来记录是否为***次执行:

function useDidUpdate(callback, prop) {    const init = useRef(true);    useEffect(() => {      if (init.current) {        init.current = false;      } else {        return callback();      }    }, prop);  }

使用Hook的注意事项

使用范围

  • 只能在React函数式组件或自定义Hook中使用Hook。

Hook的提出主要就是为了解决class组件的一系列问题,所以我们能在class组件中使用它。

声明约束

  • 不要在循环,条件或嵌套函数中调用Hook。

Hook通过数组实现的,每次 useState 都会改变下标,React需要利用调用顺序来正确更新相应的状态,如果 useState 被包裹循环或条件语句中,那每就可能会引起调用顺序的错乱,从而造成意想不到的错误。

我们可以安装一个eslint插件来帮助我们避免这些问题。

// 安装  npm install eslint-plugin-react-hooks --save-dev  // 配置  {    "plugins": [      // ...      "react-hooks"    ],    "rules": {      // ...      "react-hooks/rules-of-hooks": "error"    }  }

自定义Hook

像上面介绍的HOC和mixin一样,我们同样可以通过自定义的Hook将组件中类似的状态逻辑抽取出来。

自定义Hook非常简单,我们只需要定义一个函数,并且把相应需要的状态和effect封装进去,同时,Hook之间也是可以相互引用的。使用use开头命名自定义Hook,这样可以方便eslint进行检查。

下面我们看几个具体的Hook封装:

日志打点

我们可以使用上面封装的生命周期Hook。

const useLogger = (componentName, ...params) => {    useDidMount(() => {      console.log(`${componentName}初始化`, ...params);    });    useUnMount(() => {      console.log(`${componentName}卸载`, ...params);    })    useDidUpdate(() => {      console.log(`${componentName}更新`, ...params);    });  };  function Page1(props){    useLogger('Page1',props);    return (
...
) }

修改title

根据不同的页面名称修改页面title:

function useTitle(title) {    useEffect(      () => {        document.title = title;        return () => (document.title = "主页");      },      [title]    );  }  function Page1(props){    useTitle('Page1');    return (
...
) }

双向绑定

我们将表单onChange的逻辑抽取出来封装成一个Hook,这样所有需要进行双向绑定的表单组件都可以进行复用:

function useBind(init) {    let [value, setValue] = useState(init);    let onChange = useCallback(function(event) {      setValue(event.currentTarget.value);    }, []);    return {      value,      onChange    };  }  function Page1(props){    let value = useBind('');    return ;  }

当然,你可以向上面的HOC那样,结合context和form来封装一个更通用的双向绑定,有兴趣可以手动实现一下。

使用Hook的动机

减少状态逻辑复用的风险

Hook和Mixin在用法上有一定的相似之处,但是Mixin引入的逻辑和状态是可以相互覆盖的,而多个Hook之间互不影响,这让我们不需要在把一部分精力放在防止避免逻辑复用的冲突上。

在不遵守约定的情况下使用HOC也有可能带来一定冲突,比如props覆盖等等,使用Hook则可以避免这些问题。

避免地狱式嵌套

大量使用HOC的情况下让我们的代码变得嵌套层级非常深,使用HOC,我们可以实现扁平式的状态逻辑复用,而避免了大量的组件嵌套。

让组件更容易理解

在使用class组件构建我们的程序时,他们各自拥有自己的状态,业务逻辑的复杂使这些组件变得越来越庞大,各个生命周期中会调用越来越多的逻辑,越来越难以维护。使用Hook,可以让你更大限度的将公用逻辑抽离,将一个组件分割成更小的函数,而不是强制基于生命周期方法进行分割。

使用函数代替class

相比函数,编写一个class可能需要掌握更多的知识,需要注意的点也越多,比如this指向、绑定事件等等。另外,计算机理解一个class比理解一个函数更快。Hooks让你可以在classes之外使用更多React的新特性。

理性的选择

实际上,Hook在react 16.8.0才正式发布Hook稳定版本,笔者也还未在生产环境下使用,目前笔者在生产环境下使用的最多的是`HOC`。

React官方完全没有把classes从React中移除的打算,class组件和Hook完全可以同时存在,官方也建议避免任何"大范围重构",毕竟这是一个非常新的版本,如果你喜欢它,可以在新的非关键性的代码中使用Hook。

到此,关于"React深入分析从Mixin到HOC再到Hook"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!

组件 函数 高阶 代码 属性 面的 方法 状态 逻辑 表单 复用 实际 问题 代理 参数 就是 页面 双向 元素 情况 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 新华互联网科技学校 互联网与科技发展 互联网公司 科技公司 喔噻互联网科技 互联网科技集团 互联网金融科技平台 盛创互联网科技有限公司 卫星互联网科技股票 互联网经济与金融科技 宜昌元轩互联网科技有限公司 洛阳共同富裕互联网科技有限公司 无锡工业互联网科技有限公司 扫呗互联网科技有限公司 易企秀互联网科技是做什么的 互联网公司属于科技吗 互联网科技职业技能培训学校 亚光科技低轨卫星互联网 杭州科技互联网公司有哪些 河南商务圈互联网科技有限公司 前海云享互联网科技 有格互联网科技 科技互联网政务服务 卓越科技(互联网 ) 长亮科技互联网金融 北京大学互联网科技怎么样 重庆互联网医院沙龙医盟信息科技 互联网领先科技宣传片 山东大美朱楼互联网科技有限公司 宁波跨境商互联网科技有限公司 互联网时代高科技布局
0