再好比,下拉列表、时间选择器或者自动填充属性等自定义控件都是很是复杂的,须要考虑不少边缘的复杂状况。虽然有不少库很好的解决了这种复杂性,可是他们也带来了很差的缺点,就是这类组件没法自定义样式。css
就拿下面的标签输入控件举例:html
这个组件拥有一些有趣的功能:vue
若是你的项目中须要使用这样一个组件,把这个做为一个库引入,而且剥离这些逻辑确定可以节省不少时间和精力。react
可是假如这个时候你须要一个不一样样式展示呢?git
下面这个组件拥有和上面组件同样的行为功能,可是布局明显不同:github
经过组合css和配置选项,你能够尝试在一个组件里面都支持这些布局,但显然这不是很好的方法,万一有一天你又须要另一个布局,你又得去改这个组件,破坏了组件的封闭性,容易引发其它问题。ajax
针对以上状况,咱们来介绍本文最重要的一个知识点。json
在Vue.js中,slots是组件中的一个占位符元素,会被从父组件/消费者中传过来的内容替换。 数组
常规slots就像是给组件传递了一段html文本,scoped slots就像是给组件传递了一个可以接收数据并返回Html的回调函数。less
经过向子组件里面slot元素增长props,将参数传递给父组件。父组件经过解构destructuring slot-scope
里面接收的属性数据来得到这些参数。
这里有一个为每个list元素暴露scoped slot属性的LinksList
组件,而且经过:link
prop将每一项的数据传递回给父元素。
:link
prop 添加到LinksList组件中的slot元素,父元素组件如今可以经过
slot-scope
访问的到这些数据而且在本身的slot模块里面使用它。
你能够传递任何类型给slot,可是我发现使用如下3个类型的数据之一是最有用的。
最简单的slot prop类型就是数据类型:strings,numbers,boolean values,arrays,objects等。
在咱们的links-list组件例子中,link
就是一个data prop类型的例子,它是一个拥有一些属性的对象。
动做属性是由子组件提供的一个函数,父组件能够经过调用这个函数来触发子组件里面某些行为。
举个例子,咱们能够给父组件传递一个bookmark
方法,这个方法用来为给定连接添加书签。
Bindings是一系列属性或者监听事件的集合,经过使用v-bind
或者v-on
,绑定到特定的元素中。
当你想要封装有关如何与给定的元素进行交互的细节时,这些很是有用。
举个例子,咱们提供了bookmarkButtonAttrs
绑定和bookmarkButtonEvents
绑定用来把这些细节移动到组件自身,而不是让消费组件本身经过v-show
指令和@click
处理添加至书签的按钮逻辑。
名称解释:Renderless Components,直译为非渲染组件,我更喜欢叫函数式组件(借鉴于react中的叫法,如下统称函数式组件)。
函数式组件是一个不渲染任何html文本的组件。
相反,它只管理状态和行为,给父组件或者消费组件暴露一个做用域插槽,以便它们能本身控制该渲染的内容。
函数式组件可以准确的渲染你给它传入的内容,无需任何其它元素。
由于函数式组件只处理状态和行为,它们不会作出任何有关设计和布局的决定。
那就意味着若是你能找出一种方式将像咱们的标签输入功能这样有趣的行为从ui组件里面剥离出来,你就可以复用这个函数式组件去实现任何标签输入组件的布局。
下面都是标签输入组件,但此次是由一个函数式组件支持。
函数式组件仅仅暴露一个scoped slot,消费者能够在其中提供整个他们想要渲染的模块。
一个基本的函数式组件的骨架像下面这样:
任何父组件/消费者都可以在本身的模板中,经过解构slot-scope
中的exampleProp
去使用。
让咱们从头开始构建一个标签输入控件的函数式版本。
咱们首先要创建一个无插槽的空白的无渲染组件,
以及一个静态的,没有任何交互的父组件,而后将其传递到子组件的插槽中,
slot-scope
暴露给咱们布局的地方来完善这个组件。
首先,咱们将静态列表替换为动态列表。
这个标签输入组件是自定义表单控件,和这个原始例子同样,这个tags应该在父组件中,而且经过v-model
绑定到组件中。
咱们首先给函数式组件增长一个value属性,并将其传递给一个名为tags的插槽。
v-model
指令,从
slot-scope
中获取到tags,而后使
用v-for
指令来遍历它们。
下一步,当点击X按钮,删除一个标签。
在函数式组件中,咱们将会增长一个removeTag
的方法,而且将其做为一个slot属性传递给父元素。
@click
事件,这个事件可以在当前的标签中调用
removeTag
方法。
添加新标签比前面两个例子都要复杂些。
为了理解为何,咱们先来看一下传统的组件都是怎么实现的。
v-model
将这个属性绑定到input中。
一旦用户点击enter键,只要这个标签是合法的,咱们就把它添加到list数组中,而后清除input输入的值。
这儿的问题就是咱们怎样经过scoped-slot传递v-model
绑定。
好吧,若是你深刻了解过Vue,你应该知道v-model
其实就是一个语法糖,它负责将value特性绑定到一个名叫value的prop上,同时在其input事件被触发时,将新的值经过自定义的input事件抛出。
newTag
数据属性:value
的newTag
的绑定属性@keydown.enter
用来添加标签和绑定@input
用来更新标签的事件绑定属性在咱们的当前布局中,用户经过在输入元素中输入以及敲击enter键来完成添加一个新标签的操做。但这也很容易想到,有些用户但愿可以提供点击添加按钮来添加标签。
要实现这个很简单,咱们只须要给slot scope传递一个addTag
的方法的引用。
消费者只须要解构出它们实际须要的属性便可,因此若是你提供了它们可能用不到的属性,它们也没有什么成本。
这就是到目前为止咱们创建的函数式组件:
如今咱们已经有了一个标签输入控件的函数式组件,咱们能够很容易的编写咱们想要的任何Html并将提供的插槽属性应用到正确的位置来轻松实现替代布局。
如下就是咱们利用咱们新的函数式组件从头开始实现的堆叠式布局。
看到这么多的例子,你可能会想:“哇,当我须要另外一种形式的标签组件时,每次我都须要写这么多html”,是的,你说的对。
不管何时你须要一个标签输入组件,你确实须要写不少。
这有一个容易的解决方法:建立一个本身的包裹式组件!
<tags-input>
组件的样子
一旦你意识到一个组件能够不用渲染任何内容而只负责提供数据,那么经过组件建模的行为就没了限制。
举个列子,这里有一个使用URL做为属性,从这个URL获取json数据并给父组件传递响应数据的fetch-data
组件:
将一个组件拆分红一个视图组件和一个函数式组件是很是有用的一种模式,可使代码复用更容易,但并非每次都值得这样作。
若是有如下这类状况,能够考虑使用这种模式:
若是你正在研究一个在任何状况看来都类似的组件,那就不要走这条路了。这种情形下将全部你须要的写在一个组件里面可能会更好更简单。
此外,视图代码和业务逻辑分离只是一种下降代码耦合,进而增长代码健壮性的一种手段,其深层次的就是组件应该符合高内聚、低耦合的思想,其它符合这种思想的手段还有像控制反转(IOC)、发布订阅模式等等,我以为代码越日后写越应该培养这种意识,不然简简单单的写写业务代码,以完成需求而写代码,提高进步会比较慢。
文章翻译没有彻底按照原文来翻译,为了可以更好理解我加了一些本身的理解,若是你喜欢个人文章,请点个赞表示鼓励或者分享给你的朋友。