草庐IT

Vue3+TS+Vite 入门指南

Moonoly 2023-04-03 原文

最近尝试上手 Vue3+TS+Vite,对比起 Vue2 有些不适应,但还是真香~

上手前先说下 Vue3 的一些变化吧~

Vue3 的变化

Vue3 带来的变化主要有以下几个方面:

  • 使用层面

    • 对比起 Vue2 启动速度快很多,新项目从 1s 升级到不到 500ms
    • vite.config.ts 配置文件修改后无需重启服务就能更新
  • 代码层面

    • 函数式编程,方便组合逻辑,如mixin容易命名冲突,数据来源不清晰
    • 新增 refreative API定义变量
    • 更好的 ts 支持
    • 组件文件中 template 模板内无需用根节点标签包着组件元素
  • 底层设计

    • 双向数据绑定从 defineProperty for in 循环变量改成 proxydefineProperty 是改变原对象属性标签;而 proxy 未改变原对象,而是产生新的代理对象,js 引擎更喜欢稳定的对象
    • 重新定义 vdom 对比思路:
      • 区分动静态 dom,只对比动态数据 dom,用block 标记动态标签内部的静态标签
      • 使用最长递增子序列算法,找到所有不需要移动的元素
    • compile 编译优化,把大量计算放在 node 层,最后浏览器只需执行最少的代码

底层设计层面的改变决定了 vue3 比 vue2 更快

下面介绍上手步骤~ (官网链接)

创建项目

使用 vite 命令创建初始项目

# npm 6.x
npm create vite@latest my-vue-app --template vue

# npm 7+, extra double-dash is needed:
npm create vite@latest my-vue-app -- --template vue

cd my-vue-app

npm install
npm run dev

Vite 配置

功能一致的配置大多跟 vue-cli 配置大同小异,不过多赘述

resolve

resolve.alias:当使用文件系统路径的别名时,请始终使用绝对路径。相对路径的别名值会原封不动地被使用,因此无法被正常解析。

/* vite.config.ts */
resolve: {
   //文件系统路径的别名, 绝对路径
   alias: {
     "@": path.resolve(__dirname, "src"), 
   }
}

sass配置

安装sass依赖和配置 vite.config.ts 预定义全局变量

npm i sass -D
/* vite.config.ts */
css: {
    preprocessorOptions: {
      scss: {
        additionalData: '@import "./src/assets/scss/var.scss";'
      }
    }
}

开启服务配置

开启 http 服务

/* vite.config.ts */
server:{
    host: 'dev.moon.cn',
    port: 3000
}

开启 https 服务

/* vite.config.ts */
let httpsConfig = {
  key: fs.readFileSync("C:/Users/ca/wps.cn/_wildcard.wps.cn+3-key.pem"),
  cert: fs.readFileSync("C:/Users/ca/wps.cn/_wildcard.wps.cn+3.pem")
};

server:{
    https: httpsConfig,
    host: 'dev.moon.cn',
    port: 443,
    open: true
}

预构建依赖优化

默认情况下,Vite 会抓取你的 index.html 来检测需要预构建的依赖项。如果指定了 build.rollupOptions.input,Vite 将转而去抓取这些入口点。

optimizeDeps.include

默认情况下,不在 node_modules 中的,链接的包不会被预构建。使用此选项可强制预构建链接的包。

/* vite.config.ts */
optimizeDeps: {
  include: ['axios'],
},

optimizeDeps.exclude

在预构建中强制排除的依赖项。

eslint 配置

vue3tseslint 配置需另外自行配置,除了需配置 eslint 规则外还需调整 vite 的相关配置,感兴趣的话可以看看我另一篇文章(内附配置解析),或者直接看完整源码,这里不做赘述。

TypeScript

TypeScript 是添加了类型系统的 JavaScript,适用于任何规模的项目,在编译阶段进行类型检查。

基础知识可直接看中文文档,英文比较好的小伙伴可以直接看官方文档,这里不做赘述,这里分享一些值得说的地方

