《玩转 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

  1. Performace
  2. 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>
  1. 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() // 子节点
    )
  }
})
  1. 使用
 <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骨架屏
  • 数据加载:虚拟滚动

# 性能监测报告

  1. 首先是 First Contentful Paint,通常简写为 FCP,它表示的是页面上呈现第一个 DOM 元素的时间。
  2. 然后是 Time to interactive,通常简写为 TTI,也就是页面可以开始交互的时间;
  3. 还有和用户体验相关的 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

我们通过 Performance API 获取了 DNS 解析、网络、渲染和可交互的时间消耗。有了这些指标后,我们可以随时对用户端的性能进行检测,做到提前发现问题,提高项目的稳定性。

Back
上次更新: 1/24/2022, 5:38:42 PM
最近更新
01
taro开发实操笔记
09-29
02
前端跨端技术调研报告
07-28
03
Flutter学习笔记
07-15
更多文章>