Vue构造选项(二)

芝麻凛 2021年09月13日 173次浏览

Vue构造选项(二)

响应式原理

options.data

  • 当我们把options.data传给Vue之后
  • options.data会被Vue监听之后进行篡改、会被Vue实例代理
  • 每次对data的读写都会被Vue监控
  • Vue会在data变化时更新UI

进阶属性

computed - 计算属性

  • 不需要加括号
  • 它会根据依赖是否变化来缓存

watch - 侦听

  • 一旦data变化,就会执行的函数
  • options.watch用法、this.$watch用法、deep/immediate含义

directives - 指令

  • 内置指令 v-if 、v-for、v-bind、v-on
  • 自定义指令, 如 v-focus,指令是为了减少重复的DOM操作

mixin - 混入

  • 重复三次之后的出路
  • 混入 v.s. 全局混入
  • 选项自动合并
  • 混入就是为了减少重复的构造选项

extends - 继承

  • 先了解Vue.extend,是用了mixin 还是 重复
  • 自己写一个View,它继承Vue,还可以预先定义其它构造选项
  • 继承就是为了减少重复的构造选项,那为什么不用ES6 的extends

provide/inject

  • 爷爷想和孙子讲话,祖先想和它的所有后代讲话
  • 可以使用全局变量,但是全局变量太low
  • 所以需要使用局部的全局变量

computed - 计算属性

用途

  • 被计算出来的属性就是计算属性

  • //用户名展示
    // 引用完整版 Vue
    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";
    import App from "./App.vue";
    
    Vue.config.productionTip = false;
    let id = 0;
    const createUser = (name, gender) => {
      id += 1;
      return { id, name, gender };
    };
    new Vue({
      data() {
        return {
          users: [
            createUser("方方", "男"),
            createUser("圆圆", "女"),
            createUser("小新", "男"),
            createUser("小葵", "女")
          ]
        };
      },
      // 如何给三个按钮加事件处理函数
      // 思路一:点击之后改 users
      // 思路二:使用 computed
      template: `
        <div>
          <div><button>全部</button><button>男</button><button>女</button></div>
          <ul>
            <li v-for="u in users" :key="u.id">
              {{u.name}} - {{u.gender}}
            </li>
          </ul>
        </div>
      `
    }).$mount("#app");
    
    

缓存

  • 如果依赖的属性没有变化,就不会重新计算
  • getter/setter默认不会做缓存,Vue做了特殊处理

缓存示例

let obj1 = {
  姓: "高",
  名: "圆圆",
  get 姓名() {
    console.log('将姓与名相加')
    return this.姓 + this.名;
  },
  set 姓名(xxx){
    this.姓 = xxx[0]
    this.名 = xxx.slice(1)
  },
  age: 18
};


console.log(obj1.姓名)
console.log(obj1.姓名)
console.log(obj1.姓名)

console.log('----------------') // 精髓

// 为什么每次都要重新计算?如果计算很复杂,不就很浪费时间吗?
// 那就缓存一下吧
// 缓存是什么?就是哈希表
const cache = {} // {'高':{'圆圆': '高圆圆'}}
let obj2 = {
  姓: "高",
  名: "圆圆",
  get 姓名() {
    // 由于对象不支持 ['高','圆圆'] 数组作为 key,只能变通一下
    if(this.姓 in cache && this.名 in cache[this.姓]){
      console.log('有缓存')
      return cache[this.姓][this.名]
    }
    // 如果 cache[this.姓] 不存在,就赋值为 {}
    // 如果 cache[this.姓] 存在,就赋值为它自己(相当于什么都不做)
    // 别 TM 老问
    cache[this.姓] = cache[this.姓] || {} // 保底值
    cache[this.姓][this.名] = this.姓 + this.名
    console.log('将姓与名相加')
    return cache[this.姓][this.名];
  },
  set 姓名(xxx){
    this.姓 = xxx[0]
    this.名 = xxx.slice(1)
  },
  age: 18
};

console.log(obj2.姓名)
console.log(obj2.姓名)
console.log(obj2.姓名)

Watch 监听/侦听(一)

用途

  • 当数据变化时,执行一个函数

    // 撤销
    import Vue from "vue/dist/vue.js";
    
    Vue.config.productionTip = false;
    
    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 });
          }
        }
      },
      // 不如用 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");
    
    
    // 模拟computed
    import Vue from "vue/dist/vue.js";
    
    Vue.config.productionTip = false;
    
    new Vue({
      data: {
        user: {
          email: "fangfang@qq.com",
          nickname: "方方",
          phone: "13812312312"
        },
        displayName: ""
      },
      watch: {
        "user.email": {
          handler: "changed",
          immediate: true // 第一次渲染是也触发 watch
        },
        "user.nickname": {
          handler: "changed",
          immediate: true // 第一次渲染是也触发 watch
        },
        "user.phone": {
          handler: "changed",
          immediate: true // 第一次渲染是也触发 watch
        }
      },
      // 不如用 computed 来计算 displayName
      template: `
        <div>
           {{displayName}}
           <button @click="user.nickname=undefined">remove nickname</button>
        </div>
      `,
      methods: {
        changed() {
          console.log(arguments);
          const user = this.user;
          this.displayName = user.nickname || user.email || user.phone;
        }
      }
    }).$mount("#app");
    
    

    什么是变化?

    import Vue from "vue/dist/vue.js";
    
    Vue.config.productionTip = false;
    
    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() {
          console.log("obj 变了");
        },
        "obj.a": function() {
          console.log("obj.a 变了");
        }
      }
    }).$mount("#app");
    
    
    • obj原本是{a:‘a’},现在obj = {a:’a’}。
    • obj变了没有?变了,因为它的地址变了,obj.a变了没有?没有因为它还是字符串“a”
    • 简单类型看值,复杂类型(对象)看地址,这其实就是 === 的规则

watch - 侦听 (二)

语法一 watch文档

watch:{
    // o1: () => {}, 不能用箭头函数,这里的this是全局对象
    o2: function(value,oldValue){},
    o3(){},
    o4: [f1, f2],
    o5: 'methodName',
    o6: {handler:fn, deep:true, immediate:true},'object.a': function(){}
}

语法二

  • vm.$watch(‘xxx’,fn,
  • 其中 ‘xxx’ 可以改为一个返回字符串的函数

deep:true 是干什么的?

  • 如果 object.a 变了,请问object 要不要也变了
  • 如果需要的是 [也变了] ,那么就用deep:true
  • 如果需要的是 [没有变] ,那么就用deep:false
  • deep 的意思是,监听 object 的时候是否往深了看。

computed 和 watch 的区别

computed

  • 看上去是方法,实际上是计算属性,用来计算出一个值
  • 这个值在调用的时候不需要加()
  • computed的值在getter执行后缓存,如果这个依赖不变,computed 不会重复计算

watch

  • watch 是一个 data 的数据监听回调,当依赖的data数据变化是 执行回调,它有两个重要选项
  • immediate 表示是否需要在第一次渲染的时候执行这个函数
  • deep 如果监听这个对象,是否需要关注对象里面属性的变化

总结

  1. 如果一个数据依赖于其他数据,那么把这个数据设计为computed
  2. 如果需要在某个数据变化时做一些事情,使用watch来观察这个数据变化