开发一个 vue component 的套路是什么
在早版本的 vue 的文档中,有说 vue 的作用是一个 MVVM 开发模式中的 VM,即 view model。现在没有这回事了,但是在 instance 部分的文档中,依然有说 vue 的设计参考了这种模式。
而现在很多网络服务的框架都采用 MV* 框架(*请自己填补,比如 Rails 的MVC),我们或许可以从这里入手来理解 vue。
如果你已经读过 vue 文档中的 introduction,但是没有理清 vue component 各部分,那么我们可以一起来梳理一下。
先说一个 Vue component 在整个项目中的地位
通常,一个 Vue component 会被挂在一个 components 组成的树上,而这个 component 通常又会引用很多的 child components。
vue components tree所以,我们如果写一个 component,最终表现出来的会是 components tree 中的一个 subtree。基于 vue router 这个工具,如果你点击了一个菜单,到达了某个路径,他会自动帮你加载一个 component(subtree)到树上的某个位置。
一个 component 中的 model
一个 vue component (或者说 vue instance,两者只有细微差别)可以看成是 view 和 model 的结合,或者更直接一点:template + model。model 的属性(props)可以在 view 中引用时赋值,model 的数据(data)可以被 view 取用。还有其他一些 view 和 model 的调用关系。我们列个表:
Model | View |
---|---|
data | template 中可使用的数据 |
props | template 中可使用的数据 / 引用 template 时赋值的属性 |
methods | template 和 model 的其他地方可用的实例方法 |
computed | data 改变或查询时的回调,形成 template 中可使用的数据 |
watch | 针对 data 中的字段做更通用的回调,不像 computed 形成一个 cache 的字段 |
解释一下:
- data 只是 template 中会用到的数据中的一部分,可以通过一些事件来触发这些 data 的改变
- props 定义的是引用 template 时,在别的 component 中赋值进来的属性
- methods 是 model 的其他部分要用到的方法,或者 template 中发生的事件会触发的方法
- computed 是一种回调(或者叫监听),是当 data 中的一些数据改变时,computed 中的字段会自动更新
- watch 也是针对 data 中的数据的一些回调(监听),是当 data 中的数据改变时,watch 中的方法自动执行
而当 model 中的某些数据改变(主要是 data 和 computed),view 会自动重新渲染,这是 Vue 帮我们做的事。
以上这些属性,都是写在 component 的 model 中的,比如:
export default {
name: "MyComponent",
data() {
return {
boy: "LiLei",
girl: "HanMeimei"
}
},
props: ["verb"],
methods: {
reverse() {
[this.boy, this.girl] = [this.girl, this.body];
}
},
computed: {
sentence() {
return ${this.boy} ${this.verb} ${this.girl}!
}
},
watch: {
boy(val, oldVal) {
console.log(previous boy ${oldVal}, new boy ${val})
}
}
}
而 template 可能是这样的:
<template>
<container>
<p>{{sentence}}</p>
<button v-on:click="reverse">click me!</button>
</container>
</template>
这两部分东西 template + model 被写在同一个 vue 文件中,比如叫 MyComponent.vue
如果要引用这个 template,就需要给我们定义的 props 赋值,比如这样:
<my-component class="your-class" verb="loves"></my-component>
model 部分的内容也就这么多了,还有一些关于 component 的生命周期回调也可以写在model里,不过这个我们以后再说。
剩下的就都是关于 view,或者说 template 的内容了。
template 的属性
我们上面说的是 template 可以取用 model 的数据和方法,包括 data,computed, methods。
template 中,很多 child components 会去访问这些数据,利用这些数据给自己的属性赋值。而 template 中可用属性有为数不多的几种来源,我们可以大致列这样一张表格:
来源 | 举例 |
---|---|
props 字段定义的 | <my-component verb="loves"></my-component> |
html 中默认 | <div id="yourId"></div> |
vue 中定义的特殊属性 | <my-component ref="myComponent"></my-component> |
逻辑属性 | <my-component v-if="isAdmin"></my-component> |
前两种都比较好理解,第三种需要看看文档中指出的 6 种特殊属性,我们后面再细说。
最后一种,在 vue 文档中属于 vue 指令 的部分,但我们这些 vue 指令中,有很多可以看作是 component 的逻辑属性,包括 v-if:判断这个 component 是否需要生成,v-for :循环产生 component 等等
另外,给属性赋值时,需要区分赋值的是一个常量,还是一个 javascript 对象,或者是一个需要计算的 javascript 表达式。用文档的话说,就是要区分 static 和 dynamic:
- 如果赋值的是一个常量(constant),那么按照上面例子中的语法,就能完成赋值:
<my-component verb="loves"></my-component>
。 - 但如果要赋值的是 js 对象,或者是一个需要计算的 js 表达式,那么就需要用 v-bind,比如:
<my-component v-bind:verb="randomVerb"></my-component>
,其中 v-bind 的部分可简写为:verb="randomVerb"
(也就是说用一个冒号,代替整个v-bind:
)
template 中的事件
template 事件监听,声明起来很简单:
<button v-on:click="reverse">Click me!</button>
简写的话,会是:
<button @click="reverse">Click me!</button>
也就是说把整个v-on:
替换为@
。
我一开始以为这和 html 中的 onclick 是一样的。但如果真的是一样的,就没法解释这样的语句:
<button @click="changeTo('Harry')">Click me!</button>
export default {
methods: {
changeTo(name) {
this.boy = name;
}
}
})
html 中的 onclick 只能赋值一个 function 对象,而 vue template 中,click事件竟然可以接受一个表达式,而且这个表达式计算的结果也不是一个 function 啊。根据 vue 文档(关于 v-on 的部分),@click="changeTo('Harry')"
这部分其实是另外一种形式的简写:v-on:click="changeTo('Harry', $event)"
。也就是说:changeTo('Harry')
在被 vue 这个框架转译的时候,最后一个参数会是被 vue 整进来的 $event
,一个 DOM event对象。当然,你可以不使用这个参数,甚至在声明方法时没有这个参数的位置,但这个 $event
就在那里,是 Vue 这个框架整合进来的。
最后还有一种情况,就是@click="reverse"
,不加括号,看起来像是一个 function 对象了。但是,实际上vue对他的处理的方式也差不多:变为v-on:click=reverse($event)
。
以上,我们监听的是一个普通的html元素,所以可以监听的事件就是 dom 中默认的事件。但是我们监听的是一个 component,监听的事件就可以自定义,比如:
<my-component @my-event="doSomething"></my-component>
而在 my-component
中,只需要在某些方法中广播这个事件,比如:
export default {
name: "MyComponent",
methods: {
example() {
this.$emit("my-event", someArgs)
}
}
}
引用了这个 component 的父级 component 就可以监听这个事件。
template 中的 slot
一个 template 中,当然会有各种子级 component。但除了child 级别的 component,我们还可以定义一些 slot,比如这样:
<container>
<h2>Slot Example</h2>
<slot></slot>
<p>The above is your slot!</p>
</container>
它允许我们在调用这个 component 的 template 时,传入一部分自定义的 template,比如这样:
<my-component>
<p>
This is a paragraph I want to insert into the my component
</p>
<img src="http://example.com/a.jpg" alt="also, an image">
</my-component>
如此,这个 p 标签和 img 标签都会出现在以 my-component 为 root 的 subtree 上,甚至,这些 slot 也可以被 my-component 中的方法操作。
slot 的位置另外,slot 还有 named slot 和 scoped slot 的特殊形式,但都是为了在引用 component 的时候传入自定义的元素。
最后,如果一个 component 不是全局的,但又想被别的 component 使用,那就需要做一下引入,比如这样:
import OtherComponent from './OtherComponent.vue'
export default {
components: {
OtherComponent
}
}
<other-component></other-component>
如此这般,才能在 vue 的 template 中使用那些非全局的 component。(备注,这里 component 的标签名字,只需要和 components 对象的 key 相同即可,已验证)
model 的模块化
model 利用 mixins
引入别人写的一些 js 对象,让 component 的 model 引入一些公用的属性,避免代码的重复写作。
mixins 表明上看起来只是 model 的一个属性:
import {moduleA} from './mixins.js'
export default {
mixins: [moduleA]
}
这个属性接受一个 js objects 做成的数组。而其中的每个 object 都包含一些 model 的属性,比如这样:
export const moduleA = {
computed: {
sentence() {
return `${this.boy} ${this.verb} ${this.girl}!`
}
}
}
当这个模块(moduleA)被 mix in 到一个 component 的 model 中,那个 model 就自动有了这些属性。
以上,希望对大家帮助。
微信公众号:刘思宁