vue踩坑总结 & 优化点_说说你使用 vue 框架踩过最大的坑是什么?怎么解决的?-程序员宅基地

技术标签: JS相关  VUE  前端面试  

最近连着做了好几个vue项目,从0到版本迭代,vue教程算是好理解,把vue官方文档看一遍下来,基本上不会有什么大问题(嗯如果你看的够仔细够透彻的话),此篇是记录vue踩过的坑以及可以优化的地方。

1.踩坑

1.1.深拷贝/浅拷贝

这实际上算不上vue的问题,算是js基础没打好的坑吧。

先来看一个简单的例子:

let obj = {name:'fiona-SUN'};
let copyObj = obj;
copyObj.name = 'fiona';
console.log(copyObj.name);  // 'fiona'
console.log(obj.name);     // 'fiona'

 在js中也有栈(stack)和堆(heap)的概念:

  • 栈:自动分配的内存空间,大小确定会自动释放。存放变量/局部变量/形参等。在js中存放简单数据段(五种基本数据类型:Number、String、Boolean、Null、Undefined),他们是按值存放的,可以直接访问。
  • 堆:动态分配的内存,大小不定并且不会自动释放。存放在堆内存中的对象,栈中的变量实际保存的是一个指针,这个指针指向堆中的某一个位置。

所以上述例子中,属于浅拷贝,当我们声明一个对象,由于他不属于五种基本数据类型(即非简单数据段),栈中会存放一个我们声明的obj变量,它指向了堆中实际的这个对象的地址。当我们把这个引用地址赋值给了copyObj,实际它获得的是一个与obj一致的指向堆中的地址。当copyObj改变了指向的对象地址的实际的值的时候,obj拿到的值也就自然而然变化了。看图理解

嗯,道理我都懂,但是写代码我就自然而然的忽略了,该反思。。。

深拷贝的方法

  • 方法一:逐个去拿到简单数据项(网上可以搜到递归解决,思路类似)
let obj = {name:'fiona-SUN'};

let copyFunc = (originObj) => {
  let copyObj = {};
  for(let key in originObj){
    copyObj[key] = originObj[key];
  }
  return copyObj;
};

let copyObj = copyFunc(obj);
copyObj.name = 'fiona';
console.log(copyObj.name);  // 'fiona'
console.log(obj.name);     // 'fiona-SUN'

 

  • 方法二:通过JSON去解析
let obj = {name:'fiona-SUN'};

let copyObj = JSON.parse(JSON.stringify(obj));

copyObj.name = 'fiona';
console.log(copyObj.name);  // 'fiona'
console.log(obj.name);     // 'fiona-SUN'

 

  • 方法三:es6之展开Object.assign(拷贝obj的内容到一个新的堆内存,copyObj存储新内存的引用)
    let obj = {name:'fiona-SUN'};
    
    let copyObj = Object.assign({}, obj);
    
    copyObj.name = 'fiona';
    console.log(copyObj.name);  // 'fiona'
    console.log(obj.name);     // 'fiona-SUN'

  • 方法四:es6之展开运算符(仅用于数组)
  • let arr = [1,2,3];
    let copyArr = [...obj];
    copyArr[2] = 0;
    console.log(copyArr[2]);  // 0
    console.log(arr[2]);     // 2

    1.2.列表更新检测

1.2.1数组更新检测 

以下摘自vue官网API

由于 JavaScript 的限制,Vue 不能检测以下变动的数组:

  1. 当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue
  2. 当你修改数组的长度时,例如:vm.items.length = newLength

为了解决第一类问题,以下两种方式都可以实现和 vm.items[indexOfItem] = newValue 相同的效果,同时也将触发状态更新:

// Vue.set
Vue.set(example1.items, indexOfItem, newValue)
// Array.prototype.splice
example1.items.splice(indexOfItem, 1, newValue)

为了解决第二类问题,你可以使用 splice: 

example1.items.splice(newLength)