类型/接口/泛型

  • 类型:类型(type)不是定义一个新类型,而是一个类型别名,使类型更具体化

  • 接口:接口(interface)则是描述一个对象的形状,对值所具有的结构进行类型检查。接口的作用类似于抽象类,不同点在于接口中的所有方法和属性都是没有实值的,换句话说接口中的所有方法都是抽象方法。接口主要负责定义一个类的结构,接口可以去限制一个对象的接口,对象只有包含接口中定义的所有属性和方法时才能匹配接口。同时,可以让一个类去实现接口,实现接口时类中要保护接口中的所有属性。

  • 泛型:支持多种数据结构,有函数泛型,类泛型,接口泛型等。

你可能想问什么时候用类型,什么时候用接口?Typescript团队的建议是

可以使用接口就尽量使用接口,因为接口更灵活,更容易处理

很多时候 interface 和 type 是相同的,但有一个明显区别在于 interface 可以重复定义,类型注解会累加,而 type 重复定义会报错

类型声明

类型声明(Type Declaration) 或者类型定义(Type Definition) 文件是一个以.d.ts作为文件后缀名的TypeScript文件。文件中只包含与类型相关的代码,不包含逻辑代码,它们的作用旨在为开发者提供类型信息,所以它们只在开发阶段起作用。

typescript编译后会将类型信息移除,类型信息仅在开发阶段起作用。

全局类型/变量声明

先在项目 src 目录中新建 global.d.ts 文件

全局类型声明

项目的根目录有 tsconfig.json 可以配置 TypeScript,include 属性包含了需要校验 ts 的文件。ts 默认会将 xx.d.ts 类型文件中的类型注册成全局的,下面举个栗子:

// global.d.ts
type T1 = number
// 组件内 
<script lang="ts"> 
    let num1: T1 = 1
</script>

全局变量声明

有三种方式声明全局变量,挂载在浏览器的 window 对象中

  • 使用 window

global.d.ts文件

// 若想不带window使用userId,但需重复声明
declare let userId: string 

interface Window {
  userId: string
}

注:不声明且不带window使用开发模式不会报错,但打包时会报错

组件文件

window.userId = '1'
console.log(userId)
  • 使用 global 配合 windowvar,需加 export,否则会打包报错
// global.d.ts
export {}

declare global {
  interface Window {
    // 使用foo全局变量时得带window,否则打包会报错
    foo: string
  }
  var age: number
}
// 组件内
globalThis.age = 18
window.foo = '1'
console.log(age, window.foo)

注:加上export后其他声明会失效,其他声明可另起 *.d.ts 文件定义

  • 使用var
// global.d.ts
declare var age: number
// 组件内
globalThis.age = 18
console.log(age)

每种方式各有利弊,自行选择

第三方库声明

第三方库需有类型声明,可自动生成或者自己写,有兴趣可移步

Vue3 + TS

vue3新增了composition api的写法,更接近react的写法,下面介绍ts下的vue3写法和生命周期

setup 语法糖

一个组件选项,在组件被创建之前props 被解析之后执行。它是组合式 API 的入口。

<script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。相比于普通的 <script> 语法,它具有更多优势:

  • 更少的样板内容,更简洁的代码。
  • 能够使用纯 Typescript 声明 props 和抛出事件。
  • 更好的运行时性能 (其模板会被编译成与其同一作用域的渲染函数,没有任何的中间代理)。
  • 更好的 IDE 类型推断性能 (减少语言服务器从代码中抽离类型的工作)。

使用这个语法,需要将 setup attribute 添加到 <script> 代码块上:

<script setup lang="ts">
</script>

里面的代码会被编译成组件 setup() 函数的内容。这意味着与普通的 <script> 只在组件被首次引入的时候执行一次不同,<script setup> 中的代码会在每次组件实例被创建的时候执行

setup 函数在生命周期方面,它是在 beforeCreate 钩子之前调用的。

生命周期

选项式 API 的生命周期选项和组合式 API 之间的映射

  • beforeCreate -> 使用 setup()
  • created -> 使用 setup()
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeUnmount -> onBeforeUnmount
  • unmounted -> onUnmounted
  • errorCaptured -> onErrorCaptured
  • renderTracked -> onRenderTracked
  • renderTriggered -> onRenderTriggered
  • activated -> onActivated
  • deactivated -> onDeactivated

TIP: 因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。

响应式 ref

接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个 .value property,指向该内部值。和从 setup() 函数中返回值一样,ref 值在模板中使用的时候会自动解包。

