Vue 学习笔记

最后更新:
阅读次数:

使用 Vue 已有十个月多了,利用最近的空闲时间又把 Vue 文档走了一遍,做点笔记,查漏补缺。

基础数据相关

计算属性的 getter,setter

  • 计算属性默认只有 getter,不过在需要时你也可以提供一个 setter,从而实现计算属性与原属性的双向绑定。
new Vue({
el: "#app",
data: {
firstName: "AAA",
lastName: "BBB"
},
computed: {
fullName: {
get() {
return `${this.firstName}\u0020${this.lastName}`;
},
set(value) {
let names = value.split("\u0020");

this.firstName = names[0];
this.lastName = names[1];
}
}
}
});

深度 watch

  • 当 watch 选项中观察的是一个对象时,如果想要检测到该对象的属性变动,那么就需要设置 deep: true
let msg = {
name: "msg",
data() {
return {
obj: {
name: "ppp",
age: 22
}
};
},
template: `<div>子组件 {{obj.name}}</div>`,
watch: {
obj: {
deep: true, // 启用深度观察
handler() {
// do something
}
}
}
};

事件相关

<!-- 侦听子组件释放的事件 -->
<my-component @eventfromson="doSomething"></my-component>

<!-- 在某个组件的根元素上监听一个原生事件 -->
<my-component @click.native="doSomething"></my-component>

模版相关

动态绑定 class

<div
class="static"
:class="{ active: isActive, 'text-danger': hasError }"
></div>

<!-- 如果 isActive 为 false,hasError 为 true,则渲染为 -->

<div class="static text-danger"></div>
<div :class="[activeClass, errorClass]"></div>

data: { activeClass: 'active', errorClass: 'text-danger' }

<!-- 根据属性的值进行渲染 -->

<div class="active text-danger"></div>

动态绑定 style

这个很少用到。

过渡动画

vue-transition.png

<div id="app">
<transition name="fade">
<msg v-model="message" v-if="isShow"></msg>
</transition>
<button @click="toggleMsgComponent">显示或隐藏子组件</button>
</div>
.fade-enter,
.fade-leave-to {
opacity: 0;
}

.fade-enter-to {
opacity: 1;
}

.fade-enter-active,
.fade-leave-active {
transition: 1s;
}
  • 可以通过 appear 特性设置节点的在初始渲染的时候也有过渡效果
<transition name="fade" appear>
<msg v-model="message" v-if="isShow"></msg>
</transition>
  • 使用 <transition-group> 可以为 v-for 渲染的列表添加过渡效果
<transition-group name="list" tag="p">
<span v-for="item in items" v-bind:key="item" class="list-item">
{{ item }}
</span>
</transition-group>

ref 特性

  • ref 特性 被用来给普通 DOM 元素或子组件标记引用信息。引用信息将会注册在父组件的 $refs 对象
    • 如果 ref 特性 在普通的 DOM 元素上使用,则引用指向的就是 DOM 元素
    • 如果用在子组件上,则引用就指向组件实例
<div id="app">
<h3 ref="title">This is title!</h3>
<msg ref="msg"></msg>
</div>
new Vue({
el: "#app",
components: {
msg
},
mounted() {
// this.$refs.title 获取 DOM 节点
// this.$refs.msg 获取子组件实例
}
});

渲染函数(render)

  • Vue 建议在绝大多数情况下使用 template 来定义模板,而在一些特殊情况下,使用 render 函数 来定义模版则更为方便
  • 如果在组件中同时定义了 template 和 render,则优先使用 render 定义的模版
  • 如果组件中只有 template,在编译模版时,本质上是将 template 模版编译成了 render 函数

  • 详细了解 createElement 参数

// 样例,组件中使用 render 函数
let msg = {
name: "msg",
data() {
return {
currentColor: "#F83"
};
},
render(createElement) {
return createElement(
"div",
{
class: {
message: true
}
},
[
createElement("p", "第1行" + this.currentColor),
createElement("p", "第3行")
]
);
},
template: `<div>子组件</div>`
};
<!-- 上面代码中的 render 函数渲染的 html 如下 -->

<div class="message">
<p>第1行#F83</p>
<p>第3行</p>
</div>

在 Vue 中使用 jsx 语法

待定

slot 内容分发

  • 为了让组件可以组合使用,我们需要一种方式来混合父组件的内容与子组件自己的模板,这个过程被称为内容分发
<div id="app">
<msg v-model="message"> <h3>父组件:{{ message }}</h3> </msg>
</div>
let msg = {
name: "msg",
data() {
return {
message: "子组件的数据"
};
},
template: `<div>
<slot></slot>
<div>子组件:{{ message }}</div>
</div>`
};

new Vue({
el: "#app",
data: {
message: "父组件的数据"
},
components: {
msg
}
});
<!-- 上面的模板会被渲染为 -->
<div id="app">
<div>
<h3>父组件:父组件的数据</h3>
<div>子组件:子组件的数据</div>
</div>
</div>

