45·灵魂前端工程师养成-Vue进阶属性computed和watch

2022-11-08 17:10:56 浏览数 (1)

  • 构造选项回顾

  • 进阶属性
    • watch侦听

-曾老湿, 江湖人称曾老大。


-多年互联网运维工作经验,曾负责过大规模集群架构自动化运维管理工作。 -擅长Web集群架构与自动化运维,曾负责国内某大型金融公司运维工作。 -devops项目经理兼DBA。 -开发过一套自动化运维平台(功能如下): 1)整合了各个公有云API,自主创建云主机。 2)ELK自动化收集日志功能。 3)Saltstack自动化运维统一配置管理工具。 4)Git、Jenkins自动化代码上线及自动化测试平台。 5)堡垒机,连接Linux、Windows平台及日志审计。 6)SQL执行及审批流程。 7)慢查询日志分析web界面。


构造选项回顾


Vue的构造选项

每一类属性中都会有很多属性:

数据: - data - props - propsData - computed - methods - watch

DOM - el - template - render - renderError

生命周期钩子 - beforeCreate - created - beforeMount - mounted - beforeUpdate - updated - activated - deactivated - beforeDestroy - destroyed - errorCaptured

资源 - directives - filters - components

组合 - parent - mixins - extends - provide - inject

属性分阶段:

红色属性:好学,必学,几句话就能说明白 黄色属性:高级属性,费点劲,需要单独课程讲解 蓝色属性:不常用,可学,可不学 橙色属性:很不常用,用的时候自己看下文档即可 粉色属性:比较特殊,重点讲解一下

之前我们已经把入门属性都讲完了。

代码语言:javascript复制
#el挂载点
可以用$mount代替

#data内部数据
支持对象和函数,优先使用函数

#methods方法
事件处理函数或者是普通函数

#components
Vue组件,注意大小写
三种引入方式,推荐最后一种

#四个钩子
created,实例出现在内存中
mounted,实例出现在页面中
updated,实例更新了
destroyed,实例从页面和内存中消亡了

#props外部数据
也叫属性
message="n"传入字符串
:message="n"传入this.n数据
:fn="add"传入this.add函数

进阶属性

代码语言:javascript复制
#computed计算属性
不需要加括号
它会根据依赖是否变化来缓存

#watch侦听
一旦data发生变化,就执行的函数
options.watch用法
this.$watch用法
deep,immediate含义

#directives
内置指令v-if/v-for/v-bind/v-on
自定义指令例如:v-focus
指令是为了减少重复的DOM操作

#mixin混入
重复三次之后的出路
混入VS全局混入
选项自动合并
混入就是为了减少重复的构造选项

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

#provides/inject
爷爷想和孙子讲话怎么办?
祖宗想跟它的所有后代讲话怎么办?
答案是全局变量,但是全局变量太low
所以我们需要局部的全局变量

讲之前,我们先回顾一下响应式原理

options.data 会被Vue监听 会被Vue实例代理 每次对data的读写都会被Vue监控 Vue会在data变化时更新UI

这节课讲 data变化时除了更新UI,还能做点啥?


computed计算属性

用途 倍计算出来的属性,就是计算属性 例1:用户名展示

代码语言:javascript复制
import Vue from "vue/dist/vue.js"

Vue.config.productionTip = false

new Vue({
  data:{
    user:{
      email:"133411023@qq.com",
      nickname:"曾老湿",
      phone:"13123456789"
    }
  },
  //不如用computed来计算displayName
  template:`
    <div>
      {{user.nickname || user.email  || user.phone}}
    </div>
  `
}).$mount("#app")

目前我们代码只有一个地方需要展示用户名,但是如果说,后面还有地方使用用户名,我们还得写一遍{{user.nickname || user.email || user.phone}},如果有1万个地方都需要用到这个显示,我们要写1万遍?然后突然...老板娘说,需求改了,我们优先显示手机,然后再显示邮件{{user.nickname || user.phone || user.email}}刺激不?你是不是要改1万遍?万一哪个地方漏改或者少加一个空格那就是代码中的bug,老板娘就是要让你爽...

所以我们写代码的时候,需要遵循一个DRY原则 Don't Repeat Yourself不要重复你自己。

emm...那么,这个时候,我们需要使用计算属性

代码语言:javascript复制
import Vue from "vue/dist/vue.js";

Vue.config.productionTip = false;

new Vue({
  data: {
    user: {
      email: "133411023@qq.com",
      nickname: "曾老湿",
      phone: "13123456789"
    }
  },
  computed:{
    displayName(){
      const user = this.user
      return user.nickname || user.email  || user.phone
    }
  },
  //不如用computed来计算displayName
  template: `
    <div>
      {{displayName}}
    </div>
  `
}).$mount("#app");

使用getter和setter

代码语言:javascript复制
import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false;
new Vue({
  data: {
    user: {
      email: "133411023@qq.com",
      nickname: "曾老湿",
      phone: "13123456789"
    }
  },
  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}}
      <button @click="updateName">set</button>
    </div>
  `,
  methods:{
    updateName(){
      this.displayName = "苍老师"
    }
  }
}).$mount("#app");

例2:列表展示

代码语言:javascript复制
import Vue from "vue/dist/vue.js";
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}}
      </ul>
    </div>
  `
}).$mount("#app");

不使用计算属性,代码如下:

