VUE(四)-options(二)-watch和computed

227 阅读6分钟

computed 计算属性

定义:计算属性就是根据其他属性被计算出来的值

语法

computed :{ 
     xxx: function(){
            return yyy           //yyy与data有关
        }
}

是个对象,键名是字符串,键值可以是

①仅读取:函数形式,使用xxx会自动调用

②读取和设置:对象形式,get返回计算出来的值,set修改

缓存

  • 计算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。注意,如果某个依赖 (比如非响应式属性) 在该实例范畴之外,则计算属性是不会被更新的。
  • 如果依赖的属性没有变化,就不会重新计算
  • 但是get / set默认不会做缓存,Vue做了特殊处理
  • computed上的属性也可以放在data上面,但是不容易区分,一个属性管好自己就行
var vm = new Vue({
  data: { a: 1 },
  computed: {
    // 仅读取
    aDouble: function () {
      return this.a * 2
    },
    // 读取和设置
    aPlus: {
      get: function () {
        return this.a + 1
      },
      set: function (v) {
        this.a = v - 1
      }
    }
  }
})
vm.aPlus   // => 2
vm.aPlus = 3
vm.a       // => 2
vm.aDouble // => 4

//在template里,当数据来用,默认为返回值
{{aPlus}}
{{aDouble}}

例一:用户名展示

import Vue from "vue/dist/vue.js";

Vue.config.productionTip = false;

new Vue({
  data: {
    user: {
      email: "fangyinghang@qq.com",
      nickname: "方方",
      phone: "13812312312"
    }
  },
  computed: {
    displayName(){
      const user = this.user
      return user.nickname || user.phone  || user.email
    }
  },

  template: `
    <div>
      {{displayName}}            //自动调用
    </div>
  `
}).$mount("#app");

若想对计算属性读而且修改可以用get set

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) {
        this.user.nickname = value;
      }
    }
  },
  // 不如用 computed 来计算 displayName
  template: `
    <div>
      {{displayName}}
      <div>
      {{displayName}}
      <button @click="add">set</button>
      </div>
    </div>
  `,
  methods: {
    add() {
      this.displayName = "圆圆";
    }
  }
}).$mount("#app");

例二:列表展示

方法一:不用计算属性

全部代码

import Vue from "vue/dist/vue.js";

Vue.config.productionTip = false;

//数据中的users列表,每一项都是一个对象,每个对象都有id属性、name属性和gender属性。所以本来要写四个对象,事不过三,那就写一个函数createUsers吧。
let id = 0;
const createUser = (name, gender) => {  //函数createUsers接受两个参数name和gender,给id加一,之后返回一个我们想写的对象
  id += 1;
  return { id: id, name: name, gender: gender };
};


new Vue({
  data() {
    return {
      users: [                           //users列表,原始数据不能改
        createUser("方方", "男"),
        createUser("圆圆", "女"),
        createUser("小新", "男"),
        createUser("小葵", "女")
      ],
      displayUsers: []                   //不能直接使用users列表,要用可以修改的专门用来展示的展示列表
    };
  },
  created() {
    this.displayUsers = this.users;        //当实例出现在内存中就让展示列表等于users列表
  },
  methods: {
    showMale() {    
      this.displayUsers = this.users.filter(u => u.gender === "男");
    },
    showFemale() {   
      this.displayUsers = this.users.filter(u => u.gender === "女");
    },
    showAll() {
    //当点击全部按钮时,展示列表就是users列表
      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");

方法二:优化,把distplayUsers变成计算属性

  • distplayUsers这个属性是怎么计算出来的呢?把Users列表每一个元素用该元素的属性gender筛选出来的 result = f(users,gender)

  • 所以要有两个数据Users和gender

  • 我点击哪个按钮,就会把数据gender赋值为malefemale或空字符串。然后用数据Users列表和数据gender计算出计算属性displayUsers的值,这样视图就是显示displayUsers

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: ""   //新增加一个数据gender
    };
  },
  
  //distplayUsers这个属性是怎么计算出来的呢?把Users列表每一个元素用该元素的属性gender筛选出来的。
  computed: {
    displayUsers() {
      const { users, gender } = this;          //析构,如果析构的key相同,那么会自动找相同的key
      if (gender === "") {
        return users;
      } else if (gender === "male") {
        return users.filter(u => u.gender === '男');
      } else if (gender === "female") {
        return users.filter(u => u.gender === '女');
      } else {
        throw new Error("gender 的值是意外的值");
      }
    }
  },
  methods: {
   showMale() {    //当点击男按钮时,gender数据为male
      this.gender = 'male'
    },
    showFemale() {   //当点击女按钮时,gender数据为female
      this.gender = 'female'
    },
    showAll()   //当点击全部按钮时,gender数据为""
      this.gender = ""
    }
  },

//视图就是 遍历被算出来的计算属性displayUsers中的每一个对象,取这些对象的名字和性别
  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");

优化代码:把methods里的很简单的事件监听函数直接写入视图

// 引用完整版 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: ""   新增加一个数据gender
    };
  },
  
  //distplayUsers这个属性是怎么计算出来的呢?把Users列表每一个元素用该元素的属性gender筛选出来的。
  computed: {
    displayUsers() {
      const { users, gender } = this;
      if (gender === "") {
        return users;
      } else if (gender === "male") {
        return users.filter(u => u.gender === '男');
      } else if (gender === "female") {
        return users.filter(u => u.gender === '女');
      } else {
        throw new Error("gender 的值是意外的值");
      }
    }
  },
  
