8.4.3 封装Vue.js组件库

本文为拉勾网大前端高薪训练营第一期笔记

CDD

(Component-Driven Development)

  • 自下而上
  • 从组件级别开始,到页面级别结束

CDC的好处

  • 组件在最大程度被复用
  • 并行开发
  • 可视化测试

组件的边界情况

root: 获取根组件

parent: 获取父组件

children: 获取所有子组件

ref:拿到组件或者dom

provide&inject: 类似于react的context provider,上层定义一个provide的一些变量,下层任意一层都能inject获取之前provide提供的变量,但是变量不是响应式的,只能获取,不能修改

attrs-listeners:

  • $attrs: 把父组件中非prop属性绑定到内部组件
    • https://vuejs.org/v2/api/#inheritAttrs
    • 一般来说如果props没有去接收父组件传来的参数,会给子组件template最外层传这些参数,如果想传到最外层以外的位置,可以inheritAttrs: false,然后给想接收的位置加v-bind="$attrs",参数中class和style除外,class和style还是会和子组件根节点的class style合并
  • $listeners: 把父组件中的DOM对象的原生事件绑定到内部组件
//parent.vue





//myinput.vue





快速原型开发

VueCli中提供了一个插件可以进行原型快速开发

需要先额外安装一个全局的扩展

npm install -g @vue/cli-service-global

使用vue serve快速查看组件的运行效果

vue serve如果不指定参数的话,默认会在当前目录找以下的入口文件

  • main.js index.js App.vue app.vue

可以指定要加载的组件

vue serve ./src/login.vue

以ElementUI为例

npm init -y

vue add element

//main.js
import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import Login from './src/Login.vue'

Vue.use(ElementUI)

new Vue({
  el: '#app',
  render: h => h(Login)
})

步骤条例子

组件分类

  • 第三方组件
  • 基础组件
  • 业务组件
//Steps.vue






/* 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-test.vue





表单组件例子

//Input.vue





表单验证使用import AsyncValidator from ‘async-validator’

Form里传rules,通过provide传下去

provide () {
  return {
    form: this
  }
},

然后FormItem里使用inject拿到Form,从而拿到rules

inject: ['form'],

Monorepo

两种项目的组织方式

  • Multirepo(Multiple Repository)
    • 每一个包对应一个项目
  • Monorepo(Monolithic Repository)
    • 一个项目仓库管理多个模块/包
    • 一般在根目录下有个文件夹packages,里面放所有的模块

Storybook

  • 可视化的组件展示平台
  • 在隔离的开发环境中,以交互式的方式展示组件
  • 独立开发组件
  • 支持的框架
    • React, React Native, Vue, Angular
    • Ember, HTML, Svelte, Mithril, Riot

自动安装

npx -p @storybook/cli sb init --type vue
yarn add vue
vue yarn add vue-loader vue-template-compiler --dev

https://www.learnstorybook.com/intro-to-storybook/react/en/get-started/

yarn workspaces

开启yarn工作区

//package.json
"private": true, //将来提交npm和git禁止把当前根目录内容进行提交
"workspaces": [
	"./packages/*"
]

给工作区根目录安装开发依赖
yarn add jest -D -W
-D开发依赖  -W根目录

给指定工作区安装依赖
yarn workspace lg-button add lodash@4

给所有工作区安装依赖
删除子包的node_modules,然后yarn install
会将大部分子包共享的依赖提升到根目录,有版本冲突的子包的依赖会单独安装在子包下的node_modules

lerna介绍

lerna是一个优化使用git和npm管理多包仓库的工作流工具

用于管理具有多个包的javascript项目

可以一键把代码提交到git和npm仓库

使用

yarn global add lerna

lerna init

lerna publish

组件单元测试好处

  • 提供描述组件行为的文档
  • 节省手动测试的时间
  • 减少研发新特性时产生的bug
  • 改进设计
  • 促进重构

安装依赖

vue test utils

jest

vue-jest

babel-jest

yarn add jest @vue/test-utils vue-jest babel-jest -D -W
//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'
		] 
	]
}

需要注意的是vue-test依赖的babel6,项目里的babel7,这时候需要安装

yarn add babel-core@bridge -D -W

jest常用API

  • 全局函数
    • describe(name, fn) 把相关测试组合在一起
    • test(name,fn)测试方法
    • expect(value)断言
  • 匹配器
    • toBe(value)判断值是否相等
    • toEqual(obj)判断对象是否相等
    • toContain(value)判断数组或者字符串中是否包含
  • 快照
    • toMatchSnapshot()

vue test utils常用api

  • mount()
    • 创建一个包含被挂载和渲染的vue组件的wrapper
  • wrapper
    • vm wrapper包裹的组件实例
    • props() 返回vue实例选项中的props对象
    • html() 组件生成的html标签
    • find() 通过选择器返回匹配到的组件中的DOM元素
    • trigger() 出发DOM原生事件,自定义事件wrapper.vm.$emit()

例如

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: 'text',
        value: 'admin'
      }
    })
    expect(wrapper.vm.$el).toMatchSnapshot()
  })
})

第一次会生成快照,yarn test -u会重新生成快照

rollup

  • 模块打包器
  • 支持tree-shaking
  • 打包的结果比webpack小
  • 开发框架/组件库的时候使用rollup更合适

安装依赖

  • rollup
  • rollup-plugin-terser 对代码进行压缩
  • [email protected] 是把vue2.x的组件编译成js代码
  • vue-template-compiler 把template转换成render函数

举例

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({
        css: true,
        compileTemplate: true
      }),
      terser()
    ]
  }
]

对workspace下某一个执行npm run build

yarn workspace lg-button run build

如果想一次性打包所有插件

安装依赖

yarn add @rollup/plugin-json rollup-plugin-postcss @rollup/plugin-node-resolve -D -W

@rollup/plugin-node-resolve 把依赖的第三方库打包进来

import fs from 'fs'
import path from 'path'
import json from '@rollup/plugin-json'
import vue from 'rollup-plugin-vue'
import postcss from 'rollup-plugin-postcss'
import { terser } from 'rollup-plugin-terser'
import { nodeResolve } from '@rollup/plugin-node-resolve'

const isDev = process.env.NODE_ENV !== 'production'

// 公共插件配置
const plugins = [
  vue({
    // Dynamically inject css as a