浅谈组件增强

RoseannMcKe 4年前
   <p><img src="https://simg.open-open.com/show/d264c9b852a8a15ef351c7b446b65341.jpg" alt="浅谈组件增强" width="550" height="309"></p>    <p>11月的上海,褪去了一丝温暖,夹带着丝丝寒意。独自一人走在街上,望着路边的男男女女,不禁让我想起了那个前任的她。因为有她,我才学会了做饭,学会照顾人,学会怎么谦让,不过在生命的进程中,她不过是一个过客罢了,她的出现和离开,对我都没有本质的改变。一阵寒风吹过,我再次抬头,是啊,刚才的故事蛮好的,是时候来知乎分享刚编的故事了,显然,程序员并不会有前任。</p>    <p>细细想一想,其实生活中存在很多起修饰作用的东西,万物本通,在编程界,也有许多的起修饰作用的东东,而且基于这个概念还发展出了许许多多的名称。</p>    <h2><strong>装饰函数</strong></h2>    <p>来首先看第一个装饰函数,函数可以接受参数并且返回值。如果接受的值和返回的一样呢?如果我们有一个构造函数:</p>    <pre>  <code class="language-javascript">function Person(name) {    this.name =  name  }    Person.prototype.greet = function() {    console.log('hello:' + this.name)  }</code></pre>    <p>现在由于 Person 函数是被封装的,不可以改变其源代码,而又想在先增加一个输出时间的方法,再调用原输出。就可以简单采用如下方式:</p>    <pre>  <code class="language-javascript">const cloudic = new Person('cloudic')  cloudic.greet() // => hello:cloudic    // 函数直接包装  function sayDate(f, ...args) {   console.log('show date')   typeOf f === 'funciton' && f(...args)  }  cloudic.dateGreet = sayDate(cloudic.greet)  cloudic.dateGreet() // => show date hello:cloudic    // 采用原型继承包装  function DateDecorator(man) {    const newMan = Object.create(man)    newMan.greet = function() {       console.log('show date')       man.greet()    }    return newMan  }  const newCloudic = DateDecorator(cloudic)  newCloudic.greet() // => show date hello:cloudic</code></pre>    <p>这样子类的更改并不会影响其他子类,当然第一种看起来更加简单点,如果希望所做的改变能同步影响到子类,可以覆盖原型对象上同名的方法,当然也可以用 decorator 语法糖。</p>    <p>为什么想动态增加个功能这样繁琐呢,JS 是动态语言,在运行的时候,可以方便的修改对象的属性,但是由于函数存在作用域的限制,想要动态修改函数内部的变量很难实现。所以才绕着弯子,采用包装的方式来实现。换个角度想下,如果一个对象或者函数内部提供了一个方法,该方法可以动态的修改函数内部的变量,那我们只需要调用该方法即可,这样的模式暂且叫中间件模式吧,比如 redux 的中间件。</p>    <h2>高阶组件</h2>    <p>说回组件层面,如果把上面说的应用到组件上,那么就是传入一个组件返回另外一个组件。在 React 中,我们比较常用的叫 HOC 模式,常见的实现方式如下:</p>    <pre>  <code class="language-javascript">function HOC(WrappedComponent) {    return class Wrap extends React.Component {      render() {        return <WrappedComponent {...this.props}/>      }    }  }</code></pre>    <p>在使用的时候,只需要传入需要包装的组件,调用函数后会返回一个包装过的组件。这样的好处是通过避免直接调用原组件,采用类似代理的方式来间接调用,从而可以方便后期替换原组件,可以做适配器,同时相比于 mixin 的实现方式,高阶组件更容易被调试,由于 mixin 是混入模式,导致在组件之间共享的代码很隐晦,如果方法比较多的话,还可能会覆盖现有的组件,所以 React 官方废弃了 mixin 的模式,推崇高阶组件来代替组件之间共享代码。</p>    <h2>抽象组件</h2>    <p>在 Vue 中,如果想对一个组件或者 DOM 进行相关功能的增强,我们可以用下面几种方式:</p>    <p>其一我们可以想到是采用指令的形式:</p>    <pre>  <code class="language-javascript">Vue.directive('name', {    inserted: function (el) {      // do something...    }  })</code></pre>    <p>指令可以方便的增加功能和复用,但是指令不能使用回调函数,不能传 props,没有事件,导致使用起来的灵活度不高。指令更多的是做 DOM 层面的工作,封装一些方法来修饰元素或操作元素属性。</p>    <p>其二我们可以用函数式组件:</p>    <pre>  <code class="language-javascript">Vue.component('name', {    functional: true,    render: function (createElement, context) {      // ...do something    },    // Props 可选    props: {      // ...    }  })</code></pre>    <p>函数式组件不会被实例化,也就是没有 data 和 this 上下文,也不能使用事件回调。可以简单的认为就是一个函数而已。不过函数式组件的优点也在于此,并不会有额外的性能开销,从而可以提高程序的性能。</p>    <p>其三就是采用抽象组件:</p>    <pre>  <code class="language-javascript">Vue.component('name', {    abstract: true,    render: function (createElement) {      return this.$slots.default[0]    }  })</code></pre>    <p>抽象组件在 Vue 官网并没有文档介绍,因为这是一个内部组件定义的功能,不是很稳定,随时可能会更改,并且不会通知到你。Vue 内置的组件比如 keep-alive, transition, component, slot 都是抽象组件,抽象组件没有自己的 DOM 元素,只是简单增加功能然后返回子元素。和纯函数组件相比,它有自己的生命周期,会被实例化,内部有this,是一个真正的组件,所以可以用 emit 发放事件。</p>    <h2>总结</h2>    <p>在组件增强的各个方法中,每个都有优劣,至于到底选择那个,还是需要使用者根据业务场景来决定。</p>    <p> </p>    <p> </p>    <p>来自:<a href="https://zhuanlan.zhihu.com/p/30818429?utm_source=tuicool&utm_medium=referral">https://zhuanlan.zhihu.com/p/30818429</a></p>    <p> </p>