泡芙的时光

泡芙的时光 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织 github.com/sqaiyan 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

泡芙的时光 赞了文章 · 2018-04-04

Vue.js最佳实践(五招让你成为Vue.js大师)

本文面向对象是有一定Vue.js编程经验的开发者。如果有人需要Vue.js入门系列的文章可以在评论区告诉我,有空就给你们写。

对大部分人来说,掌握Vue.js基本的几个API后就已经能够正常地开发前端网站。但如果你想更加高效地使用Vue来开发,成为Vue.js大师,那下面我要传授的这五招你一定得认真学习一下了。

第一招:化繁为简的Watchers

场景还原:

created(){
    this.fetchPostList()
},
watch: {
    searchInputValue(){
        this.fetchPostList()
    }
}

组件创建的时候我们获取一次列表,同时监听input框,每当发生变化的时候重新获取一次筛选后的列表这个场景很常见,有没有办法优化一下呢?

招式解析:
首先,在watchers中,可以直接使用函数的字面量名称;其次,声明immediate:true表示创建组件时立马执行一次。

watch: {
    searchInputValue:{
        handler: 'fetchPostList',
        immediate: true
    }
}

第二招:一劳永逸的组件注册

场景还原:

import BaseButton from './baseButton'
import BaseIcon from './baseIcon'
import BaseInput from './baseInput'

export default {
  components: {
    BaseButton,
    BaseIcon,
    BaseInput
  }
}
<BaseInput
  v-model="searchText"
  @keydown.enter="search"
/>
<BaseButton @click="search">
  <BaseIcon name="search"/>
</BaseButton>

我们写了一堆基础UI组件,然后每次我们需要使用这些组件的时候,都得先import,然后声明components,很繁琐!秉持能偷懒就偷懒的原则,我们要想办法优化!

招式解析:
我们需要借助一下神器webpack,使用 require.context() 方法来创建自己的(模块)上下文,从而实现自动动态require组件。这个方法需要3个参数:要搜索的文件夹目录,是否还应该搜索它的子目录,以及一个匹配文件的正则表达式。

我们在components文件夹添加一个叫global.js的文件,在这个文件里借助webpack动态将需要的基础组件统统打包进来。

import Vue from 'vue'

function capitalizeFirstLetter(string) {
  return string.charAt(0).toUpperCase() + string.slice(1)
}

const requireComponent = require.context(
  '.', false, /\.vue$/
   //找到components文件夹下以.vue命名的文件
)

