1.Monorepo介绍
Monorepo 是管理项目代码的一个方式,指在一个项目仓库(repo)中管理多个模块/包(package)
优点:
一个仓库可维护多个模块,不用到处找仓库
方便版本管理和依赖管理,模块之间的引用,调用都非常方便
缺点:仓库体积会变大。
2.Vue3项目结构
reactivity:响应式系统
runtime-core:与平台无关的运行时核心 (可以创建针对特定平台的运行时 - 自定义渲染器)
runtime-dom: 针对浏览器的运行时。包括DOM API,属性,事件处理等
runtime-test:用于测试
server-renderer:用于服务器端渲染
compiler-core:与平台无关的编译器核心
compiler-dom: 针对浏览器的编译模块
compiler-ssr: 针对服务端渲染的编译模块
compiler-sfc: 针对单文件解析
size-check:用来测试代码体积
template-explorer:用于调试编译器输出的开发工具
shared:多个包之间共享的内容
vue:完整版本,包括运行时和编译器
3.实现响应式
1.实现reactive:packages\reactivity\src\reactive.ts
1)reactive的数据会经过new Proxy处理返回一个代理对象,获取或者设置代理对象属性时会走mutableHandlers的get和set(Reflect获取或者设置对象属性,设置成功返回true失败返回false)
2)通过new WeakMap缓存代理对象(即reactive两次相同的对象,返回的代理对象是同一个)
3)reactive返回的代理对象再次经过reactive返回的是同一个代理对象,第二次reactive时会走mutableHandlers的get方法给代理对象增加一个属性表示对象是否被代理过,
在createReactiveObject方法中如果对象是已经被代理过了,那么返回本身即可
2.实现effect:packages\reactivity\src\effect.ts
// 组件初次渲染执行回调,依赖的属性变化也会执行回调
// runner()强制执行回调
// runner.effect.stop()关掉effect
let runner = effect(() => {
console.log('ok')
app.innerHTML = state.name + '今年' + state.age + '岁了' + state.age;
})
runner();
runner.effect.stop();
1)组件首次调用执行effect的run方法,在run方法中收集每个属性和它依赖的effect,每个effect和它依赖的属性(在effect回调中取值时会走get方法,在get方法中调用track方法进行收集)
2)设置属性值时会走set方法执行trigger方法执行属性对应的effect的run方法即可(即执行回调)
3)执行effect的返回值函数即执行effect的run方法即可(即执行回调)
4)执行runner.effect.stop()相当于执行effect上面的stop方法解除effect对应的属性上面的effect,这样属性改变对应的effect不执行
3.总结
1.取属性值时,搜集每个属性上面的effect,effect对应的每个属性
2.设置值时,调用每个属性上面的effect
3.取消effect,就是取消每个effect上面的属性的effect
4.实现计算属性packages\reactivity\src\computed.ts
// computed的参数可以是对调也可以是对象,回调相当于get
// myAge.vaule取值
// 取值时走get,设置值时走set
// 取值,值不变走缓存
const myAge = computed({
get() { // 计算属性的缓存 (dirty)
return state.age + 18;
},
set(value) { // 依赖收集是取值的时候收集,set不需要
console.log(value)
}
})
1. 计算属性是一个effect,回调是getter(new ReactiveEffect)
2. 计算属性的依赖属性会收集computed这个effect(effect依赖的属性会自动收集回调,计算属性中将get作为回调传入)
3. 计算属性在effect中使用,会收集对应effect方法(计算属性也是一个属性,也可以成为effect回调依赖的属性,trackEffects中收集effect)
4. 第一执行effect 时会取computed的值,dirty变为false;
此时多次执行会走缓存
5.计算属性依赖的值发生变化了, dirty变为true; 触发计算属性依赖值收集的effect
在次取计算属性的值 因为dirty为true 会重新计算计算属性的值(triggerEffects)
5.实现ref:packages\reactivity\src\ref.ts
// 放对象和基本属性都能返回一个响应数据,state.vaule取值
const state = ref(0);
const state = ref(obj);
1.ref是一个RefImpl对象,取值时收集对应effect,返回值
2.设置值时触发对应effect
6.实现模板编译packages\runtime-dom
let App = {
props: {
title: {}
},
// setup可以返回一个对象,返回的就是模板所需要的数据,
// 也可以返回一个函数,相当于render函数,返回模板
setup(props, ctx) {
return {
flag
}
},
// 每次更新重新调用render方法
// proxy就是setup返回的数据,数据也可以通过this来获取
render(proxy) {
return h('h1', { onClick:this.add,title:proxy.title},this.count.value)
}
}
// createApp第二个参数为组件接受的属性
let app = createApp(App, { title: 'zf', a: 1 });
app.mount('#app');
1.模块依赖
runtime-dom依赖runtime-core,runtime-core依赖reactivity(可以使用依赖包暴露出来的方法)
1)实现操作dom的一些方法放在renderOptions对象中,包含dom的增删改查和dom属性的更新操作(packages\runtime-dom\src\index.ts)
2)createApp方法就是packages\runtime-core\src\apiCreateApp.ts文件的createAppAPI,方法内调用render方法将虚拟节点渲染到指定
容器内,render方法相当于packages\runtime-core\src\rendener.ts文件中的render方法
3)虚拟节点通过packages\runtime-core\src\createVNode.ts文件中的createVNode方法创建
4)render方法拿到虚拟节点和要挂载的容器通过调用patch方法将组件挂载在页面上和完成组件的更新
5)processComponent方法中的mountComponent方法处理组件,通过createComponentInstance方法获得组件实例,通过setupComponent方法给实例属性赋值
6)调用setupRenderEffect中调用render得到虚拟节点subTree,拿着虚拟节点subTree调用patch方法将虚拟节点变成真实节点插入到页面中
7)diff算法(那新老虚拟dom调用patch比较)
1.元素类型不一致直接干掉老元素创建新元素
2.元素一样就开始对比服用元素的属性和孩子(patchElement方法)
3.比较孩子
1)之前是数组 , 现在是文本 删除老的节点 ,用新的文本替换掉
2)之前是数组 , 现在也是数组 比较两个儿子列表的差异 (* diff算法)
1.创建新老节点的尾指针e1和e2,i是从头开始比较时会加一
2.从头开始比较复用节点,碰到不可复用的节点开始从后比较复用节点
3.i > e1并且i <= e2说明有新增的元素,但是往前新增还是往后新增不知道,通过anchor参照物找到新增的位置(老少新多)
4.i > e2并且i <= e1说明有要删除元素,删除多余元素即可(老多新少)
5.未知序列对比:根据新的节点 创造一个映射表 , 用老的列表去里面找有没有,如果有则复用,没有就删除。 最后新的多余在追加,
复用节点时通过了最长递增子序列算法来尽可能的复用节点
最长递增子序列算法:传入数组[2, 3,1,5,6,8,7,9,4],得到数组[2, 3,5,6,7,9]
3)之前是文本, 现在是是空 直接删除老的即可
4)之前是文本 现在也是文本 直接更新文本
5)之前是文本 现在是数组 删除文本 新增儿子
6)之前是空,现在是文本,直接更新文本
7.实现vuex4
1)用法
main.js文件:
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
// Vue.use(store) 插件的用法, 会默认调用store中的install方法
// 可以指定第二个参数,隔离多个store,组件中const store = useStore('my');
createApp(App).use(store,'my').mount('#app')
store/index.js文件:
import { createStore } from 'vuex'
const store = createStore({
strict: true, // 开启严格模式, 不允许用户非法操作状态 (只能在mutation中修改状态,否则就会发生异常)
plugins: [ // 会按照注册的顺序依次执行插件,执行的时候会把store传递给你
customPlugin:function customPlugin(store) {
let local = localStorage.getItem('VUEX:STATE');
if (local) {
// 替换store中的state
store.replaceState(JSON.parse(local))
}
// 调用commit方法后,会执行subscribe,将commit的{ type, payload }和store中的state传过来
store.subscribe((mutation, state) => { // 每当状态发生变化 (调用了mutation的时候 就会执行此回调)
localStorage.setItem('VUEX:STATE', JSON.stringify(state))
})
}
],
state: { // 组件中的data
},
getters: { // 计算属性 vuex4 他并没有实现计算属性的功能
},
mutations: { // 可以更改状态 必须是同步更改的
},
actions: { // 可以调用其他action,或者调用mutation
},
modules: { // 子模块 实现逻辑的拆分
aCount: {
namespaced: true, //需要加上namespaced
state: { count: 0 },
mutations: {
add(state, payload) { // aCount/add
state.count += payload
}
},
}
}
})
export default store;
App.vue组件中
import { useStore } from 'vuex'
import { computed, toRefs } from 'vue'
setup() { // vue3 有个compositionApi的入口
const store = useStore();
function add() {
store.commit('add', 1)
store.commit('aCount/add', 1)//模块化
}
function asyncAdd() {
store.dispatch('asyncAdd', 1)
}
return {
count: computed(() => store.state.count), //使用computed返回的数据才能有响应式
double: computed(() => store.getters.double),
aCount: computed(() => store.state.aCount.count), //模块化
add,
asyncAdd
}
}
//{{ count }}新的写法,{{ $store.state.count }}老的写法依然可以用
9.vite
1)下载
pnpm install vite -D
2)启动 vite
Vite 主要由两部分组成
(开发环境)一个开发服务器,它基于 原生 ES 模块 提供了 丰富的内建功能,如速度快到惊人的 模块热更新(HMR),Vite 将会使用 esbuild 预构建依赖。Esbuild 使用 Go 编写,并且比以 JavaScript 编写的打包器预构建依赖快 10-100 倍
(生产环境)一套构建指令,它使用 Rollup 打包你的代码,并且它是预配置的,可输出用于生产环境的高度优化过的静态资源
1.package.json
{
"scripts": {
"dev": "vite",
"build": "vite build"
}
}
2.vite.config.js
import { defineConfig } from "vite"
export default defineConfig({})
3.src\env.d.ts
Vite 默认的类型定义是写给它的 Node.js API 的,要将其补充到一个 Vite 应用的客户端代码环境中
客户端类型
如果你的库依赖于某个全局库
使用/// 指令
三斜线指令仅可放在包含它的文件的最顶端
三斜线引用告诉编译器在编译过程中要引入的额外的文件
///
4. src\main.ts
export function render() {
document.getElementById("app")!.innerHTML = "main";
}
render();
if (import.meta.hot) {
import.meta.hot.accept((updatedModule) => updatedModule.render());
}
3)支持 vue3
1.安装 vue
pnpm install vue
pnpm install @vitejs/plugin-vue -D
2.vite.config.ts
import { defineConfig } from 'vite'
+import vue from "@vitejs/plugin-vue";
export default defineConfig({
+ plugins: [vue()]
})
3.src\env.d.ts
///
+declare module "*.vue" {
+ import type { DefineComponent } from "vue";
+ const component: DefineComponent<{}, {}, any>;
+ export default component;
+}
4)支持 typescript
vue-tsc可以对 Vue3 进行 Typescript 类型较验
pnpm install typescript vue-tsc -D
//package.json
{
"scripts": {
"dev": "vite",
+ "build": "vue-tsc --noEmit && vite build"
},
}
5)ESLint
1.安装
pnpm install eslint eslint-plugin-vue @typescript-eslint/parser @typescript-eslint/eslint-plugin @vue/eslint-config-typescript -D
//.eslintrc.js
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true
},
extends: [
"plugin:vue/vue3-recommended",
"eslint:recommended",
"@vue/typescript/recommended"
],
parserOptions: {
parser: "@typescript-eslint/parser",
ecmaVersion: 2021
},
rules: {
"no-unused-vars": "off",
"vue/no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "off"
},
globals: {
defineProps: "readonly"
}
}
//.eslintignore
node_modules
dist
*.css
*.jpg
*.jpeg
*.png
*.gif
*.d.ts
//package.json
{
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
+ "lint": "eslint --ext .ts,.tsx,vue src/** --no-error-on-unmatched-pattern --quiet",
+ "lint:fix": "eslint --ext .ts,.tsx,vue src/** --no-error-on-unmatched-pattern --quiet --fix"
},
}
6)Prettier(ESLint会按照Prettier风格来校验代码)
prettier主要解决的是代码风格问题
pnpm install prettier eslint-plugin-prettier @vue/eslint-config-prettier -D
//.eslintrc.js
extends: [
"plugin:vue/vue3-recommended",
"eslint:recommended",
"@vue/typescript/recommended",
+ "prettier",
+ "@vue/eslint-config-prettier"
],
rules: {
+ "prettier/prettier": [
+ "error",
+ {
+ singleQuote: false,
+ tabWidth: 2,
+ indent: 2,
+ semi: false,
+ trailingComma: "none",
+ endOfLine: "auto"
+ }
+ ],
+ "no-unused-vars": "off",
+ "vue/no-unused-vars": "off",
+ "@typescript-eslint/no-unused-vars": "off"
+ },
//.prettierrc.js
module.exports = {
singleQuote: false, //使用单引号
semi: false, 末尾添加分号
tabWidth: 2,
trailingComma: "none",
useTabs: false,
endOfLine: "auto"
}
//.prettierignore
node_modules
dist
//.editorconfig
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
7)lint-staged(提交前做eslint检查)
pnpm install mrm -D
npx mrm lint-staged
8)commitlint(commit信息检查)
pnpm install @commitlint/cli @commitlint/config-conventional -D
npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"
//commitlint.config.js
module.exports = {
extends: ["@commitlint/config-conventional"],
rules: {
"type-enum": [
2,
"always",
[
"build",
"chore",
"ci",
"docs",
"feature",
"fix",
"perf",
"refactor",
"revert",
"style",
"test"
]
]
}
}
9)配置别名
//vite.config.ts
import { defineConfig } from "vite"
+import { resolve } from "path"
import vue from "@vitejs/plugin-vue"
export default defineConfig({
+ resolve: {
+ alias: {
+ "@": resolve("src")
+ }
+ },
plugins: [vue()]
})
//tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"esModuleInterop": true,
"lib": ["esnext", "dom", "es2018.promise"],
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["src/*"]
+ }
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}
10)样式
1.css(默认支持)
2.less和sass
pnpm install sass less -D
//局部默认支持
+
+
//全局
//vite.config.ts
import { defineConfig } from "vite"
import { resolve } from "path"
import vue from "@vitejs/plugin-vue"
export default defineConfig({
resolve: {
alias: {
"@": resolve("src")
}
},
plugins: [vue()],
+ css: {
+ preprocessorOptions: { //这边帮你引入文件
+ scss: {
+ additionalData: '@import "@/styles/theme.scss";'
+ },
+ less: {
+ additionalData: '@import "@/styles/theme.less";'
+ }
+ }
+ }
})
//src\styles\theme.less
$color: green;
//src\styles\theme.scss
$color: green;
//src\components\HelloWorld.vue
11)PostCSS(给样式加前缀,兼容不同浏览器)
pnpm install autoprefixer -D
//postcss.config.js
module.exports = {
plugins: [require("autoprefixer")]
}
// .browserslistrc
>0.2%
not dead
not op_mini all
12)静态资源处理
public 目录
如果有以下需求
这些资源不会被源码引用(例如 robots.txt)
这些资源必须保持原有文件名(没有经过 hash)
那么你可以将该资源放在指定的 public 目录中,它应位于你的项目根目录
该目录中的资源在开发时能直接通过 / 根路径访问到,并且打包时会被完整复制到目标目录的根目录下
//index.html
学到21.37