Vue3 + TypeScript 开发指南
0x00 概述
- 阅读以下内容需要具备一定的 Vue2 基础
- 代码采用规范为:TypeScript + 组合式 API + setup 语法糖
(1)Vue3 简介
- Vue3 第一个正式版发布于 2020 年 9 月 18 日
- Vue3 中文官网
- Vue3 相比 Vue2 的优势:
- 性能提升:打包体积减小,初次渲染和更新渲染都更快,内存使用减少
- 源码升级:使用 Proxy 实现响应式,重写虚拟 DOM 的实现和
Tree-Shaking - 支持 TypeScript
- 新增特性:组合式 API、新内置组件、新生命周期钩子等
(2)TypeScript 概述
- TypeScript 入门:学习 TypeScript | 稀土掘金-SRIGT
0x01 第一个项目
(1)创建项目
创建项目共有两种方法
使用 vue-cli 创建
在命令提示符中使用命令
npm install -g @vue/cli下载并全局安装 vue-cli如果已经安装过,则可以使用命令
vue -V查看当前 vue-cli 的版本,版本要求在 4.5.0 以上如果需要多版本 vue-cli 共存,则可以参考文章:安装多版本Vue-CLI的实现方法 | 脚本之家-webgiser
使用命令
vue create project_name开始创建项目使用方向键选择
Default ([Vue3 babel, eslint])等待创建完成后,使用命令
cd project_name进入项目目录使用命令
npm serve启动项目
使用 vite 创建
相比使用 vue-cli 创建,使用 vite 的优势在于
- 轻量快速的热重载,实现极速的项目启动
- 对 TypeScript、JSX、CSS 等支持开箱即用
- 按需编译,减少等待编译的时间
- 使用命令
npm create vue@latest创建 Vue3 项目 - 输入项目名称
- 添加 TypeScript 支持
- 不添加 JSX 支持、Vue Router、Pinia、Vitest、E2E 测试、ESLint 语法检查、Prettier 代码格式化
- 使用命令
cd [项目名称]进入项目目录 - 使用命令
npm install添加依赖 - 使用命令
npm run dev启动项目
(2)项目结构
node_modules:项目依赖
public:项目公共资源
src:项目资源
assets:资源目录
components:组件目录
main.ts:项目资源入口文件
import './assets/main.css' // 引入样式文件 import { createApp } from 'vue' // 引入创建 Vue 应用方法
import App from './App.vue' // 引入 App 组件 createApp(App).mount('#app') // 创建以 App 为根组件的应用实例,并挂载到 id 为 app 的容器中(该容器为 index.html)
App.vue:应用根组件
<template>
<!-- 模型 -->
</template> <script lang="ts">
/* 控制 */
</script> <style>
/* 样式 */
</style>
env.d.ts:TypeScript 环境配置文件,包括文件类型声明等
index.html:项目入口文件
vite.config.ts:项目配置文件
0x02 核心语法
(1)组合式 API
首先使用 Vue2 语法完成一个组件
项目结构
graph TB
src-->components-->Person.vue
src-->App.vue & main.ts详细代码
main.ts
import { createApp } from 'vue'
import App from './App.vue' createApp(App).mount('#app')
App.vue
<template>
<div class="app">
<h1>App</h1>
<Person />
</div>
</template> <script lang="ts">
import Person from './components/Person.vue';
export default {
name: 'App', // 当前组件名
components: { // 注册组件
Person
}
}
</script> <style scoped>
.app {
padding: 20px;
}
</style>
Person.vue
<template>
<h2>Name: {{ name }}</h2>
<h2>Age: {{ age }}</h2>
<button @click="showDetail">Detail</button>
<hr />
<p><button @click="changeName">Change Name</button></p>
<p><button @click="changeAge">Change Age</button></p>
</template> <script lang="ts">
export default {
name: "Person",
data() { // 数据配置
return { // 包含组件的所有数据
name: "John",
age: 18,
telephone: "1234567890",
email: "john@gmail.com"
}
},
methods: { // 方法配置
showDetail() { // 包含组件的全部方法
alert(`Detail: \ntelephone: ${this.telephone}\n email: ${this.email}`)
},
changeName() {
this.name = "Jane";
},
changeAge() {
this.age += 1;
}
}
}
</script> <style>
</style>
目前,使用 Vue2 语法完成的组件 Person 中使用了选项式 API(Options API),其中
name、data、methods均称为选项(或称配置项)选项式 API 的问题在于:数据、方法、计算属性等分散在
data、methods、computed等选项中,当需要新增或修改需求时,就需要分别修改各个选项,不便于维护和复用
Vue3 采用组合式 API
组合式 API(Composition API)优势在于:使用函数的方式组织代码,使相关代码更加有序的组织在一起,便于新增或修改需求
选项式 API 与 组合式 API 对比(下图来自《做了一夜动画,就为让大家更好的理解Vue3的Composition Api | 稀土掘金-大帅老猿》)
(2)setup
a. 概述
setup是 Vue3 中一个新的配置项,值是一个函数,其中包括组合式 API,组件中所用到的数据、方法、计算属性等均配置在setup中- 特点
setup函数返回的可以是一个对象,其中的内容可以直接在模板中使用;也可以返回一个渲染函数setup中访问this是undefined,表明 Vue3 已经弱化this的作用setup函数会在方法beforeCreate之前调用,在所有钩子函数中最优先执行
b. 使用 setup
修改本章第(1)小节采用 Vue2 语法的项目
App.vue
<template>
<Person />
</template> <script lang="ts">
import Person from './components/Person.vue';
export default {
name: 'App',
components: {
Person
}
}
</script> <style scoped>
</style>
Person.vue
引入
setup<script lang="ts">
export default {
name: "Person",
setup() {
}
}
</script>
在
setup中声明数据<script lang="ts">
export default {
name: "Person",
setup() {
let name = "John"
let age = 18
let telephone = "1234567890"
let email = "john@gmail.com"
}
}
</script>
将需要使用的数据返回到模板中
可以为返回的变量设置别名:如
n: name<template>
<h2>Name: {{ n }}</h2>
<h2>Age: {{ age }}</h2>
</template> <script lang="ts">
export default {
name: "Person",
setup() {
let name = "John"
let age = 18
let telephone = "1234567890"
let email = "john@gmail.com" return {
n: name,
age
}
}
}
</script> <style>
</style>
在
setup中声明方法并返回<template>
<h2>Name: {{ n }}</h2>
<h2>Age: {{ age }}</h2>
<button @click="showDetail">Detail</button>
<hr />
<p><button @click="changeName">Change Name</button></p>
<p><button @click="changeAge">Change Age</button></p>
</template> <script lang="ts">
export default {
name: "Person",
setup() {
// let ... function showDetail() {
alert(`Detail: \ntelephone: ${telephone}\n email: ${email}`)
}
function changeName() {
name = "Jane";
}
function changeAge() {
age += 1;
} return {
n: name,
age,
showDetail,
changeName,
changeAge
}
}
}
</script> <style>
</style>
此时可以发现,点击“姓名修改”或“年龄修改”按钮后,页面并未发生改变,这是因为之前使用
let声明的数据不是响应式的,具体方法参考本章第 x 小节
setup可以与data和methods同时存在在
data或methods中使用this均可以访问到在setup中声明的数据与函数- 因为
setup的执行早于data和methods
<template>
<h2>Age: {{ a }}</h2>
</template> <script lang="ts">
export default {
name: "Person",
data() {
return { a: this.age }
},
setup() {
let name = "John"
let age = 18
return { n: name, age }
}
}
</script>
- 因为
在
setup中无法使用data或methods中声明的数据或函数
c. 语法糖
在上述项目中,每次在
setup中声明新数据时,都需要在return中进行“注册”,之后才能在模板中使用该数据,为解决此问题,需要使用setup语法糖<script setup lang="ts"></script>相当于如下代码:<script lang="ts">
export default {
setup() {
return {}
}
}
</script>
修改 Person.vue
<script setup lang="ts">
let name = "John";
let age = 18;
let telephone = "1234567890";
let email = "john@gmail.com"; function showDetail() {
alert(`Detail: \ntelephone: ${telephone}\n email: ${email}`);
}
function changeName() {
name = "Jane";
}
function changeAge() {
age += 1;
}
</script>
在
<script setup lang="ts"></script>中无法直接设置组件的name属性,因此存在以下两种方式进行设置:使用两个
<script>标签<script lang="ts">
export default {
name: "Person"
}
</script>
<script setup lang="ts">
// ...
</script>
(推荐)基于插件实现
使用命令
npm install -D vite-plugin-vue-setup-extend安装需要的插件在 vite.config.ts 中引入并调用这个插件
// import ...
import vpvse from 'vite-plugin-vue-setup-extend' export default defineConfig({
plugins: [
vue(),
vpvse(),
],
// ...
})
重新启动项目
<script setup lang="ts" name="Person">
// ...
</script>
将 App.vue 中的 Vue2 语法修改为 Vue3
<template>
<Person />
</template> <script setup lang="ts" name="App">
import Person from './components/Person.vue';
</script> <style scoped>
</style>
(3)创建基本类型的响应式数据
使用
ref创建
引入
ref<script setup lang="ts" name="Person123">
import {ref} from 'vue'
</script>
对需要成为响应式的数据使用
ref<script setup lang="ts" name="Person123">
import {ref} from 'vue' let name = ref("John");
let age = ref(18);
let telephone = "1234567890";
let email = "john@gmail.com";
</script>
使用
console.log()可以发现使用ref的name变成了// RefImpl
{
"__v_isShallow": false,
"dep": {},
"__v_isRef": true,
"_rawValue": "John",
"_value": "John"
}
未使用
ref的telephone依然是字符串'1234567890'
修改方法
<script setup lang="ts" name="Person123">
import {ref} from 'vue' let name = ref("John");
let age = ref(18);
let telephone = "1234567890";
let email = "john@gmail.com"; function showDetail() {
alert(`Detail: \ntelephone: ${telephone}\n email: ${email}`);
}
function changeName() {
name.value = "Jane";
}
function changeAge() {
age.value += 1;
}
</script>
在函数方法中使用
name之类被响应式的数据,需要在属性value中获取或修改值,而在模板中不需要<template>
<h2>Name: {{ name }}</h2>
<h2>Age: {{ age }}</h2>
<button @click="showDetail">Detail</button>
<hr />
<p><button @click="changeName">Change Name</button></p>
<p><button @click="changeAge">Change Age</button></p>
</template>
(4)创建对象类型的响应式数据
a. 使用 reactive 创建
创建对象以及函数方法,并在模板中使用
<template>
<h2>Name: {{ Teacher.name }}</h2>
<h2>Age: {{ Teacher.age }}</h2>
<p><button @click="changeTeacherAge">Change Teacher Age</button></p>
</template> <script setup lang="ts" name="Person">
let Teacher = { name: "John", age: 18 } function changeTeacherAge() {
Teacher.age += 1;
}
</script>
引入
reactive<script setup lang="ts" name="Person">
import {reactive} from 'vue' // ...
</script>
对需要成为响应式的对象使用
reactive<script setup lang="ts" name="Person">
import {reactive} from 'vue' let Teacher = reactive({ name: "John", age: 18 }) // ...
</script>
- 使用
console.log()可以发现John变成了 Proxy(Object) 类型的对象
- 使用
按钮方法不做调整:
Teacher.age += 1;添加对象数组以及函数方法,并在模板中遍历
<template>
<!-- ... -->
<hr />
<ul>
<li v-for="student in Student" :key="student.id">{{ student.name }}</li>
</ul>
<button @click="changeFirstStudentName">Change First Student Name</button>
</template> <script setup lang="ts" name="Person">
// ...
let Student = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" }
] // ...
function changeFirstStudentName() {
Student[0].name = "Alex";
}
</script>
将对象数组变为响应式
<script setup lang="ts" name="Person">
// ...
let Student = reactive([
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" }
]) // ...
</script>
此时点击按钮即可修改第一个学生的名字为 Alex
b. 使用 ref 创建
修改上述使用
reactive的组件内容<template>
<h2>Name: {{ Teacher.name }}</h2>
<h2>Age: {{ Teacher.age }}</h2>
<p><button @click="changeTeacherAge">Change Teacher Age</button></p>
<hr />
<ul>
<li v-for="student in Student" :key="student.id">{{ student.name }}</li>
</ul>
<button @click="changeFirstStudentName">Change First Student Name</button>
</template> <script setup lang="ts" name="Person">
let Teacher = { name: "John", age: 18 }
let Student = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" }
] function changeTeacherAge() {}
function changeFirstStudentName() {}
</script>
引入
ref并为需要成为响应式的对象使用<script setup lang="ts" name="Person">
import {ref} from 'vue' let Teacher = ref({ name: "John", age: 18 })
let Student = ref([
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" }
]) // ...
</script>
修改函数方法
<script setup lang="ts" name="Person">
// ... function changeTeacherAge() {
Teacher.value.age += 1;
}
function changeFirstStudentName() {
Student.value[0].name = "Alex";
}
</script>
c. 对比 ref 与 reactive
功能上:
ref可以用来定义基本类型数据和对象类型数据- 在使用
ref创建响应式对象过程中,ref底层实际借用了reactive的方法
- 在使用
reactive可以用来定义对象类型数据
区别在于:
使用
ref创建的数据必须使用.value解决方法:使用插件 volar 自动添加
插件设置方法:
- 点击左下角齿轮图标(管理),并选择“设置”
- 在左侧选项列表中依次选择“扩展”-“Volar”
- 将功能“Auto Insert: Dot Value”打勾即可
使用
reactive重新分配一个新对象会失去响应式(reactive的局限性)解决方法:使用
Object.assign整体替换举例:修改
Teacher对象reactive<template>
<h2>Name: {{ Teacher.name }}</h2>
<h2>Age: {{ Teacher.age }}</h2>
<p><button @click="changeTeacher">Change Teacher</button></p>
</template> <script setup lang="ts" name="Person">
import {reactive} from 'vue' let Teacher = reactive({ name: "John", age: 18 }) function changeTeacher() {
Object.assign(Teacher, { name: "Mary", age: 19 })
}
</script>
ref<template>
<h2>Name: {{ Teacher.name }}</h2>
<h2>Age: {{ Teacher.age }}</h2>
<p><button @click="changeTeacher">Change Teacher</button></p>
</template> <script setup lang="ts" name="Person">
import {ref} from 'vue' let Teacher = ref({ name: "John", age: 18 }) function changeTeacher() {
Teacher.value = { name: "Mary", age: 19 }
}
</script>
使用原则:
- 若需要一个基本类型的响应式数据,则只能选择
ref - 若需要一个对象类型、层级较浅的响应式数据,则选择任何一个都可以
- 若需要一个对象类型、层级较深的响应式数据,则推荐选择
reactive
- 若需要一个基本类型的响应式数据,则只能选择
(5)toRefs 和 toRef
- 作用:将一个响应式对象中的每一个属性转换为
ref对象 toRefs与toRef功能相同,toRefs可以批量转换
修改 Person.vue
<template>
<h2>Name: {{ John.name }}</h2>
<h2>Age: {{ John.age }}</h2>
<p><button @click="changeName">Change Name</button></p>
<p><button @click="changeAge">Change Age</button></p>
</template> <script setup lang="ts" name="Person">
import {reactive} from 'vue' let John = reactive({ name: "John", age: 18 }) function changeName() {
John.name += "~"
}
function changeAge() {
John.age += 1
}
</script> <style>
</style>
声明两个新变量,并使用
Person对其进行赋值,之后修改模板<template>
<h2>Name: {{ name }}</h2>
<h2>Age: {{ age }}</h2>
<p><button @click="changeName">Change Name</button></p>
<p><button @click="changeAge">Change Age</button></p>
</template> <script setup lang="ts" name="Person">
// ... let John = reactive({ name: "John", age: 18 })
let { name, age } = John // ...
</script>
此时,新变量
name和age并非响应式,点击按钮无法在页面上修改(实际上已发生修改)引入
toRefs,将变量name和age变为响应式<script setup lang="ts" name="Person">
import {reactive, toRefs} from 'vue' let John = reactive({ name: "John", age: 18 })
let { name, age } = toRefs(John) // ...
</script>
修改函数方法
<script setup lang="ts" name="Person">
// ... function changeName() {
name.value += "~"
}
function changeAge() {
age.value += 1
}
</script>
引入
toRef替代toRefs<script setup lang="ts" name="Person">
import {reactive, toRef} from 'vue' let John = reactive({ name: "John", age: 18 })
let name = toRef(John, 'name')
let age = toRef(John, 'age') function changeName() {
name.value += "~"
}
function changeAge() {
age.value += 1
}
</script>
(6)computed
computed是计算属性,具有缓存,当计算方法相同时,computed会调用缓存,从而优化性能
修改 Person.vue
<template>
<h2>First Name: <input type="text" v-model="firstName" /></h2>
<h2>Last Name: <input type="text" v-model="lastName" /></h2>
<h2>Full Name: <span>{{ firstName }} {{ lastName }}</span></h2>
</template> <script setup lang="ts" name="Person">
import {ref} from 'vue' let firstName = ref("john")
let lastName = ref("Smith")
</script> <style>
</style>
引入计算属性
computed<script setup lang="ts" name="Person">
import {ref, computed} from 'vue' // ...
</script>
将姓与名的首字母大写,修改模板与控制器
<template>
<!-- ... -->
<h2>Full Name: <span>{{ fullName }}</span></h2>
</template> <script setup lang="ts" name="Person">
// ... let fullName = computed(() => {
return firstName.value.slice(0, 1).toUpperCase() + firstName.value.slice(1) + " " + lastName.value.slice(0, 1).toUpperCase() + lastName.value.slice(1)
})
</script>
此时的
fullName是只读的,无法修改,是一个使用ref创建的响应式对象修改
fullName,实现可读可写<script setup lang="ts" name="Person">
// ... let fullName = computed({
get() {
return firstName.value.slice(0, 1).toUpperCase() + firstName.value.slice(1) + " " + lastName.value.slice(0, 1).toUpperCase() + lastName.value.slice(1)
},
set() {}
})
</script>
在模板中添加按钮,用于修改
fullName,并在控制器中添加相应的函数方法<template>
<!-- ... -->
<button @click="changeFullName">Change Full Name</button>
</template> <script setup lang="ts" name="Person">
// ... function changeFullName() {
fullName.value = "bob jackson"
}
</script>
修改计算属性中的
set()方法<script setup lang="ts" name="Person">
// ... let fullName = computed({
get() {
return firstName.value.slice(0, 1).toUpperCase() + firstName.value.slice(1) + " " + lastName.value.slice(0, 1).toUpperCase() + lastName.value.slice(1)
},
set(value) {
const [newFirstName, newLastName] = value.split(" ")
firstName.value = newFirstName
lastName.value = newLastName
}
}) // ...
</script>
(7)watch
watch是监视属性- 作用:监视数据变化(与 Vue2 中的
watch作用一致) - 特点:Vue3 中的
watch只监视以下数据:ref定义的数据reactive定义的数据- 函数返回一个值
- 一个包含上述内容的数组
a. 情况一:监视 ref 定义的基本类型数据
直接写数据名即可,监视目标是
value值的改变
修改 Person.vue
<template>
<h2>Sum: {{ sum }}</h2>
<button @click="changeSum"> +1 </button>
</template> <script setup lang="ts" name="Person">
import {ref} from 'vue' let sum = ref(0) function changeSum() {
sum.value += 1
}
</script> <style>
</style>
引入
watch<script setup lang="ts" name="Person">
import {ref, watch} from 'vue' // ...
</script>
使用
watch,一般传入两个参数,依次是监视目标与相应的回调函数<script setup lang="ts" name="Person">
import {ref, watch} from 'vue' // ... watch(sum, (newValue, oldValue) => {
console.log("Sum changed from " + oldValue + " to " + newValue)
})
</script>
修改
watch,设置“停止监视”<script setup lang="ts" name="Person">
// ... const stopWatch = watch(sum, (newValue, oldValue) => {
console.log("Sum changed from " + oldValue + " to " + newValue)
if(newValue >= 10) {
stopWatch()
}
})
</script>
b. 情况二:监视 ref 定义的对象类型数据
- 直接写数据名即可,监视目标是对象的地址值的改变
- 如需监视对象内部的数据,需要手动开启深度监视
- 若修改的是
ref定义的对象中的属性,则newValue和newValue都是新值,因为它们是同一个对象- 若修改整个
ref定义的对象,则newValue是新值,oldValue是旧值,因为它们不是同一个对象
修改 Person.vue
<template>
<h2>Name: {{ person.name }}</h2>
<h2>Age: {{ person.age }}</h2>
<p><button @click="changeName">Change Name</button></p>
<p><button @click="changeAge">Change Age</button></p>
<hr />
<p><button @click="changeAll">Change All</button></p>
</template> <script setup lang="ts" name="Person">
import { ref, watch } from "vue" let person = ref({
name: "John",
age: 18
}) function changeName() {
person.value.name += '~'
}
function changeAge() {
person.value.age += 1
}
function changeAll() {
person.value = {
name: "Mary",
age: 19
}
}
</script> <style>
</style>
使用
watch监视整个对象的地址值变化<script setup lang="ts" name="Person">
// ... watch(person, (newValue, oldValue) => {
console.log(newValue, oldValue)
})
</script>
此时,只有点击“Change All” 按钮才会触发监视,
newValue与oldValue不同手动开启深度监视,监视对象内部的数据
<script setup lang="ts" name="Person">
// ... watch(
person,
(newValue, oldValue) => {
console.log(newValue, oldValue)
},
{
deep: true,
}
)
</script>
此时,点击“Change Name”或“Change Age”也能触发监视,
newValue与oldValue相同,但是点击“Change All”时,newValue与oldValue依旧不同
c. 情况三:监视 reactive 定义的对象类型数据
该情况下,默认开启了深度监视且无法关闭
修改 Person.vue
<template>
<h2>Name: {{ person.name }}</h2>
<h2>Age: {{ person.age }}</h2>
<p><button @click="changeName">Change Name</button></p>
<p><button @click="changeAge">Change Age</button></p>
<hr />
<p><button @click="changeAll">Change All</button></p>
</template> <script setup lang="ts" name="Person">
import { reactive, watch } from "vue" let person = reactive({
name: "John",
age: 18,
}) function changeName() {
person.name += "~"
}
function changeAge() {
person.age += 1
}
function changeAll() {
Object.assign(person, {
name: "Mary",
age: 19,
})
}
</script> <style>
</style>
使用
watch监视对象<script setup lang="ts" name="Person">
// ... watch(person, (newValue, oldValue) => {
console.log(newValue, oldValue)
})
</script>
对于使用
reactive创建的对象,在使用Object.assign()时,仅修改了对象里的内容(覆盖原来的内容),并非创建了新对象,故无法监视对象地址值的变化(因为没有变化)
d. 情况四:监视 ref 或 reactive 定义的对象类型数据中的某个属性
若该属性值不是对象类型,则需写成函数形式
若该属性值是对象类型,则建议写成函数形式
修改 Person.vue
<template>
<h2>Name: {{ person.name }}</h2>
<h2>Age: {{ person.age }}</h2>
<h2>Nickname: {{ person.nickname.n1 }}/{{ person.nickname.n2 }}</h2>
<p><button @click="changeName">Change Name</button></p>
<p><button @click="changeAge">Change Age</button></p>
<hr />
<p><button @click="changeNickname1">Change Nickname 1</button></p>
<p><button @click="changeNickname2">Change Nickname 2</button></p>
<p><button @click="changeNickname">Change Nickname</button></p>
</template> <script setup lang="ts" name="Person">
import { reactive, watch } from "vue" let person = reactive({
name: "John",
age: 18,
nickname: {
n1: "J",
n2: "Jack"
}
}) function changeName() {
person.name += "~"
}
function changeAge() {
person.age += 1
}
function changeNickname1() {
person.nickname.n1 = "Big J"
}
function changeNickname2() {
person.nickname.n2 = "Joker"
}
function changeNickname() {
person.nickname = {
n1: "Joker",
n2: "Big J"
}
}
</script> <style>
</style>
使用
watch监视全部的变化<script setup lang="ts" name="Person">
// ... watch(person, (newValue, oldValue) => {
console.log(newValue, oldValue)
})
</script>
修改
watch,设置监视基本类型数据person.name<script setup lang="ts" name="Person">
// ... watch(
() => {
return person.name;
},
(newValue, oldValue) => {
console.log(newValue, oldValue);
}
})
</script>
修改
watch,设置监视对象类型数据person.nickname<script setup lang="ts" name="Person">
// ... watch(
() => person.nickname,
(newValue, oldValue) => {
console.log(newValue, oldValue);
},
{ deep: true }
})
</script>
以下写法仅能监视对象类型内部属性数据变化
<script setup lang="ts" name="Person">
// ... watch(
person.nickname,
(newValue, oldValue) => {
console.log(newValue, oldValue);
}
})
</script>
以下写法仅能监视对象类型整体地址值变化
<script setup lang="ts" name="Person">
// ... watch(
() => person.nickname,
(newValue, oldValue) => {
console.log(newValue, oldValue);
}
})
</script>
e. 情况五:监视多个数据
修改 Person.vue
<template>
<h2>Name: {{ person.name }}</h2>
<h2>Age: {{ person.age }}</h2>
<h2>Nickname: {{ person.nickname.n1 }}/{{ person.nickname.n2 }}</h2>
<p><button @click="changeName">Change Name</button></p>
<p><button @click="changeAge">Change Age</button></p>
<hr />
<p><button @click="changeNickname1">Change Nickname 1</button></p>
<p><button @click="changeNickname2">Change Nickname 2</button></p>
<p><button @click="changeNickname">Change Nickname</button></p>
</template> <script setup lang="ts" name="Person">
import { reactive, watch } from "vue" let person = reactive({
name: "John",
age: 18,
nickname: {
n1: "J",
n2: "Jack"
}
}) function changeName() {
person.name += "~"
}
function changeAge() {
person.age += 1
}
function changeNickname1() {
person.nickname.n1 = "Big J"
}
function changeNickname2() {
person.nickname.n2 = "Joker"
}
function changeNickname() {
person.nickname = {
n1: "Joker",
n2: "Big J"
}
}
</script> <style>
</style>
使用
watch监视person.name和person.nickname.n1<script setup lang="ts" name="Person">
// ... watch(
[() => person.name, () => person.nickname.n1],
(newValue, oldValue) => {
console.log(newValue, oldValue);
},
{ deep: true }
)
</script>
(8)watchEffect
- 作用:立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行该函数
watchEffect与watch对比,两者都能监视响应式数据,但是监视数据变化的方式不同watch需要指明监视的数据watchEffect则不用指明,函数中用到哪些属性,就监视哪些属性
修改 Person.vue
<template>
<h2>Sum1: {{ sum1 }}</h2>
<h2>Sum2: {{ sum2 }}</h2>
<p><button @click="changeSum1">Sum1 +1</button></p>
<p><button @click="changeSum2">Sum2 +3</button></p>
</template> <script setup lang="ts" name="Person">
import { ref, watch } from 'vue' let sum1 = ref(0)
let sum2 = ref(0) function changeSum1() {
sum1.value += 1
}
function changeSum2() {
sum2.value += 3
}
</script> <style>
</style>
使用
watch监视sum1和sum2,获取最新的值<script setup lang="ts" name="Person">
// ... watch([sum1, sum2], (value) => {
console.log(value)
})
</script>
对
sum1和sum2进行条件判断<script setup lang="ts" name="Person">
// ... watch([sum1, sum2], (value) => {
let [newSum1, newSum2] = value
if(newSum1 >= 10 || newSum2 >= 30) {
console.log('WARNING: Sum1 or Sum2 is too high')
}
})
</script>
此时,仅对
sum1和sum2进行监视,当需要监视的目标更多时,建议使用watchEffect引入
watchEffect<script setup lang="ts" name="Person">
import { ref, watch, watchEffect } from 'vue' // ...
</script>
使用
watchEffect<script setup lang="ts" name="Person">
// ... watchEffect(() => {
if(sum1.value >= 10 || sum2.value >= 30) {
console.log('WARNING: Sum1 or Sum2 is too high')
}
})
</script>
(9)标签的 ref 属性
- 作用:用于注册模板引用
- 用在普通 DOM 标签上,获取到 DOM 节点
- 用在组件标签上,获取到组件实例对象
修改 Person.vue
<template>
<h2>Vue 3</h2>
<button @click="showH2">Show Element H2</button>
</template> <script setup lang="ts" name="Person">
function showH2() {
console.log()
}
</script> <style>
</style>
引入标签的
ref属性<template>
<h2 ref="title">Vue 3</h2>
<button @click="showH2">Show Element H2</button>
</template> <script setup lang="ts" name="Person">
import { ref } from 'vue' // ...
</script>
创建一个容器,用于存储
ref标记的内容容器名称与标签中
ref属性值相同<script setup lang="ts" name="Person">
import { ref } from 'vue' let title = ref() // ...
</script>
修改函数方法
showH2(),输出标签<script setup lang="ts" name="Person">
import { ref } from 'vue' let title = ref() function showH2() {
console.log(title.value)
}
</script>
此时,
ref属性用在普通 DOM 标签上,获取到 DOM 节点在 App.vue 中,对组件 Person 设置
ref属性<template>
<Person ref="person" />
<hr />
<button @click="showPerson">Show Element Person</button>
</template> <script setup lang="ts" name="App">
import Person from './components/Person.vue';
import { ref } from 'vue' let person = ref() function showPerson() {
console.log(person.value)
}
</script> <style scoped>
</style>
此时,
ref属性用在组件标签上,获取到组件实例对象在 Person.vue 中引入
defineExpose实现父子组件通信<script setup lang="ts" name="Person">
import { ref, defineExpose } from 'vue' let title = ref()
let number = ref(12345) function showH2() {
console.log(title.value)
} defineExpose({ title, number })
</script>
此时,再次点击按钮“Show Element Person”便可在控制台中看到来自 Person.vue 组件中的
title和number重置 App.vue
<template>
<Person />
</template> <script setup lang="ts" name="App">
import Person from './components/Person.vue'
</script> <style scoped>
</style>
(9.5)TypeScript 回顾
TypeScript 基本概述:学习 TypeScript | 稀土掘金-SRIGT
在 Vue3 项目中,TS 接口等位于 ~/src/types/index.ts 中
定义接口,用于限制对象的具体属性
interface IPerson {
id: string,
name: string,
age: number
}
暴露接口
暴露接口有三种方法:默认暴露、分别暴露、统一暴露,以下采用分别暴露方法
export interface IPerson {
id: string,
name: string,
age: number
}
修改 Person.vue,声明新变量,引入接口对新变量进行限制
<template>
</template> <script setup lang="ts" name="Person">
import { type IPerson } from '@/types' let person:IPerson = {
id: "dpb7e82nlh",
name: "John",
age: 18
}
</script> <style>
</style>
此时,仅声明了一个变量,对于同类型的数组型变量,需要使用泛型
修改 Perosn.vue,声明一个数组,使用泛型
<script setup lang="ts" name="Person">
import { type IPerson } from '@/types' let personList:Array<IPerson> = [
{ id: "dpb7e82nlh", name: "John", age: 18 },
{ id: "u55dyu86gh", name: "Mary", age: 19 },
{ id: "ad3d882dse", name: "Niko", age: 17 }
]
</script>
修改 index.ts,定义一个自定义类型,简化对数组限制的使用
export interface IPerson {
id: string,
name: string,
age: number,
} // 自定义类型
export type Persons = Array<IPerson>
或:
export type Persons = IPerson[]修改 Person.vue
<script setup lang="ts" name="Person">
import { type Persons } from '@/types' let personList:Persons = [
{ id: "dpb7e82nlh", name: "John", age: 18 },
{ id: "u55dyu86gh", name: "Mary", age: 19 },
{ id: "ad3d882dse", name: "Niko", age: 17 }
]
</script>
(10)props
修改 App.vue
<template>
<Person />
</template> <script setup lang="ts" name="App">
import Person from './components/Person.vue' import { reactive } from 'vue' let personList = reactive([
{ id: "dpb7e82nlh", name: "John", age: 18 },
{ id: "u55dyu86gh", name: "Mary", age: 19 },
{ id: "ad3d882dse", name: "Niko", age: 17 }
])
</script> <style scoped>
</style>
引入接口并使用
<script setup lang="ts" name="App">
// ...
import { type Persons } from '@/types'; let personList = reactive<Persons>([
{ id: "dpb7e82nlh", name: "John", age: 18 },
{ id: "u55dyu86gh", name: "Mary", age: 19 },
{ id: "ad3d882dse", name: "Niko", age: 17 }
])
</script>
修改父组件 App.vue 的模板内容,向子组件 Person.vue 发送数据
<template>
<Person note="This is a note" :list="personList" />
</template>
修改子组件 Person.vue,从父组件 App.vue 中接收数据
<template>
<h2>{{ note }}</h2>
<ul>
<li v-for="person in list" :key="person.id">{{ person.name }}-{{ person.age }}</li>
</ul>
</template> <script setup lang="ts" name="Person">
import { defineProps } from 'vue' // 只接收
// defineProps(['note', 'list']) // 接收并保存
let personList = defineProps(['note', 'list'])
console.log(personList)
console.log(personList.note)
</script> <style>
</style>
接收限制类型
<script setup lang="ts" name="Person">
import { defineProps } from 'vue'
import type { Persons } from '@/types' defineProps<{ list:Persons }>()
</script>
接收限制类型并指定默认值
<script setup lang="ts" name="Person">
import { defineProps } from 'vue'
import type { Persons } from '@/types' withDefaults(defineProps<{ list?: Persons }>(), {
list: () => [{ id: "dpb7e82nlh", name: "John", age: 18 }],
})
</script>
重置 App.vue
<template>
<Person />
</template> <script setup lang="ts" name="App">
import Person from './components/Person.vue'
</script> <style scoped>
</style>
(11)生命周期
- 组件的生命周期包括:创建、挂载、更新、销毁/卸载
- 组件在特定的生命周期需要调用特定的生命周期钩子(生命周期函数)
a. Vue2 的生命周期
Vue2 生命周期包括八个生命周期钩子
生命周期 生命周期钩子 创建 创建前 beforeCreate创建完成后 created挂载 挂载前 beforeMount挂载完成后 mounted更新 更新前 beforeUpdate更新完成后 updated销毁 销毁前 beforeDestroy销毁完成后 destroyed
使用命令
vue create vue2_test或vue init webpack vue2_test创建一个 Vue2 项目重置 ~/src/App.vue
<template>
</template> <script>
export default {
name: 'App',
}
</script> <style>
</style>
在 ~/src/components 中新建 Person.vue
<template>
<div>
<h2>Sum: {{ sum }}</h2>
<button @click="changeSum">+1</button>
</div>
</template> <script>
export default {
// eslint-disable-next-line
name: 'Person',
data() {
return {
sum: 0
}
},
methods: {
changeSum() {
this.sum += 1
}
}
}
</script> <style scoped>
</style>
在 App.vue 引入 Person.vue
<template>
<PersonVue />
</template> <script>
import PersonVue from './components/Person.vue' export default {
name: 'App',
components: {
PersonVue
}
}
</script> <style>
</style>
使用生命周期钩子
<script>
export default {
// eslint-disable-next-line
name: 'Person',
data() {
// ...
},
methods: {
// ...
}, // 创建前
beforeCreate() {
console.log("beforeCreate")
},
// 创建完成后
created() {
console.log("created")
}, // 挂载前
beforeMount() {
console.log("beforeMount")
},
// 挂载完成后
mounted() {
console.log("mounted")
}, // 更新前
beforeUpdate() {
console.log("beforeUpdate")
},
// 更新完成后
updated() {
console.log("updated")
}, // 销毁前
beforeDestroy() {
console.log("beforeDestroy")
},
// 销毁完成后
destroyed() {
console.log("destroyed")
}
}
</script>
使用命令
npm run serve启动项目,在开发者工具中查看生命周期过程
b. Vue3 的生命周期
Vue3 生命周期包括七个生命周期钩子
生命周期 生命周期钩子 创建 setup挂载 挂载前 onBeforeMount挂载完成后 onMounted更新 更新前 onBeforeUpdate更新完成后 onUpdated卸载 卸载前 onBeforeUnmount卸载完成后 onUnmounted
在之前的 Vue3 项目中,修改 Person.vue
<template>
<h2>Sum: {{ sum }}</h2>
<button @click="changeSum">+1</button>
</template> <script setup lang="ts" name="Person">
import { ref } from 'vue' let sum = ref(0) function changeSum() {
sum.value += 1
}
</script> <style>
</style>
引入并使用生命周期钩子
<script setup lang="ts" name="Person">
import {
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onMounted,
onUnmounted,
onUpdated,
ref
} from 'vue' // ... // 创建
console.log("setup") // 挂载前
onBeforeMount(() => {
console.log("onBeforeMount")
})
// 挂载完成后
onMounted(() => {
console.log("onMounted")
}) // 更新前
onBeforeUpdate(() => {
console.log("onBeforeUpdate")
})
// 更新完成后
onUpdated(() => {
console.log("onUpdated")
}) // 卸载前
onBeforeUnmount(() => {
console.log("onBeforeUnmount")
})
// 卸载完成后
onUnmounted(() => {
console.log("onUnmounted")
})
</script>
(12)自定义Hooks
使用命令
npm install -S axios安装 Axios,用于网络请求
修改 Person.vue
<template>
<h2>Sum: {{ sum }}</h2>
<button @click="changeSum">+1</button>
<hr />
<img
v-for="(img, index) in imgList"
:src="img"
:key="index"
style="height: 100px"
/>
<p><button @click="changeImg">Next</button></p>
</template> <script setup lang="ts" name="Person">
import { reactive, ref } from "vue"; let sum = ref(0);
let imgList = reactive([""]); function changeSum() {
sum.value += 1;
}
function changeImg() {}
</script> <style>
</style>
引入 Axios
<script setup lang="ts" name="Person">
import axios from 'axios'
// ...
</script>
使用 Axios 获取图片地址
<script setup lang="ts" name="Person">
import axios from 'axios'
// ...
async function changeImg() {
try {
let result = await axios.get(
"https://dog.ceo/api/breed/pembroke/images/random"
);
imgList.push(result.data.message);
} catch (error) {
alert("Error! Please try again.");
console.log(error);
}
}
</script>
此时,多个功能(求和、请求图片)同时在组件中互相交织,可以使用 Hooks 重新组织代码
在 src 目录下新建目录 hooks,其中分别新建 useSum.ts、useImg.ts
useSum.ts
import { ref } from 'vue' export default function () {
let sum = ref(0) function changeSum() {
sum.value += 1
} return {
sum,
changeSum
}
}
useImg.ts
import axios from 'axios'
import { reactive } from 'vue' export default function () {
let imgList = reactive([""]) async function changeImg() {
try {
let result = await axios.get(
"https://dog.ceo/api/breed/pembroke/images/random"
)
imgList.push(result.data.message)
} catch (error) {
alert("Error! Please try again.")
console.log(error)
}
} return {
imgList,
changeImg
}
}
在 Person.vue 中引入并使用两个 Hooks
<script setup lang="ts" name="Person">
import useSum from '@/hooks/useSum'
import useImg from '@/hooks/useImg' const { sum, changeSum } = useSum()
const { imgList, changeImg } = useImg()
</script>
0x03 路由
(1)概述
- 此处的路由是指前后端交互的路由
- 路由(route)就是一组键值的对应关系
- 多个路由需要经过路由器(router)的管理
- 路由组件通常存放在 pages 或 views 文件夹,一般组件通常存放在 components 文件夹
(2)路由切换
重置 ~/src 目录结构
graph TB
src-->components & pages & App.vue & main.ts
components-->Header.vue
pages-->Home.vue & Blog.vue & About.vue修改 App.vue(样式可忽略)
<template>
<div class="app">
<Header />
<div class="navigation">
<a href="/home" class="active">Home</a>
<a href="/blog">Blog</a>
<a href="/about">About</a>
</div>
<div class="main-content">
Content
</div>
</div>
</template> <script setup lang="ts" name="App">
import Header from './components/Header.vue'
</script> <style scoped>
.app {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
.navigation {
width: 15%;
height: 506px;
float: left;
background: #03deff;
}
.navigation a {
display: block;
text-decoration-line: none;
color: black;
text-align: center;
font-size: 28px;
padding-top: 10px;
padding-bottom: 10px;
border-bottom: 3px solid #fff;
}
.navigation a.active {
background: rgb(1, 120, 144);
color: white;
}
.main-content {
display: inline;
width: 80%;
height: 500px;
float: left;
margin-left: 10px;
font-size: 26px;
border: 3px solid #000;
}
</style>
修改 Header.vue、Home.vue、Blog.vue、About.vue
Header.vue
<template>
<h2 class="title">Route Test</h2>
</template> <script setup lang="ts" name="Header">
</script> <style scope>
.title {
width: 100%;
text-align: center;
}
</style>
Home.vue、Blog.vue、About.vue
<template>
<h2>Home</h2>
<!-- <h2>Blog</h2> -->
<!-- <h2>About</h2> -->
</template> <script setup lang="ts">
</script> <style scope>
</style>
创建路由器
使用命令
npm install vue-router -S安装路由器在 ~/src 目录下新建目录 router,其中新建 index.ts,用于创建一个路由器并暴露
引入
createRouter和createWebHistoryimport {
createRouter,
createWebHistory
} from 'vue-router'
其中
createWebHistory是路由器的工作模式,在本章第(3)小节有详细介绍引入子组件
// ... import Home from '@/pages/Home.vue'
import Blog from '@/pages/Blog.vue'
import About from '@/pages/About.vue'
创建路由器
// ... const router = createRouter({
history: createWebHistory(),
routes: []
})
制定路由规则
// ...
routes: [
{
path: '/home',
component: Home
},
{
path: '/blog',
component: Blog
},
{
path: '/about',
component: About
}
]
// ...
暴露路由
// ... export default router
修改 main.ts,引入并使用路由器
// ...
import router from './router' createApp(App).use(router).mount('#app')
修改 App.vue,引入路由器视图,修改模板中的超链接
<template>
<div class="app">
<Header />
<div class="navigation">
<RouterLink to="/home" active-class="active">Home</RouterLink>
<RouterLink to="/blog" active-class="active">Blog</RouterLink>
<RouterLink to="/about" active-class="active">About</RouterLink>
</div>
<div class="main-content">
<RouterView></RouterView>
</div>
</div>
</template> <script setup lang="ts" name="App">
import Header from './components/Header.vue'
import { RouterLink, RouterView } from 'vue-router'
</script> <style scoped>
/* ... */
</style>
- 此时,点击导航后,“消失”的路由组件默认是被卸载的,需要的时候再重新挂载
- 可以在组件中使用生命周期钩子验证
<RouterLink>中的to属性有两种写法to="/home":to="{path:'/home'}"- 此写法的优点在本章第()小节介绍
- 此时,点击导航后,“消失”的路由组件默认是被卸载的,需要的时候再重新挂载
(3)路由器的工作模式
a. history 模式
优点:URL 更美观,更接近于传统网站的 URL
缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会有 404 报错
使用方法:
Vue2:
mode: 'history'Vue3:
// router/index.ts
import {
createRouter,
createWebHistory
} from 'vue-router' const router = createRouter({
history: createWebHistory(),
// ...
})
React:
BrowserRouter
b. hash 模式
优点:兼容性更好,不需要服务端处理路径
缺点:URL 中带有
#号,在 SEO 优化方面相对较差使用方法:
Vue3:
// router/index.ts
import {
createRouter,
createWebHashHistory
} from 'vue-router' const router = createRouter({
history: createWebHashHistory(),
// ...
})
(4)命名路由
- 作用:简化路由跳转与传参
在 router/index.ts 中为路由命名
// ...
routes: [
{
name: 'zy',
path: '/home',
component: Home
},
{
name: 'bk',
path: '/blog',
component: Blog
},
{
name: 'gy',
path: '/about',
component: About
}
]
// ...
修改 App.vue,使用命名路由的方法进行跳转
<template>
<!-- ... -->
<div class="navigation">
<RouterLink :to="{ name: 'zy' }" active-class="active">Home</RouterLink>
<RouterLink to="/blog" active-class="active">Blog</RouterLink>
<RouterLink to="/about" active-class="active">About</RouterLink>
</div>
<!-- ... -->
</template>
此时,共有两类方式三种方法实现路由跳转:
字符串写法 to="/home"对象写法 命名跳转 :to="{ name: 'zy' }"路径跳转 :to="{ path: '/home' }"
(5)嵌套路由
在博客页面实现路由的嵌套,实现博客内容根据选择进行动态展示
在 pages 目录下新建 Detail.vue
<template>
<ul>
<li>id: id</li>
<li>title: title</li>
<li>content: content</li>
</ul>
</template> <script setup lang="ts" name="detail">
</script> <style scope>
ul {
list-style: none;
padding-left: 20px;
}
ul>li {
line-height: 30px;
}
</style>
修改 router/index.ts,引入 Detail.vue
// ...
import Detail from '@/pages/Detail.vue' const router = createRouter({
// ...
routes: [
// ...
{
path: '/blog',
component: Blog,
children: [
{
path: 'detail',
component: Detail
}
]
},
// ...
]
}) export default router
修改 Blog.vue,添加博客导航列表、博客内容展示区、相关数据以及样式
<template>
<h2>Blog</h2>
<ul>
<li v-for="blog in blogList" :key="blog.id">
<RouterLink to="/blog/detail">{{ blog.title }}</RouterLink>
</li>
</ul>
<div class="blog-content">
<RouterView></RouterView>
</div>
</template> <script setup lang="ts" name="Blog">
import { reactive } from 'vue' const blogList = reactive([
{ id: 'fhi27df4sda', title: 'Blog01', content: 'Content01' },
{ id: 'opdcd2871cb', title: 'Blog02', content: 'Content02' },
{ id: 'adi267f4hp5', title: 'Blog03', content: 'Content03' }
])
</script> <style scope>
ul {
float: left;
}
ul li {
display: block;
}
ul li a {
text-decoration-line: none;
}
.blog-content {
float: left;
margin-left: 200px;
width: 70%;
height: 300px;
border: 3px solid black;
}
</style>
(6)路由传参
- 在 Vue 中,路由用两种参数:query 和 params
a. query
修改 Blog.vue,发送参数
修改
to属性为:to,使用模板字符串,在路由后添加?,之后使用格式key1=val1&key2=val2的方式传递参数<template>
<!-- ... -->
<li v-for="blog in blogList" :key="blog.id">
<RouterLink :to="`/blog/detail?id=${blog.id}&title=${blog.title}&content=${blog.content}`">
{{ blog.title }}
</RouterLink>
</li>
<!-- ... -->
</template>
简化传参
<template>
<!-- ... -->
<RouterLink
:to="{
path: '/blog/detail',
query: {
id: blog.id,
title: blog.title,
content: blog.content
}
}"
>
{{ blog.title }}
</RouterLink>
<!-- ... -->
</template>
修改 Detail.vue,接收参数
使用 Hooks
useRoute接收<template>
<ul>
<li>id: {{ route.query.id }}</li>
<li>title: {{ route.query.title }}</li>
<li>content: {{ route.query.content }}</li>
</ul>
</template> <script setup lang="ts" name="detail">
import { useRoute } from 'vue-router'
const route = useRoute()
</script>
简化数据展示
<template>
<ul>
<li>id: {{ query.id }}</li>
<li>title: {{ query.title }}</li>
<li>content: {{ query.content }}</li>
</ul>
</template> <script setup lang="ts" name="detail">
import { toRefs } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
let { query } = toRefs(route)
</script>
此时,模板中仍旧使用了很多
query.xxx的语法,为省略query.,本章第(7)小节有相关处理方法
b. params
修改 router/index.ts
// ...
children: [
{
path: 'detail/:id/:title/:content',
component: Detail
}
]
// ...
可以在相关参数后添加
?设置参数的必要性,如:path: 'detail/:id/:title/:content?'修改 Blog.vue,发送参数
<template>
<!-- ... -->
<RouterLink :to="`/blog/detail/${blog.id}/${blog.title}/${blog.content}`">
{{ blog.title }}
</RouterLink>
<!-- ... -->
</template>
简化传参
修改 router/index.ts,为
/detail路由命名// ...
children: [
{
name: 'detail',
path: 'detail/:id/:title/:content',
component: Detail
}
]
// ...
修改 Blog.vue
<template>
<!-- ... -->
<RouterLink
:to="{
name: 'detail',
params: {
id: blog.id,
title: blog.title,
content: blog.content
}
}"
>
{{ blog.title }}
</RouterLink>
<!-- ... -->
</template>
修改 Detail.vue,接收参数
<template>
<ul>
<li>id: {{ route.params.id }}</li>
<li>title: {{ route.params.title }}</li>
<li>content: {{ route.params.content }}</li>
</ul>
</template> <script setup lang="ts" name="detail">
import { useRoute } from 'vue-router'
const route = useRoute()
</script>
简化数据展示
<template>
<ul>
<li>id: {{ params.id }}</li>
<li>title: {{ params.title }}</li>
<li>content: {{ params.content }}</li>
</ul>
</template> <script setup lang="ts" name="detail">
import { toRefs } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
let { params } = toRefs(route)
</script>
(7)路由的 props 配置
- 作用:让路由组件更方便地接收参数
- 原理:将路由参数作为
props传递给组件
a. 对象写法
修改 Blog.vue
<template>
<!-- ... -->
<RouterLink
:to="{
name: 'detail',
query: {
id: blog.id,
title: blog.title,
content: blog.content
}
}"
>
{{ blog.title }}
</RouterLink>
<!-- ... -->
</template>
修改 router/index.ts
{
name: 'detail',
path: 'detail',
component: Detail,
props(route) {
return route.query
}
}
此时相当于将
<Detail />修改为<Detail id=xxx title=xxx content=xxx />,可以按照第二章第(10)小节的方法将参数从 props 中取出使用修改 Detail.vue
<template>
<ul>
<li>id: {{ id }}</li>
<li>title: {{ title }}</li>
<li>content: {{ content }}</li>
</ul>
</template> <script setup lang="ts" name="detail">
defineProps(['id', 'title', 'content'])
</script>
b. 布尔值写法
修改 Blog.vue
<template>
<!-- ... -->
<RouterLink
:to="{
name: 'detail',
params: {
id: blog.id,
title: blog.title,
content: blog.content
}
}"
>
{{ blog.title }}
</RouterLink>
<!-- ... -->
</template>
修改 router/index.ts
// ...
{
name: 'detail',
path: 'detail/:id/:title/:content',
component: Detail,
props: true
}
// ...
修改 Detail.vue
c. 对象写法
不常用
修改 router/index.ts
// ...
{
name: 'detail',
path: 'detail/:id/:title/:content',
component: Detail,
props: {
id: xxx,
title: yyy,
content: zzz
}
}
// ...
修改 Detail.vue
(8)replace
默认情况下,采用 push 模式,即记录浏览先后顺序,允许前进和回退
replace 模式不允许前进和回退
修改 App.vue,在
RouterLink标签中添加replace属性<template>
<!-- ... -->
<RouterLink replace to="/home" active-class="active">Home</RouterLink>
<RouterLink replace to="/blog" active-class="active">Blog</RouterLink>
<RouterLink replace to="/about" active-class="active">About</RouterLink>
<!-- ... -->
</template>
(9)编程式导航
RouterLink标签的本质是a标签编程式导航的目的是脱离
RouterLink标签,实现路由跳转修改 Home.vue,实现进入该页面 3 秒后,以 push 的模式跳转到 /blog
<script setup lang="ts">
import { onMounted } from 'vue'
import { useRouter } from 'vue-router' const router = useRouter() onMounted(() => {
setTimeout(() => {
router.push('/blog')
}, 3000)
})
</script>
重置 Home.vue
修改 Blog.vue
<template>
<h2>Blog</h2>
<ul>
<li v-for="blog in blogList" :key="blog.id">
<button @click="showDetail(blog)">More</button>
<!-- ... -->
</li>
<!-- ... -->
</template> <script setup lang="ts" name="Blog">
import { useRouter } from "vue-router"; // ... const router = useRouter(); interface IBlog {
id: string;
title: string;
content: string;
} function showDetail(blog: IBlog) {
router.push({
name: "detail",
query: {
id: blog.id,
title: blog.title,
content: blog.content,
},
});
}
</script>
(10)重定向
修改 router/index.ts,将路径 / 重定向到 /home
routes: [
{
path: '/',
redirect: '/home'
},
// ...
]
此时,访问 http://localhost:5173/ 时,会重定向到 http://localhost:5173/home
0x04 pinia
(1)概述
pinia 是 Vue3 中的集中式状态管理工具
- 类似的工具有:redux(React)、vuex(Vue2)等
- 集中式:将所有需要管理的数据集中存放在一个容器中,相对的称为分布式
-
(2)准备
重置 ~/src 目录结构
graph TB
src-->components & App.vue & main.ts
components-->Count.vue & Text.vueCount.vue
<template>
<div class="count">
<h2>Sum: {{ sum }}</h2>
<select v-model.number="number">
<option value="1" selected>1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="add">Add</button>
<button @click="sub">Sub</button>
</div>
</template> <script setup lang="ts" name="Count">
import { ref } from 'vue' let sum = ref(0)
let number = ref(1) function add() {
sum.value += number.value
}
function sub() {
sum.value -= number.value
}
</script> <style scope>
.count {
text-align: center;
width: 50%;
height: 120px;
background: #03deff;
border: 3px solid black;
}
select, button {
margin: 10px;
}
</style>
当在
select标签中进行选择时,为向变量number中传入数字,可以使用以下方法之一:- 修改
select标签中的v-model为v-model.number(上面使用的方法) - 修改
option标签中的value为:value
- 修改
Text.vue
<template>
<div class="text">
<button @click="getText">Get Text</button>
<ul>
<li v-for="text in textList" :key="text.id">
{{ text.content }}
</li>
</ul>
</div>
</template> <script setup lang="ts" name="Text">
import { reactive } from 'vue'
import axios from 'axios' let textList = reactive([
{ id: '01', content: "Text01" },
{ id: '02', content: "Text02" },
{ id: '03', content: "Text03" }
]) async function getText() {
let { data:{result:{content}} } = await axios.get('https://api.oioweb.cn/api/common/OneDayEnglish')
textList.unshift({ id: Date.now().toString(), content})
console.log(content)
}
</script> <style scope>
.text {
width: 50%;
height: 150px;
background: #fbff03;
border: 3px solid black;
padding-left: 10px;
}
</style>
App.vue
<template>
<Count />
<br />
<Text />
</template> <script setup lang="ts" name="App">
import Count from './components/Count.vue'
import Text from './components/Text.vue'
</script> <style scope>
</style>
main.ts
import { createApp } from 'vue'
import App from './App.vue' createApp(App).mount('#app')
(3)搭建环境
使用命令
npm install -S pinia安装 pinia在 main.ts 中引入并安装 pinia
import { createApp } from 'vue'
import App from './App.vue' import { createPinia } from 'pinia' createApp(App).use(createPinia()).mount('#app')
(4)存储与读取数据
store 是一个保存状态、业务逻辑的实体,每个组件都可以读写它
store 中有三个概念,与组件中的一些属性类似
store 属性 statedatagettercomputedactionsmethodsstore 目录下的 ts 文件命名与组件的相同
以下涉及对文件、变量等命名的格式均符合 pinia 官方文档规范
在 ~/src 目录下,新建 store 目录,其中新建 count.ts 和 text.ts
count.ts
import { defineStore } from 'pinia' export const useCountStore = defineStore('count', {
state() {
return {
sum: 10
}
}
})
text.ts
import { defineStore } from 'pinia' export const useTextStore = defineStore('text', {
state() {
return {
textList: [
{ id: '01', content: "Text01" },
{ id: '02', content: "Text02" },
{ id: '03', content: "Text03" }
]
}
}
})
修改 Count.vue 和 Text.vue
Count.vue
<template>
<div class="count">
<h2>Sum: {{ countStore.sum }}</h2>
<select v-model.number="number">
<option value="1" selected>1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="add">Add</button>
<button @click="sub">Sub</button>
</div>
</template> <script setup lang="ts" name="Count">
import { ref } from 'vue'
import { useCountStore } from '@/store/count' const countStore = useCountStore() let number = ref(1) function add() {}
function sub() {}
</script>
Text.vue
<template>
<div class="text">
<button @click="getText">Get Text</button>
<ul>
<li v-for="text in textStore.textList" :key="text.id">
{{ text.content }}
</li>
</ul>
</div>
</template> <script setup lang="ts" name="Text">
import { useTextStore } from '@/store/text' const textStore = useTextStore() async function getText() {}
</script>
(5)修改数据
方式一:直接手动修改
修改 Count.vue
<template>
<div class="count">
<!-- ... -->
<br />
<button @click="change">Change</button>
</div>
</template> <script setup lang="ts" name="Count">
// ...
import { useCountStore } from '@/store/count' const countStore = useCountStore()
// ...
function change() {
countStore.sum = 100
}
</script>
方式二:手动批量修改
修改 Count.vue
<script setup lang="ts" name="Count">
// ...
function change() {
countStore.$patch({
sum: 100,
num: 1
})
}
</script>
方式三:使用
actions修改修改 count.ts
import { defineStore } from 'pinia' export const useCountStore = defineStore('count', {
// ...
actions: {
increment(value:number) {
if(this.sum < 20) {
this.sum += value
}
}
}
})
修改 Count.vue
<script setup lang="ts" name="Count">
// ... function add() {
countStore.increment(number.value)
}
// ...
</script>
(6)storeToRefs
对于从 store 中获得的数据,可以按以下方法在模板中展示
<template>
<div class="count">
<h2>Sum: {{ countStore.sum }}</h2>
<!-- ... -->
</template> <script setup lang="ts" name="Count">
// ...
import { useCountStore } from '@/store/count'
const countStore = useCountStore()
</script>
为使模板更加简洁,可以使用
toRefs将sum从 store 中解构出来<template>
<div class="count">
<h2>Sum: {{ sum }}</h2>
<!-- ... -->
</template> <script setup lang="ts" name="Count">
// ...
import { toRefs } from 'vue'
const { sum } = toRefs(countStore)
</script>
直接使用
toRefs会将 store 中携带的方法也变为响应式数据,因此可以使用 pinia 提供的storeToRefs<script setup lang="ts" name="Count">
// ...
import { storeToRefs } from 'pinia'
const { sum } = storeToRefs(countStore)
</script>
(7)getters
- 在
state中的数据需要经过处理后再使用时,可以使用getters配置
修改 store/count.ts
export const useCountStore = defineStore('count', {
state() {
return {
sum: 10
}
},
getters: {
bigSum(state) {
return state.sum * 100
}
},
// ...
})
使用
this可以替代参数state的传入getters: {
bigSum(): number {
return this.sum * 100
}
},
如果不使用
this则可以使用箭头函数getters: {
bigSum: state => state.sum * 100
},
修改 Count.vue,使用
bigSum<template>
<div class="count">
<h2>Sum: {{ sum }}, BigSum: {{ bigSum }}</h2>
<!-- ... -->
</template> <script setup lang="ts" name="Count">
// ...
const { sum, bigSum } = storeToRefs(countStore)
</script>
(8)$subscribe
- 作用:订阅,监听
state及其变化
修改 Text.vue
<script setup lang="ts" name="Text">
// ...
textStore.$subscribe(() => {
console.log('state.textList changed')
})
</script>
此时,点击按钮后,在开发者工具中可以看到输出了“state.textList changed”
$subscribe中可以添加参数mutate:本次修改的数据state:当前的数据
<script setup lang="ts" name="Text">
// ...
textStore.$subscribe((mutate, state) => {
console.log(mutate, state)
})
</script>
使用
state,借助浏览器本地存储,实现数据变化后,不会在刷新后初始化修改 Text.vue
<script setup lang="ts" name="Text">
import { useTextStore } from '@/store/text' const textStore = useTextStore()
textStore.$subscribe((mutate, state) => {
localStorage.setItem('textList', JSON.stringify(state.textList))
}) function getText() {
textStore.getText()
}
</script>
修改 store/text.ts
import axios from 'axios'
import { defineStore } from 'pinia' export const useTextStore = defineStore('text', {
state() {
return {
textList: JSON.parse(localStorage.getItem('textList') as string) || []
}
},
actions: {
async getText() {
let { data:{result:{content}} } = await axios.get('https://api.oioweb.cn/api/common/OneDayEnglish')
this.textList.unshift({ id: Date.now().toString(), content})
console.log(content)
}
}
})
(9)store 组合式写法
之前的内容使用了类似 Vue2 语法的选项式写法
修改 Text.ts
import axios from 'axios'
import { defineStore } from 'pinia'
import { reactive } from 'vue' export const useTextStore = defineStore('text', () => {
const textList = reactive(JSON.parse(localStorage.getItem('textList') as string) || []) async function getText() {
let { data: { result: { content } } } = await axios.get('https://api.oioweb.cn/api/common/OneDayEnglish')
textList.unshift({ id: Date.now().toString(), content })
console.log(content)
} return {
textList,
getText
}
})
0x05 组件通信
(0)概述
Vue3 中移出了事件总线,可以使用
pubsub代替- Vuex 换成了 pinia
.sync优化到v-model中$listeners合并到$attrs中
重置 ~/src 目录结构
graph TB
src-->components & App.vue & main.tsParent.vue
<template>
<div class="parent">
<h2>Parent</h2>
<Child />
</div>
</template> <script setup lang="ts" name="Parent">
import Child from './Child.vue'
</script> <style scope>
.parent {
width: 50%;
padding: 20px;
background: #03deff;
border: 3px solid black;
}
</style>
Child.vue
<template>
<div class="child">
<h2>Child</h2>
</div>
</template> <script setup lang="ts" name="Child">
</script> <style scope>
.child {
width: 50%;
padding: 20px;
background: #fbff03;
border: 3px solid black;
}
</style>
App.vue
<template>
<Parent />
</template> <script setup lang="ts" name="App">
import Parent from './components/Parent.vue'
</script> <style scope>
</style>
main.ts
import { createApp } from 'vue'
import App from './App.vue' createApp(App).mount('#app')
共有 \(7+2\) 种通信方式
(1)props
a. 父组件向子组件传递数据
修改 Parent.vue,在控制器中创建数据,在模板标签中发送数据
<template>
<div class="parent">
<h2>Parent</h2>
<h4>Parent's number: {{ numberParent }}</h4>
<Child :number="numberParent" />
</div>
</template> <script setup lang="ts" name="Parent">
import Child from './Child.vue'
import { ref } from 'vue' let numberParent = ref(12345)
</script>
修改 Child.vue,接收数据
<template>
<div class="child">
<h2>Child</h2>
<h4>Child's number: {{ numberChild }}</h4>
<h4>Number from Parent: {{ number }}</h4>
</div>
</template> <script setup lang="ts" name="Child">
import { ref } from 'vue' let numberChild = ref(56789) defineProps(['number'])
</script>
b. 子组件向父组件传递数据
修改 Parent.vue,创建用于接收子组件发送数据的方法,将方法发送给子组件
<template>
<div class="parent">
<h2>Parent</h2>
<h4>Parent's number: {{ numberParent }}</h4>
<h4 v-show="number">Number from Child: {{ number }}</h4>
<Child :number="numberParent" :sendNumber="getNumber" />
</div>
</template> <script setup lang="ts" name="Parent">
import Child from './Child.vue'
import { ref } from 'vue' let numberParent = ref(12345)
let number = ref(null) function getNumber(value: Number) {
number.value = value
}
</script>
修改 Child.vue,接收父组件发送的方法,并添加按钮使用该方法
<template>
<div class="child">
<h2>Child</h2>
<h4>Child's number: {{ numberChild }}</h4>
<h4>Number from Parent: {{ number }}</h4>
<button @click="sendNumber(numberChild)">Send Child's number to Parent</button>
</div>
</template> <script setup lang="ts" name="Child">
import { ref } from 'vue' let numberChild = ref(56789) defineProps(['number', 'sendNumber'])
</script>
(2)自定义事件
获取事件,即使用
$event<template>
<div>
<button @click="getEvent($event)">Event</button>
</div>
</template> <script setup lang="ts">
function getEvent(event: Event) {
console.log(event)
}
</script>
修改 Child.vue,声明自定义事件
<template>
<div class="child">
<h2>Child</h2>
<h4>Child's number: {{ numberChild }}</h4>
<button @click="emit('custom-event', numberChild)">Send Child's number to Parent</button>
</div>
</template> <script setup lang="ts" name="Child">
import { ref } from 'vue' let numberChild = ref(12345) const emit = defineEmits(['custom-event'])
</script>
修改 Parent.vue,绑定自定义事件
<template>
<div class="parent">
<h2>Parent</h2>
<h4 v-show="number">Number from Child: {{ number }}</h4>
<Child @custom-event="getNumber" />
</div>
</template> <script setup lang="ts" name="Parent">
import Child from './Child.vue'
import { ref } from 'vue' let number = ref(null) function getNumber(value: Number) {
number.value = value
}
</script>
(3)mitt
a. 概述与准备
- 可以实现任意组件通信
- 与 pubsub、$bus 相类似,都是消息订阅与发布
- 接收方:提前绑定事件,即订阅消息
- 发送方:适时触发事件,即发布消息
使用命令
npm install -S mitt安装 mitt在 ~/src 目录下新建 utils 目录,其中新建 emitter.ts,引入、调用、暴露 mitt
import mitt from 'mitt' const emitter = mitt() export default emitter
emitter.all():获取所有绑定事件emitter.emit():触发指定事件emitter.off():解绑指定事件emitter.on():绑定指定事件
b. 基本使用方法
在 main.ts 中引入 emitter.ts
// ...
import emitter from './utils/emitter'
修改 utils/emitter.ts,绑定事件
import mitt from 'mitt' const emitter = mitt() emitter.on('event1', () => {
console.log('event1')
})
emitter.on('event2', () => {
console.log('event2')
}) export default emitter
触发事件
// ...
setInterval(() => {
emitter.emit('event1')
emitter.emit('event2')
}, 1000) export default emitter
解绑事件
// ...
setTimeout(() => {
emitter.off('event1')
console.log('event1 off')
}, 3000) export default emitter
解绑所有事件
setTimeout(() => {
emitter.all.clear()
console.log('All clear')
}, 3000)
c. 实际应用
目录结构:
graph TB
src-->components & utils & App.vue & main.ts
components-->Child1.vue & Child2.vue & Parent.vue
utils-->emitter.ts
Child1.vue(Child2.vue)
<template>
<div class="child">
<h2>Child1</h2>
<h4>Name: {{ name }}</h4>
</div>
</template> <script setup lang="ts" name="Child1">
import { ref } from 'vue' // Child1.vue
let name = ref('Alex')
// Child2.vue
// let name = ref('Bob')
</script> <style scope>
.child {
width: 50%;
padding: 20px;
background: #fbff03;
border: 3px solid black;
}
</style>
Parent.vue
<template>
<div class="parent">
<h2>Parent</h2>
<Child1 />
<br />
<Child2 />
</div>
</template> <script setup lang="ts" name="Parent">
import Child1 from './Child1.vue'
import Child2 from './Child2.vue'
</script> <style scope>
.parent {
width: 50%;
padding: 20px;
background: #03deff;
border: 3px solid black;
}
</style>
emitter.ts
import mitt from 'mitt' const emitter = mitt() export default emitter
修改 Child2.vue,绑定事件
<template>
<div class="child">
<!-- ... -->
<h4 v-show="brotherName">Brother's name: {{ brotherName }}</h4>
</div>
</template> <script setup lang="ts" name="Child2">
// ...
import emitter from '@/utils/emitter' let brotherName = ref('') emitter.on('send-name', (value: string) => {
brotherName.value = value
})
</script>
修改 Child1.vue,发送数据
<template>
<div class="child">
<!-- ... -->
<button @click="emitter.emit('send-name', name)">Send Name</button>
</div>
</template> <script setup lang="ts" name="Child1">
// ...
import emitter from '@/utils/emitter'
</script>
修改 Child2.vue,卸载组件时解绑事件
<script setup lang="ts" name="Child2">
// ...
import { ref, onUnmounted } from 'vue' onUnmounted(() => {
emitter.off('send-name')
})
</script>
(4)v-model
- 实际开发中不常用,常见于 UI 组件库
a. HTML 标签
修改 Parent.vue,将
v-model用在input标签上<template>
<div class="parent">
<h2>Parent</h2>
<input type="text" v-model="name" placeholder="Enter your name" />
</div>
</template> <script setup lang="ts" name="Parent">
import { ref } from 'vue' let name = ref("John")
</script> <style scope>
.parent {
width: 50%;
padding: 20px;
background: #03deff;
border: 3px solid black;
}
</style>
在
input标签中使用v-model相当于:<input type="text" :value="name" @input="name=(<HTMLInputElement>$event.target).value" />
(<HTMLInputElement>$event.target):TypeScript 断言检查
b. 组件标签
在 components 中新建 CustomInput.vue
修改 Parent.vue
<template>
<div class="parent">
<h2>Parent</h2>
<CustomInput :modelValue="name" @update:modelValue="name = $event" />
</div>
</template> <script setup lang="ts" name="Parent">
import { ref } from 'vue'
import CustomInput from './CustomInput.vue' let name = ref("John")
</script>
修改 CustomInput.vue
<template>
<input
type="text"
:value="modelValue"
@input="emit('update:modelValue', (<HTMLInputElement>$event.target).value)"
/>
</template> <script setup lang="ts" name="CustomInput">
defineProps(["modelValue"])
const emit = defineEmits(["update:modelValue"])
</script> <style scope>
</style>
- 对于
$event的target判定:- 当触发的是原生事件时,
$event是事件对象,需要.target - 当触发的是自定义事件时,
$event是触发事件数据,不需要.target
- 当触发的是原生事件时,
- 对于
修改 Parent.vue,使用
v-model<template>
<div class="parent">
<h2>Parent</h2>
<!-- <CustomInput :modelValue="name" @update:modelValue="name = $event" /> -->
<CustomInput v-model="name" />
</div>
</template>
此时,可以通过设置
v-model,实现修改modelValue以及添加多个v-model修改 Parent.vue
<template>
<div class="parent">
<!-- ... -->
<CustomInput v-model:m1="name1" v-model:m2="name2" />
</div>
</template> <script setup lang="ts" name="Parent">
// ...
let name1 = ref("John")
let name2 = ref("Mary")
</script>
修改 CustomInput.vue
<template>
<input
type="text"
:value="m1"
@input="emit('update:m1', (<HTMLInputElement>$event.target).value)"
/> <input
type="text"
:value="m2"
@input="emit('update:m2', (<HTMLInputElement>$event.target).value)"
/>
</template> <script setup lang="ts" name="CustomInput">
defineProps(["m1", "m2"])
const emit = defineEmits(["update:m1", "update:m2"])
</script>
(5)$attrs
$attrs是一个对象,包含所有父组件传入的标签属性,用于实现祖父组件向孙子组件通信
目录结构:
graph TB
components-->Grand.vue & Parent.vue & Child.vue
修改 Grand.vue,创建数据,并通过 props 发送
<template>
<div class="grand">
<h2>Grand</h2>
<h4>Number: {{ number }}</h4>
<Parent :number="number" />
</div>
</template> <script setup lang="ts" name="Grand">
import Parent from './Parent.vue'
import { ref } from 'vue' let number = ref(123)
</script>
修改 Parent.vue,使用
$attrs将来自 Grand.vue 的数据以 props 的方式转发给 Child.vue<template>
<div class="parent">
<h2>Parent</h2>
<Child v-bind="$attrs"/>
</div>
</template> <script setup lang="ts" name="Parent">
import Child from './Child.vue'
</script>
修改 Child.vue,接收数据并展示
<template>
<div class="child">
<h2>Child</h2>
<h4>Number from Grand: {{ number }}</h4>
</div>
</template> <script setup lang="ts" name="Child">
defineProps(['number'])
</script>
此时,祖父组件 Grand.vue 也可以向孙子组件 Child.vue 传递方法,从而可以在孙子组件中修改祖父组件的数据
(6)$refs & $parent
父子组件通信
属性 作用 说明 $refs父组件向子组件通信 值
为
对
象包含所有被 ref属性标识的 DOM 元素或组件实例$parent子组件向父组件通信 值为对象,当前组件的父组件实例对象
目录结构:
graph TB
components-->Parent.vue & Child1.vue & Child2.vue
a. $refs
修改 Child1.vue 和 Child2.vue,创建数据并允许访问
<template>
<div class="child">
<h2>Child1</h2>
<!--
Child2.vue
<h2>Child2</h2>
-->
<h4>Number: {{ number }}</h4>
</div>
</template> <script setup lang="ts" name="Child1">
import { ref } from 'vue' let number = ref(456)
// Child2.vue
// let number = ref(789) defineExpose({ number })
</script>
修改 Parent.vue,设置按钮使其能够修改子组件中的
number<template>
<div class="parent">
<h2>Parent</h2>
<button @click="changeNumber">Change Child1's number</button>
<hr />
<Child1 ref="c1" />
<br />
<Child2 ref="c2" />
</div>
</template> <script setup lang="ts" name="Parent">
import Child1 from './Child1.vue'
import Child2 from './Child2.vue'
import { ref } from 'vue' let c1 = ref()
let c2 = ref() function changeNumber() {
c1.value.number = 123
c2.value.number = 123
}
</script>
使用
$refs,使其能够批量修改<template>
<div class="parent">
<!-- ... -->
<button @click="getAll($refs)">Get All Child's number</button>
<!-- ... -->
</div>
</template> <script setup lang="ts" name="Parent">
// ...
function getAll(refs: any) {
for (let key in refs) {
refs[key].number = 123
}
}
</script>
b. $parent
修改 Parent.vue,创建数据并允许访问
<template>
<div class="parent">
<h2>Parent</h2>
<h4>Number: {{ number }}</h4>
<hr />
<Child1 />
<br />
<Child2 />
</div>
</template> <script setup lang="ts" name="Parent">
import Child1 from './Child1.vue'
import Child2 from './Child2.vue'
import { ref } from 'vue' let number = ref(123) defineExpose({ number })
</script>
修改 Child1.vue
<template>
<div class="child">
<h2>Child1</h2>
<button @click="changeNumber($parent)">Change Number</button>
</div>
</template> <script setup lang="ts" name="Child1">
function changeNumber(parent: any) {
parent.number = 456
}
</script>
(7)provide & inject
祖孙组件通信
使用方法:
graph LR
A(祖父组件)--provide 发送数据-->B(孙子组件)--inject 接收数据-->A
目录结构:
graph TB
components-->Grand.vue & Parent.vue & Child.vue
修改 Grand.vue,创建数据
<template>
<div class="grand">
<h2>Grand</h2>
<h4>Number: {{ number }}</h4>
<Parent />
</div>
</template> <script setup lang="ts" name="Grand">
import Parent from './Parent.vue'
import { ref } from 'vue' let number = ref(123)
</script>
使用
provide发送数据<script setup lang="ts" name="Grand">
// ...
import { ref, provide } from 'vue' provide('number', number)
</script>
修改 Child.vue,接收并展示数据
<template>
<div class="child">
<h2>Child</h2>
<h4>Number from Grand: {{ number }}</h4>
</div>
</template> <script setup lang="ts" name="Child">
import { inject } from 'vue'
let number = inject('number')
</script>
当
inject的数据不存在时,可以展示默认值<template>
<div class="child">
<h2>Child</h2>
<h4>Number from Grand: {{ number }}</h4>
<h4>Number from Parent: {{ numberP }}</h4>
</div>
</template> <script setup lang="ts" name="Child">
import { inject } from 'vue'
let number = inject('number')
let numberP = inject('numberP', 456)
</script>
修改 Grand.vue,提供方法用于修改祖父组件中的数据
<script setup lang="ts" name="Grand">
// ...
function changeNumber() {
number.value = 456
} provide('number', {
number, changeNumber
})
</script>
修改 Child.vue,接收方法并使用按钮触发
<template>
<div class="child">
<h2>Child</h2>
<h4>Number from Grand: {{ number }}</h4>
<button @click="changeNumber">Change Number</button>
</div>
</template> <script setup lang="ts" name="Child">
import { inject } from 'vue'
let { number, changeNumber } = inject('number')
</script>
(8)pinia
参考第四章内容
(9)slot
- slot 翻译为“插槽”,分为默认插槽、具名插槽、作用域插槽
a. 默认插槽
修改 Parent.vue,创建数据
<template>
<div class="parent">
<h2>Parent</h2>
<div class="category">
<Child title="C1"></Child>
<Child title="C2"></Child>
<Child title="C3"></Child>
</div>
</div>
</template> <script setup lang="ts" name="Parent">
import Child from './Child.vue'
import { reactive } from 'vue' let c1List = reactive([
{ id: "01", Text: "C1-Text1" },
{ id: "02", Text: "C1-Text2" },
{ id: "03", Text: "C1-Text3" }
])
let c2List = reactive([
{ id: "01", Text: "C2-Text1" },
{ id: "02", Text: "C2-Text2" },
{ id: "03", Text: "C2-Text3" }
])
let c3List = reactive([
{ id: "01", Text: "C3-Text1" },
{ id: "02", Text: "C3-Text2" },
{ id: "03", Text: "C3-Text3" }
])
</script> <style scope>
.parent {
width: 80%;
padding: 20px;
background: #03deff;
border: 3px solid black;
}
.category {
display: flex;
justify-content: space-around;
}
.category div {
margin: 10px;
}
</style>
修改 Child.vue,接收数据并展示
<template>
<div class="child">
<h2>{{ title }}</h2>
</div>
</template> <script setup lang="ts" name="Child">
defineProps(['title'])
</script> <style scope>
.child {
width: 50%;
padding: 20px;
background: #fbff03;
border: 3px solid black;
}
</style>
修改 Parent.vue 中的模板内容
<template>
<div class="parent">
<h2>Parent</h2>
<div class="category">
<Child title="C1">
<ul>
<li v-for="item in c1List" :key="item.id">{{ item.text }}</li>
</ul>
</Child>
<Child title="C2">
<ul>
<li v-for="item in c2List" :key="item.id">{{ item.text }}</li>
</ul>
</Child>
<Child title="C3">
<!-- <ul>
<li v-for="item in c3List" :key="item.id">{{ item.text }}</li>
</ul> -->
</Child>
</div>
</div>
</template>
修改 Child.vue,引入插槽并设置默认值
<template>
<div class="child">
<h2>{{ title }}</h2>
<slot>Default</slot>
</div>
</template>
此时,C1 和 C2 的内容均正常展示;C3 的内容无法展示,而是显示默认值
b. 具名插槽
具有名字的插槽
修改 Child.vue,为插槽添加名字
<template>
<div class="child">
<h2>{{ title }}</h2>
<slot name="list">Default</slot>
</div>
</template>
修改 Paren.vue,为引用的组件标签添加
v-slot:属性使用
#简便写法可以替代v-slot:写法<template>
<div class="parent">
<h2>Parent</h2>
<div class="category">
<Child title="C1" #list>
<ul>
<li v-for="item in c1List" :key="item.id">{{ item.text }}</li>
</ul>
</Child>
<Child title="C2" v-slot:List>
<ul>
<li v-for="item in c2List" :key="item.id">{{ item.text }}</li>
</ul>
</Child>
<Child title="C3">
<ul>
<li v-for="item in c3List" :key="item.id">{{ item.text }}</li>
</ul>
</Child>
</div>
</div>
</template>
此时,C1 的内容正常展示;C2 和 C3 的内容均无法展示,而是显示默认值
修改 Child.vue,设置更多的具名插槽,使
title通过插槽进行传递<template>
<div class="child">
<slot name="title"><h2>No Title</h2></slot>
<slot name="slot">Default</slot>
</div>
</template> <script setup lang="ts" name="Child">
</script>
修改 Parent.vue,使用
template标签<template>
<div class="parent">
<h2>Parent</h2>
<div class="category">
<Child>
<template v-slot:title>
<h2>C1</h2>
</template>
<template v-slot:list>
<ul>
<li v-for="item in c1List" :key="item.id">{{ item.text }}</li>
</ul>
</template>
</Child>
<Child>
<template v-slot:list>
<ul>
<li v-for="item in c2List" :key="item.id">{{ item.text }}</li>
</ul>
</template>
<template v-slot:title>
<h2>C2</h2>
</template>
</Child>
<Child>
<template v-slot:list>
<ul>
<li v-for="item in c3List" :key="item.id">{{ item.text }}</li>
</ul>
<h2>C3</h2>
</template>
</Child>
</div>
</div>
</template>
此时,C1 和 C2 的内容均正常展示;C3 的内容无法展示,而是显示默认值
c. 作用域插槽
修改 Child.vue,原始数据在子组件中,使用 slot 将数据传递到父组件中
<template>
<div class="child">
<slot :cl="cList">Default</slot>
</div>
</template> <script setup lang="ts" name="Child">
import { reactive } from 'vue' let cList = reactive([
{ id: "01", text: "C-Text1" },
{ id: "02", text: "C-Text2" },
{ id: "03", text: "C-Text3" }
])
</script>
修改 Parent.vue,接收并使用子组件传递来的数据
<template>
<div class="parent">
<h2>Parent</h2>
<div class="category">
<Child>
<template v-slot="params"> <!-- 接收传递来的数据 -->
<ul>
<li v-for="item in params.cl" :key="item.id">{{ item.text }}</li>
</ul>
</template>
</Child>
<Child>
<template v-slot="{ cl }"> <!-- 解构传递来的数据 -->
<ol>
<li v-for="item in cl" :key="item.id">{{ item.text }}</li>
</ol>
</template>
</Child>
<Child>
<template v-slot:default="{ cl }"> <!-- 具名作用域插槽(default是插槽默认名) -->
<h4 v-for="item in cl" :key="item.id">{{ item.text }}</h4>
</template>
</Child>
</div>
</div>
</template> <script setup lang="ts" name="Parent">
import Child from './Child.vue'
</script>
(10)总结
| 组件关系 | 通信方式 |
|---|---|
| 父传子 | props |
v-model |
|
$refs |
|
| 默认插槽 / 具名插槽 | |
| 子传父 | props |
| 自定义事件 | |
v-model |
|
$parent |
|
| 作用域插槽 | |
| 祖孙互传 | $attrs |
provide & inject |
|
| 任意组件 | mitt |
| pinia |
0x06 其他 API
- 以下 API 使用场景不多,但需要了解
(1)shallowRef & shallowReactive
- 两种方法一般用于绕开深度响应,提高性能,加快访问速度
a. shallowRef
作用:创建一个响应式数据,只对顶层属性进行响应式处理
特点:只跟踪引用值的变化,不跟踪值内部的属性的变化
用法
修改 App.vue,创建数据与方法,并在模板中展示
<template>
<div class="app">
<h2>Name: {{ person.name }}</h2>
<h2>Age: {{ person.age }}</h2>
<p><button @click="changerName">Change Name</button></p>
<p><button @click="changerAge">Change Age</button></p>
<p><button @click="changerAll">Change All</button></p>
</div>
</template> <script setup lang="ts" name="App">
let person = {
name: 'John',
age: 18
} function changerName() {
person.value.name = 'Mary'
}
function changerAge() {
person.value.age = 19
}
function changerAll() {
person.value = {
name: 'Mary',
age: 19
}
}
</script>
引入
shallowRef并使用<script setup lang="ts" name="App">
import { shallowRef } from 'vue' let person = shallowRef({
name: 'John',
age: 18
})
// ...
</script>
此时,姓名和年龄均不可修改,而所有信息可以通过自定义的
changeAll()方法修改
b. shallowReactive
作用:创建一个浅层响应式对象,只将对象的最顶层属性变成响应式,其他属性不变
特点:对象的顶层属性是响应式的,嵌套对象的属性不是
用法
修改 App.vue,创建数据与方法,并在模板中展示
<template>
<div class="app">
<h2>Name: {{ person.name }}</h2>
<h2>Age: {{ person.detail.age }}</h2>
<p><button @click="changerName">Change Name</button></p>
<p><button @click="changerAge">Change Age</button></p>
<p><button @click="changerAll">Change All</button></p>
</div>
</template> <script setup lang="ts" name="App">
let person = {
name: 'John',
detail: {
age: 18
}
} function changerName() {
person.name = 'Mary'
}
function changerAge() {
person.detail.age = 19
}
function changerAll() {
Object.assign(person, {
name: 'Mary',
detail: {
age: 19
}
})
}
</script>
引入
shallowReactive并使用<script setup lang="ts" name="App">
import { shallowReactive } from 'vue' let person = shallowReactive({
name: 'John',
detail: {
age: 18
}
})
// ...
</script>
此时,姓名可以修改,但年龄不可修改,而所有信息可以通过自定义的
changeAll()方法修改
(2)readonly & shallowReadonly
a. readonly
作用:创建一个对象的深只读副本
特点:
- 对象的所有嵌套属性变为只读
- 阻止对象被修改
应用场景:
- 创建不可变的状态快照
- 保护全局状态/配置不可修改
用法
修改 App.vue,创建数据
<template>
<div class="app">
<h2>Sum: {{ sum1 }}</h2>
<h2>Sum readonly: {{ sum2 }}</h2>
<p><button @click="changeSum1">Change Sum</button></p>
<p><button @click="changeSum2">Change Sum readonly</button></p>
</div>
</template> <script setup lang="ts" name="App">
import { ref } from 'vue' let sum1 = ref(0)
let sum2 = ref(0) function changeSum1() {
sum1.value += 1
}
function changeSum2() {
sum2.value += 1
}
</script>
引入
readonly并使用<script setup lang="ts" name="App">
import { ref, readonly } from 'vue'
// ...
let sum2 = readonly(sum1)
// ...
</script>
此时,
sum2会随着sum1改变而改变,但点击按钮“Change Sum readonly”不会对sum修改
b. shallowReadonly
作用:创建一个对象的浅只读副本
特点:只将对象的顶层属性设置为只读,对象内部的嵌套属性依旧可读可写
应用场景:仅需对对象顶层属性保护时使用
用法
修改 App.vue,创建数据
<template>
<div class="app">
<h2>Name: {{ personRO.name }}</h2>
<h2>Age: {{ personRO.detail.age }}</h2>
<p><button @click="changerName">Change Name</button></p>
<p><button @click="changerAge">Change Age</button></p>
<p><button @click="changerAll">Change All</button></p>
</div>
</template> <script setup lang="ts" name="App">
import { reactive } from 'vue' let person = reactive({
name: 'John',
detail: {
age: 18
}
})
let personRO = person function changerName() {
personRO.name = 'Mary'
}
function changerAge() {
personRO.detail.age = 19
}
function changerAll() {
Object.assign(personRO, {
name: 'Mary',
detail: {
age: 19
}
})
}
</script>
引入
shallowReadonly并使用<script setup lang="ts" name="App">
import { reactive, shallowReadonly } from 'vue'
// ...
let personRO = shallowReadonly(person)
// ...
</script>
此时,姓名不可以修改,年龄可以修改,所有信息可以通过自定义的
changeAll()方法修改
(3)toRaw & markRaw
a. toRaw
作用:获取一个响应式对象的原始对象
特点:返回的对象不是响应式的,不会触发视图更新
应用场景:将响应式对象传递到非 Vue 的库或外部系统时使用
用法
修改 App.vue,创建数据
<script setup lang="ts" name="App">
import { reactive } from 'vue' let person = reactive({
name: 'John',
age: 18
}) console.log(person)
</script>
引入
toRaw并使用<script setup lang="ts" name="App">
import { reactive, toRaw } from 'vue' let person = reactive({
name: 'John',
age: 18
}) console.log(person)
console.log(toRaw(person))
</script>
b. markRaw
作用:标记一个对象,使其永远不会变成响应式对象
应用场景:例如使用 Mock.js 插件时,为防止误把
mockjs变成响应式对象而使用用法
修改 App.vue,创建数据
<script setup lang="ts" name="App">
let person = {
name: 'John',
age: 18
}
</script>
引入
markRaw并使用<script setup lang="ts" name="App">
import { reactive, markRaw } from 'vue' let person = markRaw({
name: 'John',
age: 18
}) person = reactive(person)
console.log(person)
</script>
此时,
person并未变成响应式对象
(4)customRef
作用:创建一个自定义的
ref,并对其依赖项跟踪和更新触发进行逻辑控制用法
修改 App.vue,引入
customRef<template>
<div class="app">
<h2>Message: {{ msg }}</h2>
<input type="text" v-model="msg" />
</div>
</template> <script setup lang="ts" name="App">
import { customRef } from 'vue' let msg = customRef(() => {
return {
get() {},
set() {}
}
})
</script>
其中,
get()在变量msg被读取时调用;set()在变量msg被修改时调用声明新变量
initMsg作为默认值在get()中返回<script setup lang="ts" name="App">
import { customRef } from 'vue' let initMsg = "Default"
let msg = customRef(() => {
return {
get() {
return initMsg
},
set() {}
}
})
</script>
对
msg的修改结果通过set()接收<script setup lang="ts" name="App">
// ...
let msg = customRef(() => {
return {
// ...
set(value) {
console.log('set', value)
}
}
})
</script>
在
set()中修改initMsg<script setup lang="ts" name="App">
// ...
let msg = customRef(() => {
return {
// ...
set(value) {
// console.log('set', value)
initMsg = value
}
}
})
</script>
【核心内容】在
customRef的回调函数中,接收两个参数:track(跟踪)、trigger(触发)<script setup lang="ts" name="App">
// ...
let msg = customRef((track, trigger) => {
return {
get() {
track()
return initMsg
},
set(value) {
initMsg = value
trigger()
}
}
})
</script>
track():依赖项跟踪,告知 Vue 需要对变量msg持续关注,一旦变化立即更新trigger():更新触发,告知 Vue 变量msg发生了变化
可以在
set()通过设置延时函数实现指定时间后触发更新<script setup lang="ts" name="App">
// ...
let msg = customRef((track, trigger) => {
return {
// ...
set(value) {
setTimeout(() => {
initMsg = value
trigger()
}, 1000)
}
}
})
</script>
但是当输入过快时,会导致输入的数据被覆盖丢失,此时引入防抖方法
<script setup lang="ts" name="App">
// ...
let timer: number
let msg = customRef((track, trigger) => {
return {
// ...
set(value) {
clearTimeout(timer)
timer = setTimeout(() => {
initMsg = value
trigger()
}, 1000)
}
}
})
</script>
防抖原理:清除上一次
set()中生产的定时器,并以最新定时器为准在 src 目录下新建 hooks 目录,其中新建 useMsgRef.ts,用于将上述自定义 ref 封装成 Hooks
import { customRef } from 'vue' export default function(initMsg: string, delaySecond: number) {
let timer: number
let msg = customRef((track, trigger) => {
return {
get() {
track()
return initMsg
},
set(value) {
clearTimeout(timer)
timer = setTimeout(() => {
initMsg = value
trigger()
}, delaySecond * 1000)
}
}
})
return { msg }
}
修改 App.vue,使用上述 Hooks
<script setup lang="ts" name="App">
import useMsgRef from '@/hooks/useMsgRef' let { msg } = useMsgRef('Default', 1)
</script>
0x07 新组件
(1)Teleport
是一种能够将组件 HTML 结构移动到指定位置的技术
举例:在页面上封装一个弹窗组件
目录结构:
graph TB
src-->components & App.vue & main.ts
components-->ModelDialog.vue修改 App.vue
<template>
<div class="outer">
<h2>App Component</h2>
<ModelDialog />
</div>
</template> <script setup lang="ts" name="App">
import ModelDialog from './components/ModelDialog.vue'
</script> <style scope>
.outer {
width: 80%;
height: 500px;
padding: 20px;
background: #03deff;
}
</style>
修改 ModelDialog.vue
<template>
<button>Show Dialog</button>
<div class="md">
<h2>Dialog Title</h2>
<p>Content Here</p>
<button>Close</button>
</div>
</template> <script setup lang="ts" name="ModelDialog">
</script> <style scope>
.md {
text-align: center;
width: 50%;
height: 30%;
background: #fbff03;
border: 3px solid black;
}
</style>
设置弹窗的显示与隐藏
<template>
<button @click="isShowDialog = true">Show Dialog</button>
<div class="md" v-show="isShowDialog">
<h2>Dialog Title</h2>
<p>Content Here</p>
<button @click="isShowDialog = false">Close</button>
</div>
</template> <script setup lang="ts" name="ModelDialog">
import { ref } from 'vue'
let isShowDialog = ref(false)
</script>
通过 CSS 调整弹窗位于视口正中央
<style scope>
.md {
// ...
position: fixed;
top: 5%;
left: 50%;
margin-left: -100px;
}
</style>
修改 App.vue,通过 CSS 设置变灰
<style scope>
.outer {
// ...
filter: saturate(0%);
}
</style>
此时,弹窗并未按照第 4 步的设置显示在视口正中央,可以通过 Teleport 解决此问题
修改 ModelDialog.vue
<template>
<button @click="isShowDialog = true">Show Dialog</button>
<Teleport to="body">
<div class="md" v-show="isShowDialog">
<h2>Dialog Title</h2>
<p>Content Here</p>
<button @click="isShowDialog = false">Close</button>
</div>
</Teleport>
</template>
to属性用于指定移动的目的地,上述代码将弹窗内容移动到body标签中此时,网页渲染后的 DOM 结构如下:
<!-- ... -->
<body>
<div id="app" data-v-app><!-- ... --></div>
<!-- ... -->
<div id="md"><!-- ... --></div>
</body>
(2)Suspense
- 等待异步组件时,渲染一些额外的内容,改善用户体验
目录结构:
graph TB
src-->components & App.vue & main.ts
components-->Child.vue
修改 App.vue
<template>
<div class="app">
<h2>App</h2>
<Child />
</div>
</template> <script setup lang="ts" name="App">
import Child from './components/Child.vue'
</script> <style scope>
.app {
width: 80%;
height: 500px;
padding: 20px;
background: #03deff;
}
</style>
修改 Child.vue
<template>
<div class="child"></div>
</template> <script setup lang="ts" name="Child">
</script> <style scope>
.child {
width: 200px;
height: 150px;
background: #fbff03;
}
</style>
添加异步任务
<script setup lang="ts" name="Child">
import axios from 'axios' let { data: { result: { content } } } = await axios.get("https://api.oioweb.cn/api/common/OneDayEnglish") console.log(content)
</script>
此时,子组件会在页面上“消失”,可以在 App.vue 中使用 Suspense 解决此问题
修改 App.vue,引入 Suspense 并使用
<template>
<div class="app">
<h2>App</h2>
<Suspense>
<template #default>
<Child />
</template>
</Suspense>
</div>
</template> <script setup lang="ts" name="App">
import Child from './components/Child.vue'
import { Suspense } from 'vue'
</script>
- Suspense 中预设了两个插槽,
default插槽用于展示异步完成的内容,fallback插槽用于展示异步进行中的内容 - 当网络状态不是很好时,子组件不会立即渲染完成,因此可以借助 Suspense 添加加载提示
- Suspense 中预设了两个插槽,
设置加载内容:Loading...
<template>
<div class="app">
<h2>App</h2>
<Suspense>
<template #default>
<Child />
</template>
<template #fallback>
<h2>Loading...</h2>
</template>
</Suspense>
</div>
</template>
(3)全局 API 转移到应用对象
graph LR
A(Vue2 全局API<br/>Vue.xxx) --> B(Vue3 应用对象API<br/>app.xxx)
app.component
修改 main.ts,注册全局组件
import { createApp } from 'vue'
import App from './App.vue'
import Child from './components/Child.vue' const app = createApp(App)
app.component('Child', Child)
app.mount('#app')
修改 App.vue,使用全局组件
<template>
<div class="app">
<h2>App</h2>
<Child />
</div>
</template> <script setup lang="ts" name="App">
</script>
app.config
修改 main.ts,注册全局属性
import { createApp } from 'vue'
import App from './App.vue' const app = createApp(App)
app.config.globalProperties.x = 12345 declare module 'vue' {
interface ComponentCustomProperties {
x: number;
}
} app.mount('#app')
修改 App.vue,使用全局属性
<template>
<div class="app">
<h2>App</h2>
<h4>{{ x }}</h4>
</div>
</template> <script setup lang="ts" name="App">
</script>
实际开发中,不建议该使用方法,容易污染全局
app.directive
修改 main.ts,注册全局指令
import { createApp } from 'vue'
import App from './App.vue' const app = createApp(App)
app.directive('custom', (element, {value}) => {
element.innerText += value
element.style.fontSize = '50px';
})
app.mount('#app')
修改 App.vue,使用全局指令
<template>
<div class="app">
<h2>App</h2>
<p v-custom="value">Hello,</p>
</div>
</template> <script setup lang="ts" name="App">
let value = "world!"
</script>
app.mount
在 main.ts 中挂载应用
import { createApp } from 'vue'
import App from './App.vue' const app = createApp(App)
app.mount('#app')
app.unmount
在 main.ts 中卸载应用
import { createApp } from 'vue'
import App from './App.vue' const app = createApp(App)
app.mount('#app') setTimeout(() => {
app.unmount()
}, 3000)
app.use
在 main.ts 中安装插件,如 pinia
import { createApp } from 'vue'
import App from './App.vue'
import { PiniaVuePlugin } from 'pinia' const app = createApp(App)
app.use(PiniaVuePlugin)
app.mount('#app')
(4)非兼容性改变
详见官方文档
-End-
Vue3 + TypeScript 开发指南的更多相关文章
- Vue3 + TypeScript 开发实践总结
前言 迟来的Vue3文章,其实早在今年3月份时就把Vue3过了一遍.在去年年末又把 TypeScript 重新学了一遍,为了上 Vue3 的车,更好的开车.在上家公司4月份时,上级领导分配了一个内部的 ...
- 《Vue3.x+TypeScript实践指南》已出版
转眼回长沙快2年了,图书本在去年就已经完稿,因为疫情,一直耽搁了,直到这个月才出版!疫情之下,众生皆苦!感觉每天都是吃饭.睡觉.上班.做核酸! 图书介绍 为了紧跟技术潮流,该书聚焦于当下火的Vue3和 ...
- TypeScript学习指南--目录索引
关于TypeScript: TypeScript是一种由微软开发的自由和开源的编程语言.它是JavaScript的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程. TypeS ...
- TypeScript入门指南(JavaScript的超集)
TypeScript入门指南(JavaScript的超集) 你是否听过 TypeScript? TypeScript 是 JavaScript 的超集,TypeScript结合了类型检查和静态分析 ...
- [转帖]2019 简易Web开发指南
2019 简易Web开发指南 2019年即将到来,各位同学2018年辛苦了. 不管大家2018年过的怎么样,2019年还是要继续加油的! 在此我整理了个人认为在2019仍是或者将成为主流的技术 ...
- 现代前端库开发指南系列(二):使用 webpack 构建一个库
前言 在前文中,我说过本系列文章的受众是在现代前端体系下能够熟练编写业务代码的同学,因此本文在介绍 webpack 配置时,仅提及构建一个库所特有的配置,其余配置请参考 webpack 官方文档. 输 ...
- 2019 Vue开发指南:你都需要学点啥?
转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者.原文出处:https://dzone.com/articles/vue-development-in-2019 ...
- Vite ❤ Electron——基于Vite搭建Electron+Vue3的开发环境【一】
背景 目前社区两大Vue+Electron的脚手架:electron-vue和vue-cli-plugin-electron-builder, 都有这样那样的问题,且都还不支持Vue3,然而Vue3已 ...
- 基于SqlSugar的开发框架循序渐进介绍(14)-- 基于Vue3+TypeScript的全局对象的注入和使用
刚完成一些前端项目的开发,腾出精力来总结一些前端开发的技术点,以及继续完善基于SqlSugar的开发框架循序渐进介绍的系列文章,本篇随笔主要介绍一下基于Vue3+TypeScript的全局对象的注入和 ...
- 基于SqlSugar的开发框架循序渐进介绍(22)-- Vue3+TypeScript的前端工作流模块中实现统一的表单编辑和表单详情查看处理
在工作流页面中,除了特定的业务表单信息外,往往也需要同时展示通用申请单的相关信息,因此在页面设计的时候需要使用一些组件化的概念来实现动态的内容展示处理,本篇随笔介绍Vue3+TypeScript+El ...
随机推荐
- ubuntu版本为16.04,英文改成中文解决方法和解决中文输入法无效的问题,关于无法打开锁文件的解决方法
https://jingyan.baidu.com/article/4853e1e565e1781908f7266c.html,根据这篇文章操作完成后重启ubuntu之后ubuntu就会变成中文,重启 ...
- css 布局整理2022-4
理解CSS3里的Flex布局用法(转自网上,博客园修改一些方便更易看懂) 简单有法: 几个横排元素在竖直方向上居中 display: flex; flex-direction: row;//横向排列 ...
- Java中枚举配合switch语句用法-2022新项目
一.业务场景 项目开发中经常会遇到多条件判断的情况,如果判断条件少的话使用if/elseif/else还比较好处理,如果判断条件多的话,则在使用这种语句就不太合适. 如果是自定义的一些内容,比如不同的 ...
- vscode 两种定位跳转的方法 ctrl+p 方法1 path:行号 方法2 #变量名 - 针对$store变量不好找的方案 方法1可以备注在代码里面
vscode 两种定位跳转的方法 ctrl+p 方法1 path:行号 方法2 #变量名 - 针对$store变量不好找的方案 方法1可以备注在代码里面 问题 $store的变量不能跳转,有跳转插件也 ...
- windows10 中为文件添加让自己可以使用查看、修改、运行的权限
在Win10中添加权限的方法 前一段时间重装了系统,然后,突然间就因为权限原因没法查看一些文件了.所以就想办法添加权限.尝试很多次后终于成功了,这篇文章记录一下如何为自己添加权限. 选中需要添加权限的 ...
- IP对讲广播音频模块解决方案
需求分析 随着数字化进程的不断推进,对讲已经覆盖到了各行业各业.并且也逐渐呈现出场景分散化的特点.鉴于此,团队根据市场的变化,及时推出了一款标准化的模块,方便系统集成厂商集成和运用,从而达到节省开 ...
- Android 自定义View模板代码记录
原文地址:Android 自定义View模板代码记录 - Stars-One的杂货小窝 每次写自定义View,需要重写3个构造方法,如果使用Android Studio直接创建,会导致View代码过多 ...
- dotNet8 全局异常处理
前言 异常的处理在我们应用程序中是至关重要的,在 dotNet 中有很多异常处理的机制,比如MVC的异常筛选器, 管道中间件定义try catch捕获异常处理亦或者第三方的解决方案Hellang.Mi ...
- 【Leetcode 907 907. 子数组的最小值之和】【单调栈dp】
import java.util.LinkedList; class Solution { public int sumSubarrayMins(int[] arr) { int n = arr.le ...
- 3DCAT投屏功能升级,助力企业营销与培训
3DCAT实时渲染云推出以来,深受广大客户的喜爱,3DCAT也一直根据客户的反馈优化我们的产品. 但是这段时间来,不同行业的客户都反馈着同一个问题. 汽车销售顾问:"什么时候支持投屏功能呢, ...