vue中需要注意的问题总结(上)

前言

使用vue的时候经常会遇到一些问题,其实仔细阅读查阅官方文档,就会发现文档中已提到一些格外需要注意的点; 为了深入的理解官方文档中对这些问题的解释,查阅了一些资料,再加上自己的理解,整理了一些常见的问题;如果哪方面解释的不太合理希望各路大神指出;

文章篇幅较长,但是很实用;

目录

  • 组件里面, data必须是一个函数
  • vue中$set的使用场景
  • vue生命周期详解
  • vue组件通信
  • vue组件之keep-alive
  • 生命周期函数/methods/watch里面不应该使用箭头函数
  • methods/computed/watch

1.组件里面, data必须是一个函数

类比引用数据类型Object是引用数据类型, 每个组件的data 都是内存的同一个地址,一个数据改变了其他也改变了;

那么用什么方法可以使每个组件的data相互独立,不受影响呢?

当一个组件被定义,data 必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。

2.vue中$set的使用场景

场景1:

通过数组的下标去修改数组的值,数据已经被修改了,但是不触发updated函数,视图不更新,

  1. export default {
  2. data () {
  3. return {
  4. items: [ 'a', 'b', 'c']
  5. };
  6. },
  7. updated () {
  8. console.log( '数据更新', this.items[ 0]);
  9. },
  10. methods: {
  11. changeItem1 () {
  12. this.items[ 0] = 'x';
  13. console.log( 111, this.items[ 0]);
  14. },
  15. changeItem2 () {
  16. this.$set( this.items, 0, 'x');
  17. console.log( 222, this.items[ 0]);
  18. },
  19. }
  20. };

执行changeItem1, 控制台打印 111 'x', 没有触发updated,视图不更新执行changeItem2, 控制台打印 222 'x', 数据更新 'x'; 触发updated,视图更新

场景2: vue中检测不到对象属性的添加和删除

  1. data() {
  2. userProfile: {
  3. name: '小明',
  4. }
  5. }

想要给userProfile加一个age属性

  1. addProperty () {
  2. this.userProfile.age = '12';
  3. console.log(555, this.userProfile);
  4. }

执行addProperty函数时,打印如下

555 { name: '小明', age: '12'}

但是没有触发updated, 视图未更新改成下面这种

  1. addProperty () {
  2. this. $set(this.userProfile, 'age', '12');
  3. console.log(666, this.userProfile);
  4. }

再次执行, 数据发生变化, 触发updated, 视图更新;

有时你想向已有对象上添加多个属性,例如使用 Object.assign() 或 _.extend() 方法来添加属性。但是,添加到对象上的新属性不会触发更新。在这种情况下可以创建一个新的对象,让它包含原对象的属性和新的属性:

  1. // 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
  2. this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

这是vue中很典型的一个问题,使用的时候一定要注意!

简单的解释一下原理:

vue在创建实例的时候把data深度遍历所有属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。让 Vue 追踪依赖,在属性被访问和修改时通知变化。所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的。

当你在对象上新加了一个属性newProperty,当前新加的这个属性并没有加入vue检测数据更新的机制(因为是在初始化之后添加的),vue.$set是能让vue知道你添加了属性, 它会给你做处理

3.vue生命周期详解

1. vue的生命周期
  • beforeCreate: 组件实例刚刚被创建,组件属性计算之前,如data属性
  • created: 组件实例创建完成,属性已绑定,但是DOM还未完成,$el属性还不存在
  • beforeMount:模板编译/挂载之前
  • mounted: 模板编译/挂载之后
  • beforeUpdate: 组件更新之前
  • updated: 组件更新之后
  • activated: for keep-alive,组件被激活时调用
  • deactivated: for keep-alive,组件被移除时调用
  • beforeDestroy: 组件销毁前被调用
  • destoryed: 组件销毁后调用

ps:下面代码可以直接复制出去执行

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  7. <title>Document</title>
  8. </head>
  9. <script type="text/javascript" src="https://cdn.jsdelivr.net/vue/2.1.3/vue.js"></script>
  10. <body>
  11. <div id="app">{{a}}</div>
  12. <script>
  13. var vm = new Vue({
  14. el: '#app',
  15. data: {
  16. a: 'vuejs',
  17. },
  18. beforeCreate: function() {
  19. console.log('创建前');
  20. console.log(this.a);
  21. console.log(this.$el);
  22. },
  23. created: function() {
  24. console.log('创建之后');
  25. console.log(this.a);
  26. console.log(this.$el);
  27. },
  28. beforeMount: function() {
  29. console.log('mount之前');
  30. console.log(this.a);
  31. console.log(this.$el);
  32. },
  33. mounted: function() {
  34. console.log('mount之后');
  35. console.log(this.a);
  36. console.log(this.$el);
  37. },
  38. beforeUpdate: function() {
  39. console.log('更新之前');
  40. console.log(this.a);
  41. console.log(this.$el);
  42. },
  43. updated: function() {
  44. console.log('更新完成');
  45. console.log(this.a);
  46. console.log(this.$el);
  47. },
  48. beforeDestroy: function() {
  49. console.log('组件销毁之前');
  50. console.log(this.a);
  51. console.log(this.$el);
  52. },
  53. destroyed: function() {
  54. console.log('组件销毁之后');
  55. console.log(this.a);
  56. console.log(this.$el);
  57. },
  58. })
  59. </script>
  60. </body>
  61. </html>

