[top]
一.课程安排
1.项目结构
(1)了解vue3项目的结构
- .prettierrc文件的配置
1. 在vscode中下载Prettier - Code formatter和eslint插件.
2. 打开eslint先在项目中建一个.prettierrc文件 写上需要的配置。例如:
{
"semi": false,
"singleQuote": true,
"arrowParens": "always",
"trailingComma": "all"
}
3. 在设置中搜索format后在'工作区'勾选Format on Save 在项目结构中会生成一个.vscode
4. 在设置中搜索save Auto Save 设置为 onWindowChange 切换窗口后自动保存文件
(2)学习vue3项目的一些基本开发知识
1.使用jsx开发vue3组件
- 安装 jsx-next
- 在App.tsx文件中
import { defineComponent, reactive } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
export default defineComponent({
setup() {
const state = reactive({
name: '张三',
})
function renderHelloword (num:number){
return <HelloWorld age={ num }></HelloWorld>
}
return () => {
console.log(state.name)
return (
<div id="app">
<p>{state.name}</p>
<input type="text" v-model={state.name} />
{/* <HelloWorld age={222}></HelloWorld> */}//第一种写法
{ renderHelloword(12) }//第二种写法 封装函数
</div>
)
}
},
})
(3)vue3和vue2的开发区别
2.开发模式讲解
1.确定组件接口和其定义
(1)Props 即接口
- schema(用来定义数据,定义表单)
- value(表单的数据结果,可以从外部改变这个value)
- locale(语言使用‘ajv-i18n’指定错误信息使用的语言)
- onChange (通知组件库的使用者通知数据改变了,回调方法)
- uiSchema
2.开发入口组件的实现
3.开发基础渲染的实现
3.vue3的TS定义
4.单元测试 (开发开源组件上传时 一般需先测试再上传)
//1.针对ArrayField这一个组件或着匹配it里面描述的某一个组件进行单元测试
npm run test:unit -- -t=ArrayField
npm run test:unit -- -t=multi
-
为什么要单元测试:(
本质:组件做了什么就测什么)1.检查bug 2.提升回归效率 3.保证代码质量 -
vue-test-utils
-
vue-cli的配置文件 下面代码路径:github上
vue-cli/packages/@vue/cli-plugin-unit-jest/presets/default/jest-preset.js
// eslint-disable-next-line node/no-extraneous-require
const semver = require('semver')
let vueVersion = 2
try {
const Vue = require('vue/package.json')
vueVersion = semver.major(Vue.version)
} catch (e) {}
let vueJest = null
try {
vueJest = require.resolve(`@vue/vue${vueVersion}-jest`)
} catch (e) {
throw new Error(`Cannot resolve "@vue/vue${vueVersion}-jest" module. Please make sure you have installed "@vue/vue${vueVersion}-jest" as a dev dependency.`)
}
module.exports = {
testEnvironment: 'jsdom',
moduleFileExtensions: [//自动补全某些文件的后缀名
'js',
'jsx',
'json',
'vue'
],
transform: {//哪些文件需要怎么样的编译
// process *.vue files with vue-jest
'^.+\\.vue$': vueJest,//处理vue文件
'.+\\.(css|styl|less|sass|scss|jpg|jpeg|png|svg|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga|avif)$':
require.resolve('jest-transform-stub'),
'^.+\\.jsx?$': require.resolve('babel-jest')//jsx文件通过babel编译
},
transformIgnorePatterns: ['/node_modules/'],//哪些文件不需要去编译
moduleNameMapper: {//把@映射到src目录
'^@/(.*)$': '<rootDir>/src/$1'
},
snapshotSerializers: [//怎么去跑快照测试
'jest-serializer-vue'
],
testMatch: [//通过正则去匹配单元测试文件的地方
'**/tests/unit/**/*.spec.[jt]s?(x)',
'**/__tests__/*.[jt]s?(x)'
],
testURL: 'http://localhost/',//模拟浏览器环境
watchPlugins: [//改源文件自动再跑测试
require.resolve('jest-watch-typeahead/filename'),
require.resolve('jest-watch-typeahead/testname')
]
}
用于测试vue的组件,进行单元测试
- 覆盖率
执行
npm run test:unit -- --coverage展示测试覆盖的所有文件
Stmts:语句覆盖率
Lines:行覆盖率
Branch:分支是否都执行到(if/else)
Funcs:是否每个函数都有执行到
在github上找到vue-cli的jest测试
vue-cli/packages/@vue/cli-plugin-unit-jest/presets/typescript-and-babel/jest-preset.js
Go to file
- 写一个测试
经常用的api
- describe
(描述统计单元测试) - it
(用在describe里面对于某一个套件测试应该符合什么) - test
(单独写一个测试)
//describe表示这个函数里面测试只针对HelloWorld.vue这个组件
describe('HelloWorld.vue', () => {
//it表示对HelloWorld这个组件一些方面去做测试
it('renders props.msg when passed', () => {
const msg = 'new message'
const wrapper = shallowMount(HelloWorld as any, {
props: { msg },
})
expect(wrapper.text()).toMatch(msg)
})
})
终端上跑测试后运行结果
Test Suites: 1 passed, 1 total //Test Suites统计的是describe这个api所对应的
Tests: 1 passed, 1 total//统计的是it的数量
Snapshots: 0 total
- 断言 API文档
expect(wrapper.text()).toMatch(msg) //expect期望得到什么结果
expect(wrapper.text()).not.toEqual(msg)//期望wrapper.text()这个值不等于msg这个值
- 预设和清理
- beforeEach/afterEach
- before/afterAll
- 作用域
//每一个it 单元测试都会执行beforeEach,afterEach
beforeEach(() => {
//某一个单元测试启动前执行
console.log('beforeEach')
})
afterEach(() => {
//某一个单元测试启动后执行
console.log('afterEach')
})
beforeAll(() => {
//所有单元测试在启动前会执行一次
console.log('beforeAll')
})
afterAll(() => {
//所有单元测试在启动后会执行一次
console.log('afterAll')
})
//beforeEach/afterEach before/afterAll放在describe里面 只针对这个组件的处理
describe('HelloWorld.vue', () => {
beforeEach(() => {
console.log('beforeEach')
})
afterEach(() => {
console.log('afterEach')
})
beforeAll(() => {
console.log('beforeAll')
})
afterAll(() => {
console.log('afterAll')
})
//it表示对HelloWorld这个组件一些方面去做测试
it('renders props.msg when passed', () => {
const msg = 'new message'
const wrapper = shallowMount(HelloWorld as any, {
props: { msg },
})
expect(wrapper.text()).toMatch(msg) //expect期望
})
it('should work', () => {
expect(1 + 1).toBe(2)
})
})
- 异步测试
1.通过done来告诉测试这是个异步操作,让异步执行完成后再测试
describe('HelloWorld.vue', () => {
//it表示对HelloWorld这个组件一些方面去做测试
it('renders props.msg when passed', (done) => {
const msg = 'new message'
const wrapper = shallowMount(HelloWorld as any, {
props: { msg },
})
setTimeout(() => {
expect(wrapper.text()).toMatch(msg) //expect期望
done()//执行完成后调用done执行测试
}, 100)
})
it('should work', () => {
expect(1 + 1).toBe(2)
})
})
2.通过promise来告诉测试 这是异步测试、
describe('HelloWorld.vue', () => {
it('renders props.msg when passed', () => {
const msg = 'new message'
const wrapper = shallowMount(HelloWorld as any, {
props: { msg },
})
return new Promise((resolve: any) => {
expect(wrapper.text()).toMatch(msg) //expect期望
resolve()
})
})
})
3.通过 async await的方式告诉这是异步测试
describe('HelloWorld.vue', () => {
//it表示对HelloWorld这个组件一些方面去做测试
it('renders props.msg when passed', async () => {
const msg = 'new message'
const wrapper = shallowMount(HelloWorld as any, {
props: { msg },
})
await wrapper.setProps({
msg: '123',
})
expect(wrapper.text()).toMatch(msg) //expect期望
})
})
5.高泛用性的API
6.响应式原理
7.完善功能开发
8.自动化发布流程
二.vue3概览
1.Slot API的变化
<!-- old 2.0-->
<foo>
<bar slot="one" slot-scope="one">
<div slot-scope="bar">
{{ one }} {{ bar }}
</div>
</bar>
<bar slot="two" slot-scope="two">
<div slot-scope="bar">
{{ two }} {{ bar }}
</div>
</bar>
</foo>
<!-- new 3.0 -->
** v-slot 可用 # 代替 **
<foo>
<template v-slot:one="one">
<bar v-slot="bar">
<div>{{ one }} {{ bar }}</div>
</bar>
</template>
<template v-slot:two="two">
<bar v-slot="bar">
<div>{{ two }} {{ bar }}</div>
</bar>
</template>
</foo>
2.全局API使用规范变化
- main.js 全局引入组件 vue3通过createApp创建
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.config.isCustomElement = tag => tag.startsWith('app-')
app.use(/* ... */)
app.mixin(/* ... */)
app.component(/* ... */)
app.directive(/* ... */)
app.config.globalProperties.customProperty = () => {}
app.mount(App, '#app')
- 使用某些API通过import按需引入
import Vue, { nextTick, observable } from 'vue'
Vue.nextTick // undefined
nextTick(() => {})
const obj = observable({})
3.teleport
含义:渲染元素到某个节点下
<teleport to="#endofbody">
<div id="content">
<p>
this will be moved to #endofbody.<br />
Pretend that it's a modal
</p>
<Child />
</div>
</teleport>
4.Composition API (重)
(1)defineComponent函数
(2)props的用法
- 定义config类型
<script lang="ts">
import { defineComponent, PropType } from 'vue'
//定义一个Config类型
interface Config {
name: string
}
export default defineComponent({
name: 'App',
components: {
HelloWorld,
},
props: {
age: {
//不是必须传时 this.age的类型为number或undifine
type: Number as PropType<number>, //age类型为number
},
config: {
type: Object as PropType<Config>,
required: true, //必传时 this.config类型就是Config 不会有undifine的情况
},
},
data() {
return {
name: 'zhangsan',
}
},
mounted() {
// this.config
// this.age
},
})
</script>
- props的提取
<script lang="ts">
import { defineComponent } from 'vue'
//在PropsType对象后加上 as const 告诉 PropsType 为readonly(只读对象)。不然required: true不起作用
//写在 defineComponent 里面就是只读对象
const PropsType = {
msg: String,
age: {
type: Number,
required: true,
},
} as const
export default defineComponent({
name: 'HelloWorld',
props: PropsType,
mounted() {
this.age
},
})
</script>
(3)h函数的用法(vue的template模板就是h函数的调用)
import { createApp, defineComponent, h } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
//后面 加上eslint-disable-line注释表示对这一行代码取消eslint检查
const img = require("./assets/logo.png") //eslint-disable-line
//import App from './App.vue'
const App = defineComponent({
render() {
//这里的h函数可以用createVNode代替
return h('div', { id: 'app' }, [
h('img', {
alt: 'Vue logo',
src: img,
}),
h(HelloWorld, {
msg: 'Welcome to Your Vue.js + TypeScript App',
age: 12,
}),
])
},
})
createApp(App).mount('#app')
(4)setup的使用 (和data()一样只初始化执行一次)
- reactive 在setup中实现响应式
<template>
<div id="app">
{{ state.name }}
</div>
</template>
import { defineComponent, reactive } from 'vue'
//name每隔1秒会加1
setup(props, { slots, attrs, emit }) {
let state = reactive({
name: '张三',
})
setInterval(() => {
state.name += '1'
}, 1000)
return { state }
//return { ...state } 取出reactive里面的对象 这样返回就不会有响应式
},
- ref 在setup中实现响应式
<template>
<div id="app">
{{ name }}
</div>
</template>
import { defineComponent, ref } from 'vue'
setup(props, { slots, attrs, emit }) {
const nameRef = ref('李四')
//ref 类似于 {value:xxx}这样一个对象 会自动调.value属性 所以上面直接可以用name
setInterval(() => {
nameRef.value += '1'
}, 1000)
return { name: nameRef }
},
- computed(类似于vue2中的computed)
<template>
<div id="app">
{{ name }}:{{ name2 }}
</div>
</template>
import { defineComponent, ref, computed } from 'vue'
setup(props, { slots, attrs, emit }) {
const nameRef = ref('李四')
//ref 类似于 {value:xxx}这样一个对象 会自动调.value属性 所以上面直接可以用name
setInterval(() => {
nameRef.value += '1'
}, 1000)
//每次当nameRef变化时 computedNameRef都会重新计算
const computedNameRef = computed(() => {
return nameRef.value + '2'
})
return {
name: nameRef,
name2: computedNameRef,
}
},
- watchEffect
//只监听在watchEffect里面使用到的nameRef.value值 一变化就会执行watchEffect函数
import { defineComponent, ref, computed, watchEffect } from 'vue'
watchEffect(() => {
console.log(nameRef.value)
})
- setup返回rander函数的用法
import { createApp, defineComponent, h, reactive, ref } from 'vue'
//后面 加上eslint-disable-line注释表示对这一行代码取消eslint检查
const img = require("./assets/logo.png") //eslint-disable-line
//import App from './App.vue'
const App = defineComponent({
setup() {
const state = reactive({
name: '张三',
})
const numberRef = ref(1)
setInterval(() => {
state.name += '1'
numberRef.value += 1
}, 1000)
//const number = numberRef.value//这句话放在这里setup里面是无效的,因为setup只能执行一次 所以number永远是初始值 1
return () => {
const number = numberRef.value //放在return函数里面是有效的,因为numberRef的变化会让return的函数从新执行
return h('div', { id: 'app' }, [
h('img', {
alt: 'Vue logo',
src: img,
}),
h('p', state.name + number),
])
}
},
})
createApp(App).mount('#app')
5.v-model 指令API的变化
三.TypeScript
学习TS的注意点:
1. 任何变量都声明类型
2. 不到万不得已不要用any
3. 给你的对象声明接口
四.主题系统
1.核心逻辑不变
2.组件单独打包,减少强依赖
2.不同的样式主题
1.交互可以变化
2.组件的产出可以完全不同
3.统一接口后所有内容可以自定义
五.打包配置
1.通过环境变量配置控制打包文件
//1.通过 yarn add cross-env --save-dev 安装cross-env
//2.指定build:theme这个命令环境变量为TYPE=lib
//3.--target指定打包文件及路径
//4. --name 指定打包后哪个路径下文件名
"build:theme": "cross-env TYPE=lib vue-cli-service build --target lib --name theme-default/index lib/theme-default/index.tsx",
//4.然后在vue.config.js中
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin')//eslint-disable-line
const CircularDependencyPlugin = require('circular-dependency-plugin')//eslint-disable-line
const isLib = process.env.TYPE === 'lib'//获取环境变量等于lib时
module.exports = {
configureWebpack(config) {
// console.log(config.plugins)
},
chainWebpack(config) {
if (!isLib) {
//通过配置环境变量不等于lib时才执行下面这个配置
config.plugin('monaco').use(new MonacoWebpackPlugin())
}
config.plugin('circular').use(new CircularDependencyPlugin())
},
pwa: {},
}
2.案例
说明:
打包theme-default下index.tsx
打包后会生成两种js
1.common.js 通过node安装webpack打包后用到的文件
2.umd.js可以直接通过script标签引入的文件
3.分开打包
说明:
先安装 yarn add rimraf -D
//1.rimraf dist 手动删掉dist目录
//2.npm run build:core && npm run build:theme 分开打包build:core和build:theme
"build": "rimraf dist && npm run build:core && npm run build:theme"