触发视图更新的方法:

  1. 变异方法

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()
  1. vue提供的set方法

  • Vue.set( target, key, value )

1.2.2对象更新检测 

还是由于 JavaScript 的限制,Vue 不能检测对象属性的添加或删除:

var vm = new Vue({
  data: {
    a: 1
  }
})
// `vm.a` 现在是响应式的

vm.b = 2
// `vm.b` 不是响应式的

解决方法:

  1. vue提供的set方法
  • Vue.set( target, key, value )
  1. Object.assign()
  • bject.assign({}, target, {key:value})

1.3.页面刷新vuex被清空

这真的是遇到一个很坑的问题,同一个页面(router未改变),一旦刷新(刷新或深度刷新),存储的vuex就马上和你说拜拜
  • localStorage
    网上推荐最多的方法就是用localStorage。但是我个人觉得不太合适,还得看项目吧。localStorage是永久存储的。

  • 数据重新获取
    我使用的方法是在需要某些数据之前先判断一下数据是否存在,如果不存在重新获取。

1.4nextTick适当使用

将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。

简而言之,等待DOM更新之后再进行操作。

1.5.异步问题

这个是一个亘古不变的话题。

请求后台数据异步,常不经意的带来了问题。(处理异步的方法就不详细描述了,网上一搜一大堆)

1.6.组件之间的调用方式

1.6.1.父子组件

  • prop向下传递,事件向上传递
  • 子组件添加ref属性,父组件可以获取到子组件的实例(不建议)
  • 插槽slot 作用域插槽

1.6.2.非父子组件

  • 使用状态管理
  • 实例化一个公共vue实例

1.7.计算属性设置值

计算属性是基于它们的依赖进行缓存的,一旦依赖发生变化,计算属性会重新计算

想要改变计算属性的值。要通过set方法去触发它所依赖的变量,(类似于触发它重新计算,单纯赋予一个新值,在取的时候也是不会被改变的)

1.8.vue文件中内联样式中有无scoped属性的差别

  • 有scoped属性:
    当前仅当该vue文件可以使用这个样式。
  • 无scoped属性:
    影响其他文件

 1.9 v-for v-key

当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。

为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。理想的 key 值是每项都有的且唯一的 id。这个特殊的属性相当于 Vue 1.x 的 track-by ,但它的工作方式类似于一个属性,所以你需要用 v-bind 来绑定动态值 (在这里使用简写):

1.10 v-for 和 v-if

当它们处于同一节点,v-for的优先级比v-if更高。

1.11 js文件中引入的css不会自动加前缀(新的脚手架已解决该问题)

无论是开发环境还是生成环境都不会自动加前缀,因为vue-loader只管.vue文件里面的样式,没有自动执行autoprefixer loader

参考链接

在build/utils.js下引入 postcss-loader

var postcssLoader = {
    loader: 'postcss-loader',
    options: {
        plugins: (loader) => [
            require('autoprefixer')()
        ],
        sourceMap: true
    }
}

如果还有问题在改成

var postcssLoader = {
    loader: 'postcss-loader',
    options: {
        plugins: (loader) => [
            require('autoprefixer')({
                browsers: [
                    // 加这个后可以出现额外的兼容性前缀
                    "> 0.01%"
                ]
            })
        ],
        sourceMap: true
    }
}

1.12 组件、prop大小写不敏感,事件敏感

对于组件和prop而言,html上用kebab-case (短横线分隔命名) ,其对应的js上要用

(HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:)

【但是:如果你使用字符串模板,那么这个限制就不存在了。】

components: {
    kebabCase
}

---

prop: ['kebabCase']

跟组件和 prop 不同,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称。

跟组件和 prop 不同,事件名不会被用作一个 JavaScript 变量名或属性名,所以就没有理由使用 camelCase 或 PascalCase 了。并且 v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent——导致 myEvent 不可能被监听到。

因此,我们推荐你始终使用 kebab-case 的事件名。

