React 与其说是一种框架,倒不如说是一种开发范式。它的核心理念非常简单:

界面/视图就是数据结构的可视化表达
UI = f(data)

而界面/视图由组件组合而来
UI = f1(data) + f2(data) + f3(data) + ...

That's all.

React 扮演的角色就是上述公式中的 f,它完全是函数式的,组件就是函数,给它一个输入(传参),它就返回一个输出(UI)。只有理解了这种开发理念,用 React 才会得心应手。如果只是去学“语法”,仅满足于“能用”,React 确实会显得非常怪异而麻烦。

Vue 的核心理念与 React 其实是一致的,但在 API 的实现上 Vue 对于用户而言更加友好。不像 React,Vue 不会要求用户“Think in Vue”,因为不需要,Vue 是一个渐进式的框架,它并没有大幅改变用户原有的开发方式,它既可以作为插件植入老的 jQuery 项目,也可以作为整个项目的核心框架层。并且 Vue 提供了各种实用的 API,而 React 本身的 API 非常少,许多功能需要自行封装或引用现成的组件。

前言

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

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

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

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

想要给userProfile加一个age属性

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

执行addProperty函数时,打印如下

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

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

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

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

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

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

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

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

4.vue组件通信

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

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

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

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

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

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

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

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

子组件向父组件传递数据// 子组件向父组件传递数据<!--

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

export default {
     name: 'parent',
     data: function () {
},
components: {
     child
},
methods: {
    fn: function (msg) { //回调方法,接收子组件传的参数
         this.msg = msg;
}
},
mounted: function () {
});
}
};

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

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

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

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

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

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

test1.vue

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

test2.vue

  1. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
  1. <template>
    <div>
    test2
    {{testInfo2}}
    </div>
    </template>
  2.  
  3. <script>
    export default {
    data() {
    return {
    testInfo2: '',
    }
    },
    activated() {
    console.log('测试2被激活');
    },
    deactivated() {
    console.log('测试2被缓存');
    },
    created() {
    setTimeout(() => {
    this.testInfo2 = '这是测试二的数据';
    }, 2000);
    },
    }
    </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. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
  1. export default {
    data () {
    return {
    firstName: '张',
    lastName: '三',
    };
    },
    computed: {
    fullName() {
    return this.firstName + ' ' + this.lastName
    },
    },
    methods: {
    changeFullName () {
    this.fullName = '李 四';
    }
    },
    };
  2.  
  3. 其中computed里的代码完整写法是
  4.  
  5. computed: {
    fullName: {
        // getter
    get: function () {
    return this.firstName + ' ' + this.lastName
    },
     }
    },

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

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

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

总结

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

vue中需要注意的问题总结(上)的更多相关文章

  1. (Vue中)cehart在同一个dom上画图图切换时饼图有折线图的坐标系

    网上都是别人转载的,下面是转载的代码,在Vue中根本不适用 var echartrunningstate = null; if (echartrunningstate && echar ...

  2. vue中使用cropperjs进行图片裁剪上传

    下面代码直接就可以复制使用了,但是需要在本地下个cropperjs,下载命令:npm install cropperjs --save-dev <template> <div id= ...

  3. vue 中数据没有同步渲染的解决方法

    今天在做一个页面,遇到一个数据渲染不同步的问题,如下: 代码如下:原理:点击时,对应的banklist 的选项选项变为 true 选中状态 html: <div class="PayO ...

  4. vue中$nextTick详细讲解保证你一看就明白

    1.功能描述 今天我们要实现这个一个小功能: 页面渲染完成后展示一个div元素: 当点击这个div元素后: div元素消失: 出现一个input元素:并且input元素聚焦 想必大家我觉得简单,我们一 ...

  5. vue 中使用iconfont Unicode编码线上字体图标的流程

    1.打开http://www.iconfont.cn官网,搜索你想要的图标.添加字体图标到购物车,点击购物车然后添加至项目,点击确定 2.点击图标管理/我的项目,找到对应的文件,点击Unicode,然 ...

  6. vue中el-upload上传多图片且携带参数,批量而不是一张一张的解决方案

    现在前端基本不是vue技术栈就是react技术栈. vue技术栈最常用的就是element-ui的ui框架了. 在项目中,我们经常会碰到这种需求:批量上传文件 element-ui 确实也为我们提供了 ...

  7. Vue基础系列(四)——Vue中的指令(上)

    写在前面的话: 文章是个人学习过程中的总结,为方便以后回头在学习. 文章中会参考官方文档和其他的一些文章,示例均为亲自编写和实践,若有写的不对的地方欢迎大家和我一起交流. VUE基础系列目录 < ...

  8. vue中的文件上传和下载

    文件上传 vue中的文件上传主要分为两步:前台获取到文件和提交到后台 获取文件 前台获取文件,主要是采用input框来实现 <el-dialog :title="addName&quo ...

  9. vue中解决时间在ios上显示NAN的问题

    最近在用vue,遇到倒计时在ios上显示为NAN的问题. 因为做的是倒计时支付,思路是获取服务器时间和下单时间,再转成秒级时间戳做差值. 在网上找到说是ios 不支持例如2018-09-01 10:0 ...

随机推荐

  1. noip模拟赛 赤の夜

    题目背景 下发压缩包链接: https://pan.baidu.com/s/1geC4ooz 密码: 3vpt(同T1) 正在想这么说的时候—— 突然涌出一种强烈的晕眩感. 这是,什么……? 眼花吗? ...

  2. 并行输入\输出控制器之我见(PIO)

    中断信号FIQ及IRQ0到IRQn一般通过PIO控制器复用.但是,由于PIO控制器对于输入无效且中断线(FIQ或IRQ)仅作为输入,因此不必为中断分配I/0线.          电源管理控制器控制P ...

  3. 【ACM】poj_1579_Function Run Fun_201308121654

    Function Run FunTime Limit: 1000MS  Memory Limit: 10000K Total Submissions: 14940  Accepted: 7736 De ...

  4. Spring MVC 入门(一)

    什么是 Spring MVC 学习某一样东西之前,我们一定要大致知道这个东西是什么,能干什么,为什么要用它. Spring MVC 是一个开源平台,一个基于 Spring 的 MVC 框架,它支持基于 ...

  5. 解决vim粘贴时格式混乱的问题

    vim 粘贴时格式混乱的问题,是由于缩进导致的. --------------------------------------------------------------- 原文: http:// ...

  6. iOS 9 平台上 AFNetworking 框架 3.0 版本号解决的问题和问题解决

    iOS 9 平台上 AFNetworking 框架 3.0 版本号解决的问题和问题解决 太阳火神的漂亮人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名- ...

  7. Android 高亮指示层提示

    此库出自翔神之手   用起来绝对方便 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0J ...

  8. Java命名规则详细总结

    Class名应是首字母大写的名词.命名时应该使其简洁而又具有描述性.异常类的命名,应以Exception结尾.Interface的命名规则与Class相同 1. JAVA源文件的命名 JAVA源文件名 ...

  9. mongodb 3.2配置内存缓存大小为MB/MongoDB 3.x内存限制配置

    mongodb 3.2配置内存缓存大小为MB/MongoDB 3.x内存限制配置 转载自勤奋的小青蛙 mongodb占用内存非常高,这是因为官方为了提升存储的效率,设计就这么设计的. 但是大部分的个人 ...

  10. poj 1061(扩展欧几里得定理求不定方程)

    两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面.它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止.可是它们出发之前忘记了一件很重要的事情,既没有问清楚对方的特 ...