Vue3学习 --- 计算属性和watch

969 阅读4分钟

计算属性

在某些情况,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示

对于任何包含响应式数据的复杂交互逻辑,都应该使用计算属性

计算属性会被混入到组件实例中,计算属性的getter和setter中的this会自动被绑定为当前的组件实例对象

模板中书写表达式

<template id="template">
  <h2>{{ `${user.firstName} ---- ${user.lastName}` }}</h2>
  <h2>{{ `${user.firstName} ---- ${user.lastName}` }}</h2>
  <h2>{{ `${user.firstName} ---- ${user.lastName}` }}</h2>
</template>

<script>
  Vue.createApp({
    template: '#template',

    data() {
      return {
        user: {
          firstName: 'Klaus',
          lastName: 'Wang'
        }
      }
    }
  }).mount('#app')
</script>

缺点:

  1. 模板中存在大量的复杂逻辑,不便于维护(模板中表达式的初衷是用于简单的计算)

  2. 当有多次一样的逻辑时,存在重复的代码

  3. 多次使用的时候,很多运算也需要多次执行,没有缓存;

methods中的实现

<template id="template">
  <h2>{{ getFullname() }}</h2>
  <h2>{{ getFullname() }}</h2>
  <h2>{{ getFullname() }}</h2>
</template>

<script>
  Vue.createApp({
    template: '#template',

    data() {
      return {
        user: {
          firstName: 'Klaus',
          lastName: 'Wang'
        }
      }
    },

    methods: {
      getFullname() {
        return `${this.user.firstName} ---- ${this.user.lastName}`
      }
    }
  }).mount('#app')
</script>

缺点:

  1. 我们仅仅只是想要在界面上展示对应的值,但是变成了多次的方法调用
  2. 多次使用方法的时候,没有缓存,也需要多次计算

computed

<template id="template">
    <h2>{{ fullName }}</h2>
    <h2>{{ fullName }}</h2>
    <h2>{{ fullName }}</h2>
  </template>

  <script>
    Vue.createApp({
      template: '#template',

      data() {
        return {
         user: {
           firstName: 'Klaus',
           lastName: 'Wang'
         }
        }
      },

      computed: {
        fullName() {
          return `${this.user.firstName} ---- ${this.user.lastName}`
        }
      }
    }).mount('#app')
  </script>

计算属性 VS Methods

计算属性是有缓存的

计算属性只有在初次调用 或者 依赖发生改变的时候 才会进行计算

<template id="template">
<h2>{{ fullName }}</h2>
<h2>{{ fullName }}</h2>
<h2>{{ fullName }}</h2>

<h2>{{ getFullname() }}</h2>
<h2>{{ getFullname() }}</h2>
<h2>{{ getFullname() }}</h2>
</template>

<script>
  Vue.createApp({
    template: '#template',

    data() {
      return {
        user: {
          firstName: 'Klaus',
          lastName: 'Wang'
        }
      }
    },

    computed: {
      fullName() {
        return `${this.user.firstName} ---- ${this.user.lastName}`
      }
    },

    methods: {
      getFullname() {
        return `${this.user.firstName} ---- ${this.user.lastName}`
      }
    }
  }).mount('#app')
</script>

计算属性本质上是一个对象

完整写法

computed: {
  fullName: {
    get() {
      return `${this.user.firstName} ---- ${this.user.lastName}`
    },

    set(v) {
      const names = v.split(' --- ')
      this.firstName = names[0].trim()
      this.lastName = names[1].trim()
    }
  }
}
methods: {
  // 写成方法的时候,其实是计算属性的语法糖,即只实现了get方法,而没有实现set方法
  getFullname() {
    return `${this.user.firstName} ---- ${this.user.lastName}`
  }
}

watch

开发中我们在data返回的对象中定义了数据,这个数据通过插值语法等方式绑定到template中

当数据变化时,template会自动进行更新来显示最新的数据

但是在某些情况下,我们希望在代码逻辑中监听某个数据的变化,并实现相应的处理逻辑

这个时候就需要用侦听器watch来完成

watch: {
  // 参数1: 新值 参数2: 修改之前的旧值
  fullName(newV, oldV) {
    console.log(newV, oldV)
  }
}

配置选项

watch: {
  fullName: {
    // 深度监听,默认vue对于复杂数据类型只会对引用地址进行监听
    // 所以当一个对象的属性发生改变的时候,是没有办法被监听到的
    // 如果需要被监听到,就必须使用深度监听
    deep: true, 
     
    // 监听器的执行是在渲染完毕以后,对应值发生改变的时候
    // 如果我们希望初次渲染的时候,即刻执行,可以设置immediate为true
    // 此时侦听器就会在初次渲染的时候被执行(也就意味着oldV的值是undefined)
    immediate: true,
    handler(newV, oldV) {
      console.log(newV, oldV)
    }
  }
}

其它写法

字符串形式

watch: {
  // 即将handler函数抽离到methods中进行定义
  fullName: 'handleWatch'
},

methods: {
  handleWatch(newV, oldV) {
    console.log(newV, oldV)
  }
}

数组写法

watch: {
  fullName: [
    'handlefullNameChange',
    
    // vue会自动遍历数组中的值并执行,所以这里的函数可以是一个匿名函数
    function(newV, oldV) {
      console.log(newV, oldV)
    },

    {
      immediate: true,
      handler(newV, oldV) {
        console.log('immediate', newV, oldV)
      }
    }
  ]
},

监听属性

watch: {
  'user.name'(newV, oldV) {
    console.log('immediate', newV, oldV)
  }
}

但是vue是无法监听数组对象成员的变化的

// 下面这种写法是错误的, 值会发生改变,但是并不会触发监听操作
watch: {
  'user[0].name'(newV, oldV) {
    console.log('immediate', newV, oldV)
  }
}

深度监听对象

Vue.createApp({
  template: '#template',

  data() {
    return {
      users: [
        {name:'Klaus'},
        {name:'Steven'},
        {name:'James'}
      ]
    }
  },

  watch: {
    users: {
      deep: true,
      handler(newV, oldV) {
        // 在引用数据类型的监听中 oldV是没有任何的意义的,
        // 对于引用数据类型中的旧值和新值, 仅仅只是引用地址上的赋值,并没有做浅拷贝或深拷贝
        console.log(newV === oldV) // => true
      }
    }
  },

  methods: {
    handleClick() {
      this.users[0].name = 'Alex'
    }
  }
}).mount('#app')

$watch

created() {
  // $watch 的设置方式和在watch option中的设置 效果是一致的
  // 这里第二个handler函数可以是箭头函数,因为外层作用域是created函数,其内部this就是当前组件data的proxy对象
  // $watch 返回一个取消侦听函数,用来停止监听
  this.unwatch = this.$watch('fullName', (newV, oldV) => console.log(newV, oldV), {
    immediate: true,
    deep: true
  })
},
  
beforeUnmount() {
  this.unwatch()
}

Tips: vue3已经移除了侦听器,可以使用计算属性,方法(定义在组件中的局部方法或定义在app.config.globalProperies的全局方法)替代

// 使用计算属性替换过滤器,来为价格前面加上¥
filterBooks() {
  return this.books.map(item => {
    // 为了避免需要展示的数据污染旧的实际进行逻辑判断的数据,
    // 需要对这些复杂类型数据进行浅拷贝
    const newItem = Object.assign({}, item);
    newItem.price = "¥" + item.price;
    return newItem;
  })
}