Vue+TypeScript中处理computed方式


Posted in Vue.js onApril 02, 2022

什么是 「computed」

「computed」 是Vue中提供的一个计算属性。它被混入到Vue实例中,所有的getter和setter的this上下文自动的绑定为Vue实例。计算属性的结果会被缓存,除非依赖的响应式property变化才会从新计算。

「computed」 的用途

我们可以使用 「computed」 对已有的属性数据进行加工处理,得到我们的目标数据。

在TypeScript怎么用

在 「vue-class-component」 中分别为props,watch等提供了对应的装饰器,而 「computed」 却没有对应的装饰器提供。

官网的实例中,「computed」 的功能是通过 「get」 实现的。

<template>
  <input v-model="name">
</template>
 
<script>
import Vue from 'vue'
import Component from 'vue-class-component'
 
@Component
export default class HelloWorld extends Vue {
  firstName = 'John'
  lastName = 'Doe'
 
  // Declared as computed property getter
  get name() {
    return this.firstName + ' ' + this.lastName
  }
 
  // Declared as computed property setter
  set name(value) {
    const splitted = value.split(' ')
    this.firstName = splitted[0]
    this.lastName = splitted[1] || ''
  }
}
</script>

另一种方案

在实际项目中,将组件修改为TypeScript后,使用 get 实现计算属性,浏览器控制台提示data是非响应式的,数据无法显示。组件js版

<template>
  <el-table border :data="data" style="width: 100%;" height="400" @selection-change="selectChange">
    <el-table-column type="selection" width="55"></el-table-column>
    <el-table-column prop="code" label="编码"></el-table-column>
    <el-table-column prop="name" label="名称"></el-table-column>
  </el-table>
</template>
<script>
export default {
  name: 'hierarchy-table',
  props: {
    value: {
      type: Array,
      required: true
    },
    skipCodes: {
      type: Array
    }
  },
  data() {
    return {
    };
  },
  computed: {
    data() {
      return this.skipCodes ? this.value.filter(it => !this.skipCodes.includes(it.code)) : this.value;
    }
  },
  methods: {
    selectChange(selection) {
      this.$emit('selection-change', selection);
    }
  }
};
</script>

鉴于这个问题,使用创建中间变量的方式进行解决。组件ts版

<template>
  <el-table border :data="data" style="width: 100%;" height="400" @selection-change="selectChange">
    <el-table-column type="selection" width="55"></el-table-column>
    <el-table-column prop="code" label="编码"></el-table-column>
    <el-table-column prop="name" label="名称"></el-table-column>
  </el-table>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
 
@Component
export default class HierarchyTable extends Vue {
  data: any[] = [];
 
  @Prop({ type: Array, required: true }) value!: any;
  @Prop({ type: Array }) skipCodes: any;
 
  @Watch('value')
  valueChanged(val) {
    this.updateData();
  }
 
  @Watch('skipCodes')
  skipCodesChanged() {
    this.updateData();
  }
 
  updateData() {
    this.data = this.skipCodes ? this.value.filter(it => !this.skipCodes.includes(it.code)) : this.value;
  }
 
  selectChange(selection) {
    this.$emit('selection-change', selection);
  }
}
</script>
 
<style scoped></style>

vue computed正确使用方式 

最近面试中,遇到一个小伙子,谈到了vue中的computed和watch区别,最后得到了一个让我瞠目结舌的答案,只用watch,从不用computed

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护,所以,对于复杂逻辑,vue 提倡使用计算属性。需要特别说明:计算属性的 getter 函数是没有副作用 (side effect) 的,这使它更易于测试和理解 — from Vue计算属性

讨论 computed 和 watch 之间的区别前,我们先看下 computed 和 methods 有何区别?

computed or methods

理论上,computed 所有实现可以使用 methods 完全替换。

<p>Reversed message: "{{ reversedMessage() }}"</p>
<p>Reversed message: "{{ reversedMessage }}"</p>
// 计算属性
computed: {
  reversedMessage () {
    return this.message.split('').reverse().join('')
  }
}
// 方法
methods: {
  reversedMessage: function () {
    return this.message.split('').reverse().join('')
  }
}