//原来的methods已被删掉

//视图就是 遍历被算出来的计算属性displayUsers中的每一个对象,取这些对象的名字和性别
//gender不用写this
  template: `
    <div>
      <div>
      <button @click="gender = """>全部</button>
      <button @click="gender = 'male'">男</button>
      <button @click="gender = 'female'">女</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: ""   新增加一个数据gender
    };
  },
  
  //distplayUsers这个属性是怎么计算出来的呢?把Users列表每一个元素用该元素的属性gender筛选出来的。
  computed: {
    displayUsers() {
      const hash = {   //哈希表
        male: "男",
        female: "女"
      };
       const { users, gender } = this;
      if (gender === "") {
        return users;
      } else if (typeof gender === "string") {  //只要数据gender的值是字符串(female或male)
        return users.filter(u => u.gender === hash[gender]);
      } else {
        throw new Error("gender 的值是意外的值");
      }
    }
  },
  
//原来的methods已被删掉

//视图就是 遍历被算出来的计算属性displayUsers中的每一个对象,取这些对象的名字和性别
  template: `
    <div>
      <div>
      <button @click="gender = """>全部</button>
      <button @click="gender = 'male'">男</button>
      <button @click="gender = 'female'">女</button></div>
      <ul>  
        <li v-for="(u,index) in displayUsers" :key="index">{{u.name}} - {{u.gender}}</li>
      </ul>
      
    </div>
  `
}).$mount("#app");

watch 监听/侦听

定义:当数据变化时执行一个函数

  1. watch是异步的,所有代码执行完才会执行
  2. watch监听n,undo触发n变化,watch等到undo执行完之后才会执行
  3. 何为变化?简单类型看值,复杂类型(对象) 看地址 这其实就是===的规则
  • obj原本是{a:'a'},现在obj = {a:'a'},那么obj变了,obj里的a也变了。因为obj储存了一个对象的地址,现在重新存了一个对象的地址。当然变了
  • obj原本是{a:'a'},现在obj.a = 'b',那么obj.a变了(简单类型,而且值变了),obj没有变(复杂类型,保存的地址没变),obj存的那个对象还是那个对象。
  • n:0, 现在n = 1,那么n变了(简单类型,而且值变了)

语法

 watch: {
    a: function (newVal, oldVal) {
      //新旧根据a自动传值
}
 function回调函数

具体的语法

 c: {
      handler: function (val, oldVal) { /* ... */ },
      deep: true
}
  1. deep表示要不要深入的侦听这个对象,true深入的话该对象里面的属性变化也算变化 
  2. immediate表示第一次渲染时要不要执行函数。全都默认false。
  3. 'object.a' : function( ){ }   handler如果里面有点表示监听对象上的属性记得加引号
  4. n : [fn,f1],调用两个函数
  5.   fn回调函数也可以为定义在method里面的函数直接写字符串就可以
vm.$watch('xxx',fn,{deep:  ;immetiate:  })  //不好看

//如果非要把这样新式的放到new Vue的options里,那就挂在生命周期钩子里
created(){
    this.$watch('xxx',fn,{deep:  ;immetiate:  }) 

}

例子

import Vue from "vue/dist/vue.js";

Vue.config.productionTip = false;

new Vue({
  data: {
    n: 0,
    history: [],
    inUndoMode: false  //首先默认应该在非撤销模式
  },
  watch: {  //侦听n
    n: function(newValue, oldValue) {
      if (!this.inUndoMode) {
     //当inUndoMode为false,也就是非撤销模式,才往history里添加
        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() {  //撤销函数undo会做
      //把history里最后的一项拿出来
      const last = this.history.pop();
      //变成撤销状态,那么就不会往history里加东西了
      this.inUndoMode = true;
      console.log("ha" + this.inUndoMode);
      //把最后一项的from拿出来,就是要变回去的数组
      const old = last.from;
      //把n变回去
      this.n = old; // 注意,这里n变了,所以就会watch n 但是!watch是异步的,所以得等所有代码执行完才会执行watch里的函数!
      //所以,如果直接就变回非撤销模式,然后在执行watch n 函数,还是回往history里加东西
      //所以,我们过一会再回到非撤销状态。那么就按照顺序,先执行watch n 函数,在执行变回非撤销状态
      this.$nextTick(() => {
        this.inUndoMode = false;
      });
    }
  }
}).$mount("#app");

watch还可以模拟computed,监听data的某个属性,属性变化就对属性相应运算,然后给一个data里新的变量,记得加上immediate

computed和watch的区别

  1. computed是计算属性;watch是侦听器
  2. computed是依赖其他属性计算出一个值的,这个值在调用时不需要加括号,可以当一个属性用;根据依赖自动缓存,依赖不变这个值就不会重新计算
  3. watch有两个选项,immediate表示是否在第一次渲染时执行这个函数,deep监听一个对象是否要监听这个对象里面的属性的变化。
  4. watch某个属性变化了就执行一个函数
  5. 如果一个数据依赖于其他数据,那么把这个数据设计为computed的
  6. 如果你需要在某个数据变化时做一些事情, 使用watch来观察这个数据变化
  7. 两个相似的地方就是当属性变化时会做一些事情