笔记来源:拉勾教育 大前端高薪训练营
阅读建议:内容较多,建议通过左侧导航栏进行阅读
基本介绍
CDD 的好处
$root
$parent / $children
$refs
依赖注入 provide / inject
VueCLI 中提供了一个插件可以进行原型快速开发
需要先额外安装一个全局的插件 @vue/cli-service-global
npm install -g @vue/cli-service-global
使用 vue serve
快速查看组件的运行效果
vue serve
vue serve
如果不指定参数,默认会在当前目录找以下的入口
main.js
、index.js
、App.vue
、app.vue
可以指定要加载的组件
# vue serve 组件路径
vue serve ./src/login.vue
ElementUI
初始化 package.json
npm init -y
安装 ElementUI
vue add element
加载 ElementUI,使用 Vue.use() 安装插件
import ElemnetUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElemnetUI)
样式文件 steps.css
.lg-steps {
position: relative;
display: flex;
justify-content: space-between;
}
.lg-steps-line {
position: absolute;
height: 2px;
top: 50%;
left: 24px;
right: 24px;
transform: translateY(-50%);
z-index: 1;
background: rgb(223, 231, 239);
}
.lg-step {
border: 2px solid;
border-radius: 50%;
height: 32px;
width: 32px;
display: flex;
justify-content: center;
align-items: center;
font-weight: 700;
z-index: 2;
background-color: white;
box-sizing: border-box;
}
组件文件 Steps.vue
<template>
<div class="lg-steps">
<div class="lg-steps-line">div>
<div
class="lg-step"
v-for="index in count"
:key="index"
:style="{
color: active >= index ? activeColor : defaultColor}"
>
{
{ index }}
div>
div>
template>
<script>
import './steps.css'
export default {
name: 'LgSteps',
props: {
count: {
type: Number,
default: 3
},
active: {
type: Number,
default: 0
},
activeColor: {
type: String,
default: 'red'
},
defaultColor: {
type: String,
default: 'blue'
}
}
}
script>
测试文件(父组件)Steps-test.vue
<template>
<div>
<steps :count="count" :active="active">steps>
<hr />
<button @click="next">下一步button>
div>
template>
<script>
import Steps from './Steps'
export default {
components: {
Steps
},
data () {
return {
count: 4,
active: 0
}
},
methods: {
next () {
this.active < this.count && this.active++
}
}
}
script>
使用 vue serve
快速查看组件的运行效果
vue serve src/Steps-test.vue
访问 http://localhost:8080,查看组件的使用效果,如图所示:
整体结构
展示效果
模拟 el-form
组件,文件:Form.vue
<template>
<form>
<slot />
form>
template>
<script>
export default {
name: 'LgForm',
provide () {
return {
form: this
}
},
props: {
model: {
type: Object
},
rules: {
type: Object
}
},
}
script>
模拟 el-form-item
组件,文件:FormItem.vue
<template>
<div>
<label>{
{ label }}label>
<div>
<slot />
<p v-if="errMessage">{
{ errMessage }}p>
div>
div>
template>
<script>
export default {
name: 'LgFormItem',
inject: ['form'],
props: {
label: {
type: String
},
prop: {
type: String
}
},
data () {
return {
errMessage: ''
}
},
}
script>
模拟 el-input
组件,文件:Input.vue
<template>
<div>
<input v-bind="$attrs" :type="type" :value="value">
div>
template>
<script>
export default {
name: 'LgInput',
inheritAttrs: false, // 禁用继承父组件中传入的属性
props: {
value: {
type: String
},
type: {
type: String,
default: 'text'
}
},
}
script>
模拟 el-button
组件,文件:Button.vue
<template>
<div>
<button :type="type" @click="handleClick">
<slot />
button>
div>
template>
<script>
export default {
name: 'LgButton',
methods: {
handleClick (evt) {
this.$emit('click', evt)
evt.preventDefault()
}
}
}
script>
表单验证
ElementUI 中的表单验证,是使用了 async-validator
模块
安装 async-validator
npm i async-validator
Input 组件验证
Input 组件中触发自定义事件 validate
Input.vue
<input v-bind="$attrs" :type="type" :value="value" @input="handleInput">
<script>
export default {
methods: {
handleInput (evt) {
this.$emit('input', evt.target.value)
const findParent = parent => {
while (parent) {
if (parent.$options.name === 'LgFormItem') {
break
} else {
parent = parent.$parent
}
}
return parent
}
const parent = findParent(this.$parent)
if (parent) {
parent.$emit('validate')
}
}
}
}
script>
FormItem 渲染完毕注册自定义事件 validate
FormItem.vue
<script>
import AsyncValidator from 'async-validator'
export default {
mounted () {
this.$on('validate', () => {
this.validate()
})
},
methods: {
validate () {
if (!this.prop) return
const value = this.form.model[this.prop]
const rules = this.form.rules[this.prop]
const descriptor = {
[this.prop]: rules }
const validator = new AsyncValidator(descriptor)
return validator.validate({
[this.prop]: value }, errors => {
if (errors) {
this.errMessage = errors[0].message
} else {
this.errMessage = ''
}
})
}
}
}
script>
Form 定义事件 validate
Form.vue
methods: {
validate (cb) {
const tasks = this.$children
.filter(child => child.prop)
.map(child => child.validate())
Promise.all(tasks)
.then(() => cb(true))
.catch(() => cb(false))
}
}
packages
文件夹:存放所有要开发的组件,每一个组件对应一个文件夹,每一个文件夹就是一个包,它可以单独发布到 npm 中
button
文件夹:创建的组件名
__test__
文件夹:存放测试的文件
dist
文件夹:打包目录
src
文件夹:存放源码,*.vue 文件存放的位置
index.js
文件:打包的入口,使用组件,并将组件导出
import Button from './src/button.vue'
// 安装插件时,进行调用
Button.install = Vue => {
Vue.component(Button.name, Button)
}
export default Button
LICENSE
文件:存放版权信息(本次采用 MIT 协议)
package.json
文件:包的描述信息,包的名称和版本
README.md
文件:文档
自动安装
npx -p @storybook/cli sb init --type vue
yarn add vue
yarn add vue-loader vue-template-compiler --dev
--type
标志来指示 Storybook 根据该标志进行自身配置。
启动服务,并访问
npm run storybook
# yarn storybook
项目构建
npm run build-storybook
# yarn build-storybook
1,在根目录创建 packages
文件夹,用于存放组件,一个文件夹代表一个组件,目录结构,如下图所示:
2,组件的 *.vue
存放在 src
目录下,如图所示:
3,在每一个组件中,分别创建一个 stories/*.stories.js
文件,用于存放 默认导出 描述组件,以及 命名出口 描述 storybook
package/input/stories/input.stories.js
import LgInput from '../'
// Storybook lists your stories and provides information used by addons
export default {
title: 'LgInput',
component: LgInput
}
// 渲染组件,设置组件模板
export const Text = () => ({
components: {
LgInput },
template: ' ',
data () {
return {
value: 'admin'
}
}
})
export const Password = () => ({
components: {
LgInput },
template: ' ',
data () {
return {
value: 'admin'
}
}
})
package/form/stories/form.stories.js
import LgForm from '../'
import LgFormItem from '../../formitem'
import LgInput from '../../input'
import LgButton from '../../button'
export default {
title: 'LgForm',
component: LgForm
}
export const Login = () => ({
components: {
LgForm, LgFormItem, LgInput, LgButton },
template: `
登 录
`,
data() {
return {
user: {
username: '',
password: ''
},
rules: {
username: [
{
required: true,
message: '请输入用户名'
}
],
password: [
{
required: true,
message: '请输入密码'
},
{
min: 6,
max: 12,
message: '请输入6-12位密码'
}
]
}
}
},
methods: {
login() {
console.log('button')
this.$refs.form.validate(valid => {
if (valid) {
alert('验证成功')
} else {
alert('验证失败')
return false
}
})
}
}
})
注意:
此处引入了
formitem
组件,由于formitem
组件依赖于async-validator
包,因此,需要在formitem
组件中安装async-validator
包,否则会报错
4,修改 storybook
的配置文件 ./storybook/main.js
module.exports = {
"stories": ["../packages/**/*.stories.js"],
"addons": [
"@storybook/addon-links",
"@storybook/addon-essentials"
]
}
5,启动服务,测试组件
npm run storybook
# yarn storybook
6,访问网址,如图所示:
项目依赖
基本介绍
Yarn Workspaces
允许用户在单个根 package.json
文件的子文件夹中从多个package.json
文件中安装依赖。
通过防止 Workspaces
中依赖包的重复,使原生 Workspaces
到 Yarn
可以实现更快更轻松的依赖安装。Yarn
还可以在依赖于彼此的 Workspaces
之间创建软链接,并确保所有目录的一致性和正确性。
开启 yarn 的工作区
项目根目录的 package.json
{
"private": true,
"workspaces": ["packages/*"],
}
"private": true
:将项目提交到 GitHub 或 NPM 的时候,禁止把当前根目录的内容进行提交
"workspaces": []
:存放所有要管理的包路径
基本使用
给工作区根目录安装开发依赖
yarn add jest-D -W
jest
:FaceBook 发布的单元测试工具-D
:开发依赖-W
:工作区,指的是安装到工作区的根目录
给指定工作区安装依赖
yarn workspace lg-button add lodash@4
lg-button
:包名,即在package.json
中设置的name
属性
给所有的工作区安装依赖
yarn install
总结
重复的依赖包会提升到根目录下的 node_modules
目录中,单独的依赖会安装到对应的组件的 node_modules
目录中,方便管理依赖。
全局安装
yarn global add lerna
初始化
lerna init
发布
lerna publish
清理项目中的 node_modules
lerna clean
注意:
- 在发布之前,需要建立远端 Git 仓库,并且连接本地项目
在发布之前,还需要 注册 / 登录 npm
# 注册
npm adduser
# 登录
npm login
登录结果,如图所示:
查看登录账户,如下所示:
npm whoami
查看当前的镜像源
npm config get registry
查看是否是npm官网,如果不是的话,需要将镜像源修改回来
Vue Test Utils
:Vue提供的组件单元测试官方库,需要结合单元测试框架一起使用
Jest
:FaceBook 开发的单元测试框架
vue-jest
:预处理器
babel-jest
:对测试代码进行降级处理,即将 ES6 语法转换为 ES5等
安装
yarn add jest @vue/test-utils vue-jest babel-jest -D -W
package.json
"scripts": {
"test": "jest"
}
jest.config.js
module.exports = {
// 用哪里找测试文件
"testMatch": ["**/__tests__/**/*.[jt]s?(x)"],
// 测试文件中导入的模块后缀
"moduleFileExtensions": [
"js",
"json",
// 告诉 Jest 处理 `*.vue` 文件
"vue"
],
"transform": {
// 用 `vue-jest` 处理 `*.vue` 文件
".*\\.(vue)$": "vue-jest",
// 用 `babel-jest` 处理 js
".*\\.(js)$": "babel-jest"
}
}
babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env'
]
]
}
Babel 桥接
安装 Babel 的桥接依赖
yarn add babel-core@bridge -D -W
__tests__/input.test.js
import input from '../src/input.vue'
import {
mount } from '@vue/test-utils'
describe('lg-input', () => {
test('input-text', () => {
const wrapper = mount(input)
expect(wrapper.html()).toContain('input type="text"')
})
test('input-password', () => {
const wrapper = mount(input, {
propsData: {
type: "password"
}
})
expect(wrapper.html()).toContain('input type="password"')
})
test('input-password', () => {
const wrapper = mount(input, {
propsData: {
type: "password",
value: 'admin'
}
})
expect(wrapper.props('value')).toBe('admin')
})
test('input-snapshot', () => {
const wrapper = mount(input, {
propsData: {
type: "password",
value: 'admin'
}
})
expect(wrapper.vm.$el).toMatchSnapshot()
})
})
执行 yarn test
,测试结果,如图所示:
生成的快照文件,如图所示:
Rollup
rollup-plugin-terser:对代码进行压缩
[email protected]:把单文件组件编译成 js 代码,注意:一定要指定版本
vue-template-compiler:编译器
安装
yarn add rollup rollup-plugin-terser rollup-plugin-vue@5.1.9 vue-template-compiler -D -W
在 button 目录中创建 rollup.config.js
import {
terser } from 'rollup-plugin-terser'
import vue from 'rollup-plugin-vue'
module.exports = [
{
input: 'index.js',
output: [
{
file: 'dist/index.js',
format: 'es'
}
],
plugins: [
vue({
// Dynamically inject css as a