1.13 refs是静态节点

refs是静态节点

1.14 prop值的改变--不是立即

如果父组件中给子组件传递了一个prop的值,然后调用子组件的方法去获取该值,会发现值没有立即改变。

解决方法:

  1. 可以监听值的改变去调用相应子组件的方法
  2. 将子组件相关方法的调用放在nextTick里面

1.15 fetch兼容

在vue-cli中使用fetch方式请求的时候。存在一定的兼容问题。

可以在build/webpack.base.conf.js中添加一个plugins

plugins:[
new webpack.ProvidePlugin({
  fetch: 'imports-loader?this=>global!exports-loader?global.fetch!whatwg-fetch',
  Promise:  'imports-loader?this=>global!exports-loader?global.Promise!es6-promise',
})
]

 1.16 字符串模板

字符串模板

1.17 对象中某属性值的监听

普通的   watch 中只能监听到某对象的变化才会调用,当想监听对象以及对象中属性的变化都调用函数时,可以使用 deep:true

data() {
  return {
    bet: {
      pokerState: 53,
      pokerHistory: 'local'
    }   
    }
},
watch: {
  bet: {
    handler(newValue, oldValue) {
      console.log(newValue)
    },
    deep: true
  }
}

1.18 this.$forceUpdate

强制刷新页面,触发页面重新渲染。

1.19 vue中的beforeRouteUpdate

xxx/detail/123xxx/edit/123都用了同一个组件,beforeRouteUpdate不生效,但是watch $route是生效的?

扩展: 可以考虑在路由定义处使用别名 alias

alias DOC

官方解释

beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },

相关链接

1.20 组件自身调用自身

example

在js中对组件命名(设置name属性)即可以调用自身

1.21 vue会使用一种最大限度减少动态元素并且尽可能的尝试修复/再利用相同类型元素的算法。

  • 解决方法:使用key,它会基于key的变化重新排列元素顺序,并且会移除key不存在的元素。(key特殊属性主要用在vue的虚拟DOM算法,在新旧nodes对比时辨别VNodes)

常见的场景:

  1. v-for下,有相同父元素的子元素必须有独特的key。重复的key会造成渲染错误。
  2. 它也可以用于强制替换元素/组件而不是重复使用它
    1. 完整地触发组件的声明周期钩子
    2. 触发过渡
  3. 几乎完全一样/一样的两个html结构元素

1.22 父组件的自定义事件

若在父组件上定义一个事件,相当于定义一个监听子组件的监听器。若想要单独使用父组件的事件而不是监听,加一个.native事件修饰符即可。

2.优化

2.1.错误处理

错误处理很重要但是这是最容易让开发忽略的点。

2.1.1.请求接口错误

由于我的请求是使用axios插件或者fetch单独写在了一个js,可以对其进行响应拦截。一旦失败,或者后台报错,就进行相应的错误处理以及友好提示,也避免了重复的代码,提高可维护性

2.1.2.页面错误处理(404)

  1. nginx未匹配到路由走404路由
  2. router.beforeEach是否匹配到响应的路由,否则走错误路由。

2.1.3 错误提示封装

将错误提示模块化,通过vuex来操作错误的显示以及信息等内容。

2.2.减少不必要的依赖包

性能优化是很重要的,特别是对于vue这种首屏加载时间长的。

例如有些项目用到了图表(echarts),可以选择加载依赖包,不用加载整个echarts库。

2.3.不发送多个相同的请求

不发送多个相同的请求,在点击触发请求的同时锁定请求,直至给出响应/错误解锁。

以上内容,如有错误请指出,不甚感激。
如需转载,请注明出处

2.4.组件中引入css的css依赖 -- sass-resources-loader

为了让SCSS之类的文件在CSS中引入中不需要每次都引入var.scss文件,可以引入一个sass-resources-loader解决。

2.4.1 为了解决组件内引入的外部css文件没有做css兼容处理 -- postcss-loader

