VUE3学习 第九章 webpack 构建Vue3项目、vue3性能优化、Vue3 Web Components、Vue3 响应式原理
创始人
2024-01-21 17:35:23

一、webpack 构建 Vue3项目(纯手写 不用cli)

为什么要手写webpack 不用cli (脑子有病)并不是 其实是为了加深我们对webpack 的了解方便以后灵活运用webpack 的技术

1. 初始化项目结构(跟cli 结构保持一致)

  1. 创建 publi 文件夹
    下面创建 index.html 文件 然后初始化一下 ! 然后改一下 title 标签的内容名称,一般与项目名称一样即可
  2. 创建 src 文件夹
    src文件夹 下面 创建 App.vue main.ts 文件
  3. npm init -y 创建 package.json 文件
    创建的 package.json文件 需要添加 dev 和 build 打包命令(注意: package.json 文件中 不能写 // 注释 打包会报错
  "scripts": {"test": "echo \"Error: no test specified\" && exit 1","dev": "webpack-dev-server","build": "webpack"},
  1. tsc --init 创建 tsconfig.json 文件 (如果没有 tsc 可以用 npm i -g typescript)
  2. 创建 webpack.config.js 文件
  3. 安装 webpack 和 webpack-cli 依赖 以及 webpack 启动 dev-server 等。
npm add webpack
npm add webpack -li // 注意 安装 3以上的版本
npm add webpack-dev-server  // 启动dev 的环境
npm add html-webpack-plugin // webpack  html模板插件
  1. 写好 webpack.config.js 文件后 就可以打包了 npm run build
const { Configuration } = require('webpack') // 本来 没有智能提示的  不过可以通过注解的形式,就可以有智能提示了
const path = require('path') // 引入nodejs 的path
const htmlWebpackPlugin = require('html-webpack-plugin') // 引入 html 模板插件 配置模板const { CleanWebpackPlugin } = require('clean-webpack-plugin') // 清空dist文件使用的插件
const { VueLoaderPlugin } = require('vue-loader/dist/index'); // 这个插件是 为了 webpack 能够去识别 template 组件的const FriendlyErrorsWebpackPlugin = require("friendly-errors-webpack-plugin"); // 美化打包样式的/***  @type {Configuration}*/
const config = { // 本来这里是没有智能提示的mode: "development", // 指定 打包模式   开发环境打包出来的代码是不会压缩的   如果需要压缩的  需要用生产模式 productionentry: "./src/main.ts", // 入口文件 注意 如果是TS写的 就是 main.tsmodule: {rules: [  // 放在 rules 中 都是处理文件的{test: /\.vue$/, //解析vue 模板use: "vue-loader"}]},output: { // 出口filename: '[hash].js', // 打完包后的文件名path: path.resolve(__dirname, 'dist') // 拼接一下 dirname 输出位置},resolve: {alias: {"@": path.resolve(__dirname, './src') // 别名},extensions: ['.js', '.json', '.vue', '.ts', '.tsx'] //识别后缀},stats:"errors-only", //取消提示  devServer: {  // 一些端口 跨域之类的配置proxy: {},port: 9001,hot: true,open: true,},externals: {  // 性能优化的vue: "Vue" //CDN 引入},plugins: [  // 放在 plugins 中 都是插件new htmlWebpackPlugin({template: "./public/index.html" // 指定一下模板的位置}),new CleanWebpackPlugin(), //打包清空distnew VueLoaderPlugin(), //解析vuenew FriendlyErrorsWebpackPlugin({compilationSuccessInfo:{ //美化样式messages:['You application is running here http://localhost:8080']}     })]
}module.exports = config
  1. 装一下 vue npm add vue(直接创建的最新的版本) 然后 去main.ts 入口文件中引入 (注意 因为是手写的 所以需要去 pubilc 下 添加一个 id等于 app 的 div 作为挂载点)
import { createApp } from 'vue';
import App from './App.vue'   // 如果 ts 无法识别 vue文件  需要去声明一下  在 src 下面 创建一个 env.d.ts 的文件createApp(App).mount('#app') // 挂载点  由于是手写的  所以需要去 pubilc文件下的html文件的body中创建一个 div 给个 id 叫app 作为挂载点 到时候会渲染到这个地方
// src 下 env.d.ts 声明文件
declare module "*.vue" {import { DefineComponent } from "vue"const component: DefineComponent<{}, {}, any>export default component
}
  1. 这时 直接 打包会报错 因为webpack 无法解析 template 所以需要添加新的插件 (安装完以后 需要去 webpack.config.js 中配置使用,具体看上面的代码)
    如果之后还有可能会报错,应该是我们还没支持ts功能,但是 app.vue的模板中 有 lang=“ts” 删了就好了
	npm add vue-loader@nextnpm add @vue/compiler-sfc
  1. 此时还需要一个插件,因为dist中的文件不会自动删除,所以每次打包dist里面都会添加文件 所以需要一个 npm add clean-webpack-plugin 下载好后 直接去webpack.config.js 中注册使用就可以了
  2. 此时已经可以使用 npm run dev 去热更新写代码了
  3. 然后 可以去 webpack.config.js 配置 resollve
    比如: alias 可以去修改 文件名的别名
    extensions 可以自动添加文件名的后缀
resolve: {alias: {"@": path.resolve(__dirname, './src') // 别名},extensions: ['.js', '.json', '.vue', '.ts', '.tsx'] //识别后缀},
  1. 由于 webpack 不支持 css 和 style 所以 需要继续下载 load 去解析, 下载好后 去 webpack.config.js 中 rules中配置
	npm add css-loadernpm add style-loader{test: /\.less$/, //解析 lessuse: ["style-loader", "css-loader", "less-loader"],},{test: /\.css$/, //解析cssuse: ["style-loader", "css-loader"],},
  1. 还有 ts 的load 也是需要安装 以及去 rules中配置
npm	add typescript
npm add ts-loader{test: /\.ts$/,  //解析tsloader: "ts-loader",options: {configFile: path.resolve(process.cwd(), 'tsconfig.json'),appendTsSuffixTo: [/\.vue$/]},}
  1. 每次 npm run dev 打包后 出现的一些东西太多了,需要删除一些不要的 美化一下控制台日志 需要一个新的插件
	npm add friendly-errors-webpack-plugin// 然后去 wabpackconfig 中配置const FriendlyErrorsWebpackPlugin = require("friendly-errors-webpack-plugin");new FriendlyErrorsWebpackPlugin({compilationSuccessInfo:{ //美化样式messages:['You application is running here http://localhost:8080'] // 这个是自己写的 包括端口}})
stats:"errors-only", //取消提示  
  1. 还有 devServer 配置,改端口 代理啥的
devServer: {proxy: {},  // 跨域的 apiport: 8080, // 改端口hot: true,   // 热更新open: true,  // 打开网页},
  1. externals 这个是做性能优化的
    externals: {  // 性能优化的vue: "Vue" //CDN 引入  cdn 引入 需要添加一个scripit标签 的cdn引入地址},

二、vue3性能优化

1. 性能优化 从谷歌浏览器查看

我们可以使用谷歌浏览器自带的 DevTools 进行性能分析 LightHouse

参数介绍
从Performance页的表现结果来看,并提供了很多的时间信息,我们来解释下这些选项代表的意思:

FCP (First Contentful Paint):首次内容绘制的时间,浏览器第一次绘制DOM相关的内容,也是用户第一次看到页面内容的时间。

Speed Index: 页面各个可见部分的显示平均时间,当我们的页面上存在轮播图或者需要从后端获取内容加载时,这个数据会被影响到。

LCP (Largest Contentful Paint):最大内容绘制时间,页面最大的元素绘制完成的时间。

TTI(Time to Interactive):从页面开始渲染到用户可以与页面进行交互的时间,内容必须渲染完毕,交互元素绑定的事件已经注册完成。

TBT(Total Blocking Time):记录了首次内容绘制到用户可交互之间的时间,这段时间内,主进程被阻塞,会阻碍用户的交互,页面点击无反应。

CLS(Cumulative Layout Shift):计算布局偏移值得分,会比较两次渲染帧的内容偏移情况,可能导致用户想点击A按钮,但下一帧中,A按钮被挤到旁边,导致用户实际点击了B按钮。

2. 性能优化 代码分析层面查看

由于我们使用的是vite vite打包是基于rollup 的我们可以使用 rollup 的插件

npm install rollup-plugin-visualizer

vite.config.ts 配置 记得设置open 不然无效

import { visualizer } from 'rollup-plugin-visualizer'; // 引入plugins: [vue(), vueJsx(),visualizer({ // 这里添加 然后还要设置openopen:true})],

最后进行 npm run build 打包
最后去查看 比如发现 某个ui库很大 可以使用按需导入之类的

3. Vite 配置优化

也是 vite.config.js 里面

build:{   // 具体的要深入了解  这只是一部分chunkSizeWarningLimit:2000, cssCodeSplit:true, //css 拆分sourcemap:false, //不生成sourcemapminify:false, //是否禁用最小化混淆,esbuild打包速度最快,terser打包体积最小。assetsInlineLimit:5000 //小于该值 图片将打包成Base64 
},

4. PWA离线存储技术

npm install vite-plugin-pwa -D
 // 安装 导入
import { VitePWA } from 'vite-plugin-pwa' 
plugins: [vue(),VitePWA(), vueJsx(),visualizer({open:true
})],

PWA 技术的出现就是让web网页无限接近于Native 应用

  1. 可以添加到主屏幕,利用manifest实现
  2. 可以实现离线缓存,利用service worker实现
  3. 可以发送通知,利用service worker实现
VitePWA({  // 这里也是一些基本的  需要深入了解workbox:{cacheId:"XIaoman",//缓存名称runtimeCaching:[{urlPattern:/.*\.js.*/, //缓存文件handler:"StaleWhileRevalidate", //重新验证时失效options:{cacheName:"XiaoMan-js", //缓存js,名称expiration:{maxEntries:30, //缓存文件数量 LRU算法maxAgeSeconds:30 * 24 * 60 * 60 //缓存有效期}}}]},})

最后进行 npm run build 打包会生成 sw.js
然后进入浏览器 f12 可以去应用模块(Application中的 service Workers)查看

5. 其他性能优化(VueUse-自定义hooks 里面有很多)

1. 图片懒加载
import lazyPlugin from ‘vue3-lazy’

< img v-lazy="user.avatar" > // 这个插件可以使用 以自定义指令的形式使用懒加载

2. 虚拟列表 (这个 饿了吗 plus 里面已经提供了这个组件)

3. 多线程 使用 new Worker 创建 (VueUse 库已经集成了 webWorker)

worker脚本与主进程的脚本必须遵守同源限制。他们所在的路径协议、域名、端口号三者需要相同

const myWorker1 = new Worker("./calcBox.js");

都使用postMessage发送消息

worker.postMessage(arrayBuffer, [arrayBuffer]);

都使用onmessage接收消息


self.onmessage = function (e) {// xxx这里是worker脚本的内容
};

关闭

worker.terminate();    

4. 防抖节流
同样VueUse 也是集成了

Vue3 Web Components

三、Vue3 Web Components

1. 什么是 Web Components

Web Components 提供了基于原生支持的、对视图层的封装能力,可以让单个组件相关的 javaScript、css、html模板运行在以html标签为界限的局部环境中,不会影响到全局,组件间也不会相互影响 。 再简单来说:就是提供了我们自定义标签的能力,并且提供了标签内完整的生命周期 。

Custom elements(自定义元素):JavaScript API,允许定义custom elements及其行为,然后可以在我们的用户界面中按照需要使用它们。

Shadow DOM(影子DOM):JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,开发者可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。

HTML templates(HTML模板):和元素使开发者可以编写与HTML结构类似的组件和样式。然后它们可以作为自定义元素结构的基础被多次重用。

2. 案例

  1. 创建一个 js 文件 然后以class类的形式书写
class Btn extends HTMLElement {constructor () {//调用super 来建立正确的原型链继承关系super()const p = this.h('p')p.innerText = '小满'p.setAttribute('style','height:200px;width:200px;border:1px solid #ccc;background:yellow')//表示 shadow DOM 子树的根节点。const shaDow = this.attachShadow({mode:"open"}) // 这里是重点之一shaDow.appendChild(this.p) // 这里 shaDow 一下}h (el) {return  document.createElement(el)}/*** 生命周期*///当自定义元素第一次被连接到文档 DOM 时被调用。connectedCallback () {console.log('我已经插入了!!!嗷呜')}//当自定义元素与文档 DOM 断开连接时被调用。disconnectedCallback () {console.log('我已经断开了!!!嗷呜')}//当自定义元素被移动到新文档时被调用adoptedCallback () {console.log('我被移动了!!!嗷呜')}//当自定义元素的一个属性被增加、移除或更改时被调用attributeChangedCallback () {console.log('我被改变了!!!嗷呜')}}window.customElements.define('xiao-man',Btn) // 这里暴露出去
// 这个是 template 模式
class Btn extends HTMLElement {constructor() {//调用super 来建立正确的原型链继承关系super()const template = this.h('template')template.innerHTML = `
小满
`//表示 shadow DOM 子树的根节点。const shaDow = this.attachShadow({ mode: "open" })shaDow.appendChild(template.content.cloneNode(true))}h(el) {return document.createElement(el)}/*** 生命周期*///当自定义元素第一次被连接到文档 DOM 时被调用。connectedCallback() {console.log('我已经插入了!!!嗷呜')}//当自定义元素与文档 DOM 断开连接时被调用。disconnectedCallback() {console.log('我已经断开了!!!嗷呜')}//当自定义元素被移动到新文档时被调用adoptedCallback() {console.log('我被移动了!!!嗷呜')}//当自定义元素的一个属性被增加、移除或更改时被调用attributeChangedCallback() {console.log('我被改变了!!!嗷呜')}}window.customElements.define('xiao-man', Btn)
  1. 使用


web Component // 导入

  // 使用


3. 如何在Vue 使用

defineCustomElement

1. 首先配置 vite.config.js

/*vite config ts 配置*/
vue({template:{compilerOptions:{isCustomElement:(tag)=> tag.includes('wamgsihua-') // 这个 名字 代表组件名设置为 前面必须带有这个开头的}}
})

2. 父组件


3. 子组件(custom-vue.ce.vue 文件名结尾 必须要 .ce.vue)



四、Vue3 响应式原理

Vue2 使用的是 Object.defineProperty Vue3 使用的是 Proxy

1. 2.0的不足

对象只能劫持 设置好的数据,新增的数据需要Vue.Set(xxx) 数组只能操作七种方法,修改某一项值无法劫持等。

2. reactive和effect的实现

Vue3 的响应式原理依赖了 Proxy 这个核心 API,通过 Proxy 可以劫持对象的某些操作


export const reactive = (target:T) => {return new Proxy(target,{  // 用的 Proxyget (target,key,receiver) {const res  = Reflect.get(target,key,receiver) as object// 这不  不直接用 target[key]  怕某些特定情况下上下文错乱// Reflect 这个是 es6新增的 具体的要去看一下文档return res},set (target,key,value,receiver) { // 这里多了一个 valueconst res = Reflect.set(target,key,value,receiver)// 这里 Reflect 第二个作用 正好返回一个 boolean 而set需要 booleanreturn res}})
}

3. effect track trigger

1. 实现effect 副作用函数
副作用函数,顾名思义,会产生副作用的函数被称为副作用函数。那么什么是副作用呢?如果几个函数的运行,可能会影响到其他函数或变量,那么这种影响就是一种副作用(副作用函数不一定非要对某些函数产生副作用,如果一个函数修改了全局变量,这其实也是个副作用函数。)

let activeEffect;  // 定义一个全局变量 把闭包收集起来
export const effect = (fn:Function) => {// 闭包const _effect = function () {activeEffect = _effect;fn()}_effect()
}

使用一个全局变量 active 收集当前副作用函数,并且初始化的时候调用一下

2. 实现track // 收集


const targetMap = new WeakMap()
export const track = (target,key) =>{let depsMap = targetMap.get(target)if(!depsMap){depsMap = new Map()targetMap.set(target,depsMap)}let deps = depsMap.get(key)if(!deps){deps = new Set()depsMap.set(key,deps)}deps.add(activeEffect) // 收集起来
}

3. 实现trigger // 更新

export const trigger = (target,key) => {const depsMap = targetMap.get(target)const deps = depsMap.get(key)deps.forEach(effect=>effect()) // 使用 副作用函数
}

4. 当我们进行赋值的时候会调用 set 然后 触发收集的副作用函数


import {track,trigger} from './effect' // 引用这两个export const reactive = (target:T) => {return new Proxy(target,{get (target,key,receiver) {const res  = Reflect.get(target,key,receiver) as objecttrack(target,key)return res},set (target,key,value,receiver) {const res = Reflect.set(target,key,value,receiver)trigger(target,key)return res}})
}

5. 给 reactive 添加这两个方法

4. 测试


Document

5. 递归实现reactive (针对对象数据需要改造一下)

import { track, trigger } from './effect'const isObject = (target) => target != null && typeof target == 'object' // 如果是 对象类型的数据 需要改造一下export const reactive = (target: T) => {return new Proxy(target, {get(target, key, receiver) {const res = Reflect.get(target, key, receiver) as objecttrack(target, key)if (isObject(res)) { //  这里要做一个深层次的递归 监听深层次的return reactive(res)}return res},set(target, key, value, receiver) {const res = Reflect.set(target, key, value, receiver)trigger(target, key)return res}})
}


Document

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
ps局部放大 放大某个部位的图... 软件类学习建议:边看边练习,可以先收藏后学习。一、使用Ctrl快捷键放大:Ctrl + +缩小:Ct...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
ps局部放大 放大某个部位的图... 软件类学习建议:边看边练习,可以先收藏后学习。一、使用Ctrl快捷键放大:Ctrl + +缩小:Ct...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...