一、Vue基础
1、vue初探
1)我们使用的数据需要现在data中存在;数据要先存在,才能实现数据绑定。
2)通过索引的方式、改变长度的方式去改变数组,都不能渲染视图;
数组变异方法:push,pop,shift,unshift,sort,reverse,splice。
3)$set:vm.$set(vm.obj, ‘xxx’, 90); $nextTick:dom循环结束触发的回调函数;
$el:拿到vue挂载的元素; $mount:选择vue挂载到的dom元素。
改变vue里面的值是异步操作,要拿到异步后的数据得用$nextTick
2、vue指令
1)v-pre:vue渲染时自动跳过;v-cloak:记录还没渲染,可使[v-cloak]display:none; v-once:只认第一次的渲染;
v-html === innerHTML,v-text === v-innerText,注意XSS攻击。
2)v-if(v-else、v-else-if):控制显示与隐藏,可配合template标签(临时标签、不渲染)使用;
v-show:控制显示与隐藏,但其一直存在,只是改变其display为none;v-if则是完全移除dom元素,且v-show不支持template。
3)v-bind(:):绑定属性。如 或;
绑定两个属性值时需要用数组或对象,如 :class="[redClass, blueClass, flag && red]";
:style="{widh: ‘100px’, height: ‘100px’, backgroundColor: color}" 。注意style < :style。
4)v-on(@):绑定事件。如 @click=“change(‘red’)” ;
注意methods里面不能写箭头函数,否则this指向windows。且事件名称不能和data里面的一样。
5)v-for:遍历渲染dom,要有对应的key值(浏览器会参照渲染前后key值有没有变化决定要不要重新渲染),key值最好不要用index。
6)v-model:双向数据绑定;input text,textarea。input checkbox、radio、select是把value值实现双向数据绑定。v-model.lazy(延迟绑定),v-model.number(转为number),v-model.trim(去除前后空格),7)避免使用JavaScript中字符形式的一元操作符(delete、void、typeof)作为属性名。如:@click=“delete(index)”。
8)v-slot (#) : 写组件时用的插槽。例子:
<div id="app">
<my-button>
啦啦啦
<template v-slot:right>
呵呵呵
</template>
哈哈哈
</my-button>
</div>
Vue.component('myButton', {
template: `<button>
<slot></slot>
-----------------
<slot name="right"></slot>
</button>`
});
9)vue自定义指令(directive):全局的如下定义,局部的则定义在vue实例内。
el:指令绑定的元素;
bindings:指令包含的信息(参数,修饰符),如v-slice:9.number;
vnode:vnode.context代表vue的实例;
bind():绑定时执行;update():更新时执行;insered():把dom元素插入到页面时执行。直接写表示bind和update结合。例子:
<div id="app">
<input type="text" v-slice:9.number="content"/> {{content}}
</div>
// Vue.directive('slice', (el, bindings, vnode) => {
// const val = bindings.value.slice(0, 5);
// vnode.context.content = val;
// });
Vue.directive('slice', {
bind(el, bindings, vnode) {
const context = vnode.context;
const length = bindings.arg || 5;
const numberFlag = bindings.modifiers.number;
let initVal = context[bindings.expression];
if(numberFlag) {
initVal = initVal.replace(/[^0-9]/g, "");
}
initVal = initVal.slice(0, length);
el.value = initVal;
context[bindings.expression] = initVal;
el.oninput = e => {
let value = e.target.value;
if(numberFlag) {
value = value.replace(/[^0-9]/g, "");
}
let val = value.splice(0, length); context[bindings.expression] = val;
el.value = value;
}
},
update(el, bindings, vnode) {
let context = vnode.context;
const numberFlag = bindings.modifiers.number;
let value = context[bindings.expression];
if(numberFlag) {
value = value.replace(/[^0-9]/g, "");
}
el.value = value;
context[bindings.expression] = value;
},
inserted() {
el.focus();
}
});
const vm = new Vue({
el: '#app',
data: {
content: ''
}
});
3、过滤器(filter):局部的优先级大于全局的。
<div id="app">{{money | toMoney(2)}}</div>
Vue.filter('toMoney', (value, times) => {
console.log('vue');
return (value * times).toLocaleString();
});
const vm = new Vue({
el: '#app',
filters: {
toMoney: (value) => {
console.log('app');
return (value).toLocaleString();
}
},
data: {
money: 10000000,
}
});
4、挂载
const vm = new Vue({
el: '#app',
template: '<h2>{{msg}}</h2>',
render() {
const tag = "div";
return (
<tag class="haha" style={{color: 'red', font-size: '12px'}} on-click={() => {console.log('haha')}}>
思考
<p>哈哈</p>
</tag>
)
},
data: {
msg: '哈哈哈'
}
});
5、生命周期
1)可以在created()里调用数据、ajax,mounted()里面拿到$el、调ajax;
2)不要在updated()里面更改数据,会造成死循环;
3)可以在beforeDestroy()里面清除定时器;
6、计算属性(computed)和侦听器(watch)
1)前提:data的this指向window,methods在每次改变数据的时候都会执行,消耗性能,所以需要computed或者watch;
2)computed没办法执行异步的操作;
3)优先级:data > methods > computed。
const vm = new Vue({
el: '#app',
data: {
name: 'scj',
age: 18,
person: ''
},
// mounted() {
// this.getPerson();
// },
methods: {
getPerson() {
this.person = `姓名:${this.name},年龄:${this.age}`;
}
},
computed: {
// person() {
// return `姓名:${this.name},年龄:${this.age}`
// }
},
watch: {
// name () {
// this.getPerson();
// },
// age () {
// this.getPerson();
// },
name: {
handler (newVal) {
this.getPerson();
},
immediate: true, //是否直接渲染
},
age: {
handler (newVal) {
this.getPerson();
},
}
}
});
computed应用:
<div id="app">
初始值:<input type="text" v-model.number="firstNum"> + <input type="text" v-model.number="secondNum">
计算值:<input type="text" v-model.number="sum">
</div>
const vm = new Vue({
el: '#app',
data: {
firstNum: null,
secondNum: null,
},
methods: {
},
computed: {
sum: {
get () {
return this.firstNum + this.secondNum;
},
set (val) {
this.firstNum = this.secondNum = val / 2;
}
}
},
});
7、组件
1)数据传递&属性校验
<div id="app">
<my-content :content="content" :title="title"></my-content>
</div>
const vm = new Vue({
el: '#app',
data: {
content: '组件间数据的传递与校验',
title: '组件'
},
components: {
myContent: {
data() {
return {
...
}
}
props: {
content: {
type: String,
default: '内容默认值',
},
title: {
type: String,
validator (value) {
return value === '组件';
},
required: true
},
arr: {
type: Array,
// 数组是引用值,直接指向的话容易混乱,要用函数return
default: () => [1, 2, 3]
},
},
template:
`<div>
{{arr}}
<h3>{{title}}</h3>
<h4>{{content}}</h4>
</div>`,
}
}
});
2)父向子传递数据
① $attrs:表示从父组件传过来且未被注册的数据集,可直接传给自身子组件,如:v-bind="$attrs",但是未被注册的属性会显示在行间,可通过inheritAttrs:false来取消显示。
② $parent,$children:获得父组件或子组件实例。
③ provide、inject:父组件中使用provide{ title: ‘我是标题’ },子或孙组件中使用inject: [‘title’]。
3)子向父传递数据
① 引用(ref):如对dom元素加属性ref=“parentDom”,可通过this.$refs.parentDom拿到该dom元素。
② 通过函数传递:如对dom元素追加属性func=“func”,子组件中调用该函数传值。
③ $listeners:如对dom元素加@click=“func”,在子组件中通过this.$listeners.click(this.msg)即可向父组件传值。在子组件中用v-on="$llisteners"监听所有事件。
④ $emit:接第③例子前半段,如$emit(‘click’, this.msg)。
4)兄弟组件间传递数据
① event bus
Vue.prototype.bus = new Vue();
this.bus.$emit('click', this.msg);// 要发送数据的组件触发事件并带数据
this.bus.$on('click', content => {// 其它组件监听事件触发并接受数据
this.content = content;
})
5)组件间单向数据流
子组件最好不要更改父组件的传过来的值,避免传过来是对象的话把父组件的值也改了。可先将传过来的值赋给子组件本身的值再更改,但也得注意传过来的是对象。
6)组件间双向数据流
① v-model = :value + @input。
② .sync = :value + @update:value。
8、安装脚手架
npm install -g @vue/cli 安装脚手架,用于生成项目
npm install -g @vue/cli-service-global 快速原型开发 编译.vue文件
如果之前已经安装过旧版本(非3.x)脚手架,需先卸载旧版本:
npm uninstall vue-cli -g
如果仍然需要使用旧版本的 vue init 功能,可以全局安装一个桥接工具:
npm install -g @vue/cli-init 拉取旧版本
插件名字:Vetur(VS code)
9、vue文件
运行vue serve 时必须具备入口文件,入口文件可以是:main.js, index.js, App.vue or app.vue。
<template> // 相当于component里的template
</template>
<script>
export default { // 这层里面的相当于除了template以外的东西
name: "" // name属性可以让组件调用自身组件,实现组件递归
}
</script>
<style scoped> // scoped属性约束了style的作用域
</style>
10、利用脚手架搭建项目
1)vue create vue_app
更改用户文件夹下的.vuerc文件可以更改预设
2)vue ui
更改用户文件夹下的.vue-cli-ui/db.json文件可以更改预设
11、路由
创建路由:命令行vue add router
1)router-link
router-view:路由入口处,tag="li"可以改变router-link为li标签
<router-link tag="li" to="/about">About</router-link>
<router-link tag="li" :to="{name: 'about'}">关于</router-link><router-link tag="li" :to="{path: '/community'}">社区</router-link>
2)路由器配置
import Vue from 'vue';
import Router from 'vue-router';
import Home from './views/Home';
Vue.use(Router); // 使用路由,不写则路由不生效
export default new Router({
mode: 'history', // 取消hash型(路径上的#会消失)
linkExactActiveClass: 'active-exact', // 完全匹配路由路径,这里是重新配置其名字
linkActiveClass: 'active', // 不完全匹配路径,这里是重新配置其名字
routes: [
{
path: '/home',
name: 'home',
component: Home
},
{
path: '/learn',
name: 'learn',
// 路由懒加载
component: () => import('./views/Learn')
},
{
path: '/student',
name: 'student',
component: () => import('./views/Student')
},
{
path: '/about',
name: 'about',
component: () => import('./views/About')
},
{
path: '/community',
name: 'community',
component: () => import('./views/Community'),
redirect: '/community/academic', // 重定向路径
// 路由嵌套
children: [
{
// '/academic'带/的话就得写全路径,不带就自动向上找带/的,并拼接路径
path: 'academic',
name: 'academic',
component: () => import('./views/Academic')
},
{
path: 'download',
name: 'download',
component: () => import('./views/Download')
},
{
path: 'personal',
name: 'personal',
component: () => import('./views/Personal')
}
]
},
{
path: '/NotFound',
component: () => import('./views/NotFound')
},
{
path: '*', // *代表上面没匹配到的路径
redirect (to) {
console.log(to); // to 表示路径的相关信息
if (to.path === '/') {
return '/home';
} else {
return '/NotFound';
}
}
}
]
})
3)$router跳转、$route
① $router记录的是路由的整体信息,常借助它来实现跳转
this.$router.push('/home'); // 在当前添加一个
this.$router.push({ // 动态路由跳转
name: 'question',
params: { // 或query
id: this.questionId
}
})
this.$router.replace('/home'); // 取代当前路由
this.$router.go(-1); // 回退
② $route记录的是路径信息,常用它来取动态路由传过来的值,如this.route.params.id。
4)路由守卫
组件路由守卫
① beforeRouteLeave 路由离开前会执行的函数(组件路由改变前)
beforeRouteLeave(to, from, next) {
if (this.name) {
const flag = window.confirm('你确定要离开吗');
if (flag) {
this.name = '';
next();
}
} else {
next();
}
}
② beforeRouteEnter
路由进入前会执行的函数,这个函数里面的this
为undefined,因为路由还没进入。可在next函数中使用this
(vm)。
beforeRouteEnter(to, from, next) {
next(vm => {
console.log(vm);
});
},
③ beforeRouteUpdate
路由更新前会执行的函数,常用来检测动态路由更新,可配合mounted使用,第一次进来用mounted渲染组件,不刷新组件的情况下监测路由更新去重新渲染组件。
beforeRouteUpdate(to, from, next) {
this.getData(to.params.id);
next();
},
注意 beforeRouteEnter
是支持给 next
传递回调的唯一守卫。对于 beforeRouteUpdate
和 beforeRouteLeave
来说,this
已经可用了,所以不支持传递回调,因为没有必要了。
独享路由守卫
{
path: '/home',
name: 'home',
component: Home,
beforeEnter(to, from, next){
next();
}
},
全局路由守卫
router.beforeEach((to, from, next) => { // 进来前
next();
});
router.beforeResolve((to, from, next) => { // 全部解析完成前
next();
});
router.afterEach(() => { // 全部加载完以后
next();
});
导航解析流程
5)动态路由
① 用:设置动态路由(http://localhost:8080/question/201804):
path:'/question/:id'
<router-link
tag="li"
:to="{name: 'question', params: {id: item.questionId}}"
</router-link>
this.$route.params.id;
② 用?设置动态路由(http://localhost:8080/question?id=201804):
path:'/question'
<router-link
tag="li"
:to="{name: 'question', query: {id: item.questionId}}"
</router-link>
this.$route.query.id;
6)路由元信息
存储路由信息,可检测路由里面是否有元信息来判断能否登录,配合cookie
{
path: '/student',
name: 'student',
meta: {
login: true
},
component: () => import('./views/Student')
}
12、vue-cli配置
1)打包项目: npm run build
2)安装less和less预处理器:npm install less less-loader -D
3)安装全局样式插件:vue add style-resources-loader
4)vue.config.js配置文件
const path = require('path');
module.exports = {
// 不打包.map文件
productionSourceMap: false,
// 设置输出目录
outputDir: './myDist',
// 根据环境变量设置资源路径
publicPath: process.env.NODE_ENV === 'production' ? 'http://www.baidu.com' : '/',
// 将文件都打包整合到assets里面
assetsDir: 'assets',
// 在webpack基础上再增加一些自己的配置,config表示mudule.exports
chainWebpack: config => {
// 配置文件夹别名,这里是把views的名字改为_v
config.resolve.alias.set('_v', path.resolve(__dirname, 'src/views'))
},
// webpack常规配置
configureWebpack: {
// plugin: [],
// module: {}
},
// 服务器代理配置,可实现跨域
devServer: {
proxy: {
'/api/chat/sendMsg': {
target: 'http://api.duyiedu.com'
}
}
},
pluginOptions: {
'style-resources-loader': {
preProcessor: 'less',
patterns: [
// 设置全局样式的路径
path.resolve(__dirname, 'src/assets/styles/variable.less')
]
}
}
};
二、Vuex
1、描述
当页面有很多组件的时候,采用上面的方法共用和传递数据很麻烦,就有了vuex,vuex相当于一个公共的仓库,管理着组件共用的数据,能让组件间传递数据更容易。
2、安装
命令行执行 vue add vuex
成功后项目目录会多个store.js文件
3、state
// 当代码开发的时候开启严格模式,规范代码;当项目进入生产上线时关掉严格模式,提升性能
strict: process.env.NODE_ENV !== 'production',
拿store里面的数据:
this.$store.state.xxx // 不严格模式
import {mapState} from 'vuex';
computed: {
// ...mapState(['studentList']), // 直接取
...mapState({
myStudentName: state => state.studentList // 改变量名,防止冲突
})
}
4、getters
类似computed,因为直接改变state里面的数据并不能重新渲染组件,所以用getters。
getters: {
func(state, getters) { // 通过state.xxx能拿到state里面的值,getters同理
return 'xxx';
}
}
拿getters里面的数据:
this.$store.getters.xxx // 不严格模式
import {mapGetters} from 'vuex';
computed: {
// ...mapGetters(['studentList']), // 直接取
...mapGetters({
myStudentName: 'studentList' // 改变量名,防止冲突
})
}
5、mutations
改变vuex中的状态,只能执行同步的
mutations: {
changeStudentList (state, {tempObj, name}) {
// setTimeout(() => {
state.studentList.push(tempObj);
state.name = name;
// }, 1000)
}
},
使用mutations:
this.$store.commit('xxx') // 不严格模式
import {mapMutations} from 'vuex';
computed: {
// ...mapMutations(['xxx']), // 直接取
...mapMutations({
newXXX: 'xxx' // 改变量名,防止冲突
})
}
6、actions
提交mutations,让mutations去更改状态,能够执行异步的
actions: {
changeStudentList ( {commit}, payload ) {
setTimeout(()=>{
commit('changeStudentList',payload )
}, 1000)
}
}
使用actions:
this.$store.dispatch('xxx') // 不严格模式
import {mapActions} from 'vuex';
computed: {
// ...mapActions(['xxx']), // 直接取
...mapActions({
newXXX: 'xxx' // 改变量名,防止冲突
})
}
7、Module
- 根据功能让vuex分出模块,可以在store.js里面分,也可以分出文件来
- state会放入到每一个模块下,getters、mutations、actions会直接放入到全局
获取vuex中的数据(无namespaced)
- 获取state :
this.$store.state.moduleName.xxx
- 获取getters:
this.$store.getters.xxx
- 获取mutations:
this.$store.commit('xxx')
- 获取actions:
this.$store.dispatch('xxx')
- 可以通过mapXXX 方式拿到getters、mutations、action,但是不能拿到state,如果想通过这种方式获取state,需要加命名空间:
namespaced:true
获取vuex中的数据(有namespaced)
- 获取state :
this.$store.state.moduleName.xxx
- 获取getters:
this.$store['moduleName/getters'].xxx
- 获取mutations:
this.$store.commit('moduleName/xxx')
- 获取actions:
this.$store.dispatch('moduleName/xxx')
- 可以通过mapXXX:
mapXXX('moduleName', ['xxx']) mapXXX('moduleName', {})
三、单元测试
1、测试分类
1)TDD: Test Driven Development 测试驱动开发
2)BDD: Behavior Driven Development 行为驱动开发
2、测试工具
建立测试的文件最好放在test/unit目录下,命名的时候必须以xxx.spec.js或xxx.test.js结尾
1)mocha & chai
-
mocha:测试框架
vue脚手架内部就安装好了,不需要再次引入
自己安装:mocha mocha-webpack
-
chai:断言库,断定左边的和右边的是否相等
三种断言风格:should、expect、assert
www.chaijs.com
用法
-
套件
describe(‘套件名字’, () => {})
生命周期
before(()=>{}) 调用该套件的所有测试用例之前执行且只执行一次
after(()=>{}) 调用该套件的所有测试用例之后执行且只执行一次
beforeEach(()=>{}) 调用该套件的每个测试用例之前执行,有几个测试用例就执行几次
afterEach(()=>{}) 调用该套件的所有测试用例之后执行,有几个测试用例就执行几次
-
用例
it(‘用例名字’, () => {})
done
done函数被调用的时候,才能完成测试
chai的基本用法
-
判断相等
判断基本类型 expect(1).to.be.equal(1);
判断引用类型:expect({a: 1}).to.be.deep.equal({a: 1}) / expect({a: 1}).to.be.eql({a: 1})
deep标记:该标记可以让其后的断言不是比较对象本身,而是递归比较对象的键值对
-
判断不等
expect(2).to.be.not.equal(1);
-
判断大于
expect(10).to.be.above(5);
expect(10).to.be.greaterThan(5);
-
判断小于
expect(5).to.be.below(10);
expect(5).to.be.lessThan(10);
-
判断大于等于
expect(10).to.be.at.least(10);
expect(10).to.be.not.lessThan(10);
-
判断小于等于
expect(5).to.be.at.most(5);
expect(5).to.be.not.greaterThan(5);
-
判断长度
expect([1, 2, 3]).to.be.lengthOf(3);
-
判断为truthy,(除了false、undefined、null、正负0、NaN、""的值)
expect(1).to.be.ok;
-
判断为true、false、null、undefined、NaN
expect(true).to.be.true;
expect(false).to.be.false;
expect(null).to.be.null;
expect(undefined).to.be.undefined;
expect(NaN).to.be.NaN;
-
判断包含
expect(‘shanshan’).to.be.include(‘s’); 包含
expect(‘shanshan’).to.be.contain(‘s’); 包含
expect(‘shanshan’).to.be.match(/s/); 匹配
测试函数代码示例:
import { abs, add } from "../../src/count";
import { expect } from "chai";
describe('add函数', () => {
it('测试给add传入几个值,期待返回值为这几个值的和', () => {
expect(add(1, 5, 7)).to.be.equal(13);
});
});
describe('abs函数', () => {
it('测试给abs传入正数,期待返回值与输入相同', () => {
expect(abs(1)).to.be.equal(1); // 测试基本类型数据
// expect({}).to.be.deep.equal({}); // 测试对象、引用值等,deep.equal可写成eql
});
it('测试给abs传入负数,期待返回值与输入之相反', () => {
expect(abs(-1)).to.be.equal(1);
});
it('测试给abs传入0,期待返回值是0', () => {
expect(abs(0)).to.be.equal(0);
});
it('测试给abs传入非数值,期待返回值是NaN', () => {
expect(abs("")).to.be.eql(NaN);
expect(abs([])).to.be.eql(NaN);
expect(abs(null)).to.be.eql(NaN);
expect(abs({})).to.be.eql(NaN);
expect(abs(undefined)).to.be.eql(NaN);
});
});
测试dom元素渲染示例
import HelloWorld from '@/components/HelloWorld';
import Vue from 'vue';
import { expect } from 'chai';
import { mount } from '@vue/test-utils';
// Vue.component({})
// const vm = new Vue({})
// vm.$el == > 挂载的元素 vm.$el. h1 innerHTML
// Vue.extend
// vue test units
describe('HelloWorld.vue', () => {
it('测试msg属性,能否正常渲染, 原生', () => {
const msg = 'hello world';
const Constructor = Vue.extend(HelloWorld);
const vm = new Constructor({
propsData: {
msg
}
}).$mount();
const domInner = vm.$el.getElementsByTagName('h1')[0].innerHTML.trim();
expect(domInner).to.be.equal(msg);
})
it('测试msg属性,能否正常渲染, vue test util', () => {
const msg = 'hello world';
// const wrapper = mount(HelloWorld, {
// propsData: {msg}
// });
const wrapper = mount(HelloWorld);
wrapper.setProps({msg});
const domInner = wrapper.find('h1').text();
expect(domInner).to.be.include(msg);
})
})
2)sinon
辅助我们进行前端测试。
安装:npm install sinon -D
引入: import sinon from ‘sinon’;
spy 间谍函数
const spy = sinon.spy();
-
使用方法
spy.called; 表示函数是否被调用,返回布尔值
spy.callCount; 函数被调用的次数
spy.calledOnce; 函数只被调用了一次,返回布尔值
spy.calledTwice; 函数被连续调用了两次,返回布尔值
spy.calledThrice; 函数被连续调用了三次,返回布尔值
spy.firstCall; 函数第一次被调用。返回布尔值
spy.secondCall; 函数第二次被调用。返回布尔值
spy.thirdCall; 函数第三次被调用。返回布尔值
spy.lastCall; 函数最后一次被调用。返回布尔值
spy.calledOn(‘xxx’); 调用函数时,函数的this至少有一次是xxx,返回布尔值。
spy.alwaysCalledOn(‘xxx’); 调用函数时,函数的this始终是xxx,返回布尔值。
spy.calledWidth(1, 2, 3); 函数至少被调用一次,且参数包含1, 2, 3,返回布尔值。
spy.calledOnceWith(1, 2, 3); 函数只被调用一次,且参数包含1, 2, 3,返回布尔值。
spy.alwaysCalledWith(1, 2, 3); 函数被调用时传的参数始终包括1,2,3,返回布尔值。
spy.calledWithExactly(1, 2, 3); 函数至少被调用一次,且参数只为1,2,3,返回布尔值。
spy.alwaysCalledWithExactly(1, 2, 3); 函数被调用时传的参数始终只为1,2,3,返回布尔值。
spy.calledWithNew(); 函数被作为构造函数new,返回布尔值。
spy.neverCalledWith(1, 2, 3); 函数执行时,参数从不为1, 2, 3。返回布尔值。
spy.threw(); 函数执行时,抛出一个异常。返回布尔值。
spy.threw(“TypeError”); 函数执行时,至少抛出一次TypeError异常。返回布尔值。
spy.threw(‘xxx’); 函数执行时,至少抛出一次xxx异常。返回布尔值。
spy.alwaysThrew(); 函数执行时,始终抛出异常。返回布尔值。
spy.alwaysThrew(“TypeError”); 函数执行时,始终抛出TypeError异常。返回布尔值。
spy.alwaysThrew(‘xxx’); 函数执行时,失踪抛出xxx异常。返回布尔值。
spy.returned(‘xxx’); 函数执行时,至少返回一次xxx。返回布尔值。
spy.alwaysReturned(‘xxx’); 函数执行时,始终返回’xxx’。返回布尔值。
spy.getCall(n); 返回函数被第n次调用。
spy.getCalls(); 返回一个函数被调用的数组。
spy.thisValues; 返回函数被调用时this指向的集合, 值类型为数组。
spy.args; 返回函数被调用时参数,值类型为数组。
spy.exceptions; 返回函数被调用时抛出的异常集合. 值类型为数组。
spy.returnValues; 返回函数被调用时返回的值,值类型为数组。
3)jest
-
匹配语法
https://jestjs.io/docs/zh-Hans/using-matchers
-
mock函数
https://jestjs.io/docs/zh-Hans/mock-functions
-
jest异步
// axios.js文件 export default { get: url => { switch (url) { case '/name': return Promise.resolve({ status: 200, data: { name: 'shanshan' } }) case '/age': return Promise.resolve({ status: 200, data: { age: 18 } }) } } }
// Async1.spec.js文件 import Async from '@/components/Async'; import { mount } from '@vue/test-utils'; import Vue from 'vue'; jest.mock('axios'); describe('Async.vue', () => { // 利用Vue.nextTick解决异步问题。 it('获取name', done => { const wrapper = mount(Async); wrapper.findAll('button').at(0).trigger('click'); Vue.nextTick(() => { expect(wrapper.findAll('h4').at(0).text()).toBe('shanshan'); done(); }) }) // 利用Promise解决异步问题。 it('获取name', () => { const wrapper = mount(Async); wrapper.findAll('button').at(0).trigger('click'); return Promise.resolve().then(() => { expect(wrapper.findAll('h4').at(0).text()).toBe('shanshan'); }) }) // 利用async、await解决异步问题。 it('获取name', async () => { const wrapper = mount(Async); await wrapper.vm.getName(); expect(wrapper.findAll('h4').at(0).text()).toBe('shanshan'); }) })
四、element组件库
1、安装
vue add element
可选全部引入和按需引入npm i element-ui -S
也可引入全部或按需引入- 完整引入
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';
Vue.use(ElementUI);
new Vue({
el: '#app',
render: h => h(App)
});
-
按需引入
import { Button, Select } from 'element-ui'; Vue.component(Button.name, Button); Vue.component(Select.name, Select); /* 或写为 * Vue.use(Button) * Vue.use(Select) */
2、修改element组件样式
k(() => {
expect(wrapper.findAll(‘h4’).at(0).text()).toBe(‘shanshan’);
done();
})
})
// 利用Promise解决异步问题。
it('获取name', () => {
const wrapper = mount(Async);
wrapper.findAll('button').at(0).trigger('click');
return Promise.resolve().then(() => {
expect(wrapper.findAll('h4').at(0).text()).toBe('shanshan');
})
})
// 利用async、await解决异步问题。
it('获取name', async () => {
const wrapper = mount(Async);
await wrapper.vm.getName();
expect(wrapper.findAll('h4').at(0).text()).toBe('shanshan');
})
})
## 四、element组件库
#### 1、安装
- `vue add element` 可选全部引入和按需引入
- `npm i element-ui -S` 也可引入全部或按需引入
- 完整引入
```js
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';
Vue.use(ElementUI);
new Vue({
el: '#app',
render: h => h(App)
});
-
按需引入
import { Button, Select } from 'element-ui'; Vue.component(Button.name, Button); Vue.component(Select.name, Select); /* 或写为 * Vue.use(Button) * Vue.use(Select) */
2、修改element组件样式
给组件加个class1,再找到组件样式对应的class2,写样式时在class2前面加个class1,这样的话能增加权重且不产生样式污染