可以在调用 ref 时传递一个泛型参数以覆盖默认推断

import { ref } from "vue";

let str = ref<string>("test");

还可以指定复杂类型

const foo = ref<string | number>('foo') // foo 的类型:Ref<string | number>

foo.value = 123 // ok!

props/emit

  • 仅限类型的 props/emit 声明
defineProps<{ title: string }>();

const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()
  • props 设置默认值

    有两种方法设置默认值

    • 使用运行时声明

      运行时声明 的方式只能设置参数类型、默认值、是否必传、自定义验证。报错为控制台warn警告。
      若想设置[ 编辑器报错、编辑器语法提示 ]则需要使用类型声明的方式。

      const props = defineProps({
        modelValue: { type: Boolean, default: false },
        title: { type: String, default: '弹窗提示' },
        msg: { type: String, default: '弹窗信息' }
      })
      
    • 使用类型声明时的默认 props 值

      仅限类型的 defineProps 声明的不足之处在于,它不能给 props 定义默认值。需配合 withDefaults 编译器宏解决:

interface Props {
  title?: string;
  msg?: string;
}

withDefaults(defineProps<Props>(), {
  title: "提示",
  msg: "是否跳转到app?",
});

defineProps、withDefaults 是只在 <script setup> 语法糖中才能使用的编译器宏。他不需要导入且会随着 <script setup> 处理过程一同被编译掉。

v-model 双向绑定

vue2 中的 v-model 的使用是通过传递 value 属性和接收 input 事件实现,vue3 则换成了 modelValue 属性,接收的方法是update:modelValue

以下弹窗例子以Page.vue为父组件,Dialog.vue为子组件,关键代码如下:

/* Page.vue */
<template>
  <Dialog v-model="dialogVisible"></Dialog>
  <div class="bottom-btn" @click="onTap">点击按钮</div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import Dialog from "./Dialog.vue";

let dialogVisible = ref<boolean>(false);
function onTap() {
  dialogVisible.value = true;
}
<script>
/* Dialog.vue */
<template>
  <div class="dialog" v-show="modelValue">
      <span class="dialog-content-btn" @click="onConfirm">确定</span>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";

interface Props {
  modelValue?: boolean;
}
let props = withDefaults(defineProps<Props>(), {
  modelValue: false // v-model绑定的属性值
});

// 传递的方法
const emit = defineEmits<{
  (e: "update:modelValue", visible: boolean): boolean;
}>();

function onConfirm() {
    emit("update:modelValue", false);
}
<script>

遇到的问题

做好所有配置后,主要遇到以下两个问题

vite 打包报错/告警

“@charset” must be the first rule in the file }@charset “UTF-8”;

告警如图:

原因:使用了scss类库 sass,scss编译的时候,因为被编译的文件里可能有中文导致

解决:在vite.config.js里面,加一个sass的配置,把charset关掉就行了
官网对css预处理的api

vite.config.js 中的配置

export default defineConfig({
  css: {
    preprocessorOptions: {
      scss: {
        charset: false
      }
    }
  }
})

去除 Typescript 全局变量的 eslint 报错

1. 使用 var 定义全局变量

var 相关声明下会带下划线,并报错

Unexpected var, use let or const instead.

解决:在 .eslintrc 配置文件中增加规则

rules: {
    // 全局变量允许使用 var
    'no-var': 'off',
}

2. 使用 global 定义全局变量

global 相关声明下会带下划线,并报错

Augmentations for the global scope can only be directly nested in external modules or ambient module declarations.

解决:在 global.d.ts 声明文件中添加一行代码

export {}

注:新增后会导致该文件中的其他变量/类型等声明失效,其他声明可另起 *.d.ts 文件定义

Vite 为什么更快

Vite 主要通过以下几个方面进行优化:

  • 启动应用时按需提供代码
  • 浏览器缓存(协商缓存和强缓存)进行代码更新
  • 使用 esbuild 预构建依赖和加快构建速度

启动时间和更新时间

启动时间

以往的打包工具当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。而且存在性能瓶颈——使用 JavaScript 开发的工具通常需要很长时间(甚至是几分钟!)才能启动开发服务器,即使使用 HMR,文件修改后的效果也需要几秒钟才能在浏览器中反映出来。

