一、 总览
let vm = new Vue({
/**
* 各种选项(配置参数)
* Vue 提供的选项的值如果是函数时,不可用箭头函数,因为箭头函数的 this 与父级上下文绑定,并不指向 Vue 实例本身
*/
// DOM
// 要控制的 HTML 区域
el,
// 替换 el 选项挂载的元素为模板内容
template,
// 渲染函数,字符串模板的替代方案
render,
/**
* 仅用于开发环境,在 render() 出现错误时,提供另外的渲染输出
* 其错误将会作为第二个参数传递到 `renderError`。这个功能配合 hot-reload 非常实用
*/
renderError,
// 数据
// 声明需要响应式绑定的数据对象
data,
// 接收父组件传过来的值
props,
// 创建实例时手动传递 props,方便测试 props
propsData,
// 计算属性,一般用于管理表达式
computed,
// Vue 实例化时会调用 $watch() 方法遍历 watch 对象的每个属性
watch,
// 定义可以通过vm对象访问的方法
methods,
// 生命周期钩子函数——全部组件
// 发生在 Vue 实例初始化之后,data observer 和 event/watcher 事件被配置之前
beforeCreate,
// 发生在 Vue 实例初始化以及 data observer 和 event/watcher 事件被配置之后
created,
// 挂载开始之前被调用,此时 render() 首次被调用
beforeMount,
// el 被新建的 vm.$el 替换,并挂载到实例上之后调用
mounted,
// 数据更新时调用,发生在 VDOM 重新渲染和打补丁之前
beforeUpdate,
// 数据更改导致 VDOM 重新渲染和打补丁之后被调用
updated,
// 实例销毁之前调用,Vue 实例依然可用
beforeDestroy,
// Vue 实例销毁后调用,事件监听和子实例全部被移除,释放系统资源
destroyed,
// 生命周期钩子函数——缓存组件(被 keep-alive 包裹的组件)
// 组件激活时调用
activated,
// 组件停用时调用
deactivated,
// 资源
// 包含 Vue 实例可用指令的哈希表,组件(局部)指令
directives,
// 包含 Vue 实例可用过滤器的哈希表,组件(局部)过滤器
filters,
// 包含 Vue 实例可用组件的哈希表,引入组件,组件可以写在别的文件中,也可以写在本文件中,需要赋值给变量
components,
// 组合
//指定当前实例的父实例,子实例用 this.$parent 访问父实例,父实例通过 $children 数组访问子实例
parent,
// 将属性混入 Vue 实例对象,并在 Vue 自身实例对象的属性被调用之前得到执行
mixins,
// 用于声明继承另一个组件,从而无需使用 Vue.extend,便于扩展单文件组件
extends,
// 两个属性需要一起使用,用来向所有子组件注入依赖,类似于 React 的 Context
provide && inject,
// 其它
// 允许组件递归调用自身,便于调试时显示更加友好的警告信息
name,
// 改变模板字符串的风格,默认为{
{}}
delimiters,
// 让组件无状态(没有 data)和无实例(没有 this 上下文)
functional,
// 允许自定义组件使用 v-model 时定制 prop 和 event
model,
// 默认情况下,父作用域的非 props 属性绑定会应用在子组件的根元素上。当编写嵌套有其它组件或元素的组件时,可以将该属性设置为 false 关闭这些默认行为
inheritAttrs,
// 设为 true 时会保留并且渲染模板中的 HTML 注释
comments
});
二、DOM
2.1 el && template 选项
<div id="app">hello</div>
<script>
const vm = new Vue({
el: '#app',
template: `<div id="ceshi">xxx</div>`,
})
console.log(vm.$el); // <div id="ceshi">xxx</div>
</script>
2.2 render 选项
一种可以用 JS 来创建 HTML 的方案,如果该函数存在,则 Vue 构造函数不会从 template 选项或通过 el 选项指定的挂在元素中提取出的 HTML 模板编译渲染函数,组件中的 template 会被编译成 render 函数。
因为 Vue 是 VDOM,所以在拿到 template 模板时也要转译成 VNode 的函数,而用 render 函数构建 DOM,Vue 就免去了转译的过程。当使用 render 函数描述 VDOM 时, Vue 提供一个函数,这个函数是就构建 VDOM 所需要的工具。官方名字叫 createElement,还有约定的简写叫 h。
render: (h ,params) =>{
let that = this;
// h => createElement("定义的元素 ",{ 元素的性质 }," 元素的内容"/[元素的内容])
return h('div', that.getHTML(h,params.row));
}
getHTML ( h , row ) {
let status = row.status;
let that = this;
let editButtonStyle = {
props: {
type: 'success',
size: 'small',
ghost: true
},
on: {
click: () => {
that.edit(row);
}
}
};
let editButton = h( 'Button' , editButtonStyle , '编辑' );
let viewSourceButtonStyle = {
props: {
type: 'primary',
size: 'small',
ghost: true
},
style: {
marginLeft: '20px'
},
on: {
click: () => {
that.viewSource(row);
}
}
};
let viewSourceButton = h( 'Button' , viewSourceButtonStyle , '查看' );
let obsoleteButtonStyle = {
props: {
type: 'error',
size: 'small',
ghost: true
},
style: {
marginLeft: '20px'
},
on: {
click: () => {
that.obsolete(row);
}
}
};
let obsoleteButtonButton = h( 'Button' , obsoleteButtonStyle , '作废' );
let invalidButtonStyle = {
props: {
type: 'error',
size: 'small',
ghost: true,
disabled: true
},
style: {
marginLeft: '20px'
},
};
let invalidButton = h( 'Button' , invalidButtonStyle , '已作废' );
let data = [];
if( status == 0 ){ // 正常
data = [ editButton, viewSourceButton, obsoleteButtonButton ];
}else { // 作废
data = [ viewSourceButton,invalidButton ];
}
return data;
}
通过 render() 函数实现 VDOM 比较麻烦,因此可以使用 babel-plugin-transform-vue-jsx 在 render() 函数中应用 JSX 语法。
import Header from "./Header.vue"
new Vue({
el: "#demo",
render (h) {
return (
<Header level={1}>
<span>AI-fisher</span>
</Header>
)
}
})
2.3 renderError 选项
new Vue({
render (h) {
throw new Error('oops')
},
renderError (h, err) {
// h => (createElement: () => VNode, error: Error) => VNode
return h('pre', { style: { color: 'red' }}, err.stack)
}
}).$mount('#app')
三、数据
3.1 data 选项
<div id="app">
<!-- 平台:CSDN -->
平台:<span>{
{ blog }}</span><br/>
<!-- 用户:AI-fisher -->
用户:<span>{
{ username }}</span>
</div>
<script>
let myVm = new Vue({
el: '#app',
data: {
blog: 'CSDN',
username: 'AI-fisher'
}
});
</script>
在 data 选项中自定义了两个属性:blog 和 username,然后通过一种古老的模板语法:mustach,把他们的值绑定到了页面上,下面来打印一下这个实例:
console.log(myVm);
控制台打印结果(部分):
可以看到自定义属性被设置成了该实例的属性($开头表示是系统自带),所以说页面才能拿得到 blog 和 username 这两个属性的值,既然是实例属性,那么用 this 来访问也是可以的了:
<div id="app">
<!-- 平台:CSDN -->
平台:<span>{
{ this.blog }}</span><br/>
<!-- 用户:AI-fisher -->
用户:<span>{
{ this.username }}</span>
</div>
<script>
let myVm = new Vue({
el:'#app',
data:{
blog: 'CSDN',
username: 'AI-fisher'
}
});
</script>
只不过,this 可以省略,我们直接通过双大括号包裹该实例属性的属性名就可以在页面绑定数据了。当然,该数据绑定还是响应式的,是指当数据改变后,Vue 会通知到使用该数据的代码。例如,视图渲染中使用了数据,数据改变后,视图也会自动更新(也可以理解为数据同步)。
浏览器刷新页面的时候,计算机内存中会产生一个 JS 的对象树,里面做了相应的映射来描述整个 HTML 的结构,我们只需操作这个虚拟的 JS 对象,不用操作真正的 DOM,响应式系统在数据变化的时候,会去循环这个 JS 对象,在把要改变的内容上做标记。然后等整个循环完成之后,然后去逐个去操作 DOM 。一个标签有很多的属性,改其中一个属性并不会去替换整个元素,而是保留尽可能多的属性,只是替换器中某一部分,所以性能较高。
3.2 props 选项
props 选项用来接收父组件传过来的值,然后通过和访问 data 一样的方式:mustach 语法{ { 属性名 }}就可以访问到传递的数据
- 数组——无类型校验
props: ['test', 'count', 'flag', 'tip']
- 对象——有类型校验
为了检测数据的正确性,只能起到提示作用,并不会阻止程序的正常运行,因为类型的检测本就不是 JS 的强项
-
- 写法一:验证传递数据的有效性 ---- 如果数据类型不对,会报出警告,可以及时调整
props: {
test: String,
count: Number,
flag: Boolean,
tip: String
}
-
- 写法二:既要验证数据的类型,又要设定属性的默认值,如果默认值是对象或者是数组,值为一个函数
props: {
test: {
type: String, // 数据类型
default: '测试数据了', // 默认值
required: true, // 是否必传
validator: function(value){
return value == 0 // 是否符合某种自定义的规则
}
}
}
3.3 propsData 选项
propsData 和属性无关,它用在全局扩展时进行传递数据
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>propsData属性</title>
<script src="../assets/js/vue.js"></script>
</head>
<body>
<header></header>
<script type="text/javascript">
var demo = Vue.extend({
template:`<p style="color:red">这是利用propsData传递数据-----{
{demo}}---{
{a}}</p>`,
data() {
return{
demo:'这里采用了插值的方式'
}
},
props:['a']
});
new demo({ propsData: { a: 'propsData设置值' } }).$mount('header')
</script>
</body>
</html>
3.4 computed 选项
在 HTML 模板表达式中放置太多的业务逻辑,会让模板过重且难以维护。因此,可以考虑将模板中比较复杂的表达式拆分到 computed 属性当中进行计算。每个计算属性都是函数的形式,不过要求必须有返回值,所以就相当于用函数的返回值来模拟一个属性值。相对于 watch 和 filters,它更适合当模板中的某个值需要通过一个或多个数据计算得到(依赖这些数据)的场景,有缓存性(页面重新渲染不变化,会立即返回之前的计算结果,而不必再执行函数)
<div id="app">
<!-- CSDN——AI-fisher -->
{
{ information }}
</div>
<script>
new Vue({
el: '#app',
data: {
blog: 'CSDN',
username: 'AI-fisher'
},
computed: {
information () {
return `${this.blog}——${this.username}`;
}
}
})
</script>
计算属性只在相关依赖发生改变时才会重新求值,这意味只要上面例子中的 message 没有发生改变,多次访问 计算属性总会返回之前的计算结果,而不必再次执行函数,这是 computed 和 method 的一个重要区别,计算属性默认只拥有 getter 方法,但是可以自定义一个 setter 方法。
<script>
... ... ...
computed: {
fullName: {
// getter
get: function () {
return this.firstName + " " + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(" ")
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
... ... ...
// 下面语句触发setter方法,firstName和lastName也会被相应更新
vm.fullName = "John Doe"
</script>
3.5 watch 选项
通过 watch 属性可以手动观察 Vue 实例上的数据变动,当然也可以调用实例上的 vm.$watch 达到相同的目的。相对于computed 和 filters ,它更适合监听某个值发生变化后,需要进行一些逻辑处理的场景,无缓存性(页面重新渲染时,就算值不变化也会执行)
- 浅层描述
-
- 基本操作
<div id="app"></div>
<script>
let vm= new Vue({
el: '#app',
data: {
blog: 'CSDN'
},
watch: {
blog () {
console.log('侦听的数据变化了!');
}
}
})
</script>
在谷歌浏览器的控制台中作如下操作
> vm.blog // 初始数据 <· "CSDN" > vm.blog='博客园' // 修改观测的数据 侦听的数据变化了! <· "博客园"
-
- 获取变化前和变化后的值
<div id="app"></div>
<script>
new Vue({
el: '#app',
data: {
blog: 'CSDN',
username: 'AI-fisher'
},
watch: {
blog (newValue, oldValue) {
console.log(`侦听的数据变化了!新的值为:${newValue},旧的值为:${oldValue}`);
}
}
})
</script>
> vm.blog // 初始数据 <· "CSDN" > vm.blog='博客园' // 修改观测的数据 侦听的数据变化了!新的值为:博客园,旧的值为:CSDN <· "博客园"
-
- 侦听的属性发生变化后执行某项功能,这个情况下,侦听属性就比计算属性有优势了
<div id="app"></div>
<script>
let vm = new Vue({
el:'#app',
data:{
ipt:'bmw'
},
methods:{
show(){
console.log('ipt属性的值发生改变了我才会执行!')
}
},
watch:{
ipt:'show'
}
});
</script>
在谷歌浏览器的控制台中作如下操作
> vm.ipt <· "bmw" > vm.ipt='AI-fisher' 侦听的数据变化了! <· "AI-fisher"
- 深层描述
之前的案例对侦听的属性进行的描述不够深,如果要进行深层次的描述的话,就要用到对象来进行描述,里面还要设置一些参数。 -
- 设置数据从无到有时也要执行(首次运行)
<div id="app"></div>
<script>
let vm = new Vue({
el:'#app',
data:{
ipt:'bmw'
},
watch:{
ipt: {
handler(newValue, oldValue){
// ipt的值从无到有我就执行! undefined bmw
console.log("ipt的值从无到有我就执行!",oldValue,newValue);
},
immediate: true
}
}
});
</script>
-
- 设置深度检测(监听器会一层层的往下遍历,给对象的所有属性都加上这个监听器,性能开销大,任何修改 obj 里面任何一个属性都会触发这个监听器里的 handler)
<div id="app"></div>
<script>
let vm = new Vue({
el:'#app',
data:{
json:{a:1,b:2}
},
watch:{
json:{
handler(newValue,oldValue){
console.log('深度检测!',oldValue,newValue)
},
deep: true // 深度检测 复合数据类型
}
}
});
</script>
这里因为对象用到的是索引,这是打印本身的问题,不用去管它,我们只关心前后的值确实拿到了,而且是以对象的形式呈现的。
【注】:
- 方法:每次都调用,强制渲染
- 计算属性:必须在模板中调用才能执行,性能高:,适合做筛选,基于它们的响应式依赖进行缓存
- 数据观测:不用在模板中渲染,默认状态下数据的从无到有不会调用,只有后期观测的数据发生变化时才会执行
3.6 methods 选项
<div id="app">
<button v-on:click="showMsg">无括号调用</button>
<button v-on:click="showMsg()">加括号调用</button>
</div>
<script>
new Vue({
el: '#app',
data: {
msg: 'AI-fisher'
},
methods: {
showMsg () {
alert(this.msg);
}
}
})
</script>
点击页面上的两个按钮,控制台打印的都是 "AI-fisher" ,所以说这两种调用方式是相同的效果
四、生命周期钩子函数——全部组件
4.1 简介
生命周期钩子函数其实就是 Vue 实例的选项,这些选项的值全部都是函数,代表了该实例从出生到死亡这一生当中的各阶段,只要达到该阶段就会自动触发。生命周期的钩子函数都是同步执行的,不会有异步队列,也就是初始化实例的一瞬间全部执行完毕。
4.2 图示
当初始化实例的一瞬间,它内部会构建一些响应式事件监听的机制,然后所有的钩子函数就会生成,之后接纳用户定义的那些数据,比如 data、methods 之类的。
通过 el 选项或者 vm.$mount(el) 来匹配要控制的 HTML 区域,然后拿 template 模板中的 HTML 代码去编译,如果没有的话,就直接找外面的 HTML,之后通过 VDOM 去替换真实 DOM,然后真实 DOM 就会被挂载,接下来就会处于一种停歇的状态。
当用户更新 model 层的数据,VDOM 就会重新渲染,这种可更新状态一直持续到组件被 vm.$destory 卸载,卸载后数据观测以及里面的子实例身上绑定的相应式监听式事件会被全部取消,之后再来修改 model 层数据不会有任何效果。
4.3 辅助理解
初始化阶段
beforeCreate(){} // 准备怀孕
created(){} // 怀上了
beforeMount(){} // 准备生
mounted(){} // 生下来了
运行阶段
beforeUpdate(){} // 每天都在长大
updated(){} // 每次成长后
销毁阶段
beforeDestroy(){} // 即将舍弃肉身
destroyed(){} // 羽化登仙
4.4 使用
mounted 中能拿到所有的数据,是最安全的选择。当然,在 created 或者 mounted 中都可以请求数据,如果必须使用 DOM 操作,那么可以在 mounted 和 updated 中进行。以下对一些数据在各阶段进行了测试:
初始化阶段
beforeCreate
拿不到:data 数据、实例方法、ref 元素(真实的 DOM 元素)
能拿到:钩子函数、自定义选项
created
拿不到:ref 元素(真实的 DOM 元素)
能拿到:data 数据、实例方法、钩子函数、自定义选项
beforeMount
拿不到:ref 元素(真实的 dom元素)
能拿到:data 数据、实例方法、钩子函数、自定义选项
mounted
能拿到所有
当然,之后的钩子函数(除了销毁之后)肯定也能拿到所有,不用再做讨论
运行时阶段
beforeUpdate
能拿到所有
打印的是更新后的数据,但是当这个钩子打印的时候,数据并没有渲染到页面,也就是 VDOM 更新了,但真实 DOM 并没有更新
updated
能拿到所有
这个钩子是在真实 DOM 已经渲染完成后才执行函数中的业务逻辑
销毁阶段
beforeDestroy
能拿到所有
destroyed
拿不到:ref 元素(真实的 DOM元素)
能拿到:data 数据、实例方法、钩子函数、自定义选项
4.5 多组件生命周期关系
五、生命周期钩子函数——缓存组件
缓存组件包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。 是一个抽象组件,它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。当动态组件在缓存组件内被切换,它的 activated 和 deactivated 这两个生命周期钩子函数将会被对应执行。
六、资源
6.1 directives 选项
该选项可以配置组件专属的指令
- 使用:在自定义的指令名称前面加上v-前缀使用即可
- 配置
directives: {
"local-test": function(el, binding, vnode) {
/** el 可以获取当前 DOM 节点,并且进行编译,也可以操作事件 **/
/** binding 指的是一个对象,一般不用 **/
/** VNode 是 Vue 编译生成的虚拟节点 **/
el.style.border = "1px solid red"; //操作 style 所有样式
console.log(el.value); //获取 v-model 的值
console.log(el.dataset.name); // data-nam e绑定的值,需要 el.dataset 来获取
console.log(vnode.context.$route); //获取当前路由信息
}
},
6.2 filters 选项
该选项可以配置组件专属的过滤器,相对于 computed 和 filters,它更适合对自身数据进行一些处理,比如日期格式化,无缓存性
- 使用:现在只能在 mustache 插值和 v-bind 表达式中使用,用 | (过滤管道符)把需要过滤的值和过滤器连接起来
<!-- 在双花括号中 -->
{
{ message | capitalize }}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
- 配置
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
6.3 components 选项
<div id="app">
<my-header></my-header>
</div>
<script>
import MyHeader from './components/MyHeader'
const vm = new Vue({
el: '#app',
components: {
'my-header': MyHeader
}
})
</script>
七、组合
7.1 parent 选项
指定已创建的实例之父实例,在两者之间建立父子关系。子实例可以用 this.$parent 访问父实例,子实例被推入父实例的 $children 数组中
let parentVm = new Vue({
el: "#app",
data: {
msg: "parent"
}
});
let childVm = new Vue({
data: {
msg_: "child"
},
methods: {
getChildVmFunc() {
console.log("the root data is: " + this.$parent.msg); // parent
}
},
parent: parentVm
});
7.2 mixins 选项
用来将指定的 mixin 对象复用到 Vue 组件当中
// mixin对象
var mixin = {
created: function () {
console.log("mixin 钩子")
},
methods: {
foo() {
console.log("foo")
},
conflicting() {
console.log("mixin conflicting")
}
}
}
// vue属性
var vm = new Vue({
mixins: [mixin],
created: function () {
console.log("self 钩子")
},
methods: {
bar() {
console.log("bar")
},
conflicting() {
console.log("self conflicting")
}
}
})
// mixin 钩子
// self 钩子
vm.foo() // foo
vm.bar() // bar
vm.conflicting() // self conflicting
同名组件 option 对象的属性会被合并为数组依次进行调用,其中 mixin 对象里的属性会被首先调用。如果组件 option 对象的属性值是一个对象,则 mixin 中的属性会被忽略掉。
7.3 extends 选项
- Vue 的 extends 和 mixins 类似,通过暴露一个 extends 对象到组件中使用。
- extends 会比 mixins 先执行。执行顺序:extends > mixins > 组件
- extends 只能暴露一个 extends 对象,暴露多个 extends 不会执行
// 暴露两个 mixins 对象
export const mixinsTest = {
methods: {
hello() {
console.log("hello mixins");
}
},
beforeCreate(){
console.log("混入的beforeCreated");
},
created() {
this.hello();
},
}
export const mixinsTest2 = {
methods:{
hello2(){
console.log("hello2");
}
},
created() {
this.hello2();
},
}
// 只能使用一个 extends 对象,多个无效,extends 会先于 mixins 执行
export const extendsTest = {
methods: {
hello3() {
console.log("hello extends");
}
},
beforeCreate(){
console.log("extends的beforeCreated");
},
created() {
this.hello3();
},
}
// vue组件
<template>
<div>home</div>
</template>
<script>
import {mixinsTest,mixinsTest2,extendsTest} from '../util/test.js'
export default {
name: "Home",
data () {
return {
};
},
beforeCreate(){
console.log("组件的beforeCreated");
},
created(){
console.log("1212");
},
mixins:[mixinsTest2,mixinsTest], // 先调用哪个 mixins 对象,就先执行哪个
extends:extendsTest // 使用extends
}
</script>
<style lang="css" scoped>
</style>
7.4 provide && inject 选项
实现跨组件传值,数据的流只能是向下传递
- provide:根组件(指需要传递的数据所在的最外层组件,而非 App.vue)进行使用,用来给后代组件注入依赖(属性或方法)
- inject:子组件进行使用,用来获取根组件定义的跨组件传递的数据
根组件
<template>
<div id="app">
<h1>父组件</h1>
<Child/>
</div>
</template>
<script>
import Child from './components/child'
export default {
components:{
Child9,
},
/**
* provide 作为一个方法使用 ( 推荐使用 )。
* provide(){
* return{
* 'userName' : 'AI-fisher',
* }
* },
*/
// provide 也可以作为一个对象进行使用
provide : {
"userName" : 'AI-fisher',
}
}
</script>
子组件
<template>
<div class="box">
<h2>子组件</h2>
<h3>接受到根组件的传递的数据{
{ userName }}</h3>
</div>
</template>
<script>
export default {
// inject 后面用一个数组接收
// inject: ['userName']
// inject 后面可以是一个对象
inject : {
userName : {
default : '默认值' // 指定默认值
}
}
}
</script>
八、其它
8.1 name 选项
name 选项就是给该组件起一个唯一的名字,有如下作用:
- 项目中有缓存组件时,name 可以用作 include 和 exclude 的值
- 递归组件,顾名思义,就是在自己的内部实现需要调用自身
- 调试用,在 vue-tools 调试时,显示的就是 name 选项的值
8.2 delimiters 选项
作用是改变我们插值的符号,Vue 默认的插值是双大括号{ { }},在一些特殊的情况下我们可能会用到其他的方式绑定插值,比如避免在一些使用{ { }}绑定数据的开发模式中和 Vue 冲突
// 将插值形式变成${}
delimiters : [ '${' , '}' ]
8.3 functional 选项
把组件变成只接受 prop 的函数组件,无状态(没有响应式数据),没有实例(this 上下文),只用来展示
// template 写法
// props 父组件传递的数据
<template functional>
<div>{
{props.name}}</div>
</template>
// jsx 写法
Vue.component('my-component', {
functional: true,
// 可选
props: {},
render (createElement, context) {
}
})