Vue实例-概览&options里的属性

269 阅读9分钟

概览

image.png

回顾公式:

  • 对象.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里面有什么

image.png

image.png

补充知识:方法和函数的区别

image.png

补充知识:生命周期和钩子

  • 生命周期就是代码执行的过程,于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.引用(优先使用这种方法) image.png

image.png

  • 2.全局定义
    image.png

  • 3.结合前两种的写法 image.png

  • 4.文件名尽量全部小写,组件最好首字母大写

四个钩子

  • created-实例出现在内存中
  • mounted-实例出现在页面中
  • updated-实例更新了
  • destroyed-实例从页面和内存中消亡了(例:子组件,点按钮就unvisible,当unvisible时就会触发destroyed)

props-外部数据/属性

  • 传字符串(message="n")

image.png

  • 传非字符串,如数字、变量等(加冒号即可,例:①:message="n"传入this.n数据;②:fn="add"传入this.add函数)

image.png

  • 声名

image.png

进阶属性

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。

  • 示例1

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");

// 引用完整版 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:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。
    • 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 :上一个虚拟节点,仅在 updatecomponentUpdated 钩子中可用。

自己实现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 | () => Object

    inject:Array<string> | { [key: string]: string | Symbol | Object }

  • 详细:

    这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。

    provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。在该对象中你可以使用 ES2015 Symbols 作为 key,但是只在原生支持 SymbolReflect.ownKeys 的环境下可工作。

    • inject 选项应该是:

      • 一个字符串数组,或
      • 一个对象,对象的 key 是本地的绑定名,value 是:
        • 在可用的注入内容中搜索用的 key (字符串或 Symbol),或
        • 一个对象,该对象的:
          • from property 是在可用的注入内容中搜索用的 key (字符串或 Symbol)
          • default property 是降级情况下使用的 value

提示:provideinject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 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的时候,把字符串改成对象,提供出去的就是引用。所以在按钮组件里直接修改是可以改变原本的值的。但是不推荐这样做。容易失控

进阶属性总结

image.png