除非子组件模板包含至少一个 插口,否则父组件的内容将会被丢弃。

当子组件模板只有一个没有属性的插槽时,父组件传入的整个内容片段将插入到插槽所在的 DOM 位置,并替换掉插槽标签本身。

当子组件中存在多个插槽时,我们可以给每个插槽一个唯一的名字 name ,然后可以根据 name 的值在父组件中指定内容分发的插槽。

<!-- 父组件 -->
<div id="app">
<msg v-model="message">
<h3 slot="slot111">父组件:{{ message }}</h3>
<h4 slot="slot222">插槽222</h4>
</msg>
</div>
<!-- 子组件 -->
<div>
<slot name="slot111"></slot>
<div>子组件:{{ message }}</div>
<slot name="slot222"></slot>
</div>
<!-- 渲染为 -->
<div id="app">
<div>
<h3>父组件:父组件的数据</h3>
<div>子组件:子组件的数据</div>
<h4>插槽222</h4>
</div>
</div>

內联模板

  • 如果子组件有 inline-template 特性,组件将把它的内容当作它的模板,而不是把它当作分发内容(slot),所以此时的子组件不再需要 template 选项来声明模板。
<div id="app">
<h2>父组件作用域</h2>
<h3>{{ fatherTitle }}</h3>

<msg inline-template>
<div class="son-component">
<h2>子组件作用域</h2>
<h3>{{ sonTitle }}</h3>
</div>
</msg>
</div>
let msg = {
name: "msg",
data() {
return {
sonTitle: "子组件-标题"
};
}
};

new Vue({
el: "#app",
data: {
fatherTitle: "父组件-标题"
},
components: {
msg
}
});
<!-- 渲染为 -->
<div id="app">
<h2>父组件作用域</h2>
<h3>父组件-标题</h3>
<div class="son-component">
<h2>子组件作用域</h2>
<h3>子组件-标题</h3>
</div>
</div>

组件相关

props 数据验证

  • props 可以是数组或对象,用于接收来自父组件的数据。props 可以是简单的数组,或者使用对象作为替代,对象允许配置高级选项,如类型检测、自定义校验和设置默认值。
// type 可以是:String、Number、Boolean、Function、Object、Array、Symbol

export default {
props: {
propA: [Number, String],
propB: {
type: String,
required: true
},
propC: {
type: Number, // 类型检测
default: 100 // 设置默认值
},
propD: {
// 自定义验证函数
validator: function(value) {
// 自定义校验
return value > 10;
}
}
},
template: `<div>{{ propB }}</div>`
};

.sync修饰符(语法糖)

  • .sync 修饰符存在于 vue1.x 中,但是在 vue2.0 中被移除,后来在 vue2.3 中又被重新引入

默认情况下,父组件通过 props 向子组件传递数据,并且这个过程是单向的。但是现在,我们使用 .sync 修饰符就可以使父组件与子组件的 props 数据进行双向绑定。

<div id="app">
<div>父组件:{{ message }}</div>
<msg :message.sync="message"></msg>

<!-- 上面的代码相当于是: -->
<!-- <msg :message="message" @update:message="val => message = val"></msg> -->
</div>
let msg = {
name: "msg",
props: ["message"],
template: `<div>
<div>子组件:{{ message }}</div>
<button @click="change">改变 props 数据(message)</button>
</div>`,
methods: {
change() {
// 当子组件需要更新 props 的某个属性时,它需要显式地触发一个相应的更新事件
// 下面对应的更新事件为: `update:message`
this.$emit("update:message", `通过子组件改变后的数据`);
}
}
};

new Vue({
el: "#app",
data: {
message: "最初的数据"
},
components: {
msg
}
});

组件的 v-model

  • 要让组件的 v-model 生效(vue 2.2+),需要两步:
    • 接受一个 value prop
    • 在有新的值时触发 input 事件并将新值作为参数
<div id="app">
<div>父组件:{{ message }}</div>
<msg v-model="message"></msg>
</div>
let msg = {
name: "msg",

// 接受一个 `value` prop
props: ["value"],

template: `<div>
<div>子组件:{{ value }}</div>
<button @click="change">改变 props 数据(message)</button>
</div>`,
methods: {
change() {
// 触发 `input` 事件并将新值作为参数
this.$emit("input", `通过子组件改变后的数据`);
}
}
};

new Vue({
el: "#app",
data: {
message: "最初的数据"
},
components: {
msg
}
});

默认情况下,一个组件的 v-model 会使用 value prop 和 input 事件,就像上面的例子一样。

但是我们可以使用组件的 model 选项 自定义组件的 v-model。