在build/utils中引入   postcss-loader

loader: 'postcss-loader',
options:{
  plugins: (loader) => [
      require('autoprefixer')()
  ],
  sourceMap: true 
}

2.5兼容ie9-11 

  • babel-polyfill模块
entry: {
    // app: './src/main.js'
    app: ["babel-polyfill", "./src/main.js"]
}
// build/webpack.base.config.js
// ./src/main.js是入口文件,可能存在差异
  • promise兼容
plugins: [
    new webpack.ProvidePlugin({
        // 如果用了fetch可以使用以下的进行兼容
        // fetch: 'imports-loader?this=>global!exports-loader?global.fetch!whatwg-fetch',
        Promise: 'imports-loader?this=>global!exports-loader?global.Promise!es6-promise',
    })
]

2.6 vue项目配置ico

doc

3.混淆点

3.1 camelCase vs kebab-case - prop/事件

  1. prop

HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:

 

Vue.component('blog-post', {
  // 在 JavaScript 中是 camelCase 的
  props: ['postTitle'],
  template: '<h3>{
   { postTitle }}</h3>'
})
<!-- 在 HTML 中是 kebab-case 的 -->
<blog-post post-title="hello!"></blog-post>

重申一次,如果你使用字符串模板,那么这个限制就不存在了。

2.事件

不同于组件和 prop,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称。举个例子,如果触发一个 camelCase 名字的事件:

this.$emit('myEvent')

则监听这个名字的 kebab-case 版本是不会有任何效果的:

<my-component v-on:my-event="doSomething"></my-component>

不同于组件和 prop,事件名不会被用作一个 JavaScript 变量名或属性名,所以就没有理由使用 camelCase 或 PascalCase 了。并且 v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent——导致 myEvent 不可能被监听到

因此,我们推荐你始终使用 kebab-case 的事件名。

4.vue常见面试题

doc

以上内容,如有错误请指出,不甚感激。
本文转载于博客园 FIONA-SUN  vue踩坑总结

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/bznhlhh/article/details/118364993

智能推荐

GitLab的SSH秘钥生成及配置_gitlab的ssh怎么生成-程序员宅基地

文章浏览阅读1.8k次。有两种情况,一种是在linux下面直接用命令生成ssh的秘钥,另外一中是在win上通过工具去生成1、在linu上生成秘钥:ssh-keygen -C [email protected] //-C后面是你的git邮箱地址,之后是秘钥存放的路径,再下一步是设置密码,也可以不选择直接下一步就可以了2、生成的秘钥在: cd ~/.ssh 文件夹下面 一个是id..._gitlab的ssh怎么生成

DXUT设计指南-程序员宅基地

文章浏览阅读489次。DXUT是一个建立在Direct3D API之上的,被大部分Direct3D指南和例子所使用的层。它的目标是创建Direct3D例子、原型、工具,更容易的建立坚固、专业的游戏。· DXUT概观· 初始化DXUT· 使用基于DXUT的程序窗口· 使用DXUT设备· 使用DXUT主循_dxut设计指南

计算机2016年9月试题,2016年9月计算机二级考试试题MSoffice机考试题(2)-程序员宅基地

文章浏览阅读95次。原标题:2016年9月计算机二级考试试题MSoffice机考试题(2)2016年9月计算机二级考试试题MSoffice冲刺(2)1、英文缩写CAM的中文意思是( )。A.计算机辅助设计B.计算机辅助制造C.计算机辅助教学D.计算机辅助管理2、若网络的各个节点通过中继器连接成一个闭合环路,则称这种拓扑结构称为( )。A.总线型拓扑B.星型拓扑C.树型拓扑D.环型拓扑3、字长是CPU的主要技术性能指标..._若网络的各个节点通过点到点通信线路循环连接成一个闭合环路,则这种拓朴结构是( )a星型拓扑b总线型拓扑c树形拓扑d环形拓扑

