• 浅析VUE里的插槽(内容分发)及无渲染组件


    一、插槽

      插槽,也就是slot,是组件的一块HTML模板,这块模板显示不显示、以及怎样显示由父组件来决定。实际上,一个slot最核心的两个问题这里就点出来了,是显示不显示怎样显示

      Vue中使用slot的一个重要原因,就是为了达到组件的复用,子组件的某些元素直接由调用他的父组件决定。

      由于插槽是一块模板,所以,对于任何一个组件,从模板种类的角度来分,其实都可以分为非插槽模板插槽模板两大类。

      非插槽模板指的是html模板,指的是‘div、span、ul、table’这些,非插槽模板的显示与隐藏以及怎样显示由插件自身控制;

      插槽模板是slot,它是一个空壳子,因为它显示与隐藏以及最后用什么样的html模板显示由父组件控制。但是插槽显示的位置确由子组件自身决定,slot写在组件template的哪块,父组件传过来的模板将来就显示在哪块

    1、单个插槽 | 默认插槽 | 匿名插槽

      首先是单个插槽,单个插槽是vue的官方叫法,但是其实也可以叫它默认插槽,或者与具名插槽相对,我们可以叫它匿名插槽,因为它不用设置name属性。

      单个插槽可以放置在组件的任意位置,但是就像它的名字一样,一个组件中只能有一个该类插槽。相对应的,具名插槽就可以有很多个,只要名字(name属性)不同就可以了。

      直接拿官网的例子来展示。

    // 子组件 <navigation-link>
    <a v-bind:href="url" class="nav-link">
      <slot></slot>
    </a>
    
    // 父组件调用
    <navigation-link url="/profile">
      <!-- 添加一个 Font Awesome 图标 -->
      <span class="fa fa-user"></span>
      Your Profile
    </navigation-link>
    
    // 最终会渲染为
    <a v-bind:href="url" class="nav-link">
      <!-- 添加一个 Font Awesome 图标 -->
      <span class="fa fa-user"></span>
      Your Profile
    </a>

    2、具名插槽

      具名插槽其实就是指定了名字的插槽,代码会被渲染到指定的位置。具名插槽可以在一个组件中出现N次,出现在不同的位置。

      继续上面的例子:子组件指定具名插槽和默认插槽,则父节点中slot的元素会按位置渲染。

    // 子组件
    <template>
      <a v-bind:href="url" class="nav-link">
        <slot name="up"></slot>
        <p>我是分割线</p>
        <slot></slot>
      </a>
    </template>
    <script>
    export default {
      props: {
        url: {
          type: String
        }
      },
      data() {
       return { specData: '我的内容来自子节点'};
      }
    };
    </script>
    // 父组件
    <template>
      <navigation-link url="/profile">
        <!-- 添加一个 Font Awesome 图标 -->
        <span class="fa fa-user"></span>
        Your Profile
        <span>{{specData}}</span>
    
        <p slot="up">
          <span>我是up</span>
        </p>
    
      </navigation-link>
    </template>
    
    <script>
    import navigationLink from './child.vue';
    
    export default {
      created(){
      },
      data() {
        return { specData: '我必须由父节点来传递,我的内容来自父节点};
      },
      components: { navigationLink },
    }
    </script>
    // 最终渲染为:
    <a href="/profile" class="nav-link">
      <p>
        <span>我是up</span>
      </p>
      <p>我是分割线</p>
      <span class="fa fa-user"></span>
      Your Profile
      <span>我必须由父节点来传递,我的内容来自父节点</span>
    </a>

      可见,多了一个name,其实我们就可以定制显示的位置,和默认的匿名插槽混用也不会有影响,这个在需要渲染多个插槽时十分有效。

    3、Vue的编译作用域

      在提到作用域插槽之前,必须确保已经了解Vue的编译作用域。

      父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。

      其实仔细看上面的例子,父子组件都赋值了specData,但是渲染的是父组件的内容便了解了,其实写错了也没事,Vue会给你及时提示的。

    4、作用域插槽 | 带数据的插槽

      最后,就是我们的作用域插槽,这个稍微难理解一点,官方叫它作用域插槽,实际上,对比前面两种插槽,我们可以叫它带数据的插槽。

      作用域插槽就是子组件通过给slot上绑定参数,达到给父组件传递进来的模板赋值的效果

      我们前面说了,插槽最后显示不显示是看父组件有没有在child下面写模板,像下面那样。

    <child>
       html模板
    </child>

      写了,插槽就总得在浏览器上显示点东西,东西就是html该有的模样,没写,插槽就是空壳子,啥都没有。

      我们再来对比,作用域插槽和单个插槽和具名插槽的区别,因为单个插槽和具名插槽不绑定数据,所以父组件是提供的模板要既包括样式又包括内容和数据,而作用域插槽,父组件只需要提供一套样式(在确实用作用域插槽绑定的数据的前提下),数据就是使用子组件传递过来的数据。

    // 父组件
    <template>
      <div class="father">
        <h3>这里是父组件</h3>
        <!--第一次使用:直接显示数据-->
        <child>
          <template slot-scope="user">
           {{user.data}}
          </template>
    
        </child>
    
        <!--第二次使用:不使用其提供的数据, 作用域插槽退变成匿名插槽-->
        <child>
          我就是模板
        </child>
      </div>
    </template>
    
    // 子组件
    <template>
      <div class="child">
        <h3>这里是子组件</h3>
        // 作用域插槽
        <slot :data="data"></slot>
      </div>
    </template>
    export default {
        data: function(){
          return {
            data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
          }
        }
    }

      其中slot-scope=user其实是一个对象,里面包含的属性是子组件中传给<slot>的prop,在上面的例子中就是user=={data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']},所以可以通过user.data获取到子组件的data。

    二、Vue组件本质

    1、VUE组件的本质:Vue 及其所有组件都只不过是 JavaScript。

      Vue 提供了很多种方法用于定义组件的标记:

    (1)单文件组件让我们可以像普通的 HTML 文件一样定义组件及其标记。

    (2)template 属性让我们可以使用 JavaScript 的模板字面量来定义组件的标记。

    (3)el 属性让 Vue 通过查询 DOM 来获取用作模板的标记。

      观察下面这个单文件组件,相信很多人都写过类似的代码。

    <template>
      <div class="mood">
        {{ todayIsSunny ? 'Makes me happy' : 'Eh! Doesn't bother me' }}
      </div>
    </template>
    <script>
      export default {
        data: () => ({ todayIsSunny: true })
      }
    </script>

      Vue 试图简化样式和其他资源的管理,只是 Vue 并没有直接做这些事情,而是把这些工作留给了构建过程,比如 webpack。

      webpack 在处理 .vue 文件时,会对其执行一个转换。在转换过程中,CSS 被从组件中提取出来,并放到单独的文件中,剩余部分则被转换为 JavaScript。例如:

    export default {
      template: `
        <div class="mood">
          {{ todayIsSunny ? 'Makes me happy' : 'Eh! Doesn't bother me' }}
        </div>`,
      data: () => ({ todayIsSunny: true })
    }

    2、模板编译器和渲染函数

      当模板编辑器遇到上面这段代码时,它将 template 属性提取出来,并将它的内容编译为 JavaScript,然后将一个渲染函数添加到组件对象中。这个渲染函数将返回已转换为 JavaScript 的 template 属性的内容。

      对JSX熟悉的人对这个代码就十分眼熟了。Vue提供了方法可以自己写渲染函数,有关渲染函数的更多信息,请参阅官方文档 https://vuejs.org/v2/guide/render-function.html。

    render(h) {
      return h(
        'div',
        { class: 'mood' },
        this.todayIsSunny ? 'Makes me happy' : 'Eh! Doesn't bother me'
      )
    }
      现在当组件对象被传给 Vue 时,组件的渲染函数会经过一些优化并变成 VNode(虚拟节点),然后 VNode 被传给 snabbdom(Vue 内部用于管理虚拟 DOM 的库)。Vue 通过 VNode 的方式来渲染组件。

    三、无渲染组件

      无渲染组件是指不渲染任何内容的组件。

      我们可以这样理解无渲染组件:为一个组件创建通用功能抽象,然后通过扩展这个组件来创建更好更健壮的组件,或者说,遵循 S.O.L.I.D 原则。

    1、根据 S.O.L.I.D 的单一责任原则:一个类应该只有一个用途。

      我们将这个概念移植到 Vue 开发中,让每个组件只提供一个用途。

      例如我们实现一个评论组件,当另一个需求过来,修改了样式,修改了交互时,就不得不去修改组件代码来实现这个需求。而我们之前做的和接口的通信方式,和客户端的通信方式,可能又需要重新copy一份。

      这打破了 S.O.L.I.D 的开放封闭原则,这个原则规定:类或组件应该为扩展而开放,为修改而封闭。

      也就是说,你应该扩展它,而不是直接修改组件的源代码。

      Vue 遵循了 S.O.L.I.D. 原则,让组件拥有 prop、event、slot 和 scoped slot,这些东西让组件的交互和扩展变得轻而易举。我们可以构建具备所有特性的组件,而无需修改任何样式或标记。这对于可重用性和高效代码来说非常重要。

    2、在设计时需要仔细思考的是

    (1)表现和行为分离

      由于无渲染组件只处理状态和行为,所以它们不会对设计或布局强加任何的决策。这意味着,如果你能够找到一种方法,将所有的行为从UI组件中移出,将将其转换成一个无组件的组件,那么你就可以重用无渲染组件来实现任何布局效果的标签输入控件。

    (2)简单的一个toogle组件

      这个组件完成一个简单的功能,传递给子组件一个状态on (也可以不传,例子中就没传),通过调用子组件暴露出的方法,达到利用子组件的状态,影响父组件的渲染的目的。这样,父组件的结构可以随便改,样式随便让产品设计去折腾,我们的基本功能是不变的,达到一定层度的代码复用。

      父组件:注意slot-scope不能直接用在子组件标签<toogle>

    <template>
      <toogle>
        <div slot-scope="{ on, setOn, setOff }" class="container">
          <button @click="click(setOn)" class="button">Blue pill</button>
          <button @click="click(setOff)" class="button isRed">Red pill</button>
          <div v-if="buttonPressed" class="message">
            <span v-if="on">It's all a dream, go back to sleep.</span>
            <span v-else>I don't know how far the rabbit hole goes, I'm not a rabbit, neither do I measure holes.</span>
          </div>
        </div>
      </toogle>
    </template>
    <script>
    import toogle from "./toogle";
    
    export default {
      data() {
        return {
          buttonPressed: false 
        };
      },
      components: {
        toogle
      },
      methods: {
        click(fn) {
          this.buttonPressed = true;
          fn && fn();
        }
      }
    };
    </script>
    // 子组件
    <template>
      <div>
        <slot :on="currentState" :setOn="setOn" :setOff="setOff" :toogle="toggle"></slot>
      </div>
    </template>
    <script>
    export default {
      props: {
        on: { type: Boolean, default: false }
      },
      data() {
        return { currentState: this.on };
      },
      methods: {
        setOn() {
          this.currentState = true;
        },
        setOff() {
          this.currentState = false;
        },
        toggle() {
          this.currentState = !this.currentState;
        }
      }
    };
    </script>

      如果要用render函数,子组件可以省去template部分,在script部分添加

    render: function(createElement) {
      return createElement("div", [
        this.$scopedSlots.default({
          on: this.currentState,
          setOn: this.setOn,
          setOff: this.setOff,
          toggle: this.toggle
        })
      ]);
    },

    参考链接:https://www.jianshu.com/p/50dcf932a159

  • 相关阅读:
    软件包管理(rpm&yum)
    文本处理三剑客之sed
    压缩归档tar
    linux文件查找find
    vim编辑器
    正则表达式
    文本处理三剑客之grep
    常用文本处理、统计工具
    文件权限管理
    用户和组管理
  • 原文地址:https://www.cnblogs.com/goloving/p/14942543.html
Copyright © 2020-2023  润新知