《玩转 Vue 3 全家桶》学习笔记-实战篇
# 《玩转 Vue 3 全家桶》学习笔记-实战篇
# Vuex
专门定义一个全局变量,任何组件需要数据的时候都去这个全局变量中获取。一些通用的数据,比如用户登录信息,以及一个跨层级的组件通信都可以通过这个全局变量很好地实现。
Vuex 集中式存储管理应用的所有组件的状态。
对于一个数据,如果只是组件内部使用就是用 ref 管理;如果我们需要跨组件,跨页面共享的时候,我们就需要把数据从 Vue 的组件内部抽离出来,放在 Vuex 中去管理。
# Router
- 在 jQuery 时代,对于大部分 Web 项目而言,前端都是不能控制路由的,而是需要依赖后端项目的路由系统。通常,前端项目也会部署在后端项目的模板里;
jQuery 那个时代的前端工程师,都要学会在后端的模板,比如 JSP,Smatry 等里面写一些代码。但是在这个时代,前端工程师并不需要了解路由的概念。对于每次的页面跳转,都由后端开发人员来负责重新渲染模板。这种开发方式也有很多缺点,比如前后端项目无法分离、页面跳转由于需要重新刷新整个页面、等待时间较长等等,所以也会让交互体验下降。
- 后来前端的开发模式发生了变化,项目的结构也发生了变化;前端获得了路由的控制权,在 JavaScript 中控制路由系统。也因此,页面跳转的时候就不需要刷新页面,网页的浏览体验也得到了提高。这种所有路由都渲染一个前端入口文件的方式,是单页面应用程序(SPA,single page application)应用的雏形。
前端路由的实现原理
hash 模式: 通过 URL 中 # 后面的内容做区分:
http://www.xxx.com/#/login;hash 值的变化并不会导致浏览器页面的刷新,只是会触发 hashchange 事件:
window.addEventListener('hashchange',fn)history 模式: 2014年后,HTML5 标准发布,浏览器多了两个 API:pushState 和 replaceState。通过这两个 API ,我们可以改变 URL 地址,并且浏览器不会向后端发送请求
我们监听了 popstate 事件,可以监听到通过 pushState 修改路由的变化:
window.addEventListener('popstate', fn)
实现
- router.js
import {ref,inject} from 'vue'
const ROUTER_KEY = '__router__'
function createRouter(options){
return new Router(options)
}
function useRouter(){
return inject(ROUTER_KEY)
}
function createWebHashHistory(){
function bindEvents(fn){
window.addEventListener('hashchange',fn)
}
return {
bindEvents,
url:window.location.hash.slice(1) || '/'
}
}
class Router{
constructor(options){
this.history = options.history
this.routes = options.routes
this.current = ref(this.history.url)
this.history.bindEvents(()=>{
this.current.value = window.location.hash.slice(1)
})
}
install(app){
app.provide(ROUTER_KEY,this)
}
}
export {createRouter,createWebHashHistory,useRouter}
- 使用:
import {
createRouter,
createWebHashHistory,
} from './grouter/index'
const router = createRouter({
history: createWebHashHistory(),
routes
})
# Vue Devtools
- Performace
- lighthouse
// 统计当前页面一共有多少种 HTML 标签:
new Set([...document.querySelectorAll('*')].map(n=>n.nodeName)).size
// 统计页面出现次数最多的 3 种 HTML 标签:
Object.entries([...document.querySelectorAll("*")].map(n=>n.tagName).reduce((pre, cur)=>{
pre[cur] = (pre[cur] || 0) + 1;
return pre;
}, {})).sort((a, b)=>b[1]-a[1]).slice(0, 3)
# JSX
在 JavaScript 里面写 HTML 的语法,就叫做 JSX;使用 JSX 的本质,还是在写 JavaScript。
JSX 的语法来源于 React,在 Vue 3 中会直接解析成 h 函数执行,所以 JSX 就拥有了 JS 全部的动态性。
# h函数
在 Vue 3 的项目开发中,template 是 Vue 3 默认的写法。虽然 template 长得很像 HTML,但 Vue 其实会把 template 解析为 render 函数,之后,组件运行的时候通过 render 函数去返回虚拟 DOM。
- 实现:
<h2>....<h2>
- Heading.jsx
import { defineComponent, h } from 'vue'
export default defineComponent({
props: {
level: {
type: Number,
required: true
}
},
setup(props, { slots }) {
return () => h(
'h' + props.level, // 标签名
{}, // prop 或 attribute
slots.default() // 子节点
)
}
})
- 使用
<template>
<Heading :level="3">hello geekbang</Heading>
</template>
<script setup>
import Heading from './components/Head.jsx'
</script>
JSX 只是 h 函数的一个语法糖,本质就是 JavaScript,想实现条件渲染可以用 if else,也可以用三元表达式,还可以用任意合法的 JavaScript 语法。也就是说,JSX 可以支持更动态的需求。而 template 则因为语法限制原因,不能够像 JSX 那样可以支持更动态的需求。这是 JSX 相比于 template 的一个优势。
JSX 相比于 template 还有一个优势,是可以在一个文件内返回多个组件。
实现业务需求的时候,也是优先使用 template,动态性要求较高的组件使用 JSX 实现.
- Vue3的Diff算法
静态的标签和属性会放在 _hoisted 变量中,并且放在 render 函数之外。这样,重复执行 render 的时候,代码里的 h1 这个纯静态的标签,就不需要进行额外地计算,并且静态标签在虚拟 DOM 计算的时候,会直接越过 Diff 过程。
# TypeScript
TypeScript 是微软开发的 JavaScript 的超集,这里说的超集,意思就是 TypeScript 在语法上完全包含 JavaScript。TypeScript 的主要作用是给 JavaScript 赋予强类型的语言环境。
现在大部分的开源项目都是用 TypeScript 构建的,并且 Vue 3 本身 TS 的覆盖率也超过了 95%。
TypeScript 相当于在 JavaScript 外面包裹了一层类型系统,这样可以帮助我们开发更健壮的前端应用。
这也是为什么现在大部分前端开源项目都使用 TypeScript 构建的原因,因为每个函数的参数、返回值的类型和属性都清晰可见,这就可以极大地提高我们代码的可维护性和开发效率。
Vue 2 中全部属性都挂载在 this 之上,而 this 可以说是一个黑盒子,我们完全没办法预先知道 this 上会有什么数据,这也是为什么 Vue 2 对 TypeScript 的支持一直不太好的原因。
# 性能优化
# 网络请求优化
- 减小文件体积
CSS 和 JavaScript 代码会在上线之前进行压缩;在图片格式的选择上,对于大部分图片来说,需要使用 JPG 格式,精细度要求高的图片才使用 PNG 格式;优先使用 WebP 等等。也就是说,尽可能在同等像素下,选择体积更小的图片格式。
- 图片懒加载、路由懒加载
图片懒加载的意思是,我们可以动态计算图片的位置,只需要正常加载首屏出现的图片,其他暂时没出现的图片只显示一个占位符,等到页面滚动到对应图片位置的时候,再去加载完整图片。
- 增加文件高效复用
尽可能高效地利用浏览器的缓存机制,在文件内容没有发生变化的时候,做到一次加载多次使用;浏览器的缓存机制有好几个 Headers 可以实现,Expires、Cache-control,last-modify、etag 这些缓存相关的 Header 可以让浏览器高效地利用文件缓存
# 代码效率优化
- computed 内置有缓存机制,比使用 watch 函数好一些;
- 组件里也优先使用 template 去激活 Vue 内置的静态标记,也就是能够对代码执行效率进行优化;
- v-for 循环渲染一定要有 key,从而能够在虚拟 DOM 计算 Diff 的时候更高效复用标签;
- JavaScript 本身的性能优化,或者说某些实现场景算法的选择;
- 大文件上传:断点续传; 参考项目 (opens new window)
- 页面loading骨架屏
- 数据加载:虚拟滚动
# 性能监测报告
- 首先是 First Contentful Paint,通常简写为 FCP,它表示的是页面上呈现第一个 DOM 元素的时间。
- 然后是 Time to interactive,通常简写为 TTI,也就是页面可以开始交互的时间;
- 还有和用户体验相关的 Largest Contentful Paint,通常简写为 LCP,这是页面视口上最大的图片或者文本块渲染的时间,在这个时间,用户能看到渲染基本完成后的首页,这也是用户体验里非常重要的一个指标
还可以通过代码中的 performance 对象去动态获取性能指标数据:
let timing = window.performance && window.performance.timing
let navigation = window.performance && window.performance.navigation
// DNS 解析:
let dns = timing.domainLookupEnd - timing.domainLookupStart
// 总体网络交互耗时:
let network = timing.responseEnd - timing.navigationStart
// 渲染处理:
let processing = (timing.domComplete || timing.domLoading) - timing.domLoading
// 可交互:
let active = timing.domInteractive - timing.navigationStart
Back我们通过 Performance API 获取了 DNS 解析、网络、渲染和可交互的时间消耗。有了这些指标后,我们可以随时对用户端的性能进行检测,做到提前发现问题,提高项目的稳定性。