Vite 通过在一开始将应用中的模块区分为 依赖 和 源码 两类,并只在浏览器请求源码时进行转换并按需提供源码,改进了开发服务器启动时间。而且esbuild 预构建依赖使用的语言是go,比以 JavaScript 编写的打包器预构建依赖快 10-100 倍。

更新时间

在 Vite 中,HMR 是在原生 ESM 上执行的。当编辑一个文件时,Vite 只需要精确地使已编辑的模块与其最近的 HMR 边界之间的链失活[1](大多数时候只是模块本身),使得无论应用大小如何,HMR 始终能保持快速更新。

Vite 同时利用 HTTP 头来加速整个页面的重新加载(再次让浏览器为我们做更多事情):源码模块的请求会根据 304 Not Modified 进行协商缓存,而依赖模块请求则会通过 Cache-Control: max-age=31536000,immutable 进行强缓存,因此一旦被缓存它们将不需要再次请求。

预构建依赖的前因后果

Vite 预构建依赖原因有二:

  • CommonJS 和 UMD 兼容性: 开发阶段中,Vite 的开发服务器将所有代码视为原生 ES 模块。因此,Vite 必须先将作为 CommonJS 或 UMD 发布的依赖项转换为 ESM。

  • 性能: Vite 通过预构建依赖将有许多内部模块的 ESM 依赖关系转换为单个模块,从而减少浏览器的请求数量,提升页面加载性能。

    lodash-es 有超过 600 个内置模块,当执行 import { debounce } from 'lodash-es' 时,浏览器同时发出 600 多个 HTTP 请求;通过预构建 lodash-es 成为一个模块,就只需要一个 HTTP 请求。

自动依赖搜寻

如果没有找到相应的缓存,Vite 将抓取你的源码,并自动寻找引入的依赖项(即 “bare import”,表示期望从 node_modules 解析),并将这些依赖项作为预构建包的入口点。

在服务器已经启动之后,如果遇到一个新的依赖关系导入,而这个依赖关系还没有在缓存中,Vite 将重新运行依赖构建进程并重新加载页面。

对于 monorepo 仓库中的某个依赖成为另一个包的依赖,Vite 会自动侦测没有从 node_modules 解析的依赖项,并将链接的依赖视为源码。它不会尝试打包被链接的依赖,而是会分析被链接依赖的依赖列表。

缓存

文件系统缓存

Vite 会将预构建的依赖缓存到 node_modules/.vite。它根据几个源来决定是否需要重新运行预构建步骤:

  • package.json 中的 dependencies 列表
  • 包管理器的 lockfile,例如 package-lock.jsonyarn.lock,或者 pnpm-lock.yaml
  • 可能在 vite.config.js 相关字段中配置过的

只有在上述其中一项发生更改时,才需要重新运行预构建。

如果要强制 Vite 重新构建依赖,你可以用 --force 命令行选项启动开发服务器,或者手动删除 node_modules/.vite 目录。

浏览器缓存

解析后的依赖请求会以 HTTP 头 max-age=31536000,immutable 强缓存,以提高在开发时的页面重载性能。一旦被缓存,这些请求将永远不会再到达开发服务器。如果安装了不同的版本(这反映在包管理器的 lockfile 中),则附加的版本 query 会自动使它们失效。如果你想通过本地编辑来调试依赖项,你可以:

  1. 通过浏览器调试工具的 Network 选项卡暂时禁用缓存;
  2. 重启 Vite dev server,并添加 --force 命令以重新构建依赖;
  3. 重新载入页面。

为何不用 ESBuild 打包?

虽然 esbuild 快得惊人,且是一个在构建库方面比较出色的工具,但一些针对构建 应用 的重要功能仍然还在持续开发中 —— 特别是代码分割和 CSS 处理方面。就目前来说,Rollup 在应用打包方面更加成熟和灵活。

最后

最后附上完整代码,如对前端自动化部署有兴趣,可继续看在本文 vue3 基础上搭建的 CICD

相关文章

Monorepo实战