requireComponent.keys().forEach(fileName => {
  const componentConfig = requireComponent(fileName)

  const componentName = capitalizeFirstLetter(
    fileName.replace(/^\.\//, '').replace(/\.\w+$/, '')
    //因为得到的filename格式是: './baseButton.vue', 所以这里我们去掉头和尾,只保留真正的文件名
  )

  Vue.component(componentName, componentConfig.default || componentConfig)
})

最后我们在main.js中import 'components/global.js',然后我们就可以随时随地使用这些基础组件,无需手动引入了。

第三招:釜底抽薪的router key

场景还原:
下面这个场景真的是伤透了很多程序员的心...先默认大家用的是Vue-router来实现路由的控制。
假设我们在写一个博客网站,需求是从/post-page/a,跳转到/post-page/b。然后我们惊人的发现,页面跳转后数据竟然没更新?!原因是vue-router"智能地"发现这是同一个组件,然后它就决定要复用这个组件,所以你在created函数里写的方法压根就没执行。通常的解决方案是监听$route的变化来初始化数据,如下:

data() {
  return {
    loading: false,
    error: null,
    post: null
  }
}, 
watch: {
  '$route': {
    handler: 'resetData',
    immediate: true
  }
},
methods: {
  resetData() {
    this.loading = false
    this.error = null
    this.post = null
    this.getPost(this.$route.params.id)
  },
  getPost(id){

  }
}

bug是解决了,可每次这么写也太不优雅了吧?秉持着能偷懒则偷懒的原则,我们希望代码这样写:

data() {
  return {
    loading: false,
    error: null,
    post: null
  }
},
created () {
  this.getPost(this.$route.params.id)
},
methods () {
  getPost(postId) {
    // ...
  }
}

招式解析:

那要怎么样才能实现这样的效果呢,答案是给router-view添加一个unique的key,这样即使是公用组件,只要url变化了,就一定会重新创建这个组件。(虽然损失了一丢丢性能,但避免了无限的bug)。同时,注意我将key直接设置为路由的完整路径,一举两得。

<router-view :key="$route.fullpath"></router-view>

第四招: 无所不能的render函数

场景还原:
vue要求每一个组件都只能有一个根元素,当你有多个根元素时,vue就会给你报错

<template>
  <li
    v-for="route in routes"
    :key="route.name"
  >
    <router-link :to="route">
      {{ route.title }}
    </router-link>
  </li>
</template>


 ERROR - Component template should contain exactly one root element. 
    If you are using v-if on multiple elements, use v-else-if 
    to chain them instead.

招式解析:
那有没有办法化解呢,答案是有的,只不过这时候我们需要使用render()函数来创建HTML,而不是template。其实用js来生成html的好处就是极度的灵活功能强大,而且你不需要去学习使用vue的那些功能有限的指令API,比如v-for, v-if。(reactjs就完全丢弃了template)

functional: true,
render(h, { props }) {
  return props.routes.map(route =>
    <li key={route.name}>
      <router-link to={route}>
        {route.title}
      </router-link>
    </li>
  )
}

第五招:无招胜有招的高阶组件

划重点:这一招威力无穷,请务必掌握
当我们写组件的时候,通常我们都需要从父组件传递一系列的props到子组件,同时父组件监听子组件emit过来的一系列事件。举例子:

//父组件
<BaseInput 
    :value="value"
    label="密码" 
    placeholder="请填写密码"
    @input="handleInput"
    @focus="handleFocus>
</BaseInput>

//子组件
<template>
  <label>
    {{ label }}
    <input
      :value="value"
      :placeholder="placeholder"
      @focus=$emit('focus', $event)"
      @input="$emit('input', $event.target.value)"
    >
  </label>
</template>

有下面几个优化点:

1.每一个从父组件传到子组件的props,我们都得在子组件的Props中显式的声明才能使用。这样一来,我们的子组件每次都需要申明一大堆props, 而类似placeholer这种dom原生的property我们其实完全可以直接从父传到子,无需声明。方法如下:

    <input
      :value="value"
      v-bind="$attrs"
      @input="$emit('input', $event.target.value)"
    >
   

$attrs包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定,并且可以通过 v-bind="$attrs" 传入内部组件——在创建更高层次的组件时非常有用。

2.注意到子组件的@focus=$emit('focus', $event)"其实什么都没做,只是把event传回给父组件而已,那其实和上面类似,我完全没必要显式地申明:

<input
    :value="value"
    v-bind="$attrs"
    v-on="listeners"
>

computed: {
  listeners() {
    return {
      ...this.$listeners,
      input: event => 
        this.$emit('input', event.target.value)
    }
  }
}

$listeners包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。

3.需要注意的是,由于我们input并不是BaseInput这个组件的根节点,而默认情况下父作用域的不被认作 props 的特性绑定将会“回退”且作为普通的 HTML 特性应用在子组件的根元素上。所以我们需要设置inheritAttrs:false,这些默认行为将会被去掉, 以上两点的优化才能成功。


结尾

掌握了以上五招,你就能在Vue.js的海洋中自由驰骋了,去吧少年。
陆续可能还会更新一些别的招数,敬请期待。

如果你对VueJs或者ReactJs的虚拟DOM是如何实现的感兴趣,可以去看我的这个系列文章:
【React进阶系列】从零开始手把手教你实现一个Virtual DOM(一)
【React进阶系列】从零开始手把手教你实现一个Virtual DOM(二)
【React进阶系列】从零开始手把手教你实现一个Virtual DOM(二)

标题虽然是React进阶,但事实上Vuejs从2.0开始也使用了虚拟DOM,虽然你仍然可以使用Vuejs的模板template写代码,但本质上template最后也会complile成虚拟DOM。(官方文档Vuejs 渲染函数 & JSX)。

广告时间:阿里巴巴电商板块增速最快的新业务之一内推招人,请大家多多推荐或自荐,内部推荐优先面试,技术面试一周内出结果。可以私信我了解详情,或者直接把简历发到我邮箱:zeqiang.wang@alibaba-inc.com

查看原文

赞 651 收藏 1215 评论 47

泡芙的时光 回答了问题 · 2018-03-20

解决如何在vue让this.$store.dispatch异步函数,同步执行;即注册再获取用户信息后跳转?

async await

关注 7 回答 5

泡芙的时光 赞了回答 · 2018-03-01

解决一个中高阶难度的 JavaScript 正则场景

var str = "lajishuju..Ejhohaodf98 a.length+b.length+10+:rowspan=a.length + b.length+ c.length20:rowspan=a.length+b.length lajishujudsjalfj)Ufaojd";

var r = str.replace(/(:rowspan=)(.+?)(?=\d*:rowspan)/g, function (...m)
{
    m[2] = m[2].replace(/\b([a-z]\w*\.length)/g, 'sub.$1');

    return m[1] + m[2];
});

console.log(r);

关注 4 回答 4

泡芙的时光 回答了问题 · 2018-02-08

解决Vue warn报了一大堆错误

看错误 duplicate key,重复的key,应该是你传的key不是id那种唯一值,有的值是一样的,key可以不传的 或者换个参数传

关注 4 回答 4

泡芙的时光 赞了文章 · 2017-07-30

TypeScript - 不止稳,而且快

前言

关于 TypeScript 是什么,应该大部分人都已经知道了,但是在这儿,还是摘抄一下知乎的回答:

TypeScript 是 JavaScript 的强类型版本。然后在编译期去掉类型和特有语法,生成纯粹的 JavaScript 代码。由于最终在浏览器中运行的仍然是 JavaScript,所以 TypeScript 并不依赖于浏览器的支持,也并不会带来兼容性问题。

对于我个人而言, 使用 TypeScript 写项目已经有半年多了,中间有被 TypeScript 的配置与升级折腾到想砸电脑的时候,也有提前发现错误时的暗自庆幸,同时也有因为找不到类型定义文件而自己手写,提PR补全的时候。
总的来说使用 TypeScript 的这一年,什么感觉都有。但最后还是依然坚持使用 TypeScript ,因为其带来的效率提升是远远大于环境升级所带来的开销的。

稳定压倒一切

作为程序员,自然希望代码上线之后能安安稳稳的跑着,而不是突然报错崩溃啥的。
所以 TypeScript 之前最被看重的就是静态类型检查功能。

至于静态类型检查的作用,在知乎的另一个回答中有相关的回答:

静态类型检查可以避免很多不必要的错误, 不用在调试的时候才发现问题 (其实有的时候根本调试不出问题, 只是默默地把坑挖了, 说不定埋的就是个炸弹, 之前用 TypeScript 重写应用的服务器端程序, 写完之后就发现了不少暂时没有影响到运行的严重问题).

懒人的自我救赎

然而,我是个很“懒”的人,不愿在重复的事情上花上很多时间,也不喜欢像背书一下,背下来 Api 文档。更希望自己的时间能专注于核心业务的开发,而非边边角角的事情。

去年十月,在因为实际学习需要,接触越来越多前端框架时候,感觉整天的开发,大半的时间都浪费在了查文档上,特别是一些 React 的组件,props又多又长……每次写的时候,都得回去翻文档,简直绝望。

在这种每天近乎绝望的重复劳动下,我开始尝试去找解决方法,再到后来有一天接触了 TypeScript ,感觉到这就是自己想要的功能。
嗯……看中的不是 TypeScript 的稳定性,而是 TypeScript 的代码提示。

比如写 Node.js,使用 TypeScript 与 不使用的区别是这样的:

不仅不用手动翻阅 Api, 而且参数是什么也都一清二楚了。

且TypeScript 的代码提示是基于类型文件工作的,而相比于各个编辑器自己定义的代码片段来说,不仅有大量的志愿者去维护,更新及时,而且种类繁多,基本现有的流行框架类库,都有相应的类型定义文件。

所以自打用上 TypeScript 后,就过上了再也不用去费脑子记 Api 和参数的日子,开发效率与幸福感都得到了大大的提升。

不止稳,更要快

而 TypeScript 的快,不仅体现在代码提示上,同时也体现于重构、可读性和配套的编辑器上。

代码重构

在重构上,这个自己是有实际体会的,如果写JS,重构时候不小心改了啥,除了运行时候能发现,其他时候往往难以察觉,且 ESLint 也只能是排查简单的问题,所以出了BUG会非常麻烦。
而 TypeScript 不一样,重构了,重新编译一下就知道,哪里错了,哪里改动了不应该改的。对于我自己这种时不时就会重构的人来说,省时又省力。

可读性

可读性上,TypeScript 明显占优,查看开源代码时,如果注释不是很完善,往往会看的云里雾里,而 TypeScript 在同等条件下,至少有个类型,能让自己更容易明白代码的参数、返回值和意图。

编辑器

这个是不得不提的部分,因为 VSCode 实在是太方便了,性能也高,且编辑器自身保持着一个高速的开发与迭代状态,时不时就能感受到 VSCode 开发团队的诚意和其所带来的惊喜。

因为都是微软家产品的原因,VSCode 对 TypeScript 的支持也相当完善。各种插件也层出不穷,基于 TypeScript 做的 Automatic Type Acquisition 功能使得 JavaScript 的用户也能享受到详细的代码提示功能,这一点上比 Sublime 等编辑器方便了很多。

关于 VSCode 编辑器的上手与配置,可以看阎王发表的这篇文章:如何快速上手一款 IDE - VSC 配置指南和插件推荐

解放自己,专注业务核心开发:TypeScript 编辑器插件推荐

当然,每次写 TypeScript 时,依然会遇到一些烦心的问题和重复的劳动。
比如说,TypeScript的类型定义文件是需要手动下载相应的 @types 包的,虽然相比于之前的方式已经进化了很多,但是每次还要重复,依然会觉得繁琐。
所以下面会推荐自己常用的几个插件,把自己从繁琐无趣零成长的工作中解放出来。

TypeScript Importer-告别手动重复写import的日子

插件地址:TypeScript Importer

这个是我最喜欢的插件,具体的作用,一图胜千言:

在长长的路径里,导入另一个文件夹深处的模块,那种感觉是绝望……
每次都要重复的import,每次都要重复的判断路径,每次都要重新写一遍import……

虽然工作量也不大,但是确实会影响心情和效率。

Types auto installer - 自动安装相应的类型定义文件

插件地址:Types auto installer

在之前,你安装一个模块并在 TypeScript 运行两段命令。
以lodash为例:

npm i lodash --save
npm i @types/lodash --save

当然,你也可以合并到一句话去写。
虽然工作量不大……但是架不住量多啊……每开一个项目都得来这么一次,简直绝望……

所以当时就想着自己写一个自动安装类型定义文件的小工具,后面确实也写出来了,只是再后来又发现 VSCode 有这个插件,功能也很完善,就用它的了。
插件的作用很简单,就是当你运行:

npm install --save lodash

它会自动执行:

npm install --save @types/lodash

与此同时还有一键下载安装所有 package.json 依赖类型定义文件的功能,可以说是非常方便了。

Sort Typescript Imports-给你import的模块们排序

插件地址:Sort Typescript Imports

同样,话不多说,一图胜千言:

这是一个看起来没什么作用的插件……因为其实 import顺序是否整洁有序好像对开发效率啥的并没有很大的提升。
但这是一个你接受了它的设定,就可能会觉得十分有趣的插件……

具体的作用就是,让你的 imports 更有顺序,相近文件夹的排列在一起。看起来会更好看一些。

Emmm……如果一定要说作用的话,就是更好看一些吧……很符合我这种有轻微代码洁癖的人的心态……

为什么要关注这个?

自己在知乎上有回答过一道问题:《最近一年前端技术栈哪些技术点最困扰你?》
我的回答是:

开发环境的搭建。
没有官方的cli,或者自己要做一些拓展(比如用ts)真的非常烦人。
各种报错,而这种在开发环境上积累的经验和踩的坑是价值非常低的。(因为基本最后翻官方配置文档都能解决)
耗时长,学习价值低,更新速度快。

在这儿,也是同理。
用 TypeScript 和相关插件所解决的问题,都是一些繁琐、无趣、零成长的工作,而且还影响心情。
有这个时间,为什么不多陪陪女朋友,多学点东西,多解决一些有意思的问题呢?
所以这种可以让计算机解决的问题,就让计算机去解决吧~

参考资料

查看原文

赞 51 收藏 140 评论 11

泡芙的时光 赞了文章 · 2017-07-21

VueJS 开发常见问题集锦

图片描述

由于公司的前端开始转向 VueJS,最近开始使用这个框架进行开发,遇到一些问题记录下来,以备后用。

主要写一些 官方手册 上没有写,但是实际开发中会遇到的问题,需要一定知识基础。


涉及技术栈


正文:

polyfill 与 transform-runtime

  首先,vue-cli 为我们自动添加了 babel-plugin-transform-runtime 这个插件,该插件多数情况下都运作正常,可以转换大部分 ES6 语法。

  但是,存在如下两个问题:

  1. 异步加载组件时,会产生 polyfill 代码冗余
  2. 不支持对全局函数与实例方法的 polyfill

  两个问题的原因均归因于 babel-plugin-transform-runtime 采用了沙箱机制来编译我们的代码(即:不修改宿主环境的内置对象)。

  由于异步组件最终会被编译为一个单独的文件,所以即使多个组件中使用了同一个新特性(例如:Object.keys()),那么在每个编译后的文件中都会有一份该新特性的 polyfill 拷贝。如果项目较小可以考虑不使用异步加载,但是首屏的压力会比较大。

  不支持全局函数(如:PromiseSetMap),SetMap 这两种数据结构应该大家用的也不多,影响较小。但是 Promise 影响可能就比较大了。

  不支持实例方法(如:'abc'.includes('b')['1', '2', '3'].find((n) => n < 2) 等等),这个限制几乎废掉了大部分字符串和一半左右数组的新特性。

一般情况下 babel-plugin-transform-runtime 能满足大部分的需求,当不满足需求时,推荐使用完整的 babel-polyfill

替换 babel-polyfill

  首先,从项目中移除 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 import 引用问题

  在 ES6 中,模块系统的导入与导出采用的是引用导出与导入(非简单数据类型),也就是说,如果在一个模块中定义了一个对象并导出,在其他模块中导入使用时,导入的其实是一个变量引用(指针),如果修改了对象中的属性,会影响到其他模块的使用。

  通常情况下,系统体量不大时,我们可以使用 JSON.parse(JSON.stringify(str)) 简单粗暴地来生成一个全新的深度拷贝的 数据对象。不过当组件较多、数据对象复用程度较高时,很明显会产生性能问题,这时我们可以考虑使用 Immutable.js

鉴于这个原因,进行复杂数据类型的导出时,需要注意多个组件导入同一个数据对象时修改数据后可能产生的问题。
此外,模块定义变量或函数时即便使用 let 而不是 const,在导入使用时都会变成只读,不能重新赋值,效果等同于用 const 声明。

在 Vue 中使用 Pug 与 Less

安装依赖

  Vue 中使用 vue-loader 根据 lang 属性自动判断所需要的 loader,所以不用额外配置 Loader,但是需要手动安装相关依赖:

npm i pug -D
npm i less-loader -D

还是相当方便的,不用手动修改 webpack 的配置文件添加 loader 就可以使用了

使用 pug 还是 pug-loadersass 两种语法的 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 对象上定义一个全局方法,在组件内使用的时候用 bindcallapply 来改变上下文。

  定义一个全局异常处理方法:

// 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 与 Webpack

  在使用 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#2373webpack/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'

  既解决了路径过长的麻烦,又解决了相对路径的烦恼,方便很多吧!ヾ(゚∀゚ゞ)

CSS 作用域与模块

组件内样式

  通常,组件中 <style></style> 标签里的样式是全局的,在使用第三方 UI 库(如:Element)时,全局样式很可能影响 UI 库的样式。

  我们可以通过添加 scoped 属性来使 style 中的样式只作用于当前组件:

<style lang="less" scoped>
  @import 'other.less';
  .title {
    font-size: 1.2rem;
  }
</style>

在有 scoped 属性的 style 标签内导入其他样式,同样会受限于作用域,变为组件内样式。复用程度较高的样式不建议这样使用。

另,在组件内样式中应避免使用元素选择器,原因在于元素选择器与属性选择器组合时,性能会大大降低。

--- 两种组合选择器的测试:classes selectorelements 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 指令很强大,它不仅可以用来遍历数组、对象,甚至可以遍历一个数字或字符串。

  基本语法就不讲了,这里讲个小 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>

原因参考:React-小记:组件开发注意事项#唯一根节点

项目路径配置

  由于 vue-cli 配置的项目提供了一个内置的静态服务器,在开发阶段基本不会有什么问题。但是,当我们把代码放到服务器上时,经常会遇到静态资源引用错误,导致界面一片空白的问题。

  这是由于 vue-cli 默认配置的 webpack 是以站点根目录引用的文件,然而有时候我们可能需要把项目部署到子目录中。

  我们可以通过 config/index.js 来修改文件引用的相对路径:

  build.assetsSubDirectory: 'static'
  build.assetsPublicPath: '/'

  dev.assetsSubDirectory: 'static'
  dev.assetsPublicPath: '/'

  我们可以看到导出对象中 builddev 均有 assetsSubDirectoryassetsPublicPath 这两个属性。

  其中 assetsSubDirectory 指静态资源文件夹,也就是打包后的 jscss、图片等文件所放置的文件夹,这个默认一般不会有问题。

  assetsPublicPath 指静态资源的引用路径,默认配置为 /,即网站根目录,与 assetsSubDirectory 组合起来就是完整的静态资源引用路径 /static

  写到这里解决方法已经很明显了,只要把根目录改为相对目录就好了:

  build.assetsSubDirectory: 'static'
  build.assetsPublicPath: './'

  没错!就是一个 . 的问题。ㄟ( ▔, ▔ )ㄏ

更小的 Polyfill 开销

  在引入 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

使用 ESnext class 特性

对比

  默认时,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 3Field declarations 来声明组件的初始 data

使用

  下面来看看需要做哪些改动以支持使用 class 的写法:

  1. 首先,最明显的就是我们需要 vue-class-component 这个依赖了。
  2. 然后,这个依赖需要 babeltransform-decorators-legacy 插件支持。
  3. 最后,如果你也想使用 Field declarations 字段声明写法,再添加一个 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 开发中遇到的一些问题哈 (゚▽゚)/

看看你的收藏列表,你确定收藏了会记得看吗_(:зゝ∠)_
读一读开发的时候至少会有个印象,点个赞打卡啦~

原文:VueJS 开发常见问题集锦

查看原文

赞 161 收藏 915 评论 31

泡芙的时光 回答了问题 · 2017-07-20

一个半圆如何裁掉某一部分

用svg 路径画出形状再用样式填充颜色

关注 9 回答 7

泡芙的时光 赞了文章 · 2017-07-20

[译] 如何为 Vue 项目写单元测试

译者:明非

链接:https://fanmingfei.com/posts/A_Vue_Unit_Text_Tutorial.html

原文:https://scotch.io/amp/tutorials/how-to-write-a-unit-test-for-vuejs?from=timeline&isappinstalled=0

众所周知,Vue.js 是一个非常牛逼的 JavaScript 框架,对于创建复杂功能的前端项目是非常有用的。不管是什么项目,检查应用是否正常工作,运行是否为预期,是尤为重要的。然而,为了保证业务正常运行,我们的项目,每做一次更新,都要对所有功能做一次回归测试,随着项目的增大,重复的测试工作越来越多,越来越乏味,手工测试将变成一个恶心的事情。正因如此,自动化测试诞生了,它可以随时监测我们的代码是否正常工作,运行结果是否符合预期。在这个教程中,我们将创建一个简单的VueJS项目,并为其写一个简单的单元测试。

我们创建一个基本的 to-do list 组件进行测试。我们将要测试的是,列表展示是否正确,用户是否可以正常添加到 to-do list。通过这个教程,你将学会如何去为你的组件写一个测试,测试包括HTML展示是否正确以及用户的操作是否能正常进行。

这个git库是这篇文章的所有代码。

创建项目

创建 JavaScript 项目可能是一个复杂的过程。琳琅满目的依赖库供我们选择。不过还好,我们可以使用vue-cli来创建VueJS项目,它帮我们包办一切。运行 npm install 来安装依赖:

npm install -g vue-cli
vue init webpack project-name

在这个过程中,你可能会遇到几个提示。大多数提示比较简单易懂,你可以直接选择默认选项。需要注意的是,我们需要是否安装 vue-routerKarmaMocha的提示后输入YES来引入这些工具。然后开始安装依赖:

cd project-name
npm install

接下来我们执行下面的命令,这个命令将会在本地运行你的应用并在浏览器中打开。

npm run dev

如果你的网络好的话,一会就装好了。

依赖

Webpack (2.3) 是一个打包器,它可以合并打包JavaScript,CSS,HTML文件,并且提供给应用运行。Bable (v6.22) 是一个编译器,用来把ES6编译成ES5。目前有很多 JavaScript 标准在许多浏览器中还没有被支持,所以需要将ES6转成ES。

测试依赖

Karma (v1.4) 是一个运行时,它产生一个 Web 服务环境来运行项目代码,并且执行测试。Mocha (v3.2) 是一个 JavaScript 测试框架。Chai (v3.5) 是一个 Mocha 可以使用的断言库。

在你的项目中,你可以找到下面这些目录:buildconfignode_modulessrcstatictest。对于本教程来说最重要的是src,它包括我们应用的代码,用来测试。

第一次测试

从最基本的开始去做一般都没错。我们将从创建简单的列表组件开始。在 src/components 里创建一个新文件叫做 List.vue 并且将下面代码写进去。

<template>
  <div>
    <h1>My To Do List</h1>
    </br>
    <!--displays list -->
    <ul>
      <li v-for="item in listItems">{{ item }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'list',
  data () {
    return {
      listItems: ['buy food', 'play games', 'sleep'],
    }
  }
}
</script>

在这个组件中,列表项被储存在数组(listItems)里面。数据被传递到模板,然后被遍历(v-for),然后展现在页面上。

当然,我们需要看到刚刚创建的列表,我们可以创建一个新的路由来展示这个组件。在src/router/index.js中创建一个路由,添加完了代码应该是下面这样的:

import Vue from 'vue'
import Router from 'vue-router'
import Hello from '@/components/Hello'
import List from '@/components/List'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Hello',
      component: Hello
    },
    {
      path: '/to-do',
      name: 'ToDo',
      component: List
    },
  ]
})