beforeCreated: el和data并未初始化created: 完成data数据的初始化,el没有beforeMount: 完成了el和data初始化mounted: 完成挂载

title

打开命令行在命令行中输入vm.a = 'change';查看效果

title

4.vue组件通信

1.父组件给子组件传递数据

vue中使用props向子组件传递数据1): 子组件在props中创建一个属性,用于接收父组件传过来的值2): 父组件中注册子组件3): 在子组件标签中添加子组件props中创建的属性4): 把需要传给子组件的值赋给该属性

2.子组件向父组件传递数据

子组件主要通过事件传递数据给父组件1), 子组件中需要以某种方式,例如点击事件的方法来触发一个自定义事件2),将需要传的值作为$emit的第二个参数,该值将作为实参数传给相应自定义事件的方法3),在父组件中注册子组件并在子组件标签上绑定自定义事件的监听

3.子组件向子组件传递数据

vue找那个没有直接子组件对子组件传参的方法,建议将需要传递数据的在组件,都合并为一个组件,如果一定需要子组件对子组件传参,可以先传到父组件,再传到子组件,为了方便开发,vue推出了一个状态管理工具vuex,可以啃方便的实现组件之间的参数传递

具体的实例代码如下:可以自行参考相关代码在编辑器中尝试父组件向子组件传递数据

  1. // 父组件向子组件传递数据
  2. <!--
  3. msg 是在data中(父组件)定义的变量
  4. 如果需要从父组件中获取logo的值,就需要使用props[ 'msg'], 如 30
  5. 在props中添加了元素以后,就不需要在data中(子组件)中再添加变量了
  6. -->
  7. <template>
  8. <div>
  9. <child @transferuser="getUser" :msg="msg"> </child>
  10. <p> 用户名为:{{user}}(我是子组件传递给父组件的数据) </p>
  11. </div>
  12. </template>
  13. <script>
  14. import child from './child.vue';
  15. export default {
  16. components: {
  17. child,
  18. },
  19. data() {
  20. return {
  21. user: '',
  22. msg: '我是父组件传给子组件的信息',
  23. };
  24. },
  25. methods: {
  26. getUser(msg) {
  27. this.user = msg;
  28. console.log(msg);
  29. },
  30. },
  31. };
  32. </script>

子组件向父组件传递数据

  1. // 子组件向父组件传递数据
  2. <!--
  3. 1.@ : 是 v-on的简写
  4. 2.子组件主要通过事件传递数据给父组件
  5. 3.当input的值发生变化时,将username传递给parent.vue,首先声明了一个setUser,用change事件来调用setUser
  6. 4.在setUser中,使用了$emit来遍历transferUser事件,并返回 this.username,其中transferuser是一个自定义事件,功能类似一个中转, this.username通过这个事件传递给父组件
  7. -->
  8. <template>
  9. <div>
  10. <div> {{msg}} </div>
  11. <span> 用户名 </span>
  12. <input v-model="username" @change='setUser'> 向父组件传值 </button>
  13. </div>
  14. </template>
  15. <script>
  16. export default {
  17. data() {
  18. return {
  19. username: '测试',
  20. };
  21. },
  22. props: {
  23. msg: {
  24. type: String,
  25. },
  26. },
  27. methods: {
  28. setUser() {
  29. this.$emit( 'transferuser', this.username);
  30. },
  31. },
  32. };
  33. </script>

5.vue组件之keep-alive

项目中写vue也没注意到<keep-alive></keep-alive>这个组件,最近在深入的研究vue组件的生命周期函数,每一个函数都是干嘛的,然后其中有activateddeactivated这两个函数与<keep-alive></keep-alive>这个组件有关

  • activated: keep-alive组件激活时调用
  • deactivated: keep-alive组件停用时调用

keep-alive用法

  • <keep-alive>包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们
  • <keep-alive>是一个抽象组件:它自身不会渲染一个DOM元素,也不会出现在父组件链中
  • 当组件在<keep-alive>内被切换,它的activateddeactivated这两个生命周期钩子函数将会被对应执行

具体的实例如下

  • 是一个简单的tab切换,可以尝试把<keep-alive>去掉之后,对比一下,然后就会发现它的好处