有关Vue3+TS+Vite 入门指南的更多相关文章

  1. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  2. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  3. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  4. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  5. ES基础入门 - 2

    ES一、简介1、ElasticStackES技术栈:ElasticSearch:存数据+搜索;QL;Kibana:Web可视化平台,分析。LogStash:日志收集,Log4j:产生日志;log.info(xxx)。。。。使用场景:metrics:指标监控…2、基本概念Index(索引)动词:保存(插入)名词:类似MySQL数据库,给数据Type(类型)已废弃,以前类似MySQL的表现在用索引对数据分类Document(文档)真正要保存的一个JSON数据{name:"tcx"}二、入门实战{"name":"DESKTOP-1TSVGKG","cluster_name":"elasticsear

  6. Ruby 和指南针路径与 yeoman 项目 - 2

    我安装了ruby​​、yeoman,当我运行我的项目时,出现了这个错误:Warning:Running"compass:dist"(compass)taskWarning:YouneedtohaveRubyandCompassinstalledthistasktowork.Moreinfo:https://github.com/gruUse--forcetocontinue.Use--forcetocontinue.我有进入可变session目标的路径,但它不起作用。谁能帮帮我? 最佳答案 我必须运行这个:geminstallcom

  7. 区块链入门教程(6)--WeBASE-Front节点前置服务安装 - 2

    文章目录1.任务背景2.任务目标3.相关知识点4.任务实操4.1安装配置JDK4.2启动FISCOBCOS4.3下载解压WeBASE-Front4.4拷贝sdk证书文件4.5启动节点4.6访问节点4.7检查运行状态5.任务总结1.任务背景FISCOBCOS其实是有控制台管理工具,用来对区块链系统进行各种管理操作。但是对于初学者来说,还是可视化界面更友好,本节就来介绍WeBASE管理平台,这是一款微众银行开源的自研区块链中间件平台,可以降低区块链使用的门槛,大幅提高区块链应用的开发效率。微众银行是腾讯牵头设立的民营银行,在国内民营银行里还是比较出名的。微众银行参与FISCOBCOS生态建设,一定

  8. (附源码)vue3.0+.NET6实现聊天室(实时聊天SignalR) - 2

    参考文章搭建文章gitte源码在线体验可以注册两个号来测试演示图:一.整体介绍  介绍SignalR一种通讯模型Hub(中心模型,或者叫集线器模型),调用这个模型写好的方法,去发送消息。  内容有:    ①:Hub模型的方法介绍    ②:服务器端代码介绍    ③:前端vue3安装并调用后端方法    ④:聊天室样例整体流程:1、进入网站->调用连接SignalR的方法2、与好友发送消息->调用SignalR的自定义方法 前端通过,signalR内置方法.invoke()  去请求接口3、监听接受方法(渲染消息)通过new signalR.HubConnectionBuilder().on

  9. Tcl脚本入门笔记详解(一) - 2

    TCL脚本语言简介•TCL(ToolCommandLanguage)是一种解释执行的脚本语言(ScriptingLanguage),它提供了通用的编程能力:支持变量、过程和控制结构;同时TCL还拥有一个功能强大的固有的核心命令集。TCL经常被用于快速原型开发,脚本编程,GUI和测试等方面。•实际上包含了两个部分:一个语言和一个库。首先,Tcl是一种简单的脚本语言,主要使用于发布命令给一些互交程序如文本编辑器、调试器和shell。由于TCL的解释器是用C\C++语言的过程库实现的,因此在某种意义上我们又可以把TCL看作C库,这个库中有丰富的用于扩展TCL命令的C\C++过程和函数,所以,Tcl是

  10. Simulink方法总结和避坑指南(一)——Simulink入门与基本调试方法 - 2

    文章目录一、项目场景二、基本模块原理与调试方法分析——信源部分:三、信号处理部分和显示部分:四、基本的通信链路搭建:四、特殊模块:interpretedMATLABfunction:五、总结和坑点提醒一、项目场景  最近一个任务是使用simulink搭建一个MIMO串扰消除的链路,并用实际收到的数据进行测试,在搭建的过程中也遇到了不少的问题(当然这比vivado里面的debug好不知道多少倍)。准备趁着这个机会,先以一个很基本的通信链路对simulink基础和相关的debug方法进行总结。  在本篇中,主要记录simulink的基本原理和基本的SISO通信传输链路(QPSK方式),计划在下篇记

随机推荐