现在,访问localhost:8080/#/to-do,可以看到我们做的应用。

首先,我们要测试的是数据的正确性。在test/unit/specs目录下创建一个List.spec.js,并且写入下面的代码:

import List from '@/components/List';
import Vue from 'vue';

describe('List.vue', () => {

  it('displays items from the list', () => {
      // our test goes here
  })
})

在这个文件中,我们_describing_了List.vue组件,并且我们创建了一个空的测试,他将要检查这个组件的列表展示。这是一个基本的 Mocha 测试文件。

我们首先要安装我们的Vue组件。复制下面代码放在测试文件的'our test goes here'下面:

// build component
const Constructor = Vue.extend(List);
const ListComponent = new Constructor().$mount();

我们继承了Vue组件并且安装这个组件。安装组件很重要,只有这样我们才能将通过模板来渲染HTML。也就是说,HTML已经被创建,并且我们模板中的变量(比如 item)已经被填充内容,这样我们就可以获取HTML了(使用$el)。

我们的组件准备好了,我们可以写第一个断言。在这个例子中,我们使用Chai 断言库提供的 'expect' 模式,还有 'should' 和 'assert'模式。将下面的代码放到,启动组件的后面。

// assert that component text contains items from the list
expect(ListComponent.$el.textContent).to.contain('play games');