Android播放透明视频_wlmedia-程序员宅基地

文章浏览阅读5k次,点赞7次,收藏24次。话不多说,开局一张图,内容全靠“编”:播放透明视频_wlmedia

Python7种运算符及运算符优先级-程序员宅基地

文章浏览阅读4.5k次,点赞3次,收藏3次。Python从入门到精通零基础入门篇

python及深度学习笔记三_深度学习中的常用的统计图-程序员宅基地

文章浏览阅读101次。matplotlib部分:1.windows下设置字体:my_font=font_manager.FontProperties(fname=‘C:\Windows\Fonts\simsun.ttc’)2.plt.xlabel()设置x轴意义 plt.ylabel()设置y轴意义3.plt.xticks()设置x轴坐标 plt.yticks()设置y轴坐标Course exercises:4.折线图:plt.plot()5.散点图:plt.scatter()6.绘制网格:plt.grid()7_深度学习中的常用的统计图

随便推点

软件测试————缺陷定义_软件测试什么是交互缺陷-程序员宅基地

文章浏览阅读1.5k次。_软件测试什么是交互缺陷

监听react-router路由的变化_react监听路由变化-程序员宅基地

文章浏览阅读8.8k次。我们有时候会遇到这种业务场景: 进入某个页面时,我们需要验证用户是否已经登陆,是否拥有足够权限?我们可以通过监听路由的变化来实现。但是在react下,怎么实现呢?下面讨论实现过程:手动实现react-router-watcher实现手动实现使用Hook方式实现:import { useEffect, useState, useRef, useCallback, useMemo } from 'react'import { useLocation, useHistory, useRouteM_react监听路由变化

Redis快速入门(六)_如何给redis创建文件夹-程序员宅基地

文章浏览阅读707次。Redis主从复制主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,master以写为主,slaver以读为主,特点:读写分离,性能扩展,缓解访问压力。容灾快速恢复,从服务器挂掉了,可以转换到另外的从服务器。一主多从,只能有一台主服务器,老大只能有一个,问从服务器挂了可以切换,主机挂了呢?可以配置一个集群,一台主机多个从机,一个主机挂掉换另一个主机。怎么玩?怎么配置?1.创建一个/myredis文件夹mkdir /myredis2.复制redis.co_如何给redis创建文件夹

Qt Model/View之实现Item自定义显示_qt自定义item-程序员宅基地

文章浏览阅读6.7k次,点赞2次,收藏17次。介绍Delegate 代理类被用来显示和编辑Item项。为了能实现自定义的Item,需要子类化代理类,并实现自己的paint() 和 sizeHint()方法。paint()方法会被每个Item调用,来绘制自定义的样式和相应的数据类型,sizeHint()方法用于指示每个Item的大小。在重绘Item在View中的显示时,QStyle类提供了很多的细节样式。在Qt4.4以后,有两个代理类QIte_qt自定义item

一文快速了解分布式版本控制系统Git_软件版本控制系统的运行结果图-程序员宅基地

文章浏览阅读1.7k次,点赞3次,收藏10次。一文快速了解分布式版本控制系统Git_软件版本控制系统的运行结果图

Java开发工具 IntelliJ IDEA(idea使用教程,手把手教学)内容很全,一篇管够!!!_java idea-程序员宅基地

文章浏览阅读5.1w次,点赞133次,收藏1.3k次。IDEA使用教程下载安装IntelliJ IDEA 介绍IDEA,全称 IntelliJ IDEA,是 Java 语言的集成开发环境,IDEA 在业界被公认为是最好的 java 开发工具之一,尤其在智能代码助手、代码自动提示、重构、J2EE 支持、Ant、JUnit、CVS 整合、代码审查、创新的 GUI 设计等方面的功能可以说是超常的。IntelliJ IDEA 在官网上这样介绍自己:Excel at enterprise, mobile and web development with Jav_java idea

推荐文章

热门文章

相关标签