给自己公司做网站运营,网页设计与网站开发什么区别,自己编程做网站,即墨市城乡建设局网站文章目录 学习链接1. Vue3简介1.1. 性能的提升1.2.源码的升级1.3. 拥抱TypeScript1.4. 新的特性 2. 创建Vue3工程2.1. 基于 vue-cli 创建2.2. 基于 vite 创建#xff08;推荐#xff09;vite介绍创建步骤项目结构安装插件项目结构总结 2.3. 一个简单的效果Person.vueApp.vue … 文章目录 学习链接1. Vue3简介1.1. 性能的提升1.2.源码的升级1.3. 拥抱TypeScript1.4. 新的特性 2. 创建Vue3工程2.1. 基于 vue-cli 创建2.2. 基于 vite 创建推荐vite介绍创建步骤项目结构安装插件项目结构总结 2.3. 一个简单的效果Person.vueApp.vue 3. Vue3核心语法3.1. OptionsAPI 与 CompositionAPIOptions API 的弊端Composition API 的优势 3.2. 拉开序幕的 setupsetup 概述setup 的返回值setup 与 Options API 的关系setup 语法糖 3.3. ref 创建基本类型的响应式数据3.4. reactive 创建对象类型的响应式数据3.5 ref 创建对象类型的响应式数据3.6. ref 对比 reactive宏观角度区别使用原则 3.7 toRefs 与 toRef现象toRefstoRef的使用 3.8 computed3.9 watch作用特点场景* 情况一* 情况二示例1示例2 * 情况三* 情况四没有监视的代码监视reactive定义的对象类型中的某个基本属性监视reactive定义的对象类型中的某个对象属性 * 情况五 3.10 watchEffect3.11. 标签的 ref 属性用在普通DOM标签上用在组件标签上defineExpose 3.12 回顾TSmain.tsApp.vueindex.tsPerson.vue 3.13 propsdefinePropsApp.vueindex.tsPerson.vue 3.14 生命周期App.vuePerson.vue 3.15 自定义hooks未使用hooks前App.vuePerson.vue 使用hooksApp.vuePerson.vuehooks/useSum.tshooks/useDog.ts 4.路由4.1 路由的基本理解4.2 基本切换效果安装vue-router配置路由规则router/index.ts使用router路由管理器main.ts路由展示区App.vue路由组件Home.vueNew.vueAbout.vue 路由切换效果图 4.3. 两个注意点About.vue 4.4. 路由器工作模式4.5. to的两种写法4.6. 命名路由4.7 嵌套路由main.tsrouter/index.tsApp.vueNews.vueDetail.vue效果 4.8 路由传参query参数params参数 4.9 路由的props配置4.10 replace属性示例 4.11 编程式导航示例 4.12 重定向示例 5. pinia5.1 准备一个效果main.tsApp.vueCount.vueLoveTalk.vue 5.2 搭建 pinia 环境使用步骤 5.3 存储读取数据store/count.tsstore/loveTalk.tsCount.vueLoveTalk.vueApp.vuemain.ts 5.4 修改数据(三种方式)第一种方式count.tsCount.vue 第二种方式count.tsCount.vue 第三种方式count.tsCount.vue 5.5 storeToRefs用法LoveTalk.tsLoveTask.vuecount.tsCount.vue 5.6 getters用法count.tsCount.vue 5.7 $subscribe的使用loveTalk.tsLoveTalk.vue 5.8 store组合式写法loveTalk.jsLoveTalk.vue 6. 组件通信6.1 propsFather.vueChild.vue 6.2 自定义事件Father.vueChild.vue 6.3 mittemitter.tsFather.vueChild1.vueChild2.vue 6.4 v-modelFather.vueAtguiguInput.vue 6.5 $attrsFather.vueChild.vueGrandChild.vue 6.6 r e f s 、 refs、 refs、parent、proxyFather.vueChild1.vueChild2.vue 6.7 provide、injectFather.vueChild.vueGrandChild.vue 6.8 pinia6.9 slot插槽1. 默认插槽Father.vueCategory.vue 2. 具名插槽Father.vueCategory.vue 3. 作用域插槽Father.vueCategory.vue 7. 其它 API7.1 shallowRef 与 shallowReactiveshallowRefshallowReactive示例 7.2 readonly 与 shallowReadonlyreadonlyshallowReadonly示例 7.3 toRaw 与 markRawtoRawmarkRaw示例 7.4 customRef示例App.vueuseMsgRef.ts 8. Vue3新组件8.1 Teleport传送门示例App.vueModal.vue 8.2 Suspense示例App.vueChild.vue 8.3 全局API转移到应用对象示例 8.4 其他 学习链接
尚硅谷Vue3入门到实战最新版vue3TypeScript前端开发教程
Vue3Vite4PiniaElementPlus从0-1 web项目搭建
Vue3.2后台管理系统
深入Vue3TypeScript技术栈 coderwhy
尚硅谷Vue项目实战硅谷甄选vue3项目TypeScript前端项目一套通关
基于Vue3最新标准实现后台前端综合解决方案 imooc-admin源码
Vue3 vite Ts pinia 实战 源码 electron - 百万播放量哦
1. Vue3简介 2020年9月18日Vue.js发布版3.0版本代号One Piece 经历了4800次提交、40个RFC、600次PR、300贡献者 官方发版地址Release v3.0.0 One Piece · vuejs/core 截止2023年10月最新的公开版本为3.3.4 1.1. 性能的提升 打包大小减少41%。 初次渲染快55%, 更新渲染快133%。 内存减少54%。
1.2.源码的升级 使用Proxy代替defineProperty实现响应式。 重写虚拟DOM的实现和Tree-Shaking。
1.3. 拥抱TypeScript
Vue3可以更好的支持TypeScript。
1.4. 新的特性 Composition API组合API setup ref与reactive computed与watch … 新的内置组件 Fragment Teleport Suspense … 其他改变 新的生命周期钩子 data 选项应始终被声明为一个函数 移除keyCode支持作为 v-on 的修饰符 …
2. 创建Vue3工程
2.1. 基于 vue-cli 创建
点击查看 Vue-Cli 官方文档基于vue-cli创建其实就是基于webpack来创建vue项目 备注目前vue-cli已处于维护模式官方推荐基于 Vite 创建项目。 ## 查看vue/cli版本确保vue/cli版本在4.5.0以上
vue --version## 安装或者升级你的vue/cli
npm install -g vue/cli## 执行创建命令
vue create vue_test## 随后选择3.x
## Choose a version of Vue.js that you want to start the project with (Use arrow keys)
## 3.x
## 2.x## 启动
cd vue_test
npm run serve2.2. 基于 vite 创建推荐
vite介绍
vite 是新一代前端构建工具官网地址https://vitejs.cnvite的优势如下
轻量快速的热重载HMR能实现极速的服务启动。对 TypeScript、JSX、CSS 等支持开箱即用不用配置直接就可以用。真正的按需编译不再等待整个应用编译完成。webpack构建 与 vite构建对比图如下
创建步骤
具体操作如下点击查看官方文档
## 1.创建命令基于vite创建vue3项目前提是需要安装nodejs环境
npm create vuelatest## 2.具体配置
## 配置项目名称
√ Project name: vue3_test
## 是否添加TypeScript支持
√ Add TypeScript? Yes
## 是否添加JSX支持
√ Add JSX Support? No
## 是否添加路由环境
√ Add Vue Router for Single Page Application development? No
## 是否添加pinia环境
√ Add Pinia for state management? No
## 是否添加单元测试
√ Add Vitest for Unit Testing? No
## 是否添加端到端测试方案
√ Add an End-to-End Testing Solution? » No
## 是否添加ESLint语法检查
√ Add ESLint for code quality? Yes
## 是否添加Prettiert代码格式化
√ Add Prettier for code formatting? No构建过程如下 访问vue3项目如下 项目结构
安装插件
安装官方推荐的vscode插件 项目结构 index.html
!DOCTYPE html
html langenheadmeta charsetUTF-8link relicon href/favicon.icometa nameviewport contentwidthdevice-width, initial-scale1.0titleVite App/title/headbodydiv idapp/divscript typemodule src/src/main.ts/script/body
/htmlmain.ts
import ./assets/main.css// 引入createApp用于创建应用
import { createApp } from vue// 引入App根组件
import App from ./App.vuecreateApp(App).mount(#app)App.vue
!-- 自己动手编写的一个App组件 --
templatediv classapph1你好啊/h1/div
/templatescript langts // 添加langts, 里面写ts或js都可以export default {name:App //组件名}/scriptstyle.app {background-color: #ddd;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;}
/style总结
Vite 项目中index.html 是项目的入口文件在项目最外层。加载index.html后Vite 解析 script typemodule srcxxx 指向的JavaScript。Vue3在main.ts中是通过 createApp 函数创建一个应用实例。
2.3. 一个简单的效果
Vue3向下兼容Vue2语法且Vue3中的模板中可以没有根标签
Person.vue
templatediv classpersonh2姓名{{name}}/h2h2年龄{{age}}/h2button clickchangeName修改名字/buttonbutton clickchangeAge年龄1/buttonbutton clickshowTel点我查看联系方式/button/div
/templatescript langtsexport default {name:App,data() {return {name:张三,age:18,tel:13888888888}},methods:{changeName(){this.name zhang-san},changeAge(){this.age 1},showTel(){alert(this.tel)}},}
/scriptApp.vue
templatediv classapph1你好啊/h1Person//div
/templatescript langtsimport Person from ./components/Person.vueexport default {name:App, //组件名components:{Person} //注册组件}
/scriptstyle.app {background-color: #ddd;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;}
/style3. Vue3核心语法
3.1. OptionsAPI 与 CompositionAPI
Vue2的API设计是Options配置风格的。Vue3的API设计是Composition组合风格的。
Options API 的弊端
Options类型的 API数据、方法、计算属性等是分散在data、methods、computed中的若想新增或者修改一个需求就需要分别修改data、methods、computed不便于维护和复用。 Composition API 的优势
可以用函数的方式更加优雅的组织代码让相关功能的代码更加有序的组织在一起。 3.2. 拉开序幕的 setup
setup 概述
介绍
setup是Vue3中一个新的配置项值是一个函数。它是 Composition API “表演的舞台”组件中所用到的数据、方法、计算属性、监视…等等均配置在setup中。
特点如下
setup函数返回的对象中的内容可直接在模板中使用。setup中访问this是undefined。setup函数会在beforeCreate之前调用它是“领先”所有钩子执行的。
templatediv classpersonh2姓名{{name}}/h2h2年龄{{age}}/h2button clickchangeName修改名字/buttonbutton clickchangeAge年龄1/buttonbutton clickshowTel点我查看联系方式/button/div
/templatescript langtsexport default {name:Person,// 生命周期函数beforeCreate(){console.log(beforeCreate)},setup(){// 先打印的setup..., 再打印的beforeCreate, 说明了setup函数与beforeCreate生命周期函数的执行顺序console.log(setup ...)// 【setup函数中的this是undefined】console.log(this); // undefined// 数据原来写在data中【注意此时的name、age、tel数据都不是响应式数据】// 不是响应式的意思是当这些数据变化并不会触发dom更新// 模板中应用这些变量的地方没有重新渲染let name 张三let age 18let tel 13888888888// 方法原来写在methods中function changeName(){name zhang-san // 注意此时这么修改name页面是不变化的console.log(name) // name确实改了但name不是响应式的}function changeAge(){age 1 // 注意此时这么修改age页面是不变化的console.log(age) // age确实改了但age不是响应式的}function showTel(){alert(tel)}// 返回一个对象对象中的内容模板中可以直接使用将数据、方法交出去模板中才可以使用这些交出去的数据、方法return {name,age,tel,changeName,changeAge,showTel}}}
/scriptsetup 的返回值
若返回一个对象则对象中的属性、方法等在模板中均可以直接使用**重点关注。**若返回一个函数则可以直接指定 自定义渲染的内容代码如下
templatediv classperson我特么一点都不重要了/div
/templatescript langtsexport default {name:Person,setup(){// setup的返回值也可以是一个渲染函数// 模板什么的都不重要了直接在页面上渲染成你好啊这几个字// return ()哈哈}}
/scriptstyle scoped.person {background-color: skyblue;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;}button {margin: 0 5px;}
/stylesetup 与 Options API 的关系
Vue2 的配置data、methos…中可以访问到 setup中的属性、方法。但在setup中不能访问到Vue2的配置data、methos…。如果与Vue2冲突则setup优先。
templatediv classpersonh2姓名{{name}}/h2h2年龄{{age}}/h2button clickchangeName修改名字/buttonbutton clickchangeAge修改年龄/buttonbutton clickshowTel查看联系方式/buttonhrh2测试1{{a}}/h2h2测试2{{c}}/h2h2测试3{{d}}/h2button clickb测试/button/div
/templatescript langtsexport default {name:Person,beforeCreate(){console.log(beforeCreate)},data(){return {a:100,// 在data配置项中, 可以使用this.name来使用setup中交出的数据, 因为setup执行时机更早。// 但是在setup中不能使用在data中定义的数据c:this.name, d:900,age:90}},methods:{b(){console.log(b)}},// setup可以与data、methods等配置项同时存在setup(){// 数据原来是写在data中的此时的name、age、tel都不是响应式的数据let name 张三let age 18let tel 13888888888// 方法function changeName() {name zhang-san // 注意这样修改name页面是没有变化的console.log(name) // name确实改了但name不是响应式的}function changeAge() {age 1 // 注意这样修改age页面是没有变化的console.log(age) // age确实改了但age不是响应式的}function showTel() {alert(tel)}// 将数据、方法交出去模板中才可以使用return {name,age,tel,changeName,changeAge,showTel}// setup的返回值也可以是一个渲染函数// return ()哈哈}}
/scriptstyle scoped.person {background-color: skyblue;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;}button {margin: 0 5px;}
/stylesetup 语法糖
setup函数有一个语法糖这个语法糖可以让我们把setup独立出去代码如下
templatediv classpersonh2姓名{{name}}/h2h2年龄{{age}}/h2button clickchangName修改名字/buttonbutton clickchangAge年龄1/buttonbutton clickshowTel点我查看联系方式/button/div/template!-- 专门单个弄个script标签, 特地来配置组件的名字 --
script langtsexport default {name:Person,}
/script!-- 下面的写法是setup语法糖 --
!-- 1. 相当于写了setup函数; 2. 相当于自动把其中定义的变量交出去包括里面引入的其它组件也会交出去, 可以在模板中使用引入的组件--
script setup langtsconsole.log(this) // undefined// 数据注意此时的name、age、tel都不是响应式数据let name 张三let age 18let tel 13888888888// 方法function changName(){name 李四//注意此时这么修改name页面是不变化的}function changAge(){console.log(age)age 1 //注意此时这么修改age页面是不变化的}function showTel(){alert(tel)}
/script扩展上述代码还需要编写一个不写setup的script标签去指定组件名字比较麻烦我们可以借助vite中的插件简化 第一步npm i vite-plugin-vue-setup-extend -D第二步vite.config.ts
import { fileURLToPath, URL } from node:urlimport { defineConfig } from vite
import vue from vitejs/plugin-vue
import VueSetupExtend from vite-plugin-vue-setup-extend// https://vitejs.dev/config/
export default defineConfig({plugins: [vue(),VueSetupExtend(),],resolve: {alias: {: fileURLToPath(new URL(./src, import.meta.url))}}
})
第三步script setup langts namePerson
3.3. ref 创建基本类型的响应式数据
**作用**定义响应式变量。语法let xxx ref(初始值)。**返回值**一个RefImpl的实例对象简称ref对象或refref对象的value属性是响应式的。注意点 JS中操作数据需要xxx.value但模板中不需要.value直接使用即可。对于let name ref(张三)来说name不是响应式的name.value是响应式的。
templatediv classperson!-- 模板中直接使用, 不需要.value --h2姓名{{name}}/h2h2年龄{{age}}/h2h2电话{{tel}}/h2button clickchangeName修改名字/buttonbutton clickchangeAge年龄1/buttonbutton clickshowTel点我查看联系方式/button/div
/template!-- 使用了setup语法糖, 会自动将定义的变量和方法交出去, 以供给模板使用 --
script setup langts namePerson// 引入vue中的ref函数import { ref } from vue// name和age是一个RefImpl的实例对象简称ref对象它们的value属性是响应式的。//所谓的响应式指的是, 对数据的改变后, 能够让模板中使用该数据的地方得到重新渲染更新// ref是1个函数, 向这个ref函数中传入参数, 返回的是1个RefImpl的实例对象let name ref(张三)let age ref(18)// tel就是一个普通的字符串不是响应式的let tel 13888888888function changeName(){// JS中操作ref对象时候需要.valuename.value 李四 // 页面得到刷新console.log(name.value)// 注意name不是响应式的name.value是响应式的所以如下代码并不会引起页面的更新。// name ref(zhang-san)}function changeAge(){// JS中操作ref对象时候需要.valueage.value 1 // 页面得到刷新console.log(age.value)}function showTel(){// tel是普通数据 tel 1 // tel的确改了, 但页面并未刷新alert(tel)}
/script3.4. reactive 创建对象类型的响应式数据
作用定义一个响应式对象基本类型不要用它要用ref否则报错语法let 响应式对象 reactive(源对象)。**返回值**一个Proxy的实例对象简称响应式对象。注意点reactive定义的响应式数据是“深层次”的。
template
div classpersonh2汽车信息一台{{ car.brand }}汽车价值{{ car.price }}万/h2h2游戏列表/h2ulli v-forg in games :keyg.id{{ g.name }}/li/ulh2测试{{ obj.a.b.c.d }}/h2button clickchangeCarPrice修改汽车价格/buttonbutton clickchangeFirstGame修改第一游戏/buttonbutton clicktest测试/button/div
/templatescript langts setup namePersonimport { reactive } from vue// 定义数据// reactive是1个函数, 向这个reactive函数中传入参数传入对象或数组, 返回的是1个Proxy的实例对象//Proxy是原生Js就有的函数// reactive函数中传入对象let car reactive({ brand: 奔驰, price: 100 }) console.log(car, car); // car Proxy {brand: 奔驰, price: 100}// reactive函数传入数组let games reactive([ { id: ahsgdyfa01, name: 英雄联盟 },{ id: ahsgdyfa02, name: 王者荣耀 },{ id: ahsgdyfa03, name: 原神 }])// reactive定义的响应式数据是 深层次 的let obj reactive({a: {b: {c: {d: 666}}}})// 修改对象中的属性修改使用reactive包裹对象后返回的对象function changeCarPrice() {car.price 10}// 修改数组中的对象的属性修改使用reactive包裹数组后返回的对象function changeFirstGame() {games[0].name 流星蝴蝶剑}function test() {obj.a.b.c.d 999}
/script3.5 ref 创建对象类型的响应式数据
其实ref接收的数据可以是基本类型、对象类型。若ref接收的是对象类型内部其实也是调用了reactive函数。
templatediv classpersonh2汽车信息一台{{ car.brand }}汽车价值{{ car.price }}万/h2h2游戏列表/h2ulli v-forg in games :keyg.id{{ g.name }}/li/ulh2测试{{ obj.a.b.c.d }}/h2button clickchangeCarPrice修改汽车价格/buttonbutton clickchangeFirstGame修改第一游戏/buttonbutton clicktest测试/button/div
/templatescript langts setup namePersonimport { ref,reactive } from vue// 使用ref定义对象类型响应式数据let car ref({ brand: 奔驰, price: 100 })// 使用reactive定义对象类型响应式数据let car2 reactive({brand: 奔驰, price: 100})// reactive只能用来定义对象类型的响应式数据// let name reactive(zhangsan) // 错误, value cannot be made reactive: zhangsan// 使用ref定义对象(数组)类型响应式数据let games ref([{ id: ahsgdyfa01, name: 英雄联盟 },{ id: ahsgdyfa02, name: 王者荣耀 },{ id: ahsgdyfa03, name: 原神 }])// 使用ref定义对象类型响应式数据也是深层次的let obj ref({a: {b: {c: {d: 666}}}})// 若ref接收的是对象类型内部其实也是使用的reactive函数console.log(car) // RefImpl {__v_isShallow: false, dep: undefined, // __v_isRef: true, _rawValue: {…}, _value: Proxy}console.log(car.value) // Proxy {brand: 奔驰, price: 100}console.log(car2) // Proxy {brand: 奔驰, price: 100}function changeCarPrice() {// 使用ref函数定义的响应式数据, 在js操作时, 需要带上.value, 才能碰到内部的Proxy对象car.value.price 10console.log(car.value.price);}function changeFirstGame() {// 使用ref函数定义的响应式数据, 在js操作时, 需要带上.value, 才能碰到内部的Proxy对象games.value[0].name 流星蝴蝶剑console.log(games.value); // Proxy {0: {…}, 1: {…}, 2: {…}}}function test() {// 使用ref函数定义的响应式数据, 在js操作时, 需要带上.value, 才能碰到内部的Proxy对象obj.value.a.b.c.d 999}/script3.6. ref 对比 reactive
宏观角度 ref可以定义基本类型、对象类型的响应式数据 reactive只能定义对象类型的响应式数据
区别 ref创建的变量必须使用.value可以使用volar插件自动添加.value。 可以在齿轮-设置-扩展-volar中勾选 它会在使用ref创建的变量时自动添加上.value reactive重新分配一个新对象会失去响应式可以使用Object.assign去整体替换。 templatediv classpersonh2汽车信息一台{{ car.brand }}汽车价值{{ car.price }}万/h2button clickchangeBrand改品牌/buttonbutton clickchangePrice改价格/buttonbutton clickchangeCar改car/button/div
/templatescript langts setup namePersonimport { ref,reactive } from vuelet car reactive({brand:奔驰, price:100})function changeBrand() {// 正常修改car的brand, 并且是响应式car.brand 宝马
}function changePrice() {// 正常修改car的price, 并且是响应式car.price 10
}function changeCar() {// 错误做法1// 不可以直接给reactive重新分配一个新对象这会让car直接失去响应式// car {brand:奥托, price:10}// 错误做法2// 这样也不行, 因为模板中用的car是上面定义的响应式对象, // 现在car指向的是1个新的响应式对象, 而模板中压根就没有使用这个新的响应式对象// car reactive({brand:奥托, price:10})// 正确做法car仍然是响应式的// API介绍: Object.assign(obj1, obj2, obj3, ..), // 将obj2中的每一组属性和值设置到obj1中, 然后obj3的每一组属性和值设置到obj1中Object.assign(car, {brand:奥托, price:10})
}/scripttemplatediv classpersonh2汽车信息一台{{ car.brand }}汽车价值{{ car.price }}万/h2button clickchangeBrand改品牌/buttonbutton clickchangePrice改价格/buttonbutton clickchangeCar改car/button/div
/templatescript langts setup namePersonimport { ref,reactive } from vuelet car ref({brand:奔驰, price:100})function changeBrand() {// 正常修改car的brand, 并且是响应式car.value.brand 宝马
}function changePrice() {// 正常修改car的price, 并且是响应式car.value.price 10
}function changeCar() {// 错误做法1// 不能直接给car换了个ref, 因为模板中压根就没有使用这个新的RefImpl对象// car ref({brand:奥托, price:10})// 正确做法1car仍然是响应式的// API介绍: Object.assign(obj1, obj2, obj3, ..), 将obj2中的每一组属性和值设置到obj1中, // 然后obj3的每一组属性和值设置到obj1中// Object.assign(car.value, {brand:奥托, price:10})// 正确做法2//这里相比于对car使用reactive定义而言, 使用ref定义则可以直接给car.value整体赋值// 原因在于car.value获取的是Proxy响应式对象, 凡是对Proxy响应式对象的操作都可以被拦截到car.value {brand:奥托, price:10}}/script使用原则 若需要一个基本类型的响应式数据必须使用ref。 若需要一个响应式对象层级不深ref、reactive都可以。 若需要一个响应式对象且层级较深推荐使用reactive。
3.7 toRefs 与 toRef
作用将一个响应式对象中的每一个属性转换为ref对象。备注toRefs与toRef功能一致但toRefs可以批量转换。
现象
对响应式对象直接结构赋值得到的数据不是响应式的
templatediv classpersonh2姓名{{ person.name }} {{ name }}/h2h2年龄{{ person.age }} {{ age }}/h2button clickchangeName修改名字/buttonbutton clickchangeAge修改年龄/button/div
/templatescript langts setup namePerson2import { ref, reactive, toRefs, toRef } from vue// 数据let person reactive({ name: 张三, age: 18 })console.log(person); // Proxy {name: 张三, age: 18}// 这里的解构赋值其实就等价于: let name person.name; let age person.age;// 只是记录了此时person.name、person.age的值, 仅此而已// 因此, 此处使用结构赋值语法获取的name和age都不是响应式的let {name, age } personconsole.log(name, age); // 张三 18// 方法function changeName() {name ~console.log(name, person.name); // 变化的是name, 而person.name仍然未修改}function changeAge() {age 1console.log(age, person.age); // 变化的是age, 而person.age仍然未修改}/scripttoRefstoRef的使用
通过toRefs将person对象中的所有属性都批量取出, 且依然保持响应式的能力
template
div classpersonh2姓名{{ person.name }}/h2h2年龄{{ person.age }}/h2h2性别{{ person.gender }} {{ gender }}/h2button clickchangeName修改名字/buttonbutton clickchangeAge修改年龄/buttonbutton clickchangeGender修改性别/buttonbutton clickchangeGender2修改性别2/button/div
/templatescript langts setup namePersonimport { ref, reactive, toRefs, toRef } from vue// 数据let person reactive({ name: 张三, age: 18, gender: 男 })// 通过toRefs将person对象中的所有属性都批量取出, 且依然保持响应式的能力//使用toRefs从person这个响应式对象中解构出name、age, 且name和age依然是响应式的,// name和gender的值是ref类型, 其value值指向的是person.name和person.age,// 对name.value和对age.value的修改将会修改person.name和person.age, 并且会页面渲染刷新let { name, age } toRefs(person)console.log(name.value, name); // 张三 ObjectRefImpl {_object: Proxy, _key: name, // _defaultValue: undefined, __v_isRef: true}console.log(age.value, age.value); // 18 ObjectRefImpl {_object: Proxy, _key: age, // _defaultValue: undefined, __v_isRef: true}console.log(toRefs(person)); // {name: ObjectRefImpl, age: ObjectRefImpl, // gender: ObjectRefImpl}// 通过toRef将person对象中的gender属性取出且依然保持响应式的能力let gender toRef(person, gender)console.log(gender, gender.value); // ObjectRefImpl {_object: Proxy, _key: gender, // _defaultValue: undefined, __v_isRef: true} 男// 方法function changeName() {// 此处修改name.value, 将会修改person.name, 并且页面会刷新person.name的值name.value ~console.log(name.value, person.name);}function changeAge() {// 此处修改age.value, 将会修改person.age, 并且页面会刷新person.age的值age.value 1console.log(age.value, person.age);}function changeGender() {// 此处修改gender.value, 将会修改person.age, 并且页面会刷新person.gender的值gender.value 女console.log(gender.value, person.gender);}function changeGender2() {// 此处对person.gender的修改, 将会修改上面的let gender toRef(person, gender)// 并且页面会刷新person.gender和gender的值person.gender 男console.log(gender.value, person.gender);}
/script3.8 computed
作用根据已有数据计算出新数据和Vue2中的computed作用一致。 templatediv classperson姓input typetext v-modelfirstName br名input typetext v-modellastName br全名span{{ fullName }}/span brbutton clickchangeFullName全名改为: li-si/button/div
/templatescript setup langts nameApp// 引入computed计算属性函数
import { ref, computed } from vuelet firstName ref(zhang)
let lastName ref(san)// 计算属性——只读取不修改
/*
// 1. 使用时, 在computed中传入1个函数。在模板中, 直接使用计算属性即可。
// 2. 当计算属性依赖的数据只要发生变化, 它就会重新计算, 如果页面中有使用到该计算属性, 那么就会重新渲染模板
// 3. 只会计算1次, 后面会使用缓存, 而方法是没有缓存的
let fullName computed((){return firstName.value - lastName.value
})
console.log(fullName); // ComputedRefImpl {dep: undefined, __v_isRef: true, // __v_isReadonly: true, effect: ReactiveEffect, _setter: ƒ, …}*/// 计算属性——既读取又修改
let fullName computed({// 读取get() {// 当firstName或lastName变化时, 计算属性会重新计算, 并刷新页面渲染return firstName.value - lastName.value},// 修改// 当修改计算属性时或者说给计算属性赋值时, 注意要.value, 此方法会被调用set(val) {console.log(有人修改了fullName, val)firstName.value val.split(-)[0]lastName.value val.split(-)[1]}
})function changeFullName() {// 修改fullName计算属性会触发计算属性中set方法的调用fullName.value li-si
}
/script3.9 watch
作用
监视数据的变化和Vue2中的watch作用一致
特点
Vue3中的watch只能监视以下四种数据 ref定义的数据。 reactive定义的数据。 函数返回一个值getter函数所谓的getter函数就是能返回一个值的函数。 一个包含上述内容的数组。
场景
我们在Vue3中使用watch的时候通常会遇到以下几种情况
* 情况一
监视ref定义的【基本类型】数据直接写数据名即可监视的是其value值的改变。
templatediv classpersonh1情况一监视【ref】定义的【基本类型】数据/h1h2当前求和为{{ sum }}/h2button clickchangeSum点我sum1/button/div
/templatescript langts setup namePerson// 引入watch监视函数
import { ref, watch } from vue// 数据
let sum ref(0)// 方法
function changeSum() {sum.value 1
}// 监视情况一监视【ref】定义的【基本类型】数据
//注意这里监视写的是sum, 而不是sum.value哦
const stopWatch watch(sum, (newValue, oldValue) {console.log(sum变化了, newValue, oldValue) // 注意: 这里也没带.value哦if (newValue 10) {// 解除监视即: 当调用此方法后, 不会再监视sum的变化了, 也就是当sum变化时, 当前的监视函数不再执行了stopWatch()}
})/scriptstyle scoped
...
/style* 情况二
监视ref定义的【对象类型】数据直接写数据名监视的是对象的【地址值】。若想监视对象内部的数据要手动开启深度监视。
注意 若修改的是ref定义的对象中的属性newValue 和 oldValue 都是新值因为它们是同一个对象。 若修改整个ref定义的对象newValue 是新值 oldValue 是旧值因为不是同一个对象了。
示例1
templatediv classpersonh1情况二监视【ref】定义的【对象类型】数据/h1h2姓名{{ person.name }}/h2h2年龄{{ person.age }}/h2button clickchangeName修改名字/buttonbutton clickchangeAge修改年龄/buttonbutton clickchangePerson修改整个人/button/div
/templatescript langts setup namePersonimport { ref, watch } from vue// 数据
let person ref({name: 张三,age: 18
})// 方法
function changeName() {person.value.name ~ // 当修改person.value.name时, 监视函数未被触发
}function changeAge() {person.value.age 1 // 当修改person.value.age时, 监视函数也未被触发
}function changePerson() {person.value { name: 李四, age: 90 } // 当整体修改person.value时, 此时监视函数被触发
} // 因为监视的是对象的地址值, 所以这里每次修改都会触发监视函数/* 监视情况一监视【ref】定义的【对象类型】数据监视的是对象的地址值。watch的第一个参数是被监视的数据watch的第二个参数是监视的回调
*/
watch(person, (newValue, oldValue) {console.log(person变化了, newValue, oldValue)// 一直调用changePerson方法, 控制台如下输出// person变化了 Proxy {name: 李四, age: 90} Proxy {name: 张三, age: 18}// person变化了 Proxy {name: 李四, age: 90} Proxy {name: 李四, age: 90}// person变化了 Proxy {name: 李四, age: 90} Proxy {name: 李四, age: 90}// person变化了 Proxy {name: 李四, age: 90} Proxy {name: 李四, age: 90}// ...
})/scriptstyle scoped
...
/style示例2
templatediv classpersonh1情况二监视【ref】定义的【对象类型】数据/h1h2姓名{{ person.name }}/h2h2年龄{{ person.age }}/h2button clickchangeName修改名字/buttonbutton clickchangeAge修改年龄/buttonbutton clickchangePerson修改整个人/button/div
/templatescript langts setup namePersonimport { ref, watch } from vue// 数据
let person ref({name: 张三,age: 18
})// 方法
function changeName() {person.value.name ~// 因为开启了深度监视, 当修改person.value.name时, 监视函数被触发
} //但由于原对象并未修改, 所以监视函数中输出的newVal和oldVal是一样的// 每次调用changeName都修改, 变化如下:// person变化了 Proxy {name: 张三~, age: 18} Proxy {name: 张三~, age: 18}// person变化了 Proxy {name: 张三~~, age: 18} Proxy {name: 张三~~, age: 18}// person变化了 Proxy {name: 张三~~, age: 18} Proxy {name: 张三~~, age: 18}// ...
function changeAge() {person.value.age 1 // 因为开启了深度监视, 当修改person.value.name时, 监视函数被触发
} //但由于原对象并未修改, 所以监视函数中输出的newVal和oldVal是一样的// 每次调用changeName都修改, 变化如下:// person变化了 Proxy {name: 张三, age: 19} Proxy {name: 张三, age: 19}// person变化了 Proxy {name: 张三, age: 20} Proxy {name: 张三, age: 20}// person变化了 Proxy {name: 张三, age: 21} Proxy {name: 张三, age: 21}// ...function changePerson() {person.value { name: 李四, age: 90 }// 当整体修改person.value时, 监视函数被触发//但由于原对象都改了, 所以监视函数中输出的newVal和oldVal是不一样的// 每次调用changeName都修改, 变化如下:
} // person变化了 Proxy {name: 李四, age: 90} Proxy {name: 张三, age: 18}// person变化了 Proxy {name: 李四, age: 90} Proxy {name: 李四, age: 90}// person变化了 Proxy {name: 李四, age: 90} Proxy {name: 李四, age: 90}// ...
/* 监视情况二监视【ref】定义的【对象类型】数据监视的是对象的地址值若想监视对象内部属性的变化需要手动开启深度监视watch的第一个参数是被监视的数据watch的第二个参数是监视的回调watch的第三个参数是配置对象deep、immediate等等
*/
watch(person, (newValue, oldValue) {console.log(person变化了, newValue, oldValue)
}, { deep: true, immediate: true })/scriptstyle scoped
.person {background-color: skyblue;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;
}button {margin: 0 5px;
}li {font-size: 20px;
}
/style* 情况三
监视reactive定义的【对象类型】数据且默认开启了深度监视。
template
div classpersonh1情况三监视【reactive】定义的【对象类型】数据/h1h2姓名{{ person.name }}/h2h2年龄{{ person.age }}/h2button clickchangeName修改名字/buttonbutton clickchangeAge修改年龄/buttonbutton clickchangePerson修改整个人/buttonhrh2测试{{obj.a.b.c}}/h2button clicktest修改obj.a.b.c/button/div
/templatescript langts setup namePersonimport {reactive,watch} from vue// 数据let person reactive({name:张三,age:18})let obj reactive({a:{b:{c:666}}})// 方法function changeName(){person.name ~// 每次调用changeName都修改, 变化如下:// person变化了 Proxy {name: 张三~, age: 18} Proxy {name: 张三~, age: 18}// person变化了 Proxy {name: 张三~~, age: 18} Proxy {name: 张三~~, age: 18}// person变化了 Proxy {name: 张三~~~, age: 18} Proxy {name: 张三~~~, age: 18}// ...//如上结果, // 1. 证明监视到了person的name // 2. oldVal和newVal是一样的输出, 是因为虽然监测到person的变化, 但oldVal和newVal是同一对象, 从这来说并未改变}function changeAge(){person.age 1// 每次调用changeAge都修改, 变化如下:// person变化了 Proxy {name: 张三, age: 19} Proxy {name: 张三, age: 19}// person变化了 Proxy {name: 张三, age: 20} Proxy {name: 张三, age: 20}// person变化了 Proxy {name: 张三, age: 21} Proxy {name: 张三, age: 21}// ...//如上结果, // 1. 证明监视到了person的age// 2. oldVal和newVal是一样的输出, 是因为虽然监测到person的变化, 但oldVal和newVal是同一对象, 从这来说并未改变}function changePerson(){// 此处注意: 使用reactive函数定义的数据, 不能直接替换, 可以如下方式对person中的属性做批量修改 Object.assign(person,{name:李四,age:80})// 多次调用changePerson, 仅有1次监视到到修改, 变化如下:// person变化了 Proxy {name: 李四, age: 80} Proxy {name: 李四, age: 80}//如上结果, // 1. 证明监视到了person的name和age的改变// 2. oldVal和newVal是一样的输出, 是因为虽然监测到person的变化, 但oldVal和newVal仍是同一对象, 从这来说并未改变}function test(){obj.a.b.c 888// 此处证明watch监控reactive定义的对象类型数据, 默认是开启了深度监视的}// 监视情况三监视【reactive】定义的【对象类型】数据且默认是开启深度监视的(隐式创建了深层次的监听, 无法关闭)watch(person,(newValue,oldValue){console.log(person变化了,newValue,oldValue)})watch(obj,(newValue,oldValue){console.log(Obj变化了,newValue,oldValue)})/scriptstyle scoped
...
/style* 情况四
监视ref或reactive定义的【对象类型】数据中的某个属性注意点如下
若该属性值不是【对象类型】需要写成函数形式。若该属性值是依然是【对象类型】可直接编也可写成函数建议写成函数。
结论监视的要是对象里的属性那么最好写函数式。注意点若是对象监视的是地址值需要关注对象内部则需要手动开启深度监视。
没有监视的代码
template
div classpersonh1情况四监视【ref】或【reactive】定义的【对象类型】数据中的某个属性/h1h2姓名{{ person.name }}/h2h2年龄{{ person.age }}/h2h2汽车{{ person.car.c1 }}、{{ person.car.c2 }}/h2button clickchangeName修改名字/buttonbutton clickchangeAge修改年龄/buttonbutton clickchangeC1修改第一台车/buttonbutton clickchangeC2修改第二台车/buttonbutton clickchangeCar修改整个车/button/div
/templatescript langts setup namePersonimport { reactive, watch } from vue// 数据let person reactive({name: 张三,age: 18,car: {c1: 奔驰,c2: 宝马}})// 方法function changeName() {person.name ~}function changeAge() {person.age 1}function changeC1() {person.car.c1 奥迪}function changeC2() {person.car.c2 大众}function changeCar() {// 注意此处: 因为person是使用reactive定义的, 所以person整体不能改(改是可以改, 但是不再响应式了, // 所以说不能整体直接改), // 但是person里面的car属性可以改, 因此可以如下改person.car { c1: 雅迪, c2: 爱玛 }}/scriptstyle scoped...
/style监视reactive定义的对象类型中的某个基本属性
templatediv classpersonh1情况四监视【ref】或【reactive】定义的【对象类型】数据中的某个属性/h1h2姓名{{ person.name }}/h2h2年龄{{ person.age }}/h2h2汽车{{ person.car.c1 }}、{{ person.car.c2 }}/h2button clickchangeName修改名字/buttonbutton clickchangeAge修改年龄/buttonbutton clickchangeC1修改第一台车/buttonbutton clickchangeC2修改第二台车/buttonbutton clickchangeCar修改整个车/button/div
/templatescript langts setup namePersonimport { reactive, watch } from vue// 数据let person reactive({name: 张三,age: 18,car: {c1: 奔驰,c2: 宝马}})// 方法function changeName() {person.name ~// 一直调用changeName方法, 控制台如下输出// person.name变化了 张三~ 张三// person.name变化了 张三~~ 张三~// person.name变化了 张三~~~ 张三~~// ...}function changeAge() {person.age 1}function changeC1() {person.car.c1 奥迪}function changeC2() {person.car.c2 大众}function changeCar() {// 注意此处: 因为person是使用reactive定义的, 所以person整体不能改(改是可以改, 但是不再响应式了, // 所以说不能整体直接改), // 但是person里面的car属性可以改, 因此可以如下改person.car { c1: 雅迪, c2: 爱玛 }}// 监视情况四监视响应式对象中的某个属性且该属性是基本类型的要写成函数式(不能直接写person.name哦)//如下监视, 将会只监视person的name属性的变化, // 当person的name属性发生变化时, 将会触发监听函数执行, 其它属性变化不会触发监听函数的执行watch(() person.name,(newValue,oldValue){console.log(person.name变化了,newValue,oldValue)}) // 错误写法, 因为person的name属性是基本类型, 所以不能直接写为第1个参数, 应该要用函数包一下/*watch(person.name,(newValue,oldValue){console.log(person.name变化了,newValue,oldValue)})*/// 监视person的car属性中的c1属性//当调用changeC1方法时, 此处能够监测到person.car.c1的改变;// 多次调用changeC1方法, 此处只监测到了1次, 因为后面都没改person.car.c1的值;// 当调用changeCar方法, 此处能够监测到person.car.c1的改变;// 多次调用changeCar方法, 此处只监测到了1次, 因为后面都没改person.car.c1的值;watch(() person.car.c1,(newValue,oldValue){console.log(person.car.c1变化了,newValue,oldValue)})// 错误写法, 因为person的car.c1属性是基本类型, 所以不能直接写为第1个参数, 应该要用函数包一下/*watch(person.car.c1,(newValue,oldValue){console.log(person.car.c1变化了,newValue,oldValue)})*//scriptstyle scoped
...
/style监视reactive定义的对象类型中的某个对象属性
templatediv classpersonh1情况四监视【ref】或【reactive】定义的【对象类型】数据中的某个属性/h1h2姓名{{ person.name }}/h2h2年龄{{ person.age }}/h2h2汽车{{ person.car.c1 }}、{{ person.car.c2 }}/h2button clickchangeName修改名字/buttonbutton clickchangeAge修改年龄/buttonbutton clickchangeC1修改第一台车/buttonbutton clickchangeC2修改第二台车/buttonbutton clickchangeCar修改整个车/button/div
/templatescript langts setup namePersonimport { reactive, watch } from vue// 数据let person reactive({name: 张三,age: 18,car: {c1: 奔驰,c2: 宝马}})// 方法function changeName() {person.name ~}function changeAge() {person.age 1}function changeC1() {person.car.c1 奥迪}function changeC2() {person.car.c2 大众}function changeCar() {// 注意此处: 因为person是使用reactive定义的, 所以person整体不能改(改是可以改, 但是不再响应式了, // 所以说不能整体直接改), // 但是person里面的car属性可以改, 因此可以如下改person.car { c1: 雅迪, c2: 爱玛 }}// 监视情况四监视响应式对象中的某个属性且该属性是对象类型的可以直接写也能写函数更推荐写函数// 建议写成函数的形式// 当调用changeC1或changeC2方法时, 会触发此处的监测函数执行// 当调用changeCar方法时, 会触发此处的监测函数执行// 【最佳实践】函数式来开启对person.car的地址值的监测, 然后deep:true开启对该对象的深度监视watch(() person.car, (newValue, oldValue) {console.log(person.car变化了, newValue, oldValue)}, { deep: true })// 如果写成下面这样, 监测的其实是person.car的地址值, 只有在person.car整体改变时, 才会触发此处的监测函数执行// 当调用changeC1或changeC2方法时, 不会触发此处的监测函数执行/* watch(() person.car, (newValue, oldValue) {console.log(person.car变化了, newValue, oldValue)}) */// 如果写成下面这样(直接写的做法), 那么当调用changeCar方法时, 不会触发此处的监测函数执行// 当调用changeC1或changeC2方法时, 会触发此处的监测函数执行//因为person.car是person中的对象类型属性, 因此这里可以直接写/* watch(person.car, (newValue, oldValue) {console.log(person.car变化了, newValue, oldValue)}) *//scriptstyle scoped...
/style* 情况五
监视上述的多个数据
template
div classpersonh1情况五监视上述的多个数据/h1h2姓名{{ person.name }}/h2h2年龄{{ person.age }}/h2h2汽车{{ person.car.c1 }}、{{ person.car.c2 }}/h2button clickchangeName修改名字/buttonbutton clickchangeAge修改年龄/buttonbutton clickchangeC1修改第一台车/buttonbutton clickchangeC2修改第二台车/buttonbutton clickchangeCar修改整个车/button/div
/templatescript langts setup namePersonimport {reactive,watch} from vue// 数据let person reactive({name:张三,age:18,car:{c1:奔驰,c2:宝马}})// 方法function changeName(){person.name ~}function changeAge(){person.age 1}function changeC1(){person.car.c1 奥迪}function changeC2(){person.car.c2 大众}function changeCar(){person.car {c1:雅迪,c2:爱玛}}// 监视情况五监视上述的多个数据//person.name是基本类型, 所以要写成函数式; person.car是对象类型, 所以可以直接写;// 这里的newVal和oldVal都是数组, 跟监视的2个源相对应; // deep开启深度监视, 不止可以监视地址值, 还包括内部属性的变化;watch([()person.name, person.car],(newValue, oldValue){console.log(person.car变化了,newValue,oldValue)},{deep:true})/scriptstyle scoped
...
/style3.10 watchEffect
官网立即运行一个函数同时响应式地追踪其依赖并在依赖更改时重新执行该函数。
watch对比watchEffect 都能监听响应式数据的变化不同的是监听数据变化的方式不同 watch要明确指出监视的数据 watchEffect不用明确指出监视的数据函数中用到哪些属性那就监视哪些属性。 templatediv classpersonh1需求水温达到50℃或水位达到20cm则联系服务器/h1h2 iddemo水温{{temp}}/h2h2水位{{height}}/h2button clickchangePrice水温1/buttonbutton clickchangeSum水位10/button/div
/templatescript langts setup namePersonimport {ref,watch,watchEffect} from vue// 数据let temp ref(0)let height ref(0)// 方法function changePrice(){temp.value 10}function changeSum(){height.value 1}// 用watch实现需要明确的指出要监视temp、heightwatch([temp,height],(value){// 从value中获取最新的temp值、height值const [newTemp,newHeight] value// 室温达到50℃或水位达到20cm立刻联系服务器if(newTemp 50 || newHeight 20){console.log(联系服务器)}})// 用watchEffect实现不用明确的指出要监视变量// 1. 它会从监听函数中自动分析需要监视的数据 (而watch则需要指定需要监视的数据)// 2. 一上来就会执行1次函数const stopWtach watchEffect((){// 室温达到50℃或水位达到20cm立刻联系服务器if(temp.value 50 || height.value 20){console.log(document.getElementById(demo)?.innerText)console.log(联系服务器)}// 水温达到100或水位达到50取消监视if(temp.value 100 || height.value 50){console.log(清理了)stopWtach()}})
/script3.11. 标签的 ref 属性
作用用于注册模板引用。 用在普通DOM标签上获取的是DOM节点。 用在组件标签上获取的是组件实例对象。
用在普通DOM标签上
templatediv classperson!-- ref标记在普通DOM标签上 --h1 reftitle1尚硅谷/h1h2 reftitle2前端/h2h3 reftitle3Vue/h3input typetext refinpt brbrbutton clickshowLog点我打印内容/button/div
/templatescript langts setup namePersonimport {ref} from vuelet title1 ref() // 使用ref来获取对应的节点, 其中title1要与对应节点的ref对应的值相同let title2 ref()let title3 ref()function showLog(){// 通过id获取元素const t1 document.getElementById(title1)// 打印内容console.log((t1 as HTMLElement).innerText)console.log((HTMLElementt1).innerText)console.log(t1?.innerText)// 通过ref获取元素console.log(title1.value)console.log(title2.value)console.log(title3.value)}
/script用在组件标签上defineExpose
defineExpose它属于宏函数不需要引入
!-- 父组件App.vue --
template!-- ref标记在组件标签上 --Person refren/button clicktest测试/button/templatescript langts setup nameApp// 在setUp中不需要注册Person组件, 直接使用即可import Person from ./components/Person.vueimport {ref} from vue// 变量名需要与ref标记的值相同let ren ref()function test(){// 需要子组件通过defineExpose暴露出来的属性或方法, 父组件才可以在这里访问到console.log(ren.value.name)console.log(ren.value.age)}
/script!-- 子组件Person.vue中要使用defineExpose暴露内容 --
script langts setup namePersonimport {ref,defineExpose} from vue// 数据let name ref(张三)let age ref(18)// 使用defineExpose将组件中的数据交给外部defineExpose({name,age})
/script3.12 回顾TS
main.ts
// 引入createApp用于创建应用
import { createApp } from vue// 引入App根组件
import App from ./App.vuecreateApp(App).mount(#app)App.vue
templatePerson/
/templatescript langts setup nameAppimport Person from /components/Person.vue
/scriptindex.ts
在src下创建types文件夹并在这个文件夹中创建如下index.ts文件。
在其中定义接口和自定义泛型
// 定义一个接口用于限制person对象的具体属性
export interface PersonInter {id: string,name: string,age: number,x?: number /* x是可选属性, 该类型中可以有该属性, 也可以无该属性 */
}// 一个自定义类型
// export type Persons ArrayPersonInter
export type Persons PersonInter[] // 与上面等价Person.vue
注意把vetur这个插件给禁掉, 否则老是有飘红。就开启本篇中上述的推荐的插件即可。
templatediv classperson/div
/templatescript langts setup namePerson// 引入接口 或 自定义类型 的时候, 需要在前面加上type; import { type PersonInter, type Persons } from /types// 定义1个变量, 它要符合PersonInter接口let person: PersonInter {id: a01, name: john, age:60}// 定义1个数组, 首先它是个数组, 并且里面元素类型都是符合PersonInter接口的如果里面有属性名写错会有飘红提示let personList: ArrayPersonInter [{id: a01, name: john, age:60}]// 定义1个数组, 它符合 Persons 自定义类型如果里面有属性名写错会有飘红提示let personList2: Persons [{id: a01, name: john, age:60}]/scriptstyle scoped/style3.13 propsdefineProps
defineProps它属于宏函数不需要引入
App.vue
template!-- Person子组件定义了list属性, 并且限定为Persons类型 --Person :listpersonList /
/templatescript langts setup nameAppimport Person from /components/Person.vueimport {reactive} from vueimport {type Persons} from /typeslet personList reactivePersons([{ id: asudfysafd01, name: 张三, age: 18 },{ id: asudfysafd02, name: 李四, age: 20 },{ id: asudfysaf)d03, name: 王五, age: 22 }])/scriptindex.ts
// 定义一个接口用于限制person对象的具体属性
export interface PersonInter {id: string,name: string,age: number,x?: number /* x是可选属性, 该类型中可以有该属性, 也可以无该属性 */
}// 一个自定义类型
// export type Persons ArrayPersonInter
export type Persons PersonInter[] // 与上面等价Person.vue
templatediv classpersonul!-- 在模板中直接使用list, 不需要加props.list --li v-forp in list :keyp.id{{p.name}} -- {{p.age}}/li/ul/div
/templatescript langts setup namePersonimport {reactive, withDefaults} from vue// 引入接口 或 自定义类型 的时候, 需要在前面加上type; import { type PersonInter, type Persons } from /types// 不推荐的写法, 但可用let personList:Persons reactive([{id: a01, name: john, age:60}])// 推荐的写法, 意为: personList2这个变量须符合 Persons 类型的规范let personList2 reactivePersons([{id: a01, name: john, age:60}])// 推荐的写法, 意为: personList3这个变量须符合 PersonInter[] 类型的规范let personList3 reactivePersonInter[]([{id: a01, name: john, age:60}])// 只接收// 定义接收父组件传过来的a属性, 并赋值给props以便于访问。并且defineProps只能使用1次/* let props defineProps([a, b])// 在js代码中使用props.a来访问父组件传过来的a属性对应的值, 在模板中直接使用a来访问父组件传过来的a属性对应的值console.log(props.a); */ // 接收 限制类型 限制必要性// (list2可不传; list必须传, 并且必须是Persons类型的)/* let props defineProps{list:Persons, list2?:Persons}()console.log(props.list); */// 接收 限制类型 限制必要性 指定默认值// (list属性可不传, 如果没有传的话, 就是用下面默认定义的数据)const props withDefaults(defineProps{list?: Persons}(),{list: () [{id:A001,name:张三,age:18}]})console.log(props.list);/scriptstyle scoped/style3.14 生命周期 概念Vue组件实例在创建时要经历一系列的初始化步骤在此过程中Vue会在合适的时机调用特定的函数从而让开发者有机会在特定阶段运行自己的代码这些特定的函数统称为生命周期钩子 规律 生命周期整体分为四个阶段分别是创建、挂载、更新、销毁每个阶段都有两个钩子一前一后。 Vue2的生命周期 创建阶段beforeCreate、created 挂载阶段beforeMount、mounted 更新阶段beforeUpdate、updated 销毁阶段beforeDestroy、destroyed Vue3的生命周期 创建阶段setup替代了之前vue2中的beforeCreate、created 挂载阶段onBeforeMount、onMounted 更新阶段onBeforeUpdate、onUpdated 卸载阶段onBeforeUnmount、onUnmounted就对应vue2中的销毁阶段 常用的钩子onMounted(挂载完毕)、onUpdated(更新完毕)、onBeforeUnmount(卸载之前)
App.vue
templatePerson v-ifisShow/
/templatescript langts setup nameAppimport Person from ./components/Person.vueimport {ref,onMounted} from vuelet isShow ref(true)// 挂载完毕先子组件挂载完毕, 再父挂载完毕onMounted((){console.log(父---挂载完毕)})/script
Person.vue
templatediv classpersonh2当前求和为{{ sum }}/h2button clickadd点我sum1/button/div
/templatescript langts setup namePersonimport {ref,onBeforeMount, onMounted,onBeforeUpdate, onUpdated,onBeforeUnmount, onUnmounted } from vue// 数据let sum ref(0)// 方法function add(){sum.value 1}// 创建替代了之前vue2中的beforeCreate、createdconsole.log(创建)// 挂载前这里面传入的函数由vue3帮我们调用, 这里只是将这个函数注册进去onBeforeMount((){// console.log(挂载前)})// 挂载完毕onMounted((){console.log(子---挂载完毕)})// 更新前onBeforeUpdate((){// console.log(更新前)})// 更新完毕onUpdated((){// console.log(更新完毕)})// 卸载前onBeforeUnmount((){// console.log(卸载前)})// 卸载完毕onUnmounted((){// console.log(卸载完毕)})
/script3.15 自定义hooks
未使用hooks前
App.vue
templatePerson /
/templatescript langts setup nameApp
import Person from ./components/Person.vue
/scriptPerson.vue
templatediv classpersonh2当前求和为{{ sum }}放大10倍后{{ bigSum }}/h2button clickadd点我sum1/buttonhrimg v-for(dog, index) in dogList :srcdog :keyindexbutton clickgetDog再来一只小狗/button/div
/templatescript langts setup namePersonimport { ref, reactive, onMounted, computed } from vueimport axios from axios// ---- 求和// 数据let sum ref(0)let bigSum computed(() {return sum.value * 10})// 方法function add() {sum.value 1}// 钩子onMounted(() {add()})// --- 发起请求获取图片// 数据let dogList reactive([https://images.dog.ceo/breeds/pembroke/n02113023_4373.jpg])// 方法async function getDog() {try {let result await axios.get(https://dog.ceo/api/breed/pembroke/images/random)dogList.push(result.data.message)} catch (error) {alert(error)}}// 钩子onMounted(() {getDog()})/scriptstyle scoped/style使用hooks
vue3本身就推荐使用组合式api但是如果各种功能都放到setup里面显得就有点乱了所以使用hooks将单独的功能所使用的各种数据、方法等抽离出去当需要某个功能时再引入进来。
hooks中不仅可以定义数据还可以使用声明周期钩子函数还可以写计算属性。
App.vue
templatePerson /
/templatescript langts setup nameApp
import Person from ./components/Person.vue
/scriptPerson.vue
templatediv classpersonh2当前求和为{{ sum }}放大10倍后{{ bigSum }}/h2button clickadd点我sum1/buttonhrimg v-for(dog,index) in dogList :srcdog :keyindexbrbutton clickgetDog再来一只小狗/button/div
/templatescript langts setup namePersonimport useSum from /hooks/useSumimport useDog from /hooks/useDog// 调用函数获得数据const {sum,add,bigSum} useSum()// 调用函数获得数据const {dogList,getDog} useDog()/scriptstyle scoped/stylehooks/useSum.ts
import { ref ,onMounted,computed} from vue// 暴露此函数默认暴露
export default function () {// 数据let sum ref(0)// 这里面也可以写计算属性的哦let bigSum computed((){return sum.value * 10})// 方法function add() {sum.value 1}// 钩子hooks这里面也能写钩子的哦onMounted((){add()})// 给外部提供东西要把东西放出去让外界使用return {sum,add,bigSum}
}hooks/useDog.ts
import {reactive,onMounted} from vue
import axios from axiosexport default function (){// 数据let dogList reactive([https://images.dog.ceo/breeds/pembroke/n02113023_4373.jpg])// 方法async function getDog(){try {let result await axios.get(https://dog.ceo/api/breed/pembroke/images/random)dogList.push(result.data.message)} catch (error) {alert(error)}}// 钩子hooks这里面也能写钩子的哦onMounted((){getDog()})// 向外部提供东西return {dogList,getDog}
}4.路由
4.1 路由的基本理解 当路由变化路由器会监听到此变化就会根据路由规则找到对应的组件将这个组件展示在路由出口 4.2 基本切换效果
安装vue-router
# 现在查看package.json,发现安装的版本是【vue-router: ^4.3.2】
# 路由器是用来管理路由的, 并且当路径变化时, 根据路由规则将对应的组件 展示在路由出口处
npm install vue-router配置路由规则router/index.ts
// 创建一个路由器并暴露出去// 第一步引入createRouter
import {createRouter,createWebHistory} from vue-router
// 引入一个一个可能要呈现组件
import Home from /components/Home.vue
import News from /components/News.vue
import About from /components/About.vue// 第二步创建路由器
const router createRouter({history:createWebHistory(), //路由器的工作模式稍后讲解routes:[ //一个一个的路由规则{path:/home,component:Home},{path:/news,component:News},{path:/about,component:About},]
})// 暴露出去router
export default router
使用router路由管理器main.ts
// 引入createApp用于创建应用
import {createApp} from vue
// 引入App根组件
import App from ./App.vue
// 引入路由器
import router from ./router// 创建一个应用
const app createApp(App)
// 使用路由器
app.use(router)
// 挂载整个应用到app容器中
app.mount(#app)路由展示区App.vue
templatediv classapph2 classtitleVue路由测试/h2!-- 导航区, 使用router-link标签来切换路由路径 --div classnavigateRouterLink to/home active-classactive首页/RouterLinkRouterLink to/news active-classactive新闻/RouterLinkRouterLink to/about active-classactive关于/RouterLink/div!-- 展示区 , 使用Router-view标签作为路由出口 --div classmain-contentRouterView/RouterView/div/div
/templatescript langts setup nameAppimport {RouterView,RouterLink} from vue-router/scriptstyle/* App */.title {text-align: center;word-spacing: 5px;margin: 30px 0;height: 70px;line-height: 70px;background-image: linear-gradient(45deg, gray, white);border-radius: 10px;box-shadow: 0 0 2px;font-size: 30px;}.navigate {display: flex;justify-content: space-around;margin: 0 100px;}.navigate a {display: block;text-align: center;width: 90px;height: 40px;line-height: 40px;border-radius: 10px;background-color: gray;text-decoration: none;color: white;font-size: 18px;letter-spacing: 5px;}.navigate a.active {background-color: #64967E;color: #ffc268;font-weight: 900;text-shadow: 0 0 1px black;font-family: 微软雅黑;}.main-content {margin: 0 auto;margin-top: 30px;border-radius: 10px;width: 90%;height: 400px;border: 1px solid;}
/style
路由组件
Home.vue
templatediv classhomeimg srchttp://www.atguigu.com/images/index_new/logo.png alt/div
/templatescript setup langts nameHome/scriptstyle scoped.home {display: flex;justify-content: center;align-items: center;height: 100%;}
/styleNew.vue
templatediv classnewsullia href#新闻001/a/lilia href#新闻002/a/lilia href#新闻003/a/lilia href#新闻004/a/li/ul/div
/templatescript setup langts nameNews/scriptstyle scoped
/* 新闻 */
.news {padding: 0 20px;display: flex;justify-content: space-between;height: 100%;
}
.news ul {margin-top: 30px;list-style: none;padding-left: 10px;
}
.news lia {font-size: 18px;line-height: 40px;text-decoration: none;color: #64967E;text-shadow: 0 0 1px rgb(0, 84, 0);
}
.news-content {width: 70%;height: 90%;border: 1px solid;margin-top: 20px;border-radius: 10px;
}
/styleAbout.vue
templatediv classabouth2大家好欢迎来到尚硅谷直播间/h2/div
/templatescript setup langts nameAbout/scriptstyle scoped
.about {display: flex;justify-content: center;align-items: center;height: 100%;color: rgb(85, 84, 84);font-size: 18px;
}
/style路由切换效果图 4.3. 两个注意点
1、路由组件通常存放在pages 或 views文件夹一般组件通常存放在components文件夹。
2、通过点击导航视觉效果上“消失” 了的路由组件默认是被卸载掉的需要的时候再去挂载。
About.vue
当通过切换路由路径的方式而控制About.vue组件的显示和隐藏时会分别执行onMounted 和 onUnmounted 中定义的函数
templatediv classabouth2大家好欢迎来到尚硅谷直播间/h2/div/templatescript setup langts nameAboutimport {onMounted,onUnmounted} from vue// 挂载时执行的函数onMounted((){console.log(About组件挂载了)})// 卸载时执行的函数onUnmounted((){console.log(About组件卸载了)})
/scriptstyle scoped.about {display: flex;justify-content: center;align-items: center;height: 100%;color: rgb(85, 84, 84);font-size: 18px;}
/style4.4. 路由器工作模式 history模式 优点URL更加美观不带有#更接近传统的网站URL。 缺点后期项目上线需要服务端配合处理路径问题否则刷新会有404错误。 const router createRouter({history:createWebHistory(), //history模式/******/
})hash模式 优点兼容性更好因为不需要服务器端处理路径。 缺点URL带有#不太美观且在SEO优化方面相对较差。 const router createRouter({history:createWebHashHistory(), //hash模式/******/
})4.5. to的两种写法
!-- 第一种to的字符串写法 --
router-link active-classactive to/home主页/router-link!-- 第二种to的对象写法 --
router-link active-classactive :to{path:/home}Home/router-link4.6. 命名路由
作用可以简化路由跳转及传参后面就讲。
给路由规则命名
// 创建一个路由器并暴露出去// 第一步引入createRouter
import {createRouter,createWebHistory,createWebHashHistory} from vue-router
// 引入一个一个可能要呈现组件
import Home from /pages/Home.vue
import News from /pages/News.vue
import About from /pages/About.vue// 第二步创建路由器
const router createRouter({history:createWebHashHistory(), //路由器的工作模式稍后讲解routes:[ //一个一个的路由规则{name:zhuye,path:/home,component:Home},{name:xinwen,path:/news,component:News},{name:guanyu,path:/about,component:About},]
})// 暴露出去router
export default router跳转路由
templatediv classappHeader/!-- 导航区 --div classnavigate!--简化前需要写完整的路径to的字符串写法 --RouterLink to/home active-classactive首页/RouterLink!--简化后直接通过路由规则中定义的路由的名字route的name属性跳转to的对象写法配合name属性 --RouterLink :to{name:xinwen} active-classactive新闻/RouterLinkRouterLink :to{path:/about} active-classactive关于/RouterLink/div!-- 展示区 --div classmain-contentRouterView/RouterView/div/div
/templatescript langts setup nameAppimport {RouterView,RouterLink} from vue-routerimport Header from ./components/Header.vue/script4.7 嵌套路由
main.ts
// 引入createApp用于创建应用
import {createApp} from vue
// 引入App根组件
import App from ./App.vue
// 引入路由器
import router from ./router// 创建一个应用
const app createApp(App)
// 使用路由器
app.use(router)
// 挂载整个应用到app容器中
app.mount(#app)router/index.ts
当访问/news/detail时先根据路由规则匹配到News组件这个News组件应该要展示在App.vue中的路由出口处然后匹配到子级路由找到Detail.vue然后将Detail.vue组件展示在News组件的路由出口处。
// 创建一个路由器并暴露出去// 第一步引入createRouter
import {createRouter,createWebHistory,createWebHashHistory} from vue-router
// 引入一个一个可能要呈现组件
import Home from /pages/Home.vue
import News from /pages/News.vue
import About from /pages/About.vue
import Detail from /pages/Detail.vue// 第二步创建路由器
const router createRouter({history:createWebHistory(), //路由器的工作模式稍后讲解routes:[ //一个一个的路由规则{name:zhuye,path:/home,component:Home},{name:xinwen,path:/news,component:News,children:[{path:detail,component:Detail}]},{name:guanyu,path:/about,component:About},]
})// 暴露出去router
export default router
App.vue
在App.vue中有1个路由出口一级路由出口
templatediv classappHeader/!-- 导航区 --div classnavigateRouterLink to/home active-classactive首页/RouterLinkRouterLink :to{name:xinwen} active-classactive新闻/RouterLinkRouterLink :to{path:/about} active-classactive关于/RouterLink/div!-- 展示区 --div classmain-contentRouterView/RouterView/div/div
/templatescript langts setup nameAppimport {RouterView,RouterLink} from vue-routerimport Header from ./components/Header.vue/scriptstyle/* App */.navigate {display: flex;justify-content: space-around;margin: 0 100px;}.navigate a {display: block;text-align: center;width: 90px;height: 40px;line-height: 40px;border-radius: 10px;background-color: gray;text-decoration: none;color: white;font-size: 18px;letter-spacing: 5px;}.navigate a.active {background-color: #64967E;color: #ffc268;font-weight: 900;text-shadow: 0 0 1px black;font-family: 微软雅黑;}.main-content {margin: 0 auto;margin-top: 30px;border-radius: 10px;width: 90%;height: 400px;border: 1px solid;}
/style
News.vue
在News.vue中有1个子级路由出口
templatediv classnews!-- 导航区 --ulli v-fornews in newsList :keynews.idRouterLink to/news/detail{{news.title}}/RouterLink/li/ul!-- 展示区 --div classnews-contentRouterView/RouterView/div/div
/templatescript setup langts nameNewsimport {reactive} from vueimport {RouterView,RouterLink} from vue-routerconst newsList reactive([{id:asfdtrfay01,title:很好的抗癌食物,content:西蓝花},{id:asfdtrfay02,title:如何一夜暴富,content:学IT},{id:asfdtrfay03,title:震惊万万没想到,content:明天是周一},{id:asfdtrfay04,title:好消息好消息,content:快过年了}])/scriptstyle scoped
/* 新闻 */
.news {padding: 0 20px;display: flex;justify-content: space-between;height: 100%;
}
.news ul {margin-top: 30px;list-style: none;padding-left: 10px;
}
.news lia {font-size: 18px;line-height: 40px;text-decoration: none;color: #64967E;text-shadow: 0 0 1px rgb(0, 84, 0);
}
.news-content {width: 70%;height: 90%;border: 1px solid;margin-top: 20px;border-radius: 10px;
}
/styleDetail.vue
templateul classnews-listli编号xxx/lili标题xxx/lili内容xxx/li/ul
/templatescript setup langts nameAbout/scriptstyle scoped.news-list {list-style: none;padding-left: 20px;}.news-listli {line-height: 30px;}
/style效果
可以看到在App.vue中有1个路由出口在News.vue中也有1个路由出口 4.8 路由传参
query参数
1.定义路由规则
const router createRouter({history:createWebHistory(), //路由器的工作模式稍后讲解routes:[ //一个一个的路由规则{name:zhuye,path:/home,component:Home},{name:xinwen,path:/news,component:News,children:[{name:xiang,path:detail,component:Detail}]},{name:guanyu,path:/about,component:About}]
})2.传递参数
!-- 跳转并携带query参数to的字符串写法 --
router-link to/news/detail?a1b2content欢迎你跳转
/router-link!-- 跳转并携带query参数to的对象写法 --
RouterLink :to{//name:xiang, //用name也可以跳转path:/news/detail,query:{id:news.id,title:news.title,content:news.content}}
{{news.title}}
/RouterLink3.接收参数
import {useRoute} from vue-router
import {toRefs} from vue const route useRoute()// 从1个响应式对象直接解构属性(route是响应式对象)会丢失响应式
// 然后试图在模板中使用此query, 发现点击不同的新闻时数据没有变化, 因为在解构时这里已经丢失了响应式了
// 应该使用toRefs
// const {query} route // 应该如下使用toRefs
// 然后在模板中使用, 发现点击不同的新闻时, 数据有了变化
const {query} toRefs(route)// 打印query参数
console.log(route.query)params参数
定义路由规则并定义路由路径params参数
const router createRouter({history:createWebHistory(), //路由器的工作模式稍后讲解routes:[ //一个一个的路由规则{name:zhuye,path:/home,component:Home},{name:xinwen,path:/news,component:News,children:[{name:xiang,// 添加路径参数来占位path:detail/:id/:title/:content?, // 这里加个问号的意思是可传可不传, 否则必须传component:Detail}]},{name:guanyu,path:/about,component:About}]
})传递参数
!-- 跳转并携带params参数to的字符串写法 --
RouterLink :to/news/detail/001/新闻001/内容001{{news.title}}/RouterLink!-- 跳转并携带params参数to的对象写法 --
RouterLink :to{name:xiang, // 用name跳转, 注意这里不能用path, 并且下面的params的属性对应的值不能是对象或数组params:{id:news.id,title:news.title,content:news.title}}
{{news.title}}
/RouterLink接收参数
// useRoute是hooks钩子
import {useRoute} from vue-routerconst route useRoute()// 从1个响应式对象直接解构属性(route是响应式对象)会丢失响应式
// 然后试图在模板中使用此query, 发现点击不同的新闻时数据没有变化, 因为在解构时这里已经丢失了响应式了
// 应该使用toRefs
// const {params} route // 应该如下使用toRefs
// 然后在模板中使用, 发现点击不同的新闻时, 数据有了变化
const {params} toRefs(route)// 打印params参数
console.log(route.params)备注1传递params参数时若使用to的对象写法必须使用name配置项不能用path。 备注2传递params参数时需要提前在规则中占位。 4.9 路由的props配置
作用让路由组件更方便的收到参数可以将路由参数作为props传给组件
{name:xiang,path:detail/:id/:title/:content,component:Detail,// 第一种写法将路由收到的【所有params参数】作为props传给路由组件// props的布尔值写法作用把收到了每一组params参数作为props传给Detail组件,// 类似于: Detail :idxx :titlexx :contentxx /// 这样在Detail组件中通过defineProps([id,title,content])声明属性, // 然后在模板中直接使用id,title,content就可以访问这些属性了// props:true// 第二种写法函数写法可以自己决定将什么作为props给路由组件// props的函数写法作用把返回的对象中每一组key-value作为props传给Detail组件// 这里的形参可以不叫route, 换成其它任何名字都代表路由对象// 这样在Detail组件中通过defineProps([k])声明属性, // 然后在模板中直接使用k就可以访问k属性对应的值了, route.query中的属性也是一样props(route){return {...route.query, k:v}}// 第三种写法对象写法可以自己决定将什么作为props给路由组件// props的对象写法作用把对象中的每一组key-value作为props传给Detail组件// props:{a:1,b:2,c:3}, // 以上写法请注意, 都是在指定Detail作为路由组件展示在路由出口时, 给该【路由组件】传递的props, // 注意与直接使用Detail/标签的形式的【一般组件】区别开来
}4.10 replace属性 作用控制路由跳转时操作浏览器历史记录的模式。 浏览器的历史记录有两种写入方式分别为push和replace push是追加历史记录默认值。replace是替换当前记录。 开启replace模式 RouterLink replace to/news/detail/1News/RouterLink示例
templatediv classappHeader/!-- 导航区 --div classnavigateRouterLink to/home active-classactive首页/RouterLinkRouterLink replace :to{name:xinwen} active-classactive新闻/RouterLinkRouterLink replace :to{path:/about} active-classactive关于/RouterLink/div!-- 展示区 --div classmain-contentRouterView/RouterView/div/div
/template4.11 编程式导航
路由组件的两个重要的属性$route和$router变成了两个hooks
import {useRoute,useRouter} from vue-routerconst route useRoute()
const router useRouter()console.log(route.query)
console.log(route.parmas)// RouterLink to/标签中的to属性能怎么写, 那么router.push(..)中的参数就能怎么写
console.log(router.push)
console.log(router.replace)示例
templatediv classnews!-- 导航区 --ulli v-fornews in newsList :keynews.idbutton clickshowNewsDetail(news)查看新闻/buttonRouterLink :to{name:xiang,query:{id:news.id,title:news.title,content:news.content}}{{news.title}}/RouterLink/li/ul!-- 展示区 --div classnews-contentRouterView/RouterView/div/div
/templatescript setup langts nameNewsimport {reactive} from vueimport {RouterView,RouterLink,useRouter} from vue-routerconst newsList reactive([{id:asfdtrfay01,title:很好的抗癌食物,content:西蓝花},{id:asfdtrfay02,title:如何一夜暴富,content:学IT},{id:asfdtrfay03,title:震惊万万没想到,content:明天是周一},{id:asfdtrfay04,title:好消息好消息,content:快过年了}])const router useRouter()interface NewsInter {id:string,title:string,content:string}function showNewsDetail(news:NewsInter){router.replace({name:xiang,query:{id:news.id,title:news.title,content:news.content}})}/scriptstyle scoped
/* 新闻 */
.news {padding: 0 20px;display: flex;justify-content: space-between;height: 100%;
}
.news ul {margin-top: 30px;/* list-style: none; */padding-left: 10px;
}
.news li::marker {color: #64967E;
}
.news lia {font-size: 18px;line-height: 40px;text-decoration: none;color: #64967E;text-shadow: 0 0 1px rgb(0, 84, 0);
}
.news-content {width: 70%;height: 90%;border: 1px solid;margin-top: 20px;border-radius: 10px;
}
/style4.12 重定向 作用将特定的路径重新定向到已有路由。 具体编码 {path:/,redirect:/about
}示例
// 创建一个路由器并暴露出去// 第一步引入createRouter
import {createRouter,createWebHistory,createWebHashHistory} from vue-router// 引入一个一个可能要呈现组件
import Home from /pages/Home.vue
import News from /pages/News.vue
import About from /pages/About.vue
import Detail from /pages/Detail.vue// 第二步创建路由器
const router createRouter({history:createWebHistory(), //路由器的工作模式稍后讲解routes:[ //一个一个的路由规则{name:zhuye,path:/home,component:Home},{name:xinwen,path:/news,component:News,children:[{name:xiang,path:detail,component:Detail,props(route){return route.query}}]},{name:guanyu,path:/about,component:About},{path:/,// 使用重定向, 当用户访问/时, 跳转到/home// 即: 让指定的路径重新定位到另一个路径redirect:/home}]
})// 暴露出去router
export default router5. pinia
5.1 准备一个效果 main.ts
// 引入createApp用于创建应用
import {createApp} from vue// 引入App根组件
import App from ./App.vue// 创建一个应用
const app createApp(App)// 挂载整个应用到app容器中
app.mount(#app)App.vue
templateCount/brLoveTalk/
/templatescript setup langts nameAppimport Count from ./components/Count.vueimport LoveTalk from ./components/LoveTalk.vue
/scriptCount.vue
templatediv classcounth2当前求和为{{ sum }}/h2!-- 如果不写.number, 那么绑定所获取的值是字符串 --!-- 当然也可以这样使用v-bind来绑定, 如: option :value11/option --select v-model.numbernoption value11/optionoption value22/optionoption value33/option/selectbutton clickadd加/buttonbutton clickminus减/button/div
/templatescript setup langts nameCountimport { ref } from vue;// 数据let sum ref(1) // 当前求和let n ref(1) // 用户选择的数字// 方法function add(){sum.value n.value}function minus(){sum.value - n.value}
/scriptstyle scoped.count {background-color: skyblue;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}select,button {margin: 0 5px;height: 25px;}
/styleLoveTalk.vue
templatediv classtalkbutton clickgetLoveTalk获取一句土味情话/buttonulli v-fortalk in talkList :keytalk.id{{talk.title}}/li/ul/div
/templatescript setup langts nameLoveTalkimport {reactive} from vueimport axios from axios;import {nanoid} from nanoid// 数据let talkList reactive([{id:ftrfasdf01,title:今天你有点怪哪里怪怪好看的},{id:ftrfasdf02,title:草莓、蓝莓、蔓越莓今天想我了没},{id:ftrfasdf03,title:心里给你留了一块地我的死心塌地}])// 方法async function getLoveTalk(){// 发请求下面这行的写法是连续解构赋值重命名let {data:{content:title}} await axios.get(https://api.uomg.com/api/rand.qinghua?formatjson)// 把请求回来的字符串包装成一个对象let obj {id:nanoid(),title}// 放到数组中talkList.unshift(obj)}
/scriptstyle scoped.talk {background-color: orange;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}
/style5.2 搭建 pinia 环境
使用步骤
第一步npm install pinia此处安装的版本是“pinia”: “^2.1.7”,
第二步操作src/main.ts
import { createApp } from vueimport App from ./App.vue/* 引入createPinia用于创建pinia */
import { createPinia } from pinia/* 创建pinia */
const pinia createPinia()const app createApp(App)/* 使用插件 */
app.use(pinia)app.mount(#app)此时开发者工具中已经有了pinia选项 5.3 存储读取数据 Store是一个保存状态、业务逻辑 的实体每个组件都可以读取、写入它。 它有三个概念state、getter、action相当于组件中的 data、 computed 和 methods。
store/count.ts
import { defineStore } from pinia// defineStore返回的值的命名 格式为: use{文件名}Store
export const useCountStore defineStore(count, /* 建议这里的名字与文件名保持一直, 首字母小写 */{// 真正存储数据的地方state() { // 这个只能写成1个函数return {sum: 6}}
})store/loveTalk.ts
import {defineStore} from piniaexport const useTalkStore defineStore(talk,{// 真正存储数据的地方state(){return {talkList:[{id:ftrfasdf01,title:今天你有点怪哪里怪怪好看的},{id:ftrfasdf02,title:草莓、蓝莓、蔓越莓今天想我了没},{id:ftrfasdf03,title:心里给你留了一块地我的死心塌地}]}}
})Count.vue
templatediv classcount!-- 直接使用countStore --h2当前求和为{{ countStore.sum }}/h2select v-model.numbernoption value11/optionoption value22/optionoption value33/option/selectbutton clickadd加/buttonbutton clickminus减/button/div
/templatescript setup langts nameCountimport { ref, reactive } from vue;import { useCountStore } from /store/countconst countStore useCountStore()// 以下两种方式都可以拿到state中的数据// console.log(,countStore.sum) // 注意: 这里后面不要写.value哦, 因为会自动拆包// console.log(,countStore.$state.sum) // 也可以通过$state拿到sum/* let obj reactive({a:1,b:2,c:ref(3)})let x ref(9)console.log(obj.a)console.log(obj.b)console.log(obj.c) // 注意, 这里最后面就不用.value了*/// 数据let n ref(1) // 用户选择的数字// 方法function add() {}function minus() {}
/scriptstyle scoped.count {background-color: skyblue;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}select,button {margin: 0 5px;height: 25px;}
/styleLoveTalk.vue
templatediv classtalkbutton clickgetLoveTalk获取一句土味情话/buttonulli v-fortalk in talkStore.talkList :keytalk.id{{talk.title}}/li/ul/div
/templatescript setup langts nameLoveTalkimport {reactive} from vueimport axios from axios;import {nanoid} from nanoidimport {useTalkStore} from /store/loveTalkconst talkStore useTalkStore()// 方法async function getLoveTalk(){// 发请求下面这行的写法是连续解构赋值重命名// let {data:{content:title}} await // axios.get(https://api.uomg.com/api/rand.qinghua?formatjson)// 把请求回来的字符串包装成一个对象// let obj {id:nanoid(),title}// 放到数组中// talkList.unshift(obj)}
/scriptstyle scoped.talk {background-color: orange;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}
/styleApp.vue
templateCount/brLoveTalk/
/templatescript setup langts nameAppimport Count from ./components/Count.vueimport LoveTalk from ./components/LoveTalk.vue
/scriptmain.ts
import {createApp} from vue
import App from ./App.vue
// 第一步引入pinia
import {createPinia} from piniaconst app createApp(App)
// 第二步创建pinia
const pinia createPinia()
// 第三步安装pinia
app.use(pinia)
app.mount(#app)5.4 修改数据(三种方式)
第一种方式
count.ts
import {defineStore} from piniaexport const useCountStore defineStore(count,{// 真正存储数据的地方state(){return {sum:6,school:atguigu,address:宏福科技园}}
})Count.vue
templatediv classcounth2当前求和为{{ countStore.sum }}/h2button clickadd加/button/div
/templatescript setup langts nameCountimport { ref, reactive } from vue;// 引入useCountStoreimport { useCountStore } from /store/count// 使用useCountStore得到一个专门保存count相关的storeconst countStore useCountStore()// 数据let n ref(1) // 用户选择的数字// 方法function add() {// 第一种修改方式, 直接拿到countStore去改, 注意: 这和vuex不同, vuex是不能直接修改的countStore.sum 1countStore.school 尚硅谷countStore.address 北京}
/scriptstyle scoped.count {background-color: skyblue;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}select,button {margin: 0 5px;height: 25px;}
/style第二种方式
count.ts
import {defineStore} from piniaexport const useCountStore defineStore(count,{// 真正存储数据的地方state(){return {sum:6,school:atguigu,address:宏福科技园}}
})Count.vue
templatediv classcounth2当前求和为{{ countStore.sum }}/h2h3欢迎来到:{{ countStore.school }}坐落于{{ countStore.address }}/h3select v-model.numbernoption value11/optionoption value22/optionoption value33/option/selectbutton clickadd加/buttonbutton clickminus减/button/div
/templatescript setup langts nameCountimport { ref,reactive } from vue;// 引入useCountStoreimport {useCountStore} from /store/count// 使用useCountStore得到一个专门保存count相关的storeconst countStore useCountStore()// 数据let n ref(1) // 用户选择的数字// 方法function add(){// 第二种修改方式如果很多数据都要统一一次性发生变化推荐使用$patchcountStore.$patch({sum:888,school:尚硅谷,address:北京})}function minus(){}
/scriptstyle scoped.count {background-color: skyblue;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}select,button {margin: 0 5px;height: 25px;}
/style第三种方式
count.ts
import {defineStore} from piniaexport const useCountStore defineStore(count,{// actions里面放置的是一个一个的方法用于响应组件中的“动作”// 使用actions的意义在于可以将对组件共享数据统一操作的逻辑抽取放到这里actions:{increment(value){ // value是调用方传过来的值console.log(increment被调用了,value)if( this.sum 10){// 修改数据this是当前的storethis.sum value}}},// 真正存储数据的地方state(){return {sum:6,school:atguigu,address:宏福科技园}}
})Count.vue
templatediv classcounth2当前求和为{{ countStore.sum }}/h2h3欢迎来到:{{ countStore.school }}坐落于{{ countStore.address }}/h3select v-model.numbernoption value11/optionoption value22/optionoption value33/option/selectbutton clickadd加/buttonbutton clickminus减/button/div
/templatescript setup langts nameCountimport { ref,reactive } from vue;// 引入useCountStoreimport {useCountStore} from /store/count// 使用useCountStore得到一个专门保存count相关的storeconst countStore useCountStore()// 数据let n ref(1) // 用户选择的数字// 方法function add(){// 第三种修改方式直接调用count.ts中定义的actions方法const result countStore.increment(n.value)console.log(result, result); // result undefined}function minus(){}
/scriptstyle scoped.count {background-color: skyblue;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}select,button {margin: 0 5px;height: 25px;}
/style5.5 storeToRefs用法
借助storeToRefs将store中的数据转为ref对象方便在模板中使用。注意pinia提供的storeToRefs只会将数据做转换而Vue的toRefs会转换store中数据虽然能实现功能单不建议使用哦。
LoveTalk.ts
import {defineStore} from pinia
import axios from axios
import {nanoid} from nanoidexport const useTalkStore defineStore(talk,{actions:{async getATalk(){// 发请求下面这行的写法是连续解构赋值重命名let {data:{content:title}} await axios.get(https://api.uomg.com/api/rand.qinghua?formatjson)// 把请求回来的字符串包装成一个对象let obj {id:nanoid(),title}// 放到数组中this.talkList.unshift(obj)}},// 真正存储数据的地方state(){return {talkList:[{id:ftrfasdf01,title:今天你有点怪哪里怪怪好看的},{id:ftrfasdf02,title:草莓、蓝莓、蔓越莓今天想我了没},{id:ftrfasdf03,title:心里给你留了一块地我的死心塌地}]}}
})LoveTask.vue
templatediv classtalkbutton clickgetLoveTalk获取一句土味情话/buttonulli v-fortalk in talkList :keytalk.id{{talk.title}}/li/ul/div
/templatescript setup langts nameLoveTalkimport {useTalkStore} from /store/loveTalkimport { storeToRefs } from pinia;const talkStore useTalkStore()// 这里如果直接这样解构写: const {talkList} taskStore; 那么此时这里的talkList就已经丢失了响应式// 这里虽然也可以写: const {talkList} toRefs(taskStore); 虽然可以维持talkList的响应式, 但代价过大,// toRefs会把talkStore中的全部数据包括函数state啥的都给包了一遍// 所以最好使用storeToRefs, 因为storeToRefs只会关注sotre中数据不会对方法进行ref包裹const {talkList} storeToRefs(talkStore)// 方法function getLoveTalk(){talkStore.getATalk()}
/scriptstyle scoped.talk {background-color: orange;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}
/stylecount.ts
import {defineStore} from piniaexport const useCountStore defineStore(count,{// actions里面放置的是一个一个的方法用于响应组件中的“动作”actions:{increment(value:number){console.log(increment被调用了,value)if( this.sum 10){// 修改数据this是当前的storethis.sum value}}},// 真正存储数据的地方state(){return {sum:1,school:atguigu,address:宏福科技园}}
})Count.vue
templatediv classcounth2当前求和为{{ sum }}/h2h3欢迎来到:{{ school }}坐落于{{ address }}/h3select v-model.numbernoption value11/optionoption value22/optionoption value33/option/selectbutton clickadd加/buttonbutton clickminus减/button/div
/templatescript setup langts nameCountimport { ref,reactive,toRefs } from vue;import {storeToRefs} from pinia// 引入useCountStoreimport {useCountStore} from /store/count// 使用useCountStore得到一个专门保存count相关的storeconst countStore useCountStore()// storeToRefs只会关注sotre中数据不会对方法进行ref包裹const {sum,school,address} storeToRefs(countStore)// console.log(!!!!!,storeToRefs(countStore))// 数据let n ref(1) // 用户选择的数字// 方法function add(){countStore.increment(n.value)}function minus(){countStore.sum - n.value}
/scriptstyle scoped.count {background-color: skyblue;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}select,button {margin: 0 5px;height: 25px;}
/style5.6 getters用法
概念当state中的数据需要经过处理后再使用时可以使用getters配置。
count.ts
import {defineStore} from piniaexport const useCountStore defineStore(count,{// actions里面放置的是一个一个的方法用于响应组件中的“动作”actions:{increment(value:number){console.log(increment被调用了,value)if( this.sum 10){// 修改数据this是当前的storethis.sum value}}},// 真正存储数据的地方state(){return {sum:3,school:atguigu,address:宏福科技园}},getters:{bigSum:state state.sum * 10,upperSchool():string{return this.school.toUpperCase()}}
})Count.vue
templatediv classcounth2当前求和为{{ sum }}放大10倍后{{ bigSum }}/h2h3欢迎来到:{{ school }}坐落于{{ address }}大写{{ upperSchool }}/h3select v-model.numbernoption value11/optionoption value22/optionoption value33/option/selectbutton clickadd加/buttonbutton clickminus减/button/div
/templatescript setup langts nameCountimport { ref,reactive,toRefs } from vue;import {storeToRefs} from pinia// 引入useCountStoreimport {useCountStore} from /store/count// 使用useCountStore得到一个专门保存count相关的storeconst countStore useCountStore()// storeToRefs只会关注sotre中数据不会对方法进行ref包裹, 并且同时维持解构属性结果的响应式// 可以直接解构出state和getters中定义的数据const {sum,school,address,bigSum,upperSchool} storeToRefs(countStore)// console.log(!!!!!,storeToRefs(countStore))// 数据let n ref(1) // 用户选择的数字// 方法function add(){countStore.increment(n.value)}function minus(){countStore.sum - n.value}
/scriptstyle scoped.count {background-color: skyblue;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}select,button {margin: 0 5px;height: 25px;}
/style5.7 $subscribe的使用
通过 store 的 $subscribe() 方法侦听 state 及其变化
loveTalk.ts
import {defineStore} from pinia
import axios from axios
import {nanoid} from nanoidexport const useTalkStore defineStore(talk,{actions:{async getATalk(){// 发请求下面这行的写法是连续解构赋值重命名let {data:{content:title}} await axios.get(https://api.uomg.com/api/rand.qinghua?formatjson)// 把请求回来的字符串包装成一个对象let obj {id:nanoid(),title}// 放到数组中this.talkList.unshift(obj)}},// 真正存储数据的地方state(){return {talkList:JSON.parse(localStorage.getItem(talkList) as string) || []}}
})LoveTalk.vue
templatediv classtalkbutton clickgetLoveTalk获取一句土味情话/buttonulli v-fortalk in talkList :keytalk.id{{ talk.title }}/li/ul/div
/templatescript setup langts nameLoveTalkimport { useTalkStore } from /store/loveTalkimport { storeToRefs } from pinia;const talkStore useTalkStore()const { talkList } storeToRefs(talkStore)talkStore.$subscribe((mutate, state) {// 注意: 箭头函数中没有thisconsole.log(talkStore里面保存的数据发生了变化, mutate, state)// 实现页面刷新时, 这里的talkList不丢失, 因为在loveTalk.ts中会取localStorage中读取talkList数据localStorage.setItem(talkList, JSON.stringify(state.talkList))})// 方法function getLoveTalk() {talkStore.getATalk()}
/scriptstyle scoped.talk {background-color: orange;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}
/style5.8 store组合式写法
loveTalk.js
import {defineStore} from pinia
import axios from axios
import {nanoid} from nanoid/* export const useTalkStore defineStore(talk,{actions:{async getATalk(){// 发请求下面这行的写法是连续解构赋值重命名let {data:{content:title}} await axios.get(https://api.uomg.com/api/rand.qinghua?formatjson)// 把请求回来的字符串包装成一个对象let obj {id:nanoid(),title}// 放到数组中this.talkList.unshift(obj)}},// 真正存储数据的地方state(){return {talkList:JSON.parse(localStorage.getItem(talkList) as string) || []}}
})*/import {reactive} from vue
export const useTalkStore defineStore(talk,(){// talkList就是stateconst talkList reactive(JSON.parse(localStorage.getItem(talkList) as string) || [])// getATalk函数相当于actionasync function getATalk(){// 发请求下面这行的写法是连续解构赋值重命名let {data:{content:title}} await axios.get(https://api.uomg.com/api/rand.qinghua?formatjson)// 把请求回来的字符串包装成一个对象let obj {id:nanoid(),title}// 放到数组中talkList.unshift(obj)}return {talkList,getATalk}
})LoveTalk.vue
templatediv classtalkbutton clickgetLoveTalk获取一句土味情话/buttonulli v-fortalk in talkList :keytalk.id{{talk.title}}/li/ul/div
/templatescript setup langts nameLoveTalkimport {useTalkStore} from /store/loveTalkimport { storeToRefs } from pinia;const talkStore useTalkStore()const {talkList} storeToRefs(talkStore)talkStore.$subscribe((mutate,state){console.log(talkStore里面保存的数据发生了变化,mutate,state)localStorage.setItem(talkList,JSON.stringify(state.talkList))})// 方法function getLoveTalk(){talkStore.getATalk()}
/scriptstyle scoped.talk {background-color: orange;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}
/style6. 组件通信
6.1 props
概述props是使用频率最高的一种通信方式常用与 父 ↔ 子。
若 父传子属性值是非函数。若 子传父属性值是函数。
这种不适合父子孙中父给孙组件传递数据或者兄弟组件也可以找到同1个父组件来实现兄弟组件通信
Father.vue
templatediv classfatherh3父组件/h3h4汽车{{ car }}/h4h4 v-showtoy子给的玩具{{ toy }}/h4Child :carcar :sendToygetToy //div
/templatescript setup langts nameFatherimport Child from ./Child.vueimport { ref } from vue// 数据let car ref(奔驰)let toy ref()// 方法function getToy(value: string) {toy.value value}/scriptstyle scoped.father {background-color: rgb(165, 164, 164);padding: 20px;border-radius: 10px;}
/styleChild.vue
templatediv classchildh3子组件/h3h4玩具{{ toy }}/h4h4父给的车{{ car }}/h4button clicksendToy(toy)把玩具给父亲/button/div
/templatescript setup langts nameChildimport { ref } from vue// 数据let toy ref(奥特曼)// 声明接收propsdefineProps([car, sendToy])/scriptstyle scoped.child {background-color: skyblue;padding: 10px;box-shadow: 0 0 10px black;border-radius: 10px;}
/style6.2 自定义事件
Father.vue
templatediv classfatherh3父组件/h3h4 v-showtoy子给的玩具{{ toy }}/h4!-- 给子组件Child绑定事件 --Child send-toysaveToy //div
/templatescript setup langts nameFatherimport Child from ./Child.vueimport { ref } from vue;// 数据let toy ref()// 用于保存传递过来的玩具function saveToy(value: string,e:any) {console.log(saveToy, value, e)toy.value value}/scriptstyle scoped.father {background-color: rgb(165, 164, 164);padding: 20px;border-radius: 10px;}.father button {margin-right: 5px;}
/styleChild.vue
templatediv classchildh3子组件/h3h4玩具{{ toy }}/h4!-- 在模板中可以使用$event来代表事件对象 --button clickemit(send-toy, toy, $event)测试/button/div
/templatescript setup langts nameChildimport { ref } from vue;// 数据let toy ref(奥特曼)// 声明事件const emit defineEmits([send-toy])/scriptstyle scoped.child {margin-top: 10px;background-color: rgb(76, 209, 76);padding: 10px;box-shadow: 0 0 10px black;border-radius: 10px;}
/style6.3 mitt
概述与消息订阅与发布pubsub功能类似可以实现任意组件间通信。
安装mittnpm install mitt版本是“mitt”: “^3.0.1”
emitter.ts
// 引入mitt
import mitt from mitt// 调用mitt得到emitteremitter能绑定事件、触发事件
const emitter mitt()/*
// 绑定事件
emitter.on(test1,(){console.log(test1被调用了)
})
emitter.on(test2,(){console.log(test2被调用了)
})// 触发事件
setInterval(() {emitter.emit(test1)emitter.emit(test2)
}, 1000);setTimeout(() {// emitter.off(test1)// emitter.off(test2)emitter.all.clear()
}, 3000);
*/// 暴露emitter
export default emitterFather.vue
templatediv classfatherh3父组件/h3Child1/Child2//div
/templatescript setup langts nameFatherimport Child1 from ./Child1.vueimport Child2 from ./Child2.vue
/scriptstyle scoped.father{background-color:rgb(165, 164, 164);padding: 20px;border-radius: 10px;}.father button{margin-left: 5px;}
/styleChild1.vue
templatediv classchild1h3子组件1/h3h4玩具{{ toy }}/h4button clickemitter.emit(send-toy,toy)玩具给弟弟/button/div
/templatescript setup langts nameChild1import {ref} from vueimport emitter from /utils/emitter;// 数据let toy ref(奥特曼)
/scriptstyle scoped.child1{margin-top: 50px;background-color: skyblue;padding: 10px;box-shadow: 0 0 10px black;border-radius: 10px;}.child1 button{margin-right: 10px;}
/styleChild2.vue
templatediv classchild2h3子组件2/h3h4电脑{{ computer }}/h4h4哥哥给的玩具{{ toy }}/h4/div
/templatescript setup langts nameChild2import { ref, onUnmounted } from vueimport emitter from /utils/emitter;// 数据let computer ref(联想)let toy ref()// 给emitter绑定send-toy事件emitter.on(send-toy, (value: any) {toy.value value})// 在组件卸载时解绑send-toy事件onUnmounted(() {emitter.off(send-toy)})
/scriptstyle scoped.child2 {margin-top: 50px;background-color: orange;padding: 10px;box-shadow: 0 0 10px black;border-radius: 10px;}
/style6.4 v-model
Father.vue
templatediv classfatherh3父组件/h3h4{{ username }}/h4h4{{ password }}/h4!-- v-model用在html标签上 --!-- input typetext v-modelusername --!-- input typetext :valueusername inputusername (HTMLInputElement$event.target).value --!-- v-model用在组件标签上 --!-- AtguiguInput v-modelusername/ --!-- 上面这行等价于下面这行 --!-- $event到底是啥? 啥时候能.target对于原生事件, $event就是事件对象 能.target对于自定义事件, $event就是触发事件时, 所传递的数据 不能.target--!-- AtguiguInput :modelValueusername update:modelValueusername $event/ --!-- 修改modelValue --AtguiguInput v-model:mingusername v-model:mimapassword//div
/templatescript setup langts nameFatherimport { ref } from vue;import AtguiguInput from ./AtguiguInput.vue// 数据let username ref(zhansgan)let password ref(123456)
/scriptstyle scoped.father {padding: 20px;background-color: rgb(165, 164, 164);border-radius: 10px;}
/style
AtguiguInput.vue
templateinput typetext :valueminginputemit(update:ming,(HTMLInputElement$event.target).value)brinput typetext :valuemimainputemit(update:mima,(HTMLInputElement$event.target).value)
/templatescript setup langts nameAtguiguInputdefineProps([ming,mima])const emit defineEmits([update:ming,update:mima])/scriptstyle scopedinput {border: 2px solid black;background-image: linear-gradient(45deg,red,yellow,green);height: 30px;font-size: 20px;color: white;}
/style6.5 $attrs 概述$attrs用于实现**当前组件的父组件向当前组件的子组件**通信祖→孙。 具体说明$attrs是一个对象包含所有父组件传入的标签属性。 注意$attrs会自动排除props中声明的属性(可以认为声明过的 props 被子组件自己“消费”了) 就是父组件给子组件通过标签的属性方式传递给子组件子组件使用props的方式只接收了部分属性其它没有接收的属性可以通过子组件的$attrs来访问
Father.vue
templatediv classfatherh3父组件/h3h4a{{a}}/h4h4b{{b}}/h4h4c{{c}}/h4h4d{{d}}/h4!-- v-bind{x:100,y:200}就等价: :x100 :y200 --Child :aa :bb :cc :dd :ee v-bind{x:100,y:200} :updateAupdateA//div
/templatescript setup langts nameFatherimport Child from ./Child.vueimport {ref} from vuelet a ref(1)let b ref(2)let c ref(3)let d ref(4)let e ref(5)function updateA(value:number){a.value value}
/scriptstyle scoped.father{background-color: rgb(165, 164, 164);padding: 20px;border-radius: 10px;}
/style
Child.vue
templatediv classchildh3子组件/h3h4{{ e }}/h4!-- Father组件传给Child组件的属性,但是Father组件没有使用props接收的属性,就存在$attrs中 --h4{{ $attrs }}/h4!-- Father组件传给Child组件的属性,但是Father组件没有使用props接收的属性,全部传递给GrandChild组件--GrandChild v-bind$attrs//div
/templatescript setup langts nameChildimport GrandChild from ./GrandChild.vuedefineProps([e])
/scriptstyle scoped.child{margin-top: 20px;background-color: skyblue;padding: 20px;border-radius: 10px;box-shadow: 0 0 10px black;}
/styleGrandChild.vue
templatediv classgrand-childh3孙组件/h3h4a{{ a }}/h4h4b{{ b }}/h4h4c{{ c }}/h4h4d{{ d }}/h4h4x{{ x }}/h4h4y{{ y }}/h4!-- Father组件通过Child组件的v-bind$attr将函数传给GrandChild组件,这样GrandChild组件就可以通过此函数传递数据给Father组件了 --button clickupdateA(6)点我将爷爷那的a更新/button/div
/templatescript setup langts nameGrandChild// 接收Father组件传递过来并由Child组件通过v-bind$attr中转过来的属性defineProps([a,b,c,d,x,y,updateA])
/scriptstyle scoped.grand-child{margin-top: 20px;background-color: orange;padding: 20px;border-radius: 10px;box-shadow: 0 0 10px black;}
/style6.6 r e f s 、 refs、 refs、parent、proxy 概述 $refs用于 父→子。$parent用于子→父。 原理如下 属性说明$refs值为对象包含所有被ref属性标识的DOM元素或组件实例。$parent值为对象当前组件的父组件实例对象。
Father.vue
templatediv classfatherh3父组件/h3h4房产{{ house }}/h4button clickchangeToy修改Child1的玩具/buttonbutton clickchangeComputer修改Child2的电脑/button!-- 在模板中可以直接使用$refs --button clickgetAllChild($refs)让所有孩子的书变多/buttonbutton clickgetAllChild2()让c1孩子的书变多2/buttonbutton clickgetAllChild3()让c1孩子的书变多3/buttonChild1 refc1/Child2 refc2//div
/templatescript setup langts nameFatherimport Child1 from ./Child1.vueimport Child2 from ./Child2.vueimport { ref,reactive } from vue;import { getCurrentInstance } from vue;const proxy getCurrentInstance()let c1 ref()let c2 ref()// 注意点当访问obj.c的时候底层会自动读取value属性因为c是在obj这个响应式对象中的/* let obj reactive({a:1,b:2,c:ref(3)})let x ref(4)console.log(obj.a)console.log(obj.b)console.log(obj.c)console.log(x) */// 数据let house ref(4)// 方法function changeToy(){// 必须要Child1组件通过defineExpose将toy属性暴露出来, 这样Father组件才能访问到并修改此toy属性c1.value.toy 小猪佩奇}function changeComputer(){c2.value.computer 华为}function getAllChild(refs:{[key:string]:any}){console.log(refs)for (let key in refs){// 这里不需要refs[key].value.book 3, 是因为refs本身就是个响应式对象, 它会自动解包refs[key].book 3}}function getAllChild2(){// 使用getCurrentInstance来访问感觉更加方便console.log(proxy);console.log(proxy.refs); // {c1: Proxy(Object), c2: Proxy(Object)}console.log(proxy.parent); // {uid: 0, vnode: {…}, type: {…}, parent: null, // appContext: {…}, …}console.log(proxy.attrs); // {__vInternal: 1}proxy.refs.c1.book 2}function getAllChild3(){// console.log($refs); // 注意, 在vue3的setup语法糖中不能直接访问到$refs// console.log(this.$refs); // 注意, 在vue3的setup语法糖中不能直接访问到$refsconsole.log(this.proxy); // 这个等价于getCurrentInstance()返回的值console.log(this.proxy proxy); // trueconsole.log(this.c1); // 这里可以直接访问到refc1标识的组件this.c1.book 2}// 向外部提供数据defineExpose({house})/scriptstyle scoped.father {background-color: rgb(165, 164, 164);padding: 20px;border-radius: 10px;}.father button {margin-bottom: 10px;margin-left: 10px;}
/styleChild1.vue
templatediv classchild1h3子组件1/h3h4玩具{{ toy }}/h4h4书籍{{ book }} 本/h4button clickminusHouse($parent)干掉父亲的一套房产/buttonbutton clickminusHouse2()干掉父亲的一套房产2/buttonbutton clickminusHouse3()干掉父亲的一套房产3/button/div/templatescript setup langts nameChild1import { ref,getCurrentInstance } from vue;const proxy getCurrentInstance()// 数据let toy ref(奥特曼)let book ref(3)// 方法function minusHouse(parent:any){// 需要Father组件通过defineExpose将house属性暴露出来, 这里才可以访问到parent.house - 1}function minusHouse2(){// 需要Father组件通过defineExpose将house属性暴露出来, 这里才可以访问到console.log(proxy);console.log(proxy.parent);console.log(proxy.parent.exposed);proxy.parent.exposed.house.value - 1}function minusHouse3(){// 需要Father组件通过defineExpose将house属性暴露出来, 这里才可以访问到console.log(this); // Proxy(Object) {proxy: {…}, minusHouse: ƒ, minusHouse2: ƒ, …console.log(this.parent); // undefinedconsole.log(this.proxy); // 这个等价于getCurrentInstance()返回的值console.log(this.proxy proxy); // true}// 把数据交给外部defineExpose({toy,book})/scriptstyle scoped.child1{margin-top: 20px;background-color: skyblue;padding: 20px;border-radius: 10px;box-shadow: 0 0 10px black;}
/styleChild2.vue
templatediv classchild2h3子组件2/h3h4电脑{{ computer }}/h4h4书籍{{ book }} 本/h4/div
/templatescript setup langts nameChild2import { ref } from vue;// 数据let computer ref(联想)let book ref(6)// 把数据交给外部defineExpose({ computer, book })/scriptstyle scoped.child2 {margin-top: 20px;background-color: orange;padding: 20px;border-radius: 10px;box-shadow: 0 0 10px black;}
/style6.7 provide、inject 概述实现祖孙组件直接通信 具体使用 在祖先组件中通过provide配置向后代组件提供数据在后代组件中通过inject配置来声明接收数据
Father.vue
templatediv classfatherh3父组件/h3h4银子{{ money }}万元/h4h4车子一辆{{car.brand}}车价值{{car.price}}万元/h4Child//div
/templatescript setup langts nameFatherimport Child from ./Child.vueimport {ref,reactive,provide} from vuelet money ref(100)let car reactive({brand:奔驰,price:100})function updateMoney(value:number){money.value - value}// 向后代提供数据provide(moneyContext,{money,updateMoney})// 注意数据的后面不要.value, 否则不具备响应式provide(car,car)/scriptstyle scoped.father {background-color: rgb(165, 164, 164);padding: 20px;border-radius: 10px;}
/styleChild.vue
templatediv classchildh3我是子组件/h3GrandChild//div
/templatescript setup langts nameChildimport GrandChild from ./GrandChild.vue
/scriptstyle scoped.child {margin-top: 20px;background-color: skyblue;padding: 20px;border-radius: 10px;box-shadow: 0 0 10px black;}
/styleGrandChild.vue
templatediv classgrand-childh3我是孙组件/h3h4银子{{ money }}/h4h4车子一辆{{car.brand}}车价值{{car.price}}万元/h4button clickupdateMoney(6)花爷爷的钱/button/div
/templatescript setup langts nameGrandChildimport { inject } from vue;let {money,updateMoney} inject(moneyContext,{money:0,updateMoney:(param:number){}})// 第二个参数的含义是: 如果没有提供car, 那么就把第二个参数作为默认值这样可以避免使用car时模板中红色波浪线let car inject(car,{brand:未知,price:0})/scriptstyle scoped.grand-child{background-color: orange;padding: 20px;border-radius: 10px;box-shadow: 0 0 10px black;}
/style6.8 pinia
直接参考pinia章节即可。
6.9 slot插槽
1. 默认插槽 Father.vue
templatediv classfatherh3父组件/h3div classcontentCategory title热门游戏列表ulli v-forg in games :keyg.id{{ g.name }}/li/ul/CategoryCategory title今日美食城市img :srcimgUrl alt/CategoryCategory title今日影视推荐video :srcvideoUrl controls/video/Category/div/div
/templatescript setup langts nameFatherimport Category from ./Category.vueimport { ref,reactive } from vue;let games reactive([{id:asgytdfats01,name:英雄联盟},{id:asgytdfats02,name:王者农药},{id:asgytdfats03,name:红色警戒},{id:asgytdfats04,name:斗罗大陆}])let imgUrl ref(https://z1.ax1x.com/2023/11/19/piNxLo4.jpg)let videoUrl ref(http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4)/scriptstyle scoped.father {background-color: rgb(165, 164, 164);padding: 20px;border-radius: 10px;}.content {display: flex;justify-content: space-evenly;}img,video {width: 100%;}
/styleCategory.vue
templatediv classcategoryh2{{title}}/h2!-- 1. 如果父组件在使用当前组件时, 父组件标签中没有传入内容, 那么这里就显示“默认内容” 2. 如果这里这里写多个slot, 那么父组件标签中传入的内容就会在每个slot地方都展示一遍3. 其实, 这里省略了name属性, 它的默认值为default, 即这里相当于: slot namedefault默认内容/slot--slot默认内容/slot!-- 这里同样会再展示一遍 --slot namedefault默认内容/slot/div/templatescript setup langts nameCategorydefineProps([title])/scriptstyle scoped.category {background-color: skyblue;border-radius: 10px;box-shadow: 0 0 10px;padding: 10px;width: 200px;height: 300px;}h2 {background-color: orange;text-align: center;font-size: 20px;font-weight: 800;}
/style2. 具名插槽
Father.vue
templatediv classfatherh3父组件/h3div classcontentCategory!-- v-slot只能用在组件标签上 或者 template标签中 --template v-slot:s2ul!-- Category标签中的内容可以直接使用Father组件中的数据 --li v-forg in games :keyg.id{{ g.name }}/li/ul/templatetemplate v-slot:s1h2热门游戏列表/h2/template/Category!-- 还可以直接把v-slot直接写在组件上, 它将会把内部的所有内容都塞到s2的插槽中 --Category v-slot:s2ul!-- Category标签中的内容可以直接使用Father组件中的数据 --li v-forg in games :keyg.id{{ g.name }}/li/ul/CategoryCategorytemplate v-slot:s2img :srcimgUrl alt/templatetemplate v-slot:s1h2今日美食城市/h2/template/Category!-- 简写写法 --Categorytemplate #s2!-- Category标签中的内容可以直接使用Father组件中的数据 --video video :srcvideoUrl controls/video/templatetemplate #s1h2今日影视推荐/h2/template/Category/div/div
/templatescript setup langts nameFatherimport Category from ./Category.vueimport { ref,reactive } from vue;let games reactive([{id:asgytdfats01,name:英雄联盟},{id:asgytdfats02,name:王者农药},{id:asgytdfats03,name:红色警戒},{id:asgytdfats04,name:斗罗大陆}])let imgUrl ref(https://z1.ax1x.com/2023/11/19/piNxLo4.jpg)let videoUrl ref(http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4)/scriptstyle scoped.father {background-color: rgb(165, 164, 164);padding: 20px;border-radius: 10px;}.content {display: flex;justify-content: space-evenly;}img,video {width: 100%;}h2 {background-color: orange;text-align: center;font-size: 20px;font-weight: 800;}
/styleCategory.vue
templatediv classcategoryslot names1默认内容1/slotslot names2默认内容2/slot/div
/templatescript setup langts nameCategory/scriptstyle scoped.category {background-color: skyblue;border-radius: 10px;box-shadow: 0 0 10px;padding: 10px;width: 200px;height: 300px;}
/style3. 作用域插槽
理解数据在组件的自身但根据数据生成的结构需要组件的使用者来决定。新闻数据在News组件中但使用数据所遍历出来的结构由App组件决定
Father.vue
templatediv classfatherh3父组件/h3div classcontentGame!-- 这里的params可以拿到所有子组件中传给slot插槽标签的所有属性和对应的值 --!-- 形成的效果就是: 结构是由父组件决定的, 而数据的提供者是子组件(至于子组件的这个数据哪来的就不用管了, 反正就是有); 或者换句话说: 父组件通过插槽的方式“直接”访问到了子组件通过插槽传递的数据;--!-- 这里默认其实是: v-slot:defaultparams--template v-slotparamsulli v-fory in params.youxi :keyy.id{{ y.name }}/li/ul/template/GameGametemplate v-slotparamsolli v-foritem in params.youxi :keyitem.id{{ item.name }}/li/ol/template/GameGametemplate #default{youxi}h3 v-forg in youxi :keyg.id{{ g.name }}/h3/template/Game/div/div
/templatescript setup langts nameFatherimport Game from ./Game.vue
/scriptstyle scoped.father {background-color: rgb(165, 164, 164);padding: 20px;border-radius: 10px;}.content {display: flex;justify-content: space-evenly;}img,video {width: 100%;}
/styleCategory.vue
templatediv classgameh2游戏列表/h2!-- 给插槽提供数据 --slot :youxigames x哈哈 y你好/slot/div
/templatescript setup langts nameGameimport {reactive} from vuelet games reactive([{id:asgytdfats01,name:英雄联盟},{id:asgytdfats02,name:王者农药},{id:asgytdfats03,name:红色警戒},{id:asgytdfats04,name:斗罗大陆}])/scriptstyle scoped.game {width: 200px;height: 300px;background-color: skyblue;border-radius: 10px;box-shadow: 0 0 10px;}h2 {background-color: orange;text-align: center;font-size: 20px;font-weight: 800;}
/style7. 其它 API
7.1 shallowRef 与 shallowReactive
shallowRef 作用创建一个响应式数据但只对顶层属性进行响应式处理。 用法 let myVar shallowRef(initialValue);特点只跟踪引用值的变化不关心值内部的属性变化。
shallowReactive 作用创建一个浅层响应式对象只会使对象的最顶层属性变成响应式的对象内部的嵌套属性则不会变成响应式的 用法 const myObj shallowReactive({ ... });特点对象的顶层属性是响应式的但嵌套对象的属性不是。
总结
通过使用 shallowRef() 和 shallowReactive() 来绕开深度响应。浅层式 API 创建的状态只在其顶层是响应式的对所有深层的对象不会做任何处理避免了对每一个内部属性做响应式所带来的性能成本这使得属性的访问变得更快可提升性能。
示例
templatediv classapph2求和为{{ sum }}/h2h2名字为{{ person.name }}/h2h2年龄为{{ person.age }}/h2h2汽车为{{ car }}/h2button clickchangeSumsum1/buttonbutton clickchangeName修改名字/buttonbutton clickchangeAge修改年龄/buttonbutton clickchangePerson修改整个人/buttonspan|/spanbutton clickchangeBrand修改品牌/buttonbutton clickchangeColor修改颜色/buttonbutton clickchangeEngine修改发动机/button/div
/templatescript setup langts nameAppimport { ref, reactive, shallowRef, shallowReactive } from vuelet sum shallowRef(0)let person shallowRef({name: 张三,age: 18})/* 如果使用ref来定义sum和person, 那么下面的方法被调用时, 数据都会发生改变, 并且都会有响应式;但因为使用shallowRef定义, 因此只有第1层修改才会数据发生改变, 具有响应式, 第1层指的是xxx.value, 不能再点下去了, 否则就不是第1层了*/function changeSum() {sum.value 1 // 数据发生改变, 有响应式}function changeName() {person.value.name 李四 // 数据未发生改变}function changeAge() {person.value.age 1 // 数据未发生改变}function changePerson() {person.value { name: tony, age: 100 } // 数据发生改变, 有响应式}/* ****************** *//* 如果使用reactive来定义car, 那么下面的方法被调用时, 数据都会发生改变, 并且都会有响应式;但因为使用shallowReactive定义, 因此只有第1层修改才会数据发生改变, 具有响应式, 第1层指的是brand和options, 不能再点下去了, 否则就不是第1层了*/let car shallowReactive({brand: 奔驰,options: {color: 红色,engine: V8}})function changeBrand() {car.brand 宝马}function changeColor() {car.options.color 紫色}function changeEngine() {car.options.engine V12}/scriptstyle scoped.app {background-color: #ddd;border-radius: 10px;box-shadow: 0 0 10px;padding: 10px;}button {margin: 0 5px;}
/style7.2 readonly 与 shallowReadonly
readonly 作用用于创建一个对象的深只读副本。 用法 const original reactive({ ... });
const readOnlyCopy readonly(original);特点 对象的所有嵌套属性都将变为只读。任何尝试修改这个对象的操作都会被阻止在开发模式下还会在控制台中发出警告。 应用场景 创建不可变的状态快照。保护全局状态或配置不被修改。
shallowReadonly 作用与 readonly 类似但只作用于对象的顶层属性。 用法 const original reactive({ ... });
const shallowReadOnlyCopy shallowReadonly(original);特点 只将对象的顶层属性设置为只读对象内部的嵌套属性仍然是可变的。 适用于只需保护对象顶层属性的场景。
示例
templatediv classapph2当前sum1为{{ sum1 }}/h2h2当前sum2为{{ sum2 }}/h2button clickchangeSum1点我sum11/buttonbutton clickchangeSum2点我sum21/button!-- ******************* --h2当前car1为{{ car1 }}/h2h2当前car2为{{ car2 }}/h2button clickchangeBrand2修改品牌(car2)/buttonbutton clickchangeColor2修改颜色(car2)/buttonbutton clickchangePrice2修改价格(car2)/button/div
/templatescript setup langts nameAppimport { ref, reactive, readonly, shallowReadonly } from vue;let sum1 ref(0)// 这里要传入1个响应式对象, 注意不要.value// 当sum1数据发生变化的时候, sum2也会发生变化, 但不能直接改sum2, 因为sum2只读,// (这样就可以达到一种保护数据的目的)let sum2 readonly(sum1)function changeSum1() {sum1.value 1}function changeSum2() {sum2.value 1 // sum2是不能修改的}/******************/let car1 reactive({brand: 奔驰,options: {color: 红色,price: 100}})// 这里要传入1个响应式对象// 当car1数据发生变化的时候, car2也会发生变化, // 但不能直接改car2的第一层属性, 因为这里使用的是shallowReadOnly, 意味着car2的第一层属性都只读,// 这里也可以使用readOnly, 这就意味着car2的任何属性都不能改了// (这样就可以达到一种保护数据的目的)let car2 shallowReadonly(car1)function changeBrand2() {car2.brand 宝马}function changeColor2() {// 由于car2是对car1使用了shallowReadOnly, 因此这里是允许改的car2.options.color 绿色}function changePrice2() {car2.options.price 10}
/scriptstyle scoped.app {background-color: #ddd;border-radius: 10px;box-shadow: 0 0 10px;padding: 10px;}button {margin: 0 5px;}
/style7.3 toRaw 与 markRaw
toRaw 作用用于获取一个响应式对象的原始对象 toRaw 返回的对象不再是响应式的不会触发视图更新。 官网描述这是一个可以用于临时读取而不引起代理访问/跟踪开销或是写入而不触发更改的特殊方法。不建议保存对原始对象的持久引用请谨慎使用。 何时使用 在需要将响应式对象传递给非 Vue 的库或外部系统时使用 toRaw 可以确保它们收到的是普通对象
markRaw
作用标记一个对象使其永远不会变成响应式的。 例如使用mockjs时为了防止误把mockjs变为响应式对象可以使用 markRaw 去标记mockjs 示例
templatediv classapph2姓名{{ person.name }}/h2h2年龄{{ person.age }}/h2button clickperson.age 1修改年龄/button{{ rawPerson }}!-- 这里修改rawPerson不会影响到person的数据的变化, 并且由于rawPerson不是响应式数据, 因此上面的{{ rawPerson }}也不会变化 --button clickrawPerson.age 1修改年龄rawPerson/buttonhrh2{{ car2 }}/h2button clickcar2.price 10点我价格10/button/div
/templatescript setup langts nameAppimport { reactive,toRaw,markRaw } from vue;import mockjs from mockjs/* toRaw */let person reactive({name:tony,age:18})// 用于获取一个响应式对象的原始对象let rawPerson toRaw(person)console.log(响应式对象,person) // Proxy(Object) {name: tony, age: 18}console.log(原始对象,rawPerson) // {name: tony, age: 18}console.log(------------------------);/* markRaw */// 如果这里没加markRaw, 那么这里的这个car就可以作为响应式对象的源头// 加上了markRaw之后, 就意味着car永远不能作为响应式对象的源头, 只能是1个原始的对象, 不能做成1个响应式对象let car markRaw({brand:奔驰,price:100})let car2 reactive(car) // 这里的car2不是响应式的了// 从输出看, 其实就是加了个标记__v_skip: true, 当遇到这个标记时, 就不对这个对象做响应式处理console.log(car) // {brand: 奔驰, price: 100, __v_skip: true}console.log(car2) // {brand: 奔驰, price: 100, __v_skip: true}// 例如使用mockjs时为了防止误把mockjs变为响应式对象可以使用 markRaw 去标记mockjslet mockJs markRaw(mockjs)/scriptstyle scoped.app {background-color: #ddd;border-radius: 10px;box-shadow: 0 0 10px;padding: 10px;}button {margin:0 5px;}
/style7.4 customRef
作用创建一个自定义的ref并对其依赖项跟踪和更新触发进行逻辑控制。
示例
App.vue
templatediv classapph2{{ msg }}/h2input typetext v-modelmsg/div
/templatescript setup langts nameAppimport {ref} from vueimport useMsgRef from ./useMsgRef// 使用Vue提供的默认ref定义响应式数据数据一变页面就更新// 这是vue给我们提供的功能, 也是承诺// let msg ref(你好)// 使用useMsgRef来定义一个响应式数据且有延迟效果let {msg} useMsgRef(你好,1000)/scriptstyle scoped.app {background-color: #ddd;border-radius: 10px;box-shadow: 0 0 10px;padding: 10px;}button {margin:0 5px;}
/styleuseMsgRef.ts
import { customRef } from vue;export default function (initValue: string, delay: number) {// 使用Vue提供的customRef定义响应式数据let timer: number// track(跟踪)、trigger(触发)let msg customRef((track, trigger) {return {// get何时调用—— msg被读取时get() {track() // 告诉Vue数据msg很重要你要对msg进行持续关注一旦msg变化就去更新console.log(get);return initValue},// set何时调用—— msg被修改时set(value) {console.log(set);clearTimeout(timer)timer setTimeout(() {initValue valuetrigger() // 通知Vue一下数据msg变化了}, delay);}}})return { msg }
}8. Vue3新组件
8.1 Teleport传送门
什么是Teleport—— Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。
示例
这个示例有个奇怪的地方css还有这种操作的给outer加上filter之后fixed定位就变成相对于父元素定位了而不是body定位这时使用teleport可以解决这个问题因为它把dom都传送走了当然teleport不仅可以适用于这种情况也可用于其它场景。
App.vue
templatediv classouterh2我是App组件/h2img srchttp://www.atguigu.com/images/index_new/logo.png altbr!-- 遮罩 --Modal//div
/templatescript setup langts nameAppimport Modal from ./Modal.vue;
/scriptstyle.outer{background-color: #ddd;border-radius: 10px;padding: 5px;box-shadow: 0 0 10px;width: 400px;height: 400px;filter: saturate(200%);}img {width: 270px;}
/styleModal.vue
templatebutton clickisShow true展示弹窗/button!-- 数据用的还是当前组件的, 但渲染的地方被传送到了body那里;to这里写的是选择器哦;--teleport tobodydiv classmodal v-showisShowh2我是弹窗的标题/h2p我是弹窗的内容/pbutton clickisShow false关闭弹窗/button/div/teleport/templatescript setup langts nameModalimport {ref} from vuelet isShow ref(false)/scriptstyle scoped.modal {width: 200px;height: 150px;background-color: skyblue;border-radius: 10px;padding: 5px;box-shadow: 0 0 5px;text-align: center;position: fixed;left: 50%;top: 20px;margin-left: -100px;}
/style8.2 Suspense
等待异步组件时渲染一些额外内容让应用有更好的用户体验使用步骤 异步引入组件使用Suspense包裹组件并配置好default 与 fallback
示例
App.vue
templatediv classapph2我是App组件/h2Child/Suspensetemplate v-slot:defaultChild//template!-- 当组件未加载完成时, 显示的临时内容 --template v-slot:fallbackh2加载中....../h2/template/Suspense/div
/templatescript setup langts nameAppimport {Suspense} from vueimport Child from ./Child.vue
/scriptstyle.app {background-color: #ddd;border-radius: 10px;padding: 10px;box-shadow: 0 0 10px;}
/styleChild.vue
templatediv classchildh2我是Child组件/h2h3当前求和为{{ sum }}/h3/div
/templatescript setup langtsimport {ref} from vueimport axios from axioslet sum ref(0);// 当下面多了这行请求数据的异步代码时, Child组件将不会展示出来setup顶层最外面有async,// 需要父组件在使用时, 借助Suspense组件才能展示Child组件let {data:{content}} await axios.get(https://api.uomg.com/api/rand.qinghua?formatjson)console.log(content,content)/* // 使用这种方式, 可以不借助Suspense组件也能展示Child组件let content (async function() {let {data:{content}} await axios.get(https://api.uomg.com/api/rand.qinghua?formatjson)return content})(); *//scriptstyle scoped.child {background-color: skyblue;border-radius: 10px;padding: 10px;box-shadow: 0 0 10px;}
/style8.3 全局API转移到应用对象
app.componentapp.configapp.directiveapp.mountapp.unmountapp.use
示例
import {createApp} from vue
import App from ./App.vue
import Hello from ./Hello.vue// 创建应用
const app createApp(App)// 全局注册组件, 然后所有的地方都可以使用Hello这个组件了
app.component(Hello,Hello)// 全局挂载
// 类似于vue2的Vue.prototype.x99, 然后所有的组件中都可以使用x了
app.config.globalProperties.x 99// 解决全局挂载x的时候, ts报错的问题
declare module vue {interface ComponentCustomProperties {x:number}
}// 全局注册指令, 然后所有的组件中都可以使用v-beauty了, 如: h1 v-beautysum好开心/h1
app.directive(beauty,(element,{value}){element.innerText valueelement.style.color greenelement.style.backgroundColor yellow
})// 挂载应用
app.mount(#app)// 卸载应用
setTimeout(() {app.unmount()
}, 2000);8.4 其他 过渡类名 v-enter 修改为 v-enter-from、过渡类名 v-leave 修改为 v-leave-from。 keyCode 作为 v-on 修饰符的支持。 v-model 指令在组件上的使用已经被重新设计替换掉了 v-bind.sync。 v-if 和 v-for 在同一个元素身上使用时的优先级发生了变化。 移除了$on、$off 和 $once 实例方法。 移除了过滤器 filter。 移除了$children 实例 propert。