<div id="app">
<div>父组件:{{ message }}</div>
<msg v-model="message"></msg>
</div>
let msg = {
name: "msg",

// 自定义 v-model
model: {
prop: "msgFromParent",
event: "changemsg"
},

// 仍然需要显式地声明上面的 prop 属性
props: ["msgFromParent"],

template: `<div>
<div>子组件:{{ msgFromParent }}</div>
<button @click="change">改变 props 数据(message)</button>
</div>`,
methods: {
change() {
// 触发用于 v-model 的自定义事件
this.$emit("changemsg", `通过子组件改变后的数据`);
}
}
};

new Vue({
el: "#app",
data: {
message: "最初的数据"
},
components: {
msg
}
});

动态组件

<div id="app"><component :is="currentComponent"></component></div>
  • 如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以使用 keep-alive。
<keep-alive>
<component :is="currentComponent"> <!-- 非活动组件将被缓存! --> </component>
</keep-alive>
  • 当使用了动态组件时,组件会多两个钩子:activateddeactivated
    • activated: keep-alive 组件激活时调用
    • deactivated: keep-alive 组件停用时调用

可复用性

混合(Mixin)

混合可以将不同组件中可复用的组件选项抽离出来,提高组件的可复用性。

  • 全局混合(使用 Vue.mixin(obj)
    • 使用全局混合对象,将会影响到所有之后创建的 Vue 实例(谨慎使用)
<div id="app"><msg></msg> <msg></msg></div>
Vue.mixin({
mounted() {
console.log("mixin: mounted");
}
});

let msg = {
name: "msg",
template: `<div>子组件</div>`,
mounted() {
console.log("son: mounted");
}
};

new Vue({
el: "#app",
components: {
msg
},
mounted() {
console.log("father: mounted");
}
});

// 打印如下
// mixin: mounted
// son: mounted
// mixin: mounted
// son: mounted
// mixin: mounted
// father: mounted
  • 局部混合:使用组件的 mixins 选项(mixins 选项接受一个包含混合对象的数组)
    • 混合对象内定义的钩子函数将在组件自身钩子调用之前被调用
    • 值为对象的选项,例如 methods, componentsdirectives,将被混合为同一个对象,当两个对象键名冲突时,取组件对象的键值对
<div id="app"><msg></msg></div>
let mixinObj1 = {
mounted() {
console.log("mixin1: mounted");
}
};

let mixinObj2 = {
mounted() {
console.log("mixin2: mounted");
}
};

let msg = {
name: "msg",
mixins: [mixinObj1, mixinObj2],
template: `<div>子组件</div>`,
mounted() {
console.log("son: mounted");
}
};

new Vue({
el: "#app",
mixins: [mixinObj1],
components: {
msg
},
mounted() {
console.log("father: mounted");
}
});

// 打印如下
// mixin1: mounted
// mixin2: mounted
// son: mounted
// mixin1: mounted
// father: mounted

自定义指令

  • 全局自定义指令:Vue.directive(str, obj)
// 自定义指令 v-test
Vue.directive("test", {
inserted(el) {
el.innerHTML = "test insert success";
}
});
  • 局部自定义指令:使用组件的 directives 选项
let msg = {
name: "msg",
template: `<div v-red>子组件</div>`,
directives: {
red: {
inserted(el) {
el.style.color = "red";
}
}
}
};
  • 自定义指令有 5 个钩子
    • bind: 只调用一次,指令第一次绑定到元素时调用
    • inserted: 被绑定元素插入父节点时调用 (父节点存在即可调用,不必存在于 document 中)
    • update
    • componentUpdated
    • unbind

钩子函数的第一个参数 el 是与指令绑定的元素,第二个参数 binding 是一个与指令有关的对象。参见钩子函数参数

let msg = {
name: 'msg',
data() {
return {
currentColor: '#F83'
};
},
template: `<div v-color:test.aaa.bbb="currentColor+'38A'">子组件</div>`,
directives: {
color(el, binding) {
console.log(binding);
}
}
};

// 打印 binding
{
name: 'color', // 指令名,不包括 v- 前缀
rawName: 'v-color:test.aaa.bbb', // 模版中实际的指令名
expression: `currentColor+'38A'`, // 指令的绑定值的字符串形式
arg: 'test', // 传给指令的参数
value: '#F8338A', // 指令的绑定值
modifiers: { // 包含修饰符的对象
aaa: true,
bbb: true
}
}
  • 在自定义指令的大多数情况下,我们可能只想在 bind 和 update 钩子上做重复动作,并且不想关心其它的钩子函数,那么我们可以用下面的写法(直接传函数)
// 全局
Vue.directive("color-swatch", function(el, binding) {
el.style.backgroundColor = binding.value;
});

// 局部
let msg = {
name: "msg",
template: `<div v-color="'#5EA0CE'">子组件</div>`,
directives: {
color(el, binding) {
el.style.color = binding.value;
}
}
};

其它

不熟悉的 Vue 的 API

  • Vue.set( target, key, value ): 为 data 选项的响应式对象添加响应式属性

  • Vue.use( plugin ): 安装 Vue 插件

  • Vue.compile( template ): 实时编译模板字符串

  • Vue.version: 返回字符串形式的 Vue 的版本号