因为公司的前端开始转向 VueJS
,最近开始使用这个框架进行开发,遇到一些问题记录下来,以备后用。javascript
主要写一些 官方手册 上没有写,可是实际开发中会遇到的问题,须要必定知识基础。php
正文:css
首先,vue-cli
为咱们自动添加了 babel-plugin-transform-runtime
这个插件,该插件多数状况下都运做正常,能够转换大部分 ES6
语法。html
可是,存在以下两个问题:前端
polyfill
代码冗余polyfill
两个问题的缘由均归因于 babel-plugin-transform-runtime
采用了沙箱机制来编译咱们的代码(即:不修改宿主环境的内置对象)。vue
因为异步组件最终会被编译为一个单独的文件,因此即便多个组件中使用了同一个新特性(例如:Object.keys()
),那么在每一个编译后的文件中都会有一份该新特性的 polyfill
拷贝。若是项目较小能够考虑不使用异步加载,可是首屏的压力会比较大。java
不支持全局函数(如:Promise
、Set
、Map
),Set
跟 Map
这两种数据结构应该你们用的也很少,影响较小。可是 Promise
影响可能就比较大了。react
不支持实例方法(如:'abc'.includes('b')
、['1', '2', '3'].find((n) => n < 2)
等等),这个限制几乎废掉了大部分字符串和一半左右数组的新特性。webpack
通常状况下
babel-plugin-transform-runtime
能知足大部分的需求,当不知足需求时,推荐使用完整的babel-polyfill
。git
首先,从项目中移除 babel-plugin-transform-runtime
卸载该依赖:
npm un babel-plugin-transform-runtime -D
修改 babel
配置文件
// .babelrc { //... "plugins": [ // - "transform-runtime" ] //... }
而后,安装 babel-polyfill
依赖:
npm i babel-polyfill -D
最后,在入口文件中导入
// src/main.js import 'babel-polyfill'
在 ES6
中,模块系统的导入与导出采用的是引用导出与导入(非简单数据类型),也就是说,若是在一个模块中定义了一个对象并导出,在其余模块中导入使用时,导入的实际上是一个变量引用(指针),若是修改了对象中的属性,会影响到其余模块的使用。
一般状况下,系统体量不大时,咱们可使用 JSON.parse(JSON.stringify(str))
简单粗暴地来生成一个全新的深度拷贝的 数据对象。不过当组件较多、数据对象复用程度较高时,很明显会产生性能问题,这时咱们能够考虑使用 Immutable.js。
鉴于这个缘由,进行复杂数据类型的导出时,须要注意多个组件导入同一个数据对象时修改数据后可能产生的问题。
此外,模块定义变量或函数时即使使用let
而不是const
,在导入使用时都会变成只读,不能从新赋值,效果等同于用const
声明。
Vue
中使用 vue-loader
根据 lang
属性自动判断所须要的 loader
,因此不用额外配置 Loader
,可是须要手动安装相关依赖:
npm i pug -D npm i less-loader -D
仍是至关方便的,不用手动修改 webpack
的配置文件添加 loader
就可使用了
使用
pug
仍是pug-loader
?sass
两种语法的loader
如何设置?
--- 请参考 预处理器 · vue-loader
<!-- xxx.vue --> <style lang="less"> .action { color: #ddd; ul { overflow: hidden; li { float: left; } } } </style> <template lang="pug"> .action(v-if='hasRight') ul li 编辑 li 删除 </template> <script> export default { data () { return { hasRight: true } } } </script>
许多时候咱们须要定义一些全局函数或变量,来处理一些频繁的操做(这里拿 AJAX
的异常处理举例说明)。可是在 Vue
中,每个单文件组件都有一个独立的上下文(this
)。一般在异常处理中,须要在视图上有所体现,这个时候咱们就须要访问 this
对象,可是全局函数的上下文一般是 window
,这时候就须要一些特殊处理了。
最简单的方法就是直接在 window
对象上定义一个全局方法,在组件内使用的时候用 bind
、call
或 apply
来改变上下文。
定义一个全局异常处理方法:
// errHandler.js window.errHandler = function () { // 不能使用箭头函数 if (err.code && err.code !== 200) { this.$store.commit('err', true) } else { // ... } }
在入口文件中导入:
// src/main.js import 'errHandler.js'
在组件中使用:
// xxx.vue export default { created () { this.errHandler = window.errHandler.bind(this) }, method: { getXXX () { this.$http.get('xxx/xx').then(({ body: result }) => { if (result.code === 200) { // ... } else { this.errHandler(result) } }).catch(this.errHandler) } } }
在大型多人协做的项目中,污染 window
对象仍是不太稳当的。特别是一些比较有我的特点的全局方法(可能在你写的组件中几乎到处用到,可是对于其余人来讲可能并不须要)。这时候推荐写一个模块,更优雅安全,也比较天然,惟一不足之处就是每一个须要使用该函数或方法的组件都须要进行导入。
使用方法与前一种大同小异,就很少做介绍了。 ̄ω ̄=
在使用 Moment.js
遇到一些问题,发现最终打包的文件中将 Moment.js
的所有语言包都打包了,致使最终文件徒然增长 100+kB。查了一下,发现多是 webpack
打包或是 Moment.js
资源引用问题(?),目前该问题还未被妥善处理,须要经过一些 trick
来解决这个问题。
在 webpack
的生产配置文件中的 plugins
字段中添加一个插件,使用内置的方法类 ContextReplacementPlugin 过滤掉 Moment.js
中那些用不到的语言包:
// build/webpack.prod.conf.js new webpack.ContextReplacementPlugin(/moment[\\/]locale$/, /^\.\/(zh-cn)$/)
解决方案采自 oleg-nogin@webpack/webpack#3128。
问题讨论详见 GitHub Issue: moment/moment#2373、webpack/webpack#3128。
可能有些人注意到了,在 vue-cli
生成的模板中在导入组件时使用了这样的语法:
import Index from '@/components/Index'
这个 @
是什么东西?后来改配置文件的时候发现这个是 webpack
的配置选项之一:路径别名。
咱们也能够在基础配置文件中添加本身的路径别名,好比下面这个就把 ~
设置为路径 src/components
的别名:
// build/webpack.base.js { resolve: { extensions: ['.js', '.vue', '.json'], alias: { 'vue$': 'vue/dist/vue.esm.js', '@': resolve('src'), '~': resolve('src/components') } } }
而后咱们导入组件的时候就能够这样写:
// import YourComponent from 'YourComponent' // import YourComponent from './YourComponent' // import YourComponent from '../YourComponent' // import YourComponent from '/src/components/YourComponent' import YourComponent from '~/YourComponent'
既解决了路径过长的麻烦,又解决了相对路径的烦恼,方便不少吧!ヾ(゚∀゚ゞ)
一般,组件中 <style></style>
标签里的样式是全局的,在使用第三方 UI 库(如:Element
)时,全局样式极可能影响 UI 库的样式。
咱们能够经过添加 scoped
属性来使 style
中的样式只做用于当前组件:
<style lang="less" scoped> @import 'other.less'; .title { font-size: 1.2rem; } </style>
在有
scoped
属性的style
标签内导入其余样式,一样会受限于做用域,变为组件内样式。复用程度较高的样式不建议这样使用。另,在组件内样式中应避免使用元素选择器,缘由在于元素选择器与属性选择器组合时,性能会大大下降。
--- 两种组合选择器的测试:classes selector,elements selector
相对于 style
使用 scoped
属性时的组件内样式,有时候咱们也须要添加一些全局样式。固然咱们能够用没有 scoped
属性的 style
来写全局样式。
可是相比较,更推荐下面这种写法:
/* 单独的全局样式文件 */ /* style-global.less */ body { font-size: 10px; } .title { font-size: 1.4rem; font-weight: bolder; }
而后在入口文件中导入全局样式:
// src/main.js import 'style-global.less'
一般咱们能够直接使用 v-model
将表单控件与数据进行绑定,可是有时候咱们也会须要在用户输入的时候获取当前值(好比:实时验证当前输入控件内容的有效性)。
这时咱们可使用 @input
或 @change
事件绑定咱们本身的处理函数,并传入 $event
对象以获取当前控件的输入值:
<input type='text' @change='change($event)'>
change (e) { let curVal = e.target.value if (/^\d+$/.test(curVal)) { this.num = +curVal } else { console.error('%s is not a number!', curVal) } }
固然,若是 UI 框架采用
Element
会更简单,它的事件回调会直接传入当前值。
v-for
指令很强大,它不只能够用来遍历数组、对象,甚至能够遍历一个数字或字符串。
基本语法就不讲了,这里讲个小 tips:
在使用 v-for
根据对象或数组生成 DOM
时,有时候须要知道当前的索引。咱们能够这样:
<ul> <li v-for='(item, key) in items' :key='key'> {{ key }} - {{ item }} </ul>
可是,在遍历数字的时候须要注意,数字的 value
是从 1 开始,而 key
是从 0 开始:
<ul> <li v-for='(v, k) in 3' :key='k'> {{ k }}-{{ v }} <!-- output to be 0-1, 1-2, 2-3 --> </ul>
2.2.0+
的版本里,当在组件中使用v-for
时,key
如今是必须的。
与 JSX
相同,组件中的模板只能有一个根节点,即下面这种写法是 错误 的:
<template> <h1>Title</h1> <article>Balabala...</article> </template>
咱们须要用一个块级元素把他包裹起来:
<template> <div> <h1>Title</h1> <article>Balabala...</article> </div> </template>
因为 vue-cli
配置的项目提供了一个内置的静态服务器,在开发阶段基本不会有什么问题。可是,当咱们把代码放到服务器上时,常常会遇到静态资源引用错误,致使界面一片空白的问题。
这是因为 vue-cli
默认配置的 webpack
是以站点根目录引用的文件,然而有时候咱们可能须要把项目部署到子目录中。
咱们能够经过 config/index.js
来修改文件引用的相对路径:
build.assetsSubDirectory: 'static' build.assetsPublicPath: '/' dev.assetsSubDirectory: 'static' dev.assetsPublicPath: '/'
咱们能够看到导出对象中 build
与 dev
均有 assetsSubDirectory
、assetsPublicPath
这两个属性。
其中 assetsSubDirectory 指静态资源文件夹,也就是打包后的 js
、css
、图片等文件所放置的文件夹,这个默认通常不会有问题。
assetsPublicPath 指静态资源的引用路径,默认配置为 /
,即网站根目录,与 assetsSubDirectory 组合起来就是完整的静态资源引用路径 /static
。
写到这里解决方法已经很明显了,只要把根目录改成相对目录就行了:
build.assetsSubDirectory: 'static' build.assetsPublicPath: './'
没错!就是一个 .
的问题。ㄟ( ▔, ▔ )ㄏ
在引入 Polyfill
以后,能够在 .babelrc
文件中开启 useBulitIns
属性。启用该属性后,编译项目时会根据项目中新特性的使用状况将完整的 polyfill
拆分红独立的模块序列。
启用 useBuiltIns
属性:
// .babelrc { "presets": [ ["env", { "modules": false, "useBuiltIns": true }], "es2015", "stage-2" ] // ... }
安装后引入 babel-polyfill
:
// src/main.js import 'babel-polyfill' [1, 2, 3].find((v => v > 2))
启用 useBulitIns
后自动拆分 babel-polyfill
import 'core-js/modules/es6.array.find' [1, 2, 3].find((v => v > 2))
经测试最大减小了一半左右的
polyfill
体积
没深刻研究哈,猜想可能加了core-js
跟一些基础的polyfill
默认时,Vue
单文件组件使用一个对象来描述组件内部的实现:
const App = { // initialized data data () { return { init: false } } // lifecycle hook created () {} mounted () {} // ... } export default App
咱们能够经过安装一些依赖来支持最新的 class
写法:
import Vue from 'vue' import Component from 'vue-class-component' @Component class App extends Vue { init = false; created () {} mounted () {} } export default App
不能否认,确实多些了一些代码哈,不过我的仍是比较倾向新语法特性的写法的,毕竟标准便是灯塔
P.S 这里使用了还处于Stage 3
的 Field declarations 来声明组件的初始data
下面来看看须要作哪些改动以支持使用 class
的写法:
vue-class-component
这个依赖了。babel
的 transform-decorators-legacy
插件支持。transform-class-properties
就行了。安装依赖:
npm i vue-class-component -D npm i babel-plugin-transform-decorators-legacy -D npm i babel-plugin-transform-class-properties -D
配置 babel
// .babelrc { // ... "plugins": [ "transform-runtime", "transform-decorators-legacy", "transform-class-properties" ] // ... }
注意:
transform-decorators-legacy
需放在transform-class-properties
以前
因为 Vue.js
响应式数据依赖于对象方法 Object.defineProperty
。但很明显,数组这个特殊的“对象”并无这个方法,天然也没法设置对象属性的 descriptor
,从而也就没有 getter()
和 setter()
方法。因此在使用数组索引角标的形式更改元素数据时(arr[index] = newVal
),视图每每没法响应式更新。
为解决这个问题,Vue.js
中提供了 $set()
方法:
vm.arr.$set(0, 'newVal') // vm.arr[0] = 'newVal'
受现代
JavaScript
的限制(以及废弃Object.observe
),Vue
不能检测到对象属性的添加或删除。因为Vue
会在初始化实例时对属性执行getter/setter
转化过程,因此属性必须在data
对象上存在才能让Vue
转换它,这样才能让它是响应的。
Ref: 深刻响应式原理 - Vue.js
var vm = new Vue({ data: { a: 1 } }) // `vm.a` 是响应的 vm.b = 2 // `vm.b` 是非响应的
推荐在开发较复杂的组件时使用 props
静态类型检测,提升组件的健壮性,多数状况下能够在转码阶段提早发现错误。
// before prop: [ 'id', 'multiple', 'callback', ]
// after props: { id: { type: [ Number, Array ], required: true, }, multiple: { type: Boolean, default: false, }, callback : Function, }
使用处于 Stage.3
阶段的动态导入函数 import()
,同时借助 webpack
的代码分割功能,在 Vue.js
中咱们能够很轻松地实现一个异步组件。
const AsyncComponent = () => import('./AsyncComponent')
Vue.component( 'async-webpack-example', () => import('./my-async-component') )
相比于异步路由组建,异步组件工厂通常适用于组件内进一步小颗粒度的拆分处理,如:大致量组件内初次加载时的非必要组件(组件内嵌套的弹窗组件或
Popover
组件等)。
To be continue...
文章还在完善中,欢迎你们一块儿讨论 Vue.JS 开发中遇到的一些问题哈 (゚▽゚)/
看看你的收藏列表,你肯定收藏了会记得看吗_(:зゝ∠)_
读一读开发的时候至少会有个印象,点个赞打卡啦~