如何创建实例?
每一个实例都是通过 Vue
函数创建一个新的Vue实例开始的:
let vm = new Vue({
//选项
})
可以通过内存图来看待这个实例函数:
- 该函数把Vue的实例命名为
vm
,vm对象封装了对视图的所有操作,包括数据读写、事件绑定、DOM更新。 vm
的构造函数是Vue,按照ES6的说法,vm
所属的类是Vue。optioins
是new Vue
的参数,一般称之为选项或构造选项。
options里面有什么?
官方文档列出了该选项有什么:文档
注意:
- 红色:必学、
- 黄色:高级需要费点脑子、
- 绿色:稍微说一下就记住、
- 紫色:比较特殊,重点讲、
- 蓝色:不常用,可学可不学、
- 灰色:不需要学习,用到再看文档
options的五类属性:
数据:
- data:内部数据,支持对象和函数,优先用函数。
- props:外部数据,可以是数组或对象,用于接收来自父组件的数据。
- methods:事件处理函数或者是普通函数。
- computed:Vue组件。所有
getter
和setter
的this
上下文自动地绑定为Vue实例。 - watch:一个对象,键是需要观察的表达式,值是对应回调函数。值也是可以是方法名,或者包含选项的对象。
- propsData:创建实例时传递props。主要作用是方便测试。
DOM:
- el:挂载点,提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标。可以用$mount代替。
- template:一个字符串模版作为Vue实例的标识使用。
- renderError:只在开发环境下工作。
- render:字符串模板的代替方案,允许你发挥JavaScript最大的编程能力。
生命周期钩子:
- created:实例创建完成之后被立即调用,但还没挂载到DOM节点。
- mounted:实例挂载到DOM节点后被调用。
- updated:由于数据更改导致的虚拟DOM重新渲染和打补丁,在之后会调用该钩子。
- destroyed:实例销毁后调用。
- activated:被keep-alive缓存的组件激活时调用。
- deactivated:被keep-alive缓存的组件停用时调用。
- beforeCreate:~之前调用
- beforeMount:同上
- beforeUpdate:同上
- beforeDestroy:同上
- errorCaptured
资源:
- components:包含Vue实例可用组件的哈希表。
- filters:包含Vue实例可用过滤器组件的哈希表。
- directives:指令。
组合:
- mixins:复制,减少data等操作。
- extends:也是复制,允许声明扩展另一个组件。
- provide/inject:需要两个一起使用
- provide:一个对象或者返回一个对象的函数。
- inject:一个字符串数组或者一个对象。
- parent
其它:
- 先不看
入门
el
想挂载到想挂载的节点,甚至可以替换挂载的节点。
如
public/index.html :
<div id="jeff">jeff</div>
src/main.js :
new Vue({
el: `#jeff`,
template: `<p>Vue成功</p>`
})
最后在预览链接里可以看到,最后显示的是“Vue成功”。
或者也可以代替 el
属性,效果是一样的:
new Vue({
template:`<p>Vue成功</p>`
}).$mount('#jeff')
data
内部存放数据的,并且支持对象和函数:
new Vue({
data: {
n:0 //存放n属性为0的data对象。
}
})
函数的写法:
new Vue({
data:function(){
return {
n: 0
}
}
//上面可以缩写成:
data(){
return {
n:0
}
}
})
在组件(文件后缀为.vue)里必须使用函数的写法,当然也请尽量写函数的写法。
methods
可以写事件处理的函数:
new vue({
data(){
n:0,
array: [1,2,3,4,5,6,7,8]
},
temoplate: `
<div>
{{n}}
<button @click="add">+1</button>
<hr/>
{{filter()}}
</div>
`,
methods:{
add(){
this.n +=1
},
filter(){
return this.array.filter(i = i % 2 === 0)
}
}
})
components
使用vue组件
import Demo from './Demo.vue'
const vm = new Vue({
components:{
jeff: Demo //把Demo组件命名为jeff
//如果是下面组件名和命名是一致的,那么可以直接简写成:Demo
Demo:Demo
//简写:
Demo
},
template:`
<div>
<jeff/> 这里就使用了名为jeff的组件。
</div>
`
})
上面使用了vue的components,还有一种方法是使用js的写法:
import Demo from './Demo.vue'
vue.component('jeff',{
data(){
return {
n:'jeff'
}
},
template:`
<div>{{n}}</div>
`
})
const vm = new Vue({
template:`
<div>
<jeff/> 这里就使用了名为jeff的组件。
</div>
`
})
组件:
顾名思义,就是可以和别的文件组合在一起的文件。
props
接受外部数据
src/Demo.vue:
<template>
<div class="red">
{{message}}
<button @click="fn">call fn</button>//可以接受props
</div>
</template>
<script>
export default{
props:['message','fn'], //声明两个分别名为‘message’和'fn'的props。
}
</script>
<style>
.red {
border: 1px solid #42b983;
}
</style>
src/main.js
import Demo from './Demo.vue'
new Vue({
components:{Demo},
//下面传了一个字符串“你好”
template:`
<div>
<Demo message="你好">
</div>
`
})
如果我想传一个data里的数据n如下:
需要注意,如果message前面没有加上”:”,就会传字符串。
import Demo from './Demo.vue'
new Vue({
components:{Demo},
data:{
n:0
},
//下面传了一个字符串“n”,没错字符串"n"
template:`
<div>
<Demo message="n">
</div>
`,
//传入一个数据的写法如下:
template:`
<div>
<Demo :message="n">
</div>
`,
//但我又想冒号和字符串怎么办:
template:`
<div>
<Demo :message=" 'n' ">
</div>
`,
//当然也可以传一个函数,注意冒号:
template:`
<div>
<Demo :message="n">
<Demo :fn="add">
</div>
`,
methods:{
add(){
.....
}
}
})
深入进阶
computed
计算属性,类似与公平过滤器,对于绑定到视图的数据进行处理,并监听变化而执行对应的方法。
栗子:
new Vue({
data: {
user: {
email: "1234565@qq.com",
nickname: "小明",
phone: "13234567"
}
},
// DRY don't repeat yourself
// 不如用 computed 来计算 displayName
template: `
<div>
{{user.nickname || user.email || user.phone}}
<div>
{{user.nickname || user.email || user.phone}}
</div>
{{user.nickname || user.email || user.phone}}
</div>
`,
}).$mount("#app");
上面的栗子里,template里的{{user.nickname || user.email || user.phone}}
,如果有 user.nickname
就展示,如果没有而 user.email
有,那么展示,同理 user.phone
。
假如,上面的栗子里,有100个这样的 {{user.nickname || user.email || user.phone}}
,然后需求变了,要求user.nickname
先展示,后展示user.phone
!难道我要把改100次这样的代码吗?
因此计算属性就来了:
new Vue({
data: {
user: {
email: "1234565@qq.com",
nickname: "小明",
phone: "13234567"
}
},
computed: {
displayName(){
const user = this.user;
return user.nickname || user.email || user.phone;
}
},
// DRY don't repeat yourself
// 不如用 computed 来计算 displayName
template: `
<div>
{{displayName}}
<div>
{{displayName}}
<button @click="add">set</button>
</div>
</div>
`
}).$mount("#app");
添加100个 {{displayName}}
就可以了,当然可以配合v-for。
也可以使用函数来改变里面的计算属性(使用getter和setter):
new Vue({
data: {
user: {
email: "1234567@qq.com",
nickname: "小明",
phone: "12345678"
}
},
computed: {
displayName: {
get() {
const user = this.user;
return user.nickname || user.email || user.phone;
},
set(value) {
console.log(value);
this.user.nickname = value;
}
}
},
// DRY don't repeat yourself
// 不如用 computed 来计算 displayName
template: `
<div>
{{displayName}}
<div>
{{displayName}}
<button @click="add">set</button>
</div>
</div>
`,
methods: {
add() {
console.log("add");
this.displayName = "圆圆";
}
}
}).$mount("#app");
小结:计算属性是基于它们的依赖进行缓存的。只在相关依赖发生改变时它们才会重新求值。注意,“displayName”不能在组件的 props
和 data
定义,否则会报错。
watch 侦听
用途:watch是一个侦听的动作,用来观察和响应Vue数据的变化实例上的数据变动。当data发生变化的时候,就会发生一个回调,它有两个参数,一个newValue(修改后的data的数据),一个oldValue(原来的data数据)
简单例子:
new Vue({
el: '#app',
data:{
cityName: 'shanghai'
},
watch: {
cityName(){
console.log('cityName改变了:'+ this.cityName)
}
},
template:`
<div>
<div id="app">
<input type='text' v-model='cityName'/>
</div>
</div>
`
})
运行后发现,每改一次 cityName
,watch就执行一次。
注意:不应该使用箭头函数定义watcher函数。因为箭头函数没有this,它的this会继承它的父级函数,但它的父级函数是window,导致箭头函数的this指向window,而不是Vue实例。
可以写一个撤销功能的代码,如下:
new Vue({
data: {
n: 0,
history: [],
inUndoMode: false
},
watch: {
n: function(newValue, oldValue) {
console.log(this.inUndoMode);
if (!this.inUndoMode) {
this.history.push({ from: oldValue, to: newValue });
}
}
},
template: `
<div>
{{n}}
<hr />
<button @click="add1">+1</button>
<button @click="add2">+2</button>
<button @click="minus1">-1</button>
<button @click="minus2">-2</button>
<hr/>
<button @click="undo">撤销</button>
<hr/>
{{history}}
</div>
`,
methods: {
add1() {
this.n += 1;
},
add2() {
this.n += 2;
},
minus1() {
this.n -= 1;
},
minus2() {
this.n -= 2;
},
undo() {
const last = this.history.pop();
this.inUndoMode = true;
console.log("ha" + this.inUndoMode);
const old = last.from;
this.n = old; // watch n 的函数会异步调用
this.$nextTick(() => {
this.inUndoMode = false;
});
}
}
}).$mount("#app");
- 模拟
computed
,但没有computed
好用,建议能用computed
的场景用computed
。
注意:watch是异步操作!
怎么样才算变化?
watch 只要数据变化就会执行,那么,怎样才算变化?
例子:
new Vue({
data: {
n: 0,
obj: {
a: "a"
}
},
template: `
<div>
<button @click="n += 1">n+1</button>
//属性的值改变
<button @click="obj.a += 'hi'">obj.a + 'hi'</button>
//对象的属性的值改变
<button @click="obj = {a:'a'}">obj = 新对象</button>
//对象的地址改变
</div>
`,
watch: {
n() {
console.log("n 变了");
},
obj:{
handler: function (val, oldVal) {
console.log("obj 变了")
},
},
"obj.a":{
handler: function (val, oldVal) {
console.log("obj.a 变了")
},
}
}
}).$mount("#app");
只要以下之一:
- 属性的值改变
- 对象的属性的值改变
- 对象的地址改变
就算是变化,简单类型看值,复杂类型看对象(地址),和 ===
规则差不多。
handler方法和immediate属性
watch有个特点,最初绑定时是不会执行的,要等到 firstName
改变才执行监听计算,那么要是我想要一开始就可以执行监听,需要怎么做?
可以这样,如下代码:
watch: {
firstName:{
handler(newName, oldName){
this.fulName = newName + ' ' this.lastName;
},
immediate: true
}
}
可以选择给 firstName
绑定一个 handler
方法,之前写的 watch
方法默认写这个,Vue自动去处理这个逻辑,最终编译出来其实就是这个 handler
方法。
而 immediate: true
代表如果在watch里面声明了 firstName
后,就会立刻先去执行里面的 handler
方法,相之在绑定时就不会执行!
watch
的 deep
有什么用?
按刚刚 obj.a
变了,那么 obj
不会变,但一个新的需求:要求 obj.a
变了,那么 obj
也要变!这时就可以使用 deep:true
了(默认:false),如下:
new Vue({
data: {
n: 0,
obj: {
a: "a"
}
},
template: `
<div>
<button @click="obj.a += 'hi'">obj.a + 'hi'</button>
//对象的属性的值改变
</div>
`,
watch: {
"obj.a":{
handler: function (val, oldVal) {
console.log("obj.a 变了")
},
deep: true // 该属性设定在任何被侦听的对象的 property 改变时都要执行 handler 的回调,不论其被嵌套多深
},
}
}).$mount("#app");
watch与computed总结
这两个区别是什么?
答:
computed
是计算属性的意思,watch是监听的意思;- 其次computed调用时不需要加括号,可以当属性用,并根据依赖进行缓存,如果依赖没变就不需要重新计算;
- 而
watch
有immdiate
表示在第一次渲染的时候是否执行这个函数, 另一个是deep
,如果监听的对象是否监听里面的属性的变化。
watch
和 computed
都是数据变化的时候去执行一个函数,面对不同的场景:
- 如果一个数据需要经过复杂的计算就用
computed
- 如果一个数据需要被监听并且对数据做一些操作就用
watch