从Mixin到HOC再到Hook

1,321 阅读4分钟

导读

项目的前端工程越来越大,页面和组件越来越复杂,怎样更好的实现状态逻辑复用,直接影响项目的质量以及维护成本。

下面介绍React采用的三种实现状态逻辑复用的技术,并分析了他们的实现原理、使用方法、实际应用以及如何选择使用他们。

image.png

1. Mixin

广义的mixin方法,就是用赋值的方式将mixin对象中的方法都挂载到原对象上,来实现对象的混入,类似ES6中的Object.assign()的作用。原理如下:

const mixin = function(obj, mixins){
    const newObj = obj;
    newObj.prototype = Object.create(obj.prototype);

    for(let prop in mixins){ // 遍历mixins的属性
        if(mixins.hasOwnPrototype(prop)){ // 判断是否为mixin的自身属性
            newObj.prototype[prop] = mixins[prop]; // 赋值
        }
    }

    return newObj;
}

实质上就是把任意多个源对象拥有的自身可枚举属性复制给目标对象,然后返回目标对象。

在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 (<div>...</div>)
  }
});

var Goods = React.createClass({
  mixins: [LogMixin],
  render: function() {
    return (<div>...</div>)
  }
});

不同mixin的方法或许会有重合,如何处理视重合部分是普通方法还是生命周期方法而定。

  • 在不同mixin里实现两个名字一样的普通方法:并不会覆盖,且控制台会报错。
  • 重合的是生命周期方法:将各个mixin的生命周期方法叠加在一起顺序执行。

React.createClass的mixins的危害

  1. Mixin 可能会相互依赖,相互耦合,不利于代码维护
  2. 不同的 Mixin中的方法可能会相互冲突
  3. Mixin非常多时,组件是可以感知到的,甚至还要为其做相关处理,这样会给代码造成滚雪球式的复杂性 React.createClass现在已经不再推荐使用Mixin来解决代码复用问题,因为Mixin带来的危害比他产生的价值还要巨大,并且HOC是纯净的JavaScript,不用担心他们会被废弃。

2. 高阶组件

参考文章:https://juejin.cn/post/6844903815762673671#heading-9

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

function visisble(WrapperComponent) {
    return class extends Component {
        render() {
            const { visisble, ...props } = this.props
            if (visisble === false) return null
            return <WrapperComponent {...props}></WrapperComponent>
        }
    }
}

高阶组件实现的方法有两种:

  1. 属性代理:通过被包裹组件的props来进行相关操作。主要进行组件的复用。
  2. 反向继承:继承被包裹的组件。主要进行渲染的劫持。

高阶组件可以实现什么功能

  1. 组合渲染: 可使用任何其他组件和原组件进行组合渲染,达到样式、布局复用等效果。
  2. 条件渲染: 根据特定的属性决定原组件是否渲染
  3. 操作props: 可以对传入组件的props进行增加、修改、删除或者根据特定的 props进行特殊的操作。
  4. 获取refs: 高阶组件中可获取原组件的 ref,通过 ref获取组件实例, 从而可以实现对组件中的方法进行调用
  5. 状态管理: 将原组件的状态提取到 HOC中进行管理
  6. 操作state: 通过反向继承,我们可以直接操作原组件的 state
  7. 渲染劫持: 高阶组件可以在render函数中做非常多的操作,从而控制原组件的渲染输出。只要改变了原组件的渲染,我们都将它称之为一种 渲染劫持。

HOC的实际应用-双向绑定

import { createForm } from 'rc-form';
function ExpAccountFrom(props) {
    const { form } = props;
    const { getFieldProps, getFieldError } = form;
    return (
        <InputItem
            {...getFieldProps('name', {
              initialValue: '默认值',
              rules: [
                {
                  required: true,
                  message: 'name是必填项',
                },
              ],
            })}
            type="money"
            placeholder="请输入name"
            defaultValue="默认值"
          >
            <span style={{ color: propsFileds.enname }}>
              {propsFileds.defaultshowname}
            </span>
          </InputItem>
    )
}
export default createForm()(ExpAccountFrom);

HOC的缺陷

  1. HOC需要在原组件上进行包裹或者嵌套,如果大量使用 HOC,将会产生非常多的嵌套,这让调试变得非常困难。
  2. HOC可以劫持 props,在不遵守约定的情况下也可能造成冲突。

Hooks

使用 Hooks,你可以在将含有 state的逻辑从组件中抽象出来,这将可以让这些逻辑容易被测试。 Hooks可以帮助你在不重写组件结构的情况下复用这些逻辑

参考文章:https://juejin.cn/post/6844903815762673671#heading-35

使用Hooks的动机

  1. 减少状态逻辑复用的风险: Hook和 Mixin在用法上有一定的相似之处,但是 Mixin引入的逻辑和状态是可以相互覆盖的,而多个 Hook之间互不影响,这让我们不需要在把一部分精力放在防止避免逻辑复用的冲突上。
  2. 避免地狱式嵌套
  3. 让组件更容易理解
  4. 使用函数代替class