之前提到过,我们可以使用ListComponent.$el来获取组件的HTML,如果想去获取HTML内的内容(比如 文本),我们可以使用ListComponent.$el.textContent。这个断言用来检查HTML列表中的文本是否和组件的data里的数据列表吻合。

为了检查所有的事情都符合我们的预期,我们可以运行测试!通过 vue-cli 创建的项目,我们可以简单的使用npm run unit来运行cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run

npm run unit

如果测试都通过了,将会有一个绿色的列表来显示测试报告,让你了解测试都覆盖了哪些代码。

模拟用户输入

虽然前面的功能赞赞哒,但没有多少应用只是用来展示数据。下一步我们要做到是添加新的项目到to-do list中。看这里,我们创建了一个input框来输入内容,然后创建一个button用来提交内容。下面是更新后的 List.vue:

<template>
  <div>
    <h1>My To Do List</h1>
    </br>
    <input v-model="newItem" >
    <button @click="addItemToList">Add</button>
    <!-- displays list --> 
    <ul>
      <li v-for="item in listItems">{{ item }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'test',
  data () {
    return {
      listItems: ['buy food', 'play games', 'sleep'],
      newItem: ''
    }
  },
  methods: {
      addItemToList() {
        this.listItems.push(this.newItem);
        this.newItem = '';
      }
  }
}
</script>