计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage计算属性会立即返回之前的计算结果,而不必再次执行函数。而方法却会执行。

这也同样意味着下面的计算属性将不再更新,因为 Date.now() 不是响应式依赖:

computed: {
  now: function () {
    return Date.now()
  }
}

我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A 。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。

相同之处: computed 和 methods 将被混入到 Vue 实例中。vm.reversedMessage/vm.reversedMessage() 即可获取相关计算属性/方法。

接下来,看下 computed 和 watch 有何区别?

computed or watch

Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch,然而,通常更好的做法是使用计算属性而不是命令式的 watch 回调。

当需要在数据变化时执行异步或开销较大的操作时, watch 方式是最有用的。其允许我们执行异步操作 (访问一个 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

methods: {
  getAnswer: function () {
    this.answer = 'Thinking...'
    var vm = this
    axios.get('https://yesno.wtf/api')
      .then(function (response) {
        vm.answer = _.capitalize(response.data.answer)
      })
      .catch(function (error) {
        vm.answer = 'Error! Could not reach the API. ' + error
      })
  }
},
created: function () {
  // debounce 反弹函数
  this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
}

这样来看,watch 完全可以替代 computed ?什么情况下,只能使用computed呢?

回顾 computed 最大特点就是缓存,所以上述问题可以转换为:哪些情况下,我们需要依赖缓存?

示例:父组件给子组件传值,值的类型为引用类型

父组件

<template>
  <div>
    <child :user="user"></child>
    <label for="user">parent:</label>
    <input id="user" type="text" v-model="user.name">
  </div>
</template>
<script>
import Child from './child.vue'
export default {
  data () {
    return {
      user: { name: 'ligang' }
    }
  },
  components: { Child }
}
</script>

子组件

<template>
  <div>child: {{user}}</div>
</template>
<script>
export default {
  name: 'child',
  props: ['user']
}
</script>

现在有这样一个需求,子组件中需要同时显示改变前和改变后的值。

So Easy,只需要在 watch 中保存 oldVal 即可。

<template>
  <div>
    <div>child:</div>
    <div>修改前:{{oldUser}} 修改后:{{user}}</div>
  </div>
</template>
<script>
export default {
  name: 'child',
  props: ['user'],
  data () {
    return {
      oldUser: {}
    }
  },
  watch: {
    user: {
      handler (val, oldVal) {
        this.oldUser = oldVal || val
      },
      deep: true,
      immediate: true
    }
  }
}
</script>

查看结果,WTF,啥情况~~

Vue+TypeScript中处理computed方式

问题在于,user为引用类型,且 watch 没有做缓存,导致了修改的是同一个对象,所以,watch 方法中**val === olVal is true!!**

如何达到要求呢,这里我们就可以借用 computed 缓存的特性,来完成上述情况。

计算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。注意,如果某个依赖 (比如非响应式属性) 在该实例范畴之外,则计算属性是不会被更新的。 — vue-computed-api

<template>
  <div>
    <div>child:</div>
    <div>修改前:{{oldUser}} 修改后:{{user}}</div>
  </div>
</template>
<script>
export default {
  name: 'child',
  props: ['user'],
  data () {
    return {
      oldUser: {}
    }
  },
  // 缓存 userInfo   
  computed: {
    userInfo () {
      return { ...this.user }
    }
  },
  watch: {
    userInfo: {
      handler (val, oldVal) {
        this.oldUser = oldVal || val
      },
      deep: true,
      immediate: true
    }
  }
}
</script>

需要注意:{ ...this.user } 或者使用 Object.assign({}, this.user) 来创建新的引用!

以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。 

Vue.js 相关文章推荐
如何使用 vue-cli 创建模板项目
Nov 19 Vue.js
Vue-router中hash模式与history模式的区别详解
Dec 15 Vue.js
vue监听滚动事件的方法
Dec 21 Vue.js
vue 动态生成拓扑图的示例
Jan 03 Vue.js
vue自定义组件实现双向绑定
Jan 13 Vue.js
vue element el-transfer增加拖拽功能
Jan 15 Vue.js
vue 计算属性和侦听器的使用小结
Jan 25 Vue.js
VUE实现吸底按钮
Mar 04 Vue.js
解读Vue组件注册方式
May 15 Vue.js
Vue实现跑马灯样式文字横向滚动
Nov 23 Vue.js
vue 数字翻牌器动态加载数据
Apr 20 Vue.js
vue使用watch监听属性变化
Apr 30 Vue.js
Vue+Flask实现图片传输功能
Apr 01 #Vue.js
在vue中import()语法不能传入变量的问题及解决
Apr 01 #Vue.js
Vue中使用import进行路由懒加载的原理分析
Apr 01 #Vue.js
vue使用refs获取嵌套组件中的值过程
Mar 31 #Vue.js
vue ref如何获取子组件属性值
Mar 31 #Vue.js
vue如何使用模拟的json数据查看效果
vue+iview实现手机号分段输入框
Mar 25 #Vue.js
You might like
php 读取shell管道传输过来的内容
2010/03/01 PHP
PHP开发环境配置(MySQL数据库安装图文教程)
2010/04/28 PHP
解析php二分法查找数组是否包含某一元素
2013/05/23 PHP
ThinkPHP静态缓存简单配置和使用方法详解
2016/03/23 PHP
PHP文件上传类实例详解
2016/04/08 PHP
php 截取utf-8格式的字符串实例代码
2016/10/30 PHP
关于php 高并发解决的一点思路
2017/04/16 PHP
用js遍历 table的脚本
2008/07/23 Javascript
js+css 实现遮罩居中弹出层(随浏览器窗口滚动条滚动)
2013/12/11 Javascript
jquery xMarquee实现文字水平无缝滚动效果
2014/04/29 Javascript
jQuery创建自定义的选择器用以选择高度大于100的超链接实例
2015/03/18 Javascript
AngularJS控制器继承自另一控制器
2016/05/09 Javascript
jquery输入数字随机抽奖特效的简单实现代码
2016/06/10 Javascript
angularjs 中$apply,$digest,$watch详解
2016/10/13 Javascript
Bootstrap按钮组简单实现代码
2017/03/06 Javascript
关于Vue实现组件信息的缓存问题
2017/08/23 Javascript
在vue中使用jointjs的方法
2018/03/24 Javascript
jQuery实现动态加载select下拉列表项功能示例
2018/05/31 jQuery
浅谈JavaScript中this的指向问题
2020/07/28 Javascript
vue单应用在ios系统中实现微信分享功能操作
2020/09/07 Javascript
javascript中call,apply,bind的区别详解
2020/12/11 Javascript
[03:59]5分钟带你了解什么是DOTA2(第二期)
2017/02/07 DOTA
Python自定义scrapy中间模块避免重复采集的方法
2015/04/07 Python
Python标准库06之子进程 (subprocess包) 详解
2016/12/07 Python
Python笔记之代理模式
2019/11/20 Python
Python使用Pyqt5实现简易浏览器(最新版本测试过)
2020/04/27 Python
CSS3 linear-gradient线性渐变生成加号和减号的方法
2017/11/21 HTML / CSS
自动化专业个人求职信范文
2013/12/30 职场文书
运动会广播稿80字
2014/01/23 职场文书
酒店仓管员岗位职责
2014/04/28 职场文书
小组名称和口号
2014/06/09 职场文书
redis实现的四种常见限流策略
2021/06/18 Redis
详解JAVA的控制语句
2021/11/11 Java/Android
警用民用对讲机找不同
2022/02/18 无线电
《金肉人》米特&《航海王》阿鹤声优松岛实因胰脏癌去世 享寿81岁
2022/04/13 日漫
Python探索生命起源 matplotlib细胞自动机动画演示
2022/04/21 Python