app开发定制公司最近尝试上手 Vue3+TS+Vite
,对比起 Vue2
app开发定制公司有些不适应,app开发定制公司但还是真香~
app开发定制公司上手前先说下 Vue3 app开发定制公司的一些变化吧~
Vue3 的变化
Vue3 app开发定制公司带来的变化主要有以下几个方面:
-
使用层面
- 对比起 Vue2 app开发定制公司启动速度快很多,新项目从 1s app开发定制公司升级到不到 500ms
vite.config.ts
app开发定制公司配置文件修改后无需重app开发定制公司启服务就能更新
-
代码层面
- app开发定制公司函数式编程,app开发定制公司方便组合逻辑,如mixinapp开发定制公司容易命名冲突,app开发定制公司数据来源不清晰
- 新增
ref
,reative
API定义变量 - 更好的
ts
支持 - app开发定制公司组件文件中
template
app开发定制公司模板内无需用根节点标app开发定制公司签包着组件元素
-
底层设计
- app开发定制公司双向数据绑定从
defineProperty
for in app开发定制公司循环变量改成proxy
。defineProperty
是改变原对象属性标签;而proxy
未改变原对象,而是产生新的代理对象,js 引擎更喜欢稳定的对象 - 重新定义
vdom
对比思路:- 区分动静态 dom,只对比动态数据 dom,用block 标记动态标签内部的静态标签
- 使用最长递增子序列算法,找到所有不需要移动的元素
- compile 编译优化,把大量计算放在 node 层,最后浏览器只需执行最少的代码
- app开发定制公司双向数据绑定从
底层设计层面的改变决定了 vue3 比 更快
下面介绍上手步骤~ ()
创建项目
使用 vite
命令创建初始项目
# npm 6.xnpm create vite@latest my-vue-app --template vue# npm 7+, extra double-dash is needed:npm create vite@latest my-vue-app -- --template vuecd my-vue-appnpm installnpm run dev
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Vite
配置
功能一致的配置大多跟 vue-cli
配置大同小异,不过多赘述
resolve
resolve.alias:当使用文件系统路径的别名时,请始终使用绝对路径。相对路径的别名值会原封不动地被使用,因此无法被正常解析。
/* vite.config.ts */resolve: { //文件系统路径的别名, 绝对路径 alias: { "@": path.resolve(__dirname, "src"), }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
sass配置
安装sass依赖和配置 vite.config.ts
预定义全局变量
npm i sass -D
- 1
/* vite.config.ts */css: { preprocessorOptions: { scss: { additionalData: '@import "./src/assets/scss/var.scss";' } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
开启服务配置
开启 http
服务
/* vite.config.ts */server:{ host: 'dev.moon.cn', port: 3000}
- 1
- 2
- 3
- 4
- 5
开启 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}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
预构建依赖优化
默认情况下, 会抓取你的 index.html 来检测需要预构建的依赖项。如果指定了 build.rollupOptions.input
,Vite 将转而去抓取这些入口点。
optimizeDeps.include
默认情况下,不在 node_modules
中的,链接的包不会被预构建。使用此选项可强制预构建链接的包。
/* vite.config.ts */optimizeDeps: { include: ['axios'],},
- 1
- 2
- 3
- 4
optimizeDeps.exclude
在预构建中强制排除的依赖项。
eslint 配置
vue3
和 ts
的 eslint
配置需另外自行配置,除了需配置 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.tstype T1 = number
- 1
- 2
// 组件内 <script lang="ts"> let num1: T1 = 1</script>
- 1
- 2
- 3
- 4
全局变量声明
有三种方式声明全局变量,挂载在浏览器的 window 对象中
- 使用 window
global.d.ts
文件
// 若想不带window使用userId,但需重复声明declare let userId: string interface Window { userId: string}
- 1
- 2
- 3
- 4
- 5
- 6
注:不声明且不带window使用开发模式不会报错,但打包时会报错
组件文件
window.userId = '1'console.log(userId)
- 1
- 2
- 使用
global
配合window
或var
,需加export
,否则会打包报错
// global.d.tsexport {}declare global { interface Window { // 使用foo全局变量时得带window,否则打包会报错 foo: string } var age: number}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
// 组件内globalThis.age = 18window.foo = '1'console.log(age, window.foo)
- 1
- 2
- 3
- 4
注:加上export后其他声明会失效,其他声明可另起 *.d.ts 文件定义
- 使用var
// global.d.tsdeclare var age: number
- 1
- 2
// 组件内globalThis.age = 18console.log(age)
- 1
- 2
- 3
每种方式各有利弊,自行选择
第三方库声明
第三方库需有类型声明,可自动生成或者自己写,有兴趣可。
Vue3 + TS
vue3新增了composition api的写法,更接近react的写法,下面介绍ts下的vue3写法和生命周期
setup 语法糖
一个组件选项,在组件被创建之前,props
被解析之后执行。它是组合式 API 的入口。
<script setup>
是在单文件组件 (SFC) 中使用 的编译时语法糖。相比于普通的 <script>
语法,它具有更多优势:
- 更少的样板内容,更简洁的代码。
- 能够使用纯 Typescript 声明 props 和抛出事件。
- 更好的运行时性能 (其模板会被编译成与其同一作用域的渲染函数,没有任何的中间代理)。
- 更好的 IDE 类型推断性能 (减少语言服务器从代码中抽离类型的工作)。
使用这个语法,需要将 setup
attribute 添加到 <script>
代码块上:
<script setup lang="ts"></script>
- 1
- 2
里面的代码会被编译成组件 setup()
函数的内容。这意味着与普通的 <script>
只在组件被首次引入的时候执行一次不同,<script setup>
中的代码会在每次组件实例被创建的时候执行。
setup
函数在生命周期方面,它是在 钩子之前调用的。
生命周期
选项式 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");
- 1
- 2
- 3
还可以指定复杂类型
const foo = ref<string | number>('foo') // foo 的类型:Ref<string | number>foo.value = 123 // ok!
- 1
- 2
- 3
props/emit
- 仅限类型的 props/emit 声明
defineProps<{ title: string }>();const emit = defineEmits<{ (e: 'change', id: number): void (e: 'update', value: string): void}>()
- 1
- 2
- 3
- 4
- 5
- 6
-
props 设置默认值
有两种方法设置默认值
-
使用
运行时声明
运行时声明 的方式只能设置参数类型、默认值、是否必传、自定义验证。报错为控制台warn警告。
若想设置[ 编辑器报错、编辑器语法提示 ]则需要使用类型声明的方式。const props = defineProps({ modelValue: { type: Boolean, default: false }, title: { type: String, default: '弹窗提示' }, msg: { type: String, default: '弹窗信息' }})
- 1
- 2
- 3
- 4
- 5
-
使用类型声明时的默认 props 值
仅限类型的
defineProps
声明的不足之处在于,它不能给 props 定义默认值。需配合withDefaults
编译器宏解决:
-
interface Props { title?: string; msg?: string;}withDefaults(defineProps<Props>(), { title: "提示", msg: "是否跳转到app?",});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
/* 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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
遇到的问题
做好所有配置后,主要遇到以下两个问题
vite
打包报错/告警
“@charset” must be the first rule in the file }@charset “UTF-8”;
告警如图:
原因:使用了scss类库 ,scss编译的时候,因为被编译的文件里可能有中文导致
解决:在vite.config.js里面,加一个sass的配置,把charset关掉就行了
vite.config.js 中的配置
export default defineConfig({ css: { preprocessorOptions: { scss: { charset: false } } }})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
去除 Typescript
全局变量的 eslint
报错
1. 使用 var
定义全局变量
var
相关声明下会带下划线,并报错
Unexpected var, use let or const instead.
解决:在 .eslintrc
配置文件中增加规则
rules: { // 全局变量允许使用 var 'no-var': 'off',}
- 1
- 2
- 3
- 4
2. 使用 global
定义全局变量
global 相关声明下会带下划线,并报错
Augmentations for the global scope can only be directly nested in external modules or ambient module declarations.
解决:在 global.d.ts
声明文件中添加一行代码
export {}
- 1
注:新增后会导致该文件中的其他变量/类型等声明失效,其他声明可另起 *.d.ts 文件定义
Vite 为什么更快
Vite 主要通过以下几个方面进行优化:
- 启动应用时按需提供代码
- 浏览器缓存(协商缓存和强缓存)进行代码更新
- 使用 esbuild 预构建依赖和加快构建速度
启动时间和更新时间
启动时间
以往的打包工具当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。而且存在性能瓶颈——使用 JavaScript 开发的工具通常需要很长时间(甚至是几分钟!)才能启动开发服务器,即使使用 HMR,文件修改后的效果也需要几秒钟才能在浏览器中反映出来。
Vite 通过在一开始将应用中的模块区分为 依赖 和 源码 两类,并只在浏览器请求源码时进行转换并按需提供源码,改进了开发服务器启动时间。而且 使用的语言是go,比以 JavaScript 编写的打包器预构建依赖快 10-100 倍。
更新时间
在 Vite 中,HMR 是在原生 ESM 上执行的。当编辑一个文件时,Vite 只需要精确地使已编辑的模块与其最近的 HMR 边界之间的链失活(大多数时候只是模块本身),使得无论应用大小如何,HMR 始终能保持快速更新。
Vite 同时利用 HTTP 头来加速整个页面的重新加载(再次让浏览器为我们做更多事情):源码模块的请求会根据 304 Not Modified
进行协商缓存,而依赖模块请求则会通过 Cache-Control: max-age=31536000,immutable
进行强缓存,因此一旦被缓存它们将不需要再次请求。
预构建依赖的前因后果
Vite 预构建依赖原因有二:
-
CommonJS 和 UMD 兼容性: 开发阶段中,Vite 的开发服务器将所有代码视为原生 ES 模块。因此,Vite 必须先将作为 CommonJS 或 UMD 发布的依赖项转换为 ESM。
-
性能: Vite 通过预构建依赖将有许多内部模块的 ESM 依赖关系转换为单个模块,从而减少浏览器的请求数量,提升页面加载性能。
如,当执行
import { debounce } from 'lodash-es'
时,浏览器同时发出 600 多个 HTTP 请求;通过预构建lodash-es
成为一个模块,就只需要一个 HTTP 请求。
自动依赖搜寻
如果没有找到相应的缓存,Vite 将抓取你的源码,并自动寻找引入的依赖项(即 “bare import”,表示期望从 node_modules
解析),并将这些依赖项作为预构建包的入口点。
在服务器已经启动之后,如果遇到一个新的依赖关系导入,而这个依赖关系还没有在缓存中,Vite 将重新运行依赖构建进程并重新加载页面。
对于 仓库中的某个依赖成为另一个包的依赖,Vite 会自动侦测没有从 node_modules
解析的依赖项,并将链接的依赖视为源码。它不会尝试打包被链接的依赖,而是会分析被链接依赖的依赖列表。
缓存
文件系统缓存
Vite 会将预构建的依赖缓存到 node_modules/.vite
。它根据几个源来决定是否需要重新运行预构建步骤:
package.json
中的dependencies
列表- 包管理器的 lockfile,例如
package-lock.json
,yarn.lock
,或者pnpm-lock.yaml
- 可能在
vite.config.js
相关字段中配置过的
只有在上述其中一项发生更改时,才需要重新运行预构建。
如果要强制 Vite 重新构建依赖,你可以用 --force
命令行选项启动开发服务器,或者手动删除 node_modules/.vite
目录。
浏览器缓存
解析后的依赖请求会以 HTTP 头 max-age=31536000,immutable
强缓存,以提高在开发时的页面重载性能。一旦被缓存,这些请求将永远不会再到达开发服务器。如果安装了不同的版本(这反映在包管理器的 lockfile 中),则附加的版本 query 会自动使它们失效。如果你想通过本地编辑来调试依赖项,你可以:
- 通过浏览器调试工具的 Network 选项卡暂时禁用缓存;
- 重启 Vite dev server,并添加
--force
命令以重新构建依赖; - 重新载入页面。
为何不用 ESBuild 打包?
虽然 esbuild
快得惊人,且是一个在构建库方面比较出色的工具,但一些针对构建 应用 的重要功能仍然还在持续开发中 —— 特别是代码分割和 CSS 处理方面。就目前来说,Rollup 在应用打包方面更加成熟和灵活。
最后
最后附上,如对前端自动化部署有兴趣,可继续看在本文 vue3 基础上搭建的
相关文章