Vue 组件 & Slot基础 & 无渲染组件

Vue 及其所有组件都只不过是 JavaScript.

如果不认同这句话,可以看下接下来的vue组件基础。

一、 Vue组件本质

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

<template>
  <div class="mood">
    {{ todayIsSunny ? 'Makes me happy' : 'Eh! Doesn't bother me' }}
  </div>
</template>

<script>
  export default {
    data: () => ({ todayIsSunny: true })
  }
</script>

<style>
  .mood:after {
    content: '&#x1f389;&#x1f389;';
  }
</style>

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 })
}

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

对JSX熟悉的人对这个代码就十分眼熟了。

Vue提供了方法可以自己写渲染函数,有关渲染函数的更多信息,请参阅官方文档

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 的方式来渲染组件。

二、Slot基本功

Vue中使用slot的一个重要原因,就是为了达到组件的复用,子组件的某些元素直接由调用他的父组件决定。
不能免俗,先记一下目前的三种插槽。

单个插槽

其实就是最简单的配置,不额外写任何东西,直接拿官网的例子
子组件 <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>

具名插槽

其实就是指定了名字的插槽,代码会被渲染到指定的位置。
继续上面的例子:
子组件指定具名插槽和默认插槽,则父节点中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,其实我们就可以定制显示的位置,和默认的匿名插槽混用也不会有影响,这个在需要渲染多个插槽时十分有效。

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

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

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

作用域插槽

子组件通过给slot上绑定参数,达到给父组件传递进来的模板赋值的效果。
子组件:

<template>
  <a v-bind:href="url" class="nav-link">
    <slot name="up"></slot>

    <p>我是分割线</p>

    <slot></slot>

    <p>我是分割线</p>

    <slot name="list" :list="nameList"></slot>

  </a>
</template>

<script>
export default {
  props: {
    url: {
      type: String
    }
  },
  data() {
    return { 
      specData: "我的内容来自子节点",
      nameList: ['小王', '小张', '隔壁大爷']
    };
  }
};
</script>

父组件:(这里slot-scope="{ list }"直接用了结构)

<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>

    <template slot="list" slot-scope="{ list }">
      <p>
        <span v-for="(item, index) in list" :key="index">{{item}}</span>
      </p>
    </template>

  </navigation-link>
</template>

<script>
import "@mfelibs/base-css";
import navigationLink from "./child.vue";

export default {
  created() {},
  data() {
    return {
      specData: "我必须由父节点来传递"
    };
  },
  components: { navigationLink }
};
</script>

最终渲染为

三、无渲染组件

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

根据 S.O.L.I.D 的单一责任原则:

一个类应该只有一个用途。

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

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

这打破了 S.O.L.I.D 的开放封闭原则,这个原则规定:

类或组件应该为扩展而开放,为修改而封闭。

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

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

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

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

简单的一个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
    })
  ]);
},

关于这部分可参考 官网api


2018.11.12更新
发现了一篇好文,讲解的不错
Vue中的无渲染组件 (原文 70%付费)
直接搜题目 就能搜到有人买过了 粘出来的文章,这里不再贴了。

参考资料

如何构建无渲染Vue组件?
深入理解vue中的slot与slot-scope

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • 组件(Component)是Vue.js最核心的功能,也是整个架构设计最精彩的地方,当然也是最难掌握的。...
    六个周阅读 5,587评论 0 32
  • 什么是组件? 组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装...
    youins阅读 9,465评论 0 13
  • 此文基于官方文档,里面部分例子有改动,加上了一些自己的理解 什么是组件? 组件(Component)是 Vue.j...
    陆志均阅读 3,807评论 5 14
  • 这篇笔记主要包含 Vue 2 不同于 Vue 1 或者特有的内容,还有我对于 Vue 1.0 印象不深的内容。关于...
    云之外阅读 5,046评论 0 29
  • 还记得这周的开始 发生一件深痛无比的事情 刚开完11月闭营大会的第二天早晨 我忘记了打卡。。。理由比较尴尬。不说了...
    赵咕噜啊阅读 295评论 0 0