概览
回顾公式:
- 对象.proto === 其构造函数.prototype
- 函数.proto === Function.prototype
所以
- vm.proto === Vue.prototype
- Vue.proto === Function.prototype
- 把Vue的实例命名为vm是尤雨溪的习惯,我们应该沿用
- vm对象封装了对视图的所有操作,包括数据读写、事件绑定、DOM更新
- vm的构造函数是Vue,按照ES6的说法,vm所属的类是Vue
- options是new Vue的参数,一般称之为选项或构造选项(即构造函数后面的选项)
- 接下来一一了解图中的五个问号
一:options里面有什么
补充知识:方法和函数的区别
补充知识:生命周期和钩子
- 生命周期就是代码执行的过程,于Vue而言,生命周期就是created,mounted,updated和destroyed等
- 钩子就是在代码执行过程中的回调函数,也就是代码执行过程中所需的一些逻辑处理。
- 一般而言,生命周期是框架中定义好的,你只需将你的逻辑挂载在生命周期上,称为钩子。
入门属性
el-挂载点(与$mount有替换关系)
-
类型:
string | Element -
限制:只在用 new 创建实例时生效。
-
详细:
提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标。可以是 CSS 选择器,也可以是一个 HTMLElement 实例。
在实例挂载之后,元素可以用 vm.$el 访问。
如果在实例化时存在这个选项,实例将立即进入编译过程,否则,需要显式调用 vm.$mount() 手动开启编译。
-
示例:与$mount有替换关系
//el
new Vue({
el:'#app',//对应html中id=app
render:h=>h(Demo)
})
//mount
new Vue({
render:h=>h(Demo)
}).$mount('#app')
data-内部数据(优先使用函数,避免两个组件共用data的问题)
-
类型:
Object | Function -
限制:组件的定义只接受 function。
-
详细:
Vue 实例的数据对象。Vue 会递归地把 data 的 property 转换为 getter/setter,从而让 data 的 property 能够响应数据变化。对象必须是纯粹的对象 (含有零个或多个的 key/value 对):浏览器 API 创建的原生对象,原型上的 property 会被忽略。大概来说,data 应该只能是数据 - 不推荐观察拥有状态行为的对象。
-
示例
//对象
new Vue({
data:{
n:0
},
template:'',
methods:{}
}).$mount('#app')
//函数
new Vue({
data:function(){//:function可省略
return{
n:0
}
},
template:'',
methods:{}
}).$mount('#app')
methods-方法(事件处理函数/普通处理函数)
-
类型:
{ [key: string]: Function } -
详细:
methods 将被混入到 Vue 实例中。可以直接通过 VM 实例访问这些方法,或者在指令表达式中使用。方法中的 this 自动绑定为 Vue 实例。
注意,不应该使用箭头函数来定义 method 函数 (例如 plus: () => this.a++)。理由是箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向 Vue 实例,this.a 将是 undefined。
-
示例
var vm = new Vue({
data: { a: 1 },
methods: {
plus: function () {
this.a++
}
}
})
vm.plus()
vm.a // 2
componens-使用Vue组件,注意大小写
-
类型:Object
-
详细:
包含 Vue 实例可用组件的哈希表。
-
示例
-
1.引用(优先使用这种方法)
-
2.全局定义
-
3.结合前两种的写法
-
4.文件名尽量全部小写,组件最好首字母大写
四个钩子
- created-实例出现在内存中
- mounted-实例出现在页面中
- updated-实例更新了
- destroyed-实例从页面和内存中消亡了(例:子组件,点按钮就unvisible,当unvisible时就会触发destroyed)
props-外部数据/属性
- 传字符串(message="n")
- 传非字符串,如数字、变量等(加冒号即可,例:①:message="n"传入this.n数据;②:fn="add"传入this.add函数)
- 声名
进阶属性
computed-计算属性
-
类型:
{ [key: string]: Function | { get: Function, set: Function } } -
详细:
计算属性将被混入到 Vue 实例中。所有 getter 和 setter 的 this 上下文自动地绑定为 Vue 实例。
注意如果你为一个计算属性使用了箭头函数,则 this 不会指向这个组件的实例,不过你仍然可以将其实例作为函数的第一个参数来访问。
计算属性的结果会被缓存,除非依赖的响应式 property 变化才会重新计算。注意,如果某个依赖 (比如非响应式 property) 在该实例范畴之外,则计算属性是不会被更新的。
computed: {
aDouble: vm => vm.a * 2
}
-
特征
-
不需要加括号
模板内使用computed时,直接写方法名,不需要在后面加括号
-
会根据依赖是否变化来缓存(相比于methods)
只要message没发生变化,多次访问reversedMessage计算属性会立即返回之前的结果,而不必再次执行函数
相比于methods中写同一个函数,每当出发重新渲染,调用方法就会再次执行函数
缓存的优点在于 : 此时如果computed有个性能开销大的A,需要进行大量的计算,然后有其他computed属性依赖A。如果没有缓存,肯定要多次执行A的getter。
-
import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false;
new Vue({
data: {
user: {
email: "fangyinghang@qq.com",
nickname: "方方",
phone: "13812312312"
}
},
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");
- 示例2
// 引用完整版 Vue,方便讲解
import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false;
let id = 0;
const createUser = (name, gender) => {
id += 1;
return { id: id, name: name, gender: gender };
};
new Vue({
data() {
return {
users: [
createUser("方方", "男"),
createUser("圆圆", "女"),
createUser("小新", "男"),
createUser("小葵", "女")
],
displayUsers: []
};
},
created() {//初始化
this.displayUsers = this.users;
},
methods: {
showMale() {
this.displayUsers = this.users.filter(u => u.gender === "男");
},
showFemale() {
this.displayUsers = this.users.filter(u => u.gender === "女");
},
showAll() {
this.displayUsers = this.users;
}
},
template: `
<div>
<div>
<button @click="showAll">全部</button>
<button @click="showMale">男</button>
<button @click="showFemale">女</button></div>
<ul>
<li v-for="(u,index) in displayUsers" :key="index">{{u.name}} - {{u.gender}}</li>
</ul>
</div>
`
}).$mount("#app");
// 引用完整版 Vue,方便讲解
import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false;
let id = 0;
const createUser = (name, gender) => {
id += 1;
return { id: id, name: name, gender: gender };
};
new Vue({
data() {
return {
users: [
createUser("方方", "男"),
createUser("圆圆", "女"),
createUser("小新", "男"),
createUser("小葵", "女")
],
gender: ""
};
},
computed: {
displayUsers() {
const hash = {
male: "男",
female: "女"
};
const { users, gender } = this;//析构,user和gender直接从this上拿到
if (gender === "") {
return users;
} else if (typeof gender === "string") {
return users.filter(u => u.gender === hash[gender]);
} else {
throw new Error("gender 的值是意外的值");
}
}
},
methods: {
setGender(string) {
this.gender = string;
}
},
template: `
<div>
<div>
<button @click="setGender('') ">全部</button>
<button @click="setGender('male')">男</button>
<button @click="setGender('female')">女</button></div>
<ul>
<li v-for="(u,index) in displayUsers" :key="index">{{u.name}} - {{u.gender}}</li>//v-for循环
</ul>
</div>
`
}).$mount("#app");
watch-监听/侦听
-
类型:
{ [key: string]: string | Function | Object | Array } -
详细:
一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。Vue 实例将会在实例化时调用 $watch(),遍历 watch 对象的每一个 property。
当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
var vm = new Vue({
data: {
a: 1,
b: 2,
obj: {
x:"kiki",
}
},
watch: {
a: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
}
}
})
- 特征
-
一旦data变化,就执行函数
注意,此时的更新是异步的
-
何为变化?
简单类型看值,复杂类型(对象)看地址(其实就是"==="的规则)
-
- 示例1
// 引用完整版 Vue,方便讲解
import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false;
new Vue({
data: {
n: 0,
history: [],
inUndoMode: false//状态监控
},
watch: {
n: function(newValue, oldValue) {//new和old是Vue定义好可以直接用来监听某个值的(这里是监听n),new在前,old在后
console.log(this.inUndoMode);
if (!this.inUndoMode) {
this.history.push({ from: oldValue, to: newValue });
}
}
},
// 不如用 computed 来计算 displayName
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");
-
示例2:用watch模拟computed,很傻。
需要加入handler和immediate的原因是watch默认从无到有的过程不监听
deep含义
template,watch部分增加如下代码
template:`
<div>
<button @click = "a += 1">a+1</button>
<button @click = "obj.x += 1">obj.x + 'hi'</button>
</div>
`,
watch: {
b() {
console.log("b 变了")
},
obj() {
console.log(:"obj 变了")
},
"obj.a":function() {
console.log("obj.a 变了")
}
}
此时如果改变obj.a,控制台会打出"obj.a 变了"而不会打出"obj 变了",因为obj的地址没有改变,只是a的值发生了改变,此时加上deep:true:
obj() {
handler(){
console.log(:"obj 变了")
}
deep:true
}
再次改变obj.a,控制台会打出 "obj变了",无论嵌套多深,只要对象的属性改变,就会被监听。
deep的作用就是,确定监听obj的时候是否往深了看。
watch是异步的,如何在 Vue 中执行延迟回调
Vue.nextTick([callback,context])
参数:
{Function}[callback]
{Object}[context]
vm.msg = 'Hello'
//DOM还没更新
Vue.nextTick(function(){
//Dom更新了
})
watch的完整语法
- 语法一
var vm = new Vue({
data: {
a: 1,
b: 2,
c: 3,
d: 4,
e: {
f: {
g: 5
}
}
},
watch: {
a: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
},
// 方法名
b: 'someMethod',
// 该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深
c: {
handler: function (val, oldVal) { /* ... */ },
deep: true
},
// 该回调将会在侦听开始之后被立即调用
d: {
handler: 'someMethod',
immediate: true
},
// 你可以传入回调数组,它们会被逐一调用
e: [
'handle1',
function handle2 (val, oldVal) { /* ... */ },
{
handler: function handle3 (val, oldVal) { /* ... */ },
/* ... */
}
],
// watch vm.e.f's value: {g: 5}
'e.f': function (val, oldVal) { /* ... */ }
}
})
vm.a = 2 // => new: 2, old: 1
- 语法二
vm.$watch('xxx',function(){...},{deep:...,immediate:...})
- 注意,不应该使用箭头函数来定义 watcher 函数 (例如
searchQuery: newValue => this.updateAutocomplete(newValue))。理由是箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向 Vue 实例,this.updateAutocomplete将是 undefined。(即箭头函数里面没有this,在箭头函数里面用this,指代的只可能是window或者global)
computed和watch的区别
-
computed是计算属性,watch是监听
-
computed是用来计算一个值的,这个值:
- 1.调用时不需要加括号。可以当属性一样用
- 2.根据依赖会自动缓存,如果依赖不变,computed的值就不会重新计算
-
watch是用来监听的,当监听的数据变化时,那么就执行一个函数,里面有两个选项:
- immediate,表示是否在第一次渲染的时候执行这个函数
- deep,用来控制监听对象时是否要看这个对象里面的属性变化(否则的话,就算obj里面的a的值变化,由于obj地址没有变,watch默认obj不变)
-
watch允许我们:
-
1.执行异步操作 (访问一个 API),
-
2.限制我们执行该操作的频率,
-
3.并在我们得到最终结果前,设置中间状态,
这些computed无法做到
-
-
总结:
如果一个数据依赖于其他数据, 那么把这个数据设计为 computed 的
如果你需要在某个数据变化是做一些事情, 使用 watch 来观察这个数据变化
directives-指令
-
类型:
Object -
详细:
包含 Vue 实例可用指令的哈希表。
-
声明一个全局指令 全局的意思是,这个指令在任何组件里都能使用
Vue.directive('x',{
inserted: function (el) {
el.addEventListener('click',()=>{
console.log('x')
})
}
})
比如定义在main.js里,实例引用了其他组件,那么其他组件里都可以使用 v-x 指令。 v-x 被加到哪个元素上,哪个元素就是el。 inserted 表示元素被插入到DOM中后,给el添加一个点击事件。
- 声明一个局部指令
new Vue({
...,
directives:{
'x':{
inserted: function(el) {
el.addEventListener("click", () => {
console.log("x");
});
}
},
'y':directiveOptions
}
})
在一个实例或组件的选项里声明,使用 directives 选项,可以声明多个指令。 v-x 指令只能在该组件里使用,其他地方用不了,包括它引用的组件。
directiveOptions
directiveOptions 里有五个函数属性:
bind(el,info,vnode,oldVnode):只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。(类似created)inserted(参数同上):被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。(类似mounted)update(参数同上):所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新。 (类似updated)componentUpdated(参数同上):指令所在组件的 VNode 及其子 VNode 全部更新后调用。unbind(参数同上):只调用一次,指令与元素解绑时调用。(类似destroyed)
前两个和最后一个比较常用。
函数属性里的四个参数:
-
el:指令所绑定的元素,可以用来直接操作 DOM。 -
info:一个对象,包含以下 property:name:指令名,不包括 v- 前缀。value:指令的绑定值,例如:v-my-directive="1 + 1"中,绑定值为 2。oldValue:指令绑定的前一个值,仅在update和componentUpdated钩子中可用。无论值是否改变都可用。expression:字符串形式的指令表达式。例如v-my-directive="1 + 1"中,表达式为"1 + 1"。arg:传给指令的参数,可选。例如v-my-directive:foo中,参数为"foo"。modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar中,修饰符对象为{ foo: true, bar: true }。
-
vnode:Vue 编译生成的虚拟节点。 -
oldVnode:上一个虚拟节点,仅在update和componentUpdated钩子中可用。
自己实现v-on
import Vue from "vue/dist/vue.js"; // 故意使用完整版
import App from "./App.vue";
Vue.config.productionTip = false;
new Vue({
directives: {
on2: {
// bind 可以改为 inserted
bind(el, info) {
el.addEventListener(info.arg, info.value);
// Vue 自带的 v-on 并不是这样实现的,它更复杂,用了事件委托
},
unbind(el, info) {
el.removeEventListener(info.arg, info.value);
}
}
},
template: `
<button v-on2:click="hi">点我</button>
`,
methods: {
hi() {
console.log("hi");
}
}
}).$mount("#app");
声明一个局部指令,on2。当指令绑到元素上时,给元素添加一个事件监听。事件名现在还不知道,需要用户传,比如是一个click事件,可以通过 info.arg 获取。事件处理函数通过 info.value 获取。
好的习惯是给元素绑定事件监听后,要在一个时间解除绑定。于是在元素消亡时解除事件监听。
指令的作用:减少重复的DOM操作
- Vue 实例/组件主要用于数据绑定,事件监听,DOM更新
- 指令的主要目的就是原生DOM操作
- 而原生的DOM操作就可以封装成Vue指令
- 如果某种DOM操作经常使用,或者比较复杂,就可以封装成指令
mixins-混入/复制
-
类型:
Array<Object> -
详细:
mixins 选项接收一个混入对象的数组。这些混入对象可以像正常的实例对象一样包含实例选项,这些选项将会被合并到最终的选项中,使用的是和 Vue.extend() 一样的选项合并逻辑。也就是说,如果你的混入包含一个 created 钩子,而创建组件本身也有一个,那么两个函数都会被调用。
Mixin 钩子按照传入顺序依次调用,并在调用组件自身的钩子之前被调用。
-
示例一
var mixin = {
created: function () { console.log(1) }
}
var vm = new Vue({
created: function () { console.log(2) },
mixins: [mixin]
})
// => 1
// => 2
- 示例二
假设App.vue上有五个组件,每个组件都有数据name,time; 钩子created, beforeDestroy,被调用时打印提示,并且报出存活时间。
//APP.vue
<template>
<div id="app">
<Child1/>
<button>x</button>
<Child2/>
<button>x</button>
<Child3/>
<button>x</button>
<Child4/>
<button>x</button>
<Child5/>
<button>x</button>
</div>
</template>
<script>
import Child1 from "./components/Child1.vue";
import Child2 from "./components/Child2.vue";
import Child3 from "./components/Child3.vue";
import Child4 from "./components/Child4.vue";
import Child5 from "./components/Child5.vue";
export default {
name: "App",
data() {},
components: {
Child1,
Child2,
Child3,
Child4,
Child5
}
};
</script>
<style>
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
//Child1.vue,2345同
<template>
<div>Child1</div>
</template>
<script>
import log from "../mixins/log.js";
export default {
data() {
return {
name: "Child1"
time: undefined
};
},
created() {
this.time = new Date();
console.log(`${this.name}出生了`);
},
beforeDestroy() {
const now = new Date();
console.log(`${this.name}死亡了,共生存了 ${now - this.time} ms`);
}
};
</script>
通过一个变量控制组件出现或死亡。点击按钮,变量变成false,组件就死亡了,就会调用beforeDestroy 钩子。
混入对象
既然五个组件的选项data,created,beforeDestroy都是一样的,重复写五次太麻烦了。于是就可以使用 mixins 选项。把相同的选项都放到一起,放在一个对象里。然后通过 mixins 选项把这个对象复制过来,和自己的属性合并。
//log.js
const log={
data() {
return {
name: undefined,
time: undefined
};
},
created() {
if (!this.name) {
throw new Error("没有name");
}
this.time = new Date();
console.log(`${this.name} 出生了`);
},
beforeDestroy() {
console.log(`${this.name} 死亡了,存活了 ${new Date() - this.time} ms`);
}
}
export default log
每个组件的name不一样,所以先在mixins对象里定义成undefined,在自己的组件里自己定义name。
使用混入对象的组件
//Child1.vue
<template>
<div>Child1</div>
</template>
<script>
import log from "../mixins/log.js";
export default {
data() {
return {
name: "Child1"
};
},
mixins: [log]//name会自动合并
};
</script>
五个组件只需要import 混入对象,在 mixins 选项里使用混入对象,这个组件就拥有了混入对象里的所有属性,也就是把混入对象的属性都复制过来。
智能合并
当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。
这个例子里,混入对象和组件都有一个name属性,结果是以组件自己选项里的name优先。
同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
指令的作用:减少重复
- directives的作用是减少DOM操作的重复
- mixin的作用是减少data,methods,钩子的重复
extends-继承(用的很少)
-
类型:
Object | Function -
详细:
允许声明扩展另一个组件 (可以是一个简单的选项对象或构造函数),而无需使用 Vue.extend。这主要是为了便于扩展单文件组件。
这和
mixins类似。 -
示例
import Vue from "vue";
const myVue=Vue.extend({
data() {
return {
name: undefined,
time: undefined
};
},
created() {
if (!this.name) {
throw new Error("没有name");
}
this.time = new Date();
console.log(`${this.name} 出生了`);
},
beforeDestroy() {
console.log(`${this.name} 死亡了,存活了 ${new Date() - this.time} ms`);
}
})
export default myVue
使用 Vue.extend 基于基础 的Vue 构造器,创建一个“子类” myVue。然后可以使用 new myVue()
Vue.extend() 里边也可以使用mixins,如:
//写法2
import Vue from "vue";
import log from "./mixins/log.js";
const myVue=Vue.extend({
mixins:[log]
})
export default myVue
在组件里:
//Child1.vue
<template>
<div>Child1</div>
</template>
<script>
import log from "../mixins/log.js";
export default {
extends:myVue,
data() {
return {
name: "Child1"
};
}
};
</script>
这里extends不是数组,因为只能继承一个
总结
能用mixins,就用mixins。因为同样都是先导入,再加一个mixins/extends选项。
除非你有很多个mixins,不想都写一遍。就把这么多mixins一次性写在Vue.extend({}) 里边,然后只写一个extends。不过这种情况也很少见
provide & inject-提供和注入
-
类型:
provide:
Object | () => Objectinject:
Array<string> | { [key: string]: string | Symbol | Object } -
详细:
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。
provide选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。在该对象中你可以使用 ES2015 Symbols 作为 key,但是只在原生支持Symbol和Reflect.ownKeys的环境下可工作。-
inject选项应该是:- 一个字符串数组,或
- 一个对象,对象的 key 是本地的绑定名,value 是:
- 在可用的注入内容中搜索用的 key (字符串或 Symbol),或
- 一个对象,该对象的:
fromproperty 是在可用的注入内容中搜索用的 key (字符串或 Symbol)defaultproperty 是降级情况下使用的 value
-
提示:
provide和inject绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。
- 示例
-
需求
一键换肤:点击一个按钮,整个页面的按钮和文字颜色变化。
-
结构
我们采用一个三层嵌套的组件。
最上层是App.vue,它包含五个Child组件。
第二层是Child1.vue(2/3/4/5),它包含一段文本Child1,和切换颜色组件。
第三层是ChangeThemeButton.vue,它只有一个“换肤”按钮。
-
//App.vue
//通过类名的改变,切换颜色。
//data里有一个 themeName 数据,默认是 blue 。
//当变为 red 时,通过CSS样式变成红色。
<template>
<div :class="`app theme-${themeName}`">
<Child1/>
<button>x</button>
<Child2/>
<button>x</button>
<Child3/>
<button>x</button>
<Child4/>
<button>x</button>
<Child5/>
<button>x</button>
</div>
</template>
<script>
import Child1 from "./components/Child1.vue";
import Child2 from "./components/Child2.vue";
import Child3 from "./components/Child3.vue";
import Child4 from "./components/Child4.vue";
import Child5 from "./components/Child5.vue";
export default {
name: "App",
data() {
return {
themeName: "blue", // 'red'
fontSize: "normal" // 'big' | 'small'
};
},
components: {
Child1,
Child2,
Child3,
Child4,
Child5
}
};
</script>
<style>
.app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.app.theme-blue button {
background: blue;
color: white;
}
.app.theme-blue {
color: darkblue;
}
.app.theme-red button {
background: red;
color: white;
}
.app.theme-red {
color: darkred;
}
</style>
//Child1.vue
<template>
<div>Child 1
<change-theme-button/>//注意大小写,不把`大写`改成`-小写`也可以
</div>
</template>
<script>
import ChangeThemeButton from "./ChangeThemeButton.vue";
export default {
components: {
ChangeThemeButton
}
};
</script>
//ChangeThemeButton.vue
<template>
<div>
<button>换肤</button>
</div>
</template>
我们需要在第三层组件里改变最上层组件的一个data。 需要用到 provide ,在最上层组件里把需要用到的数据提供出来。即 themeName
//App.vue
<script>
import Child1 from "./components/Child1.vue";
import Child2 from "./components/Child2.vue";
import Child3 from "./components/Child3.vue";
import Child4 from "./components/Child4.vue";
import Child5 from "./components/Child5.vue";
export default {
name: "App",
provide() {
return {
themeName: this.themeName,
fontSizeNmae: this.fontSizeName,
changeTheme: this.changeTheme, // 如果不生效,刷新一下 codesandbox
changeFontSize: this.changeFontSize
};
},
data() {
return {
themeName: "blue", // 'red'
fontSizeName: "normal" // 'big' | 'small'
};
},
methods: {
changeTheme() {
if (this.themeName === "blue") {
this.themeName = "red";
} else {
this.themeName = "blue";
}
},
changeFontSize(size) {
if (["normal", "big", "small"].indexOf(size) === -1) {
throw new Error(`wront size: ${size}`);
}
this.fontSizeName = size;
}
},
components: {
Child1,
Child2,
Child3,
Child4,
Child5
}
};
</script>
<style>
.app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.app.theme-blue button {
background: blue;
color: white;
}
.app.theme-blue {
color: darkblue;
}
.app.theme-red button {
background: red;
color: white;
}
.app.theme-red {
color: darkred;
}
.app.fontSize-normal {
font-size: 16px;
}
.app button {
font-size: inherit;
}
.app.fontSize-small {
font-size: 12px;
}
.app.fontSize-big {
font-size: 20px;
}
</style>
因为this.themeName是一个字符串,所以提供出去的themeName是原本字符串的复制品。即,如果在其他地方修改themeName,是不会影响原本的数据的
所以在按钮组件里,注入这个变量后,不能点击按钮直接修改 this.themeName。
那怎么办呢?
在你这里修改不好使,那我(App.vue)在我自己身上写一个函数,修改我自己的数据,然后把这个函数提供给你,你注入这个函数,就可以使用了。
//ChangeThemeButton.vue
<template>
<div>
<button @click="changeTheme">换肤</button>
<button @click="changeFontSize('big')">大字</button>
<button @click="changeFontSize('small')">小字</button>
<button @click="changeFontSize('normal')">正常字</button>
</div>
</template>
<script>
export default {
inject: ["themeName", "changeTheme", "changeFontSize"]
};
</script>
可以看到,provide/inject选项类似于祖先栽树,后人乘凉。
在最上边把我的方法,数据都提供好,下边的子孙们谁都可以用,谁想用就inject,注入到自己身上,就是this上。
作用:大范围的data和methods共用。
不能只传themeName,不传changeTheme方法。因为我提供出去的时候,themeName的值是被复制给provide的,和原来的不是同一个,所以改变复制出去的值,并不会影响原本的值。
也可以在定义这个data的时候,把字符串改成对象,提供出去的就是引用。所以在按钮组件里直接修改是可以改变原本的值的。但是不推荐这样做。容易失控