test.vue

  1. <template>
  2. <div class="test">
  3. <div class="testNav">
  4. <div :class="{'selected':tab === 1,'testTitle':true}" @click="toTab(1)">标题一 </div>
  5. <div :class="{'selected':tab === 2,'testTitle':true}" @click="toTab(2)">标题二 </div>
  6. </div>
  7. <div class="container">
  8. <keep-alive>
  9. <Test1 v-if="tab === 1">
  10. </Test1>
  11. <Test2 v-else>
  12. </Test2>
  13. </keep-alive>
  14. </div>
  15. </div>
  16. </template>
  17. <script>
  18. import Test1 from './test1.vue';
  19. import Test2 from './test2.vue';
  20. export default {
  21. data() {
  22. return {
  23. tab: 1,
  24. };
  25. },
  26. components: {
  27. Test1,
  28. Test2,
  29. },
  30. methods: {
  31. toTab(index) {
  32. this.tab = index;
  33. },
  34. },
  35. }
  36. < /script>
  37. <style lang="less">
  38. .test {
  39. width: 100%;
  40. .testNav {
  41. height: 60px;
  42. line-height: 60px;
  43. display: flex;
  44. border-bottom: 1px solid #e5e5e5;
  45. .testTitle {
  46. flex: 1;
  47. text-align: center;
  48. }
  49. .selected {
  50. color: red;
  51. }
  52. }
  53. }
  54. </style>

测试结果如下:注意看一下页面和控制台输出的信息,可以更加直观的注意到<keep-alive>的作用及activateddeactivated这两个函数什么时候会被触发

  • 打开页面,会出现下面这样1

用setTimeout模拟请求后端接口的场景

  • 点击title2,出现下面的情况2
  • 再次点击title1,出现下面的情况,你会发现从后端请求的数据会快速显示出来,但是如果你此时不用3

test1.vuetest2.vue的相关代码如下:

test1.vue

  1. <template>
  2. <div class="test1">
  3. test1
  4. {{testInfo1}}
  5. </div>
  6. < /template>
  7. <script>
  8. export default {
  9. data() {
  10. return {
  11. testInfo1: '',
  12. };
  13. },
  14. activated() {
  15. console.log('测试1被激活');
  16. },
  17. deactivated() {
  18. console.log('测试1被缓存');
  19. },
  20. created() {
  21. setTimeout(() => {
  22. this.testInfo1 = '这是测试一的数据';
  23. }, 2000);
  24. },
  25. }
  26. </script>

test2.vue

  1. <template>
  2. <div>
  3. test2
  4. {{testInfo2}}
  5. </div>
  6. < /template>
  7. <script>
  8. export default {
  9. data() {
  10. return {
  11. testInfo2: '',
  12. }
  13. },
  14. activated() {
  15. console.log('测试2被激活');
  16. },
  17. deactivated() {
  18. console.log('测试2被缓存');
  19. },
  20. created() {
  21. setTimeout(() => {
  22. this.testInfo2 = '这是测试二的数据';
  23. }, 2000);
  24. },
  25. }
  26. </script>

6. 生命周期函数/methods/watch里面不应该使用箭头函数

es6的箭头函数的出现,是我们可以用更少的代码实现功能,但是应该注意箭头函数和普通函数的最大区别是this的指向问题: 箭头函数的this指向函数所在的所用域,普通函数的this指向函数的调用者;

官方文档中特别提醒中已经指出这一点:

vue中生命周期函数, methods, watch 自动绑定 this 上下文到实例中,因此你可以访问数据,对属性和方法进行运算。这意味着 你不能使用箭头函数来定义一个生命周期方法, 这是因为箭头函数绑定了父上下文,因此 this 与你期待的 Vue 实例不同

7.methods/computed/watch

methods VS computed

我们可以将同一个函数定义为methods或者computed,用这两种方式,得到的结果是相同的,不同的是computed是基于它们的依赖进行缓存的,计算属性只有在它相关的依赖发生改变时才重新求值;

适用场景:

重新计算开销很大的话,选computed; 不希望有缓存的选methods

computed vs watch

watch 有新旧值两个参数, 计算属性没有,但是计算属性可以从setter获得新值

关于computed

对于计算属性要特别说明一点: vue的计算属性computed默认只有getter,需要使用getter的时候需要自己加一个setter

  1. export default {
  2. data () {
  3. return {
  4. firstName: '张',
  5. lastName: '三',
  6. };
  7. },
  8. computed: {
  9. fullName() {
  10. return this.firstName + ' ' + this.lastName
  11. },
  12. },
  13. methods: {
  14. changeFullName () {
  15. this.fullName = '李 四';
  16. }
  17. },
  18. };
  19. 其中computed里的代码完整写法是
  20. computed: {
  21. fullName: {
  22.     // getter
  23. get: function () {
  24. return this.firstName + ' ' + this.lastName
  25. },
  26.  }
  27. },

执行 changeFullName 发现报错[Vue warn]: Computed property "fullame" was assigned to but it has no setter.

我们需要给计算属性fullName添加一个setter

  1. computed: {
  2. fullName: {
  3.     // getter
  4. get: function () {
  5. return this.firstName + ' ' + this.lastName
  6. },
  7. // setter
  8. set: function (newValue) {
  9. var names = newValue.split( ' ')
  10. this.firstName = names[ 0]
  11. this.lastName = names[names.length - 1]
  12. }
  13. }
  14. },

总结

上述这些问题从vue官方文档中均能找到答案,当然想要更深入的理解为什么,还需要从vue源码分析入手;

下一篇文章打算从源码入手去解释这些问题,理解vue整体的程序设计;

vue系列文章


作者:funnycoderstar
链接:https://juejin.im/post/5ad56d86518825556534ff4b
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值