代码语言:javascript复制
import Vue from "vue/dist/vue.js";
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("饭岛爱", "女")
      ],
      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}}
      </ul>
    </div>
  `
}).$mount("#app");

使用计算属性

代码语言:javascript复制
import Vue from "vue/dist/vue.js";
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("饭岛爱", "女")
      ],
      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 === '女')
      }
    }
  },
  methods:{
    showMale(){
      this.gender = 'male'
    },
    showFemale(){
      this.gender = 'female'
    },
    showAll(){
      this.gender = ''
    },
  },
  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}}
      </ul>
    </div>
  `
}).$mount("#app");

使用计算属性,高端写法

代码语言:javascript复制
import Vue from "vue/dist/vue.js";
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("饭岛爱", "女")
      ],
      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 === '女')
      }
    }
  },
  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}}
      </ul>
    </div>
  `
}).$mount("#app");

使用计算属性,更高端写法

代码语言:javascript复制
import Vue from "vue/dist/vue.js";
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("饭岛爱", "女")
      ],
      gender: ''
    };
  },
  computed:{
    displayUsers(){
      const hash = {
        male:'男',
        female:'女'
      }
      const {users,gender} = this;
      if(gender === ''){
        return users
      }else if(typeof gender === 'string'){
        return users.filter(u => u.gender === hash[gender])
      }else{
        throw new Error("gender 的值是意外的值")
      }
    }
  },
  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}}
      </ul>
    </div>
  `
}).$mount("#app");

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

watch侦听


案例1:撤销

代码语言:javascript复制
import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false;

new Vue({
  data:{
    n:0,
    history:[],
    inUndomode:false
  },
  watch: {
    n(newValue,oldValue){
      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')

案例2:模拟computed

代码语言:javascript复制
import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false;

new Vue({
  data:{
    user:{
      email:"zls@qq.com",
      nickname:"曾老湿",
      phone:"1334567890"
    },
    displayName: ""
  },
  watch: {
    'user.email':{
      handler(){
        const {user:{email,nickname,phone}} = this
        this.displayName = nickname || email || phone
      },
      immediate: true // watch的第一次是不监听的,从0到有不算是变化,所以使用immediate第一次也运行
    },
    'user.phone':{
      handler(){
        const {user:{email,nickname,phone}} = this
        this.displayName = nickname || email || phone
      },
      immediate:true
    },
    'user.nickname':{
      handler(){
        const {user:{email,nickname,phone}} = this
        this.displayName = nickname || email || phone
      },
      immediate:true
    }
  },
  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')

说实话,这样做很傻...


watch的deep属性

代码语言:javascript复制
import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false;

new Vue({
  data:{
    n:0,
    obj:{
      a:'a',
      b:'b'
    }
  },
  template: `
    <div>
      <button @click="n  = 1">n 1</button>
      <button @click="obj.a  = 'hi'">obj.a   'hi'</button>
      <button @click="obj = {a:'a',b:'b'}">obj = 新对象</button>
    </div>
  `,
  watch: {
    n(){
      console.log('n 变了')
    },
    obj() {
      console.log('obj 变了')
    },
    "obj.a":function(){
      console.log('obj.a 变了')
    },
    "obj.b":function(){
      console.log("obj.b 变了")
    }
  },
}).$mount('#app')

按理来说,如果是obj.a变了我们就认为obj变了。但是当我们点击obj.a hi的时候,结果只有obj.a变了,obj并没有变化,因为还是存储的原来的内存地址。所以在Vue中,想看看obj里面的内容有没有变化,需要使用deep属性。

deep属性代码如下:首先将obj变成一个对象

代码语言:javascript复制
import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false;

new Vue({
  data:{
    n:0,
    obj:{
      a:'a',
      b:'b'
    }
  },
  template: `
    <div>
      <button @click="n  = 1">n 1</button>
      <button @click="obj.a  = 'hi'">obj.a   'hi'</button>
      <button @click="obj = {a:'a',b:'b'}">obj = 新对象</button>
    </div>
  `,
  watch: {
    n(){
      console.log('n 变了')
    },
    obj: {
      handler(){
        console.log('obj 变了')
      },
      deep:true
    },
  }
}).$mount('#app')

同样点击obj.a hi,就会出现obj变了,就不需要再监听obj.a了


watch的完整用法

语法1 不要使用箭头函数来定义watch

代码语言:javascript复制
watch:{
    o1: () => {}
}
// 这里的this是全局对象,很多前端学JS几年,都没搞清楚this,忘了的同学可以写博客总结,我之前的博客中有介绍this,JS的三座大山,必须搞清楚this

可以使用以下语法

代码语言:javascript复制
watch:{
    o2: functions(value,oldValue){},
    o3(){},
    o4: [f1,f2],
    o5: 'methodName',
    o6: {handler:fn,deep:true,immediate:true},
    'object.a':function(){}
}

语法2

代码语言:javascript复制
vm.$watch('xxx',fn,{deep:...,immediate:..})
//其中'xxx'可以改为一个返回字符串的函数

computed和watch的区别

代码语言:javascript复制
1.computed是计算一个值的
2.computed在调用值的时候不需要加括号,可以当属性去用
3.computed的依赖会自动缓存
4.watch是监听一个值的
5.watch有两个选项,immediate:是否在第一次渲染的时候执行该函数,deep:是否监听对象里面的属性变化
6.watch在方法中会传入newVal和oldVal

0 人点赞