使用v-model,输入框里面的内容将和newItem进行双向绑定。当按钮被点击后,执行addItemToList,将newItem添加到to-do list数组里面,并且清空newItem里面的内容,新的项目将会被添加到列表中。

可以为新功能写测试文件了,创建List.spec.js,并且添加以下测试代码。

it('adds a new item to list on click', () => {
    // our test goes here
})

第一步,我们需要创建我们的组件,并且模拟一个用户在输入框的输入行为。因为 VueJs 将输入框和 newItem 变量进行了绑定,我们可以给newItem设置内容。

// build component
const Constructor = Vue.extend(List);
const ListComponent = new Constructor().$mount();

// set value of new item
ListComponent.newItem = 'brush my teeth';

下一步,我们需要点击按钮。我们需要在HTML中找到按钮,在$el中即可找到。这是,我们可以使用querySelector,像选择真是元素一样选择这个按钮。也可以使用class(.buttonClass)、ID(#buttonID)或者标签名(button)来选择。

// find button
const button = ListComponent.$el.querySelector('button');

为了模拟点击,我们需要给按钮一个新的事件对象。在测试环境中,List组件不会监听任何事件,因此我们需要手动运行watcher

// simulate click event
const clickEvent = new window.Event('click');
button.dispatchEvent(clickEvent);
ListComponent._watcher.run();

最后,我们需要检查我们添加的新项目是否显示在HTML中,这个在前面已经介绍过。我们也需要检查newItem是否被存储在了数组里面。

//assert list contains new item
expect(ListComponent.$el.textContent).to.contain('brush my teeth');
expect(ListComponent.listItems).to.contain('brush my teeth');

下面是整个测试文件的内容:

import List from '@/components/List';
import Vue from 'vue';

describe('List.vue', () => {
  it('displays items from the list', () => {
    const Constructor = Vue.extend(List);
    const ListComponent = new Constructor().$mount();
    expect(ListComponent.$el.textContent).to.contain('play games');
  })

  it('adds a new item to list on click', () => {
    // build component
    const Constructor = Vue.extend(List);
    const ListComponent = new Constructor().$mount();

    // set input value
    ListComponent.newItem = 'brush my teeth';

    // simulate click event
    const button = ListComponent.$el.querySelector('button');
    const clickEvent = new window.Event('click');
    button.dispatchEvent(clickEvent);
    ListComponent._watcher.run();

    // assert list contains new item
    expect(ListComponent.$el.textContent).to.contain('brush my teeth');
    expect(ListComponent.listItems).to.contain('brush my teeth');
  })
})

现在跑一次这个测试,应该全是绿色的。

希望你读这些代码的时候思路能够清晰,不过它对于刚刚开始接触VueJs单元测试的人来说可读性并不是很高。有一个VueJS实用程序库,它将一些复杂的代码进行了封装。如果想使用它,可以在项目的根目录下输入以下命令安装。

npm install avoriaz

下面这个测试实际上和上面测试相同,只不过写法上有些不同。我们使用了mount()法来安装Vue组件,使用find()获取按钮,使用dispatch()来触发点击。

import { mount } from 'avoriaz';
import List from '@/components/List';
import Vue from 'vue';

describe('List.vue', () => {
  // previous tests ..

  it('adds new item to list on click with avoriaz', () => {
       // build component
    const ListComponent = mount(List);

    // set input value
    ListComponent.setData({
      newItem: 'brush my teeth',
    });

    // simulate click event
    const button = ListComponent.find('button')[0];
    button.dispatch('click');

    // assert list contains new item
    expect(ListComponent.text()).to.contain('brush my teeth');
    expect(ListComponent.data().listItems).to.contain('brush my teeth');
  })
})

总结

在日常工作以及JavaScript开发中,尤其是VueJS项目,测试是非常重要的。因为刚开始接触测试的时候,我遇到了一些问题,所以总结出一篇文章供大家参考。希望这篇文章能够帮到所有像我一样的人。

这个git库是这次教程所有的代码。

查看原文

赞 8 收藏 28 评论 2

泡芙的时光 赞了回答 · 2017-07-18

axios无法获取本地资源?

1、第一个是data.json文件目录位置不对
2、第二个不支持跨域
3、第三个支持CORS跨域

关注 14 回答 7

认证与成就

  • 获得 6 次点赞
  • 获得 7 枚徽章 获得 1 枚金徽章, 获得 2 枚银徽章, 获得 4 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2013-08-12
个人主页被 494 人浏览