vue3与vue2性能的提升打包大小减少41% 初次渲染快55%, 更新渲染快133% 内存减少54% 源码的升级使用Proxy代替defineProperty实现响应式 重写DOM的实现和Tree-Shaking 支持typescript 新的特性Composition API (组合式api)setup ref与reactive computed与watch
新的内置组件Teleport Suspense Fragment
其他改变新的生命周期钩子:setup beforeCreate created onBeforeMount onMounted onBeforeUpdate onUpdated onBeforeUnmount onUnmounteddata选项应始终被声明为一个函数
创建vue3工程 vue-cli查看 vue-cli版本号, 确保@vue/cli版本在4.5.0以上
1 2 3 4 5 6 7 8 9 npm install -g @vue/cli npm update -g @vue/cli npm uninstall -g @vue/cli npm install -g @vue/cli
vite(推荐)引用站外地址
引用站外地址
vue3文档
https://cn.vuejs.org/guide/quick-start.html
创建vue3项目
按照命令提示进行选择
1 2 3 4 5 6 7 8 9 10 11 12 ✔ Project name: … <your-project-name> ✔ Add TypeScript? … No / Yes ✔ Add JSX Support? … No / Yes ✔ Add Vue Router for Single Page Application development? … No / Yes ✔ Add Pinia for state management? … No / Yes ✔ Add Vitest for Unit testing? … No / Yes ✔ Add an End-to-End Testing Solution? … No / Cypress / Playwright ✔ Add ESLint for code quality? … No / Yes ✔ Add Prettier for code formatting? … No / Yes Scaffolding project in ./<your-project-name>... Done.
进入项目 1 2 3 cd <your-project-name>npm install npm run dev
启动时,如果遇到以下错误!
1 2 3 4 5 (node:43520) UnhandledPromiseRejectionWarning: SyntaxError: Unexpected token '??=' at Loader.moduleStrategy (internal/modules/esm/translators.js:149:18) (Use `node --trace-warnings ...` to show where the warning was created) (node:43520) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html (node:43520) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
原因是逻辑空赋值(??=)是ES2021的语法,node v15.0.0以上才支持逻辑空赋值(??=)的语法。之前为了兼容旧代码使用的node版本是14。使用nvm切换16的node,成功解决
如果使用`node v14`已经安装了`node_modules`,先删除`node_modules`,然后在切换版本`v16`
1 2 3 4 rm -r node_modulesnvm use v16 nvm alias default v16
vue3工程目录结构1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ├── public │ └── favicon.ico ├── src │ ├── App.vue │ ├── assets │ │ ├── base.css │ │ ├── logo.svg │ │ └── main.css │ ├── components │ │ ├── HelloWorld.vue │ │ ├── TheWelcome.vue │ │ ├── WelcomeItem.vue │ │ └── icons │ │ ├── IconCommunity.vue │ │ ├── IconDocumentation.vue │ │ ├── IconEcosystem.vue │ │ ├── IconSupport.vue │ │ └── IconTooling.vue │ └── main.ts ├── env.d.ts ├── index.html ├── package-lock.json ├── package.json ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json ├── README.md └── vite.config.ts
index.htmlvite项目中, index.html是项目中的入口文件,在项目最外层!加载index.html后,vite解析<script type="module" src="/src/main.ts"></script>指向的javaScript! vue3项目中通过createApp方法创建实例!env.d.tsenv.d.ts 用来识别文件类型的.txt .ts…
如果main.ts文件引入App.vue时出现以下错误:
找不到模块“./App.vue”或其相应的类型声明。
在env.d.ts文件中 按住commnd 或者 ctrl 点击 vite/client 进入 文件类型(client.d.ts)配置!加入以下配置代码!
1 2 3 4 5 6 7 8 declare module '*.vue' { import type { DefineComponent } from 'vue' const component : DefineComponent <{}, {}, any> export default component }
tsconfig是 typescript相关配置文件
OptionsApi & ComponsitionApivue2 的设计风格是 Options(配置)风格!vue3 的设计风格是 ComponsitionApi(组合)风格!选项式->optionsApioptionsApi 选项式 api 可配置 api!data methods watch computed …
Options类型的 API, 数据 方法 计算属性等,是分散在:data methods watch computed 中的,若想新增或者修改,就需要分别改动data methods watch computed地方!
Options gif图片 图片地址来源: https://blog.csdn.net/m0_45911911/article/details/126023757
组合式->componsitionsApicomposition gif图片 图片地址来源: https://blog.csdn.net/m0_45911911/article/details/126023757
setup 函数setup函数是vue3的组合式api的入口函数!将options选项式中的data methods等,全部整合到setup函数中!
options & setup 写法对比Options Api写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <template > <div class ="options-page" > <h2 > vue2 OptionsPage </h2 > <div class ="name" > 姓名: {{name}}</div > <div class ="age" > 年龄: {{age}}</div > <div class ="emial" > 邮箱: {{email}}</div > <div class ="tel" > 电话: {{tel}}</div > <div class ="msg" > {{getMsg()}} </div > </div > </template >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <script > export default { name : "OptionsPage" , data ( ){ return { msg : 'Welcome to Your HelloWord App Components' , name : "李四" , age : 16 , email : "mjw1046665443@163.com" , tel : "15121235122" } }, methods : { getMsg ( ){ return this .msg ; } } } </script >
Composition Api写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <template > <div class ="setup-page" > <h2 > vue3 setupPage </h2 > <div class ="name" > 姓名: {{name}}</div > <div class ="age" > 年龄: {{age}}</div > <div class ="emial" > 邮箱: {{email}}</div > <div class ="tel" > 电话: {{tel}}</div > <div class ="msg" > {{getMsg()}} </div > </div > </template >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <script > export default { name : "SetupPage" , setup () { let name = "张三" let age = 18 let email = "1046665443@qq.com" let tel = "15121235122" let msg = "Welcome to Your SetupPage App Components" function getMsg ( ){ return msg; } return { name, age, email, tel } } } </script >
setup注意事项setup中的this问题
setup函数中无法使用this,函数中访问this时,此时为undefind!
setup函数中return值
setup函数中需要 return 返回值,否则在页面中无法使用setup函数中的data methods等!`return值可以不是Object,也可以是return () => "哈哈",这样以来,界面只会显示哈哈,哪怕是template中定义的元素,都会被return覆盖掉!
setup函数和options api混合写法1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template > <div class ="setup-options-blend-page" > <h2 > vue3 setupAndOptionsBlend </h2 > <div class ="name" > 姓名: {{name}}</div > <div class ="age" > 年龄: {{age}}</div > <div class ="emial" > 邮箱: {{email}}</div > <div class ="tel" > 电话: {{tel}}</div > <div class ="msg" > {{getMsg()}} </div > <div class ="test" > setup & options 混合写法: {{test}} </div > <div class ="setup-name" > 试图在 options 中 访问 setup 中的 name 属性 {{ setupName }} </div > </div > </template >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <script > export default { name : "SetupAndOptionsBlend" , data ( ){ return { test : "setup & options 混合写法!" , setupName : this .name } }, methods : { test ( ){} }, setup () { let name = "张三" let age = 18 let email = "1046665443@qq.com" let tel = "15121235122" let msg = "Welcome to Your SetupPage App Components" function getMsg ( ){ return msg; } return { name, age, email, tel, getMsg } } } </script >
注意事项:
setup函数中无法使用this,函数中访问this时,此时为undefind,且无法访问data methods中定义的属性和方法!反而data methods中可以通过this来访问setup中定义的属性和方法! setup函数回调优先 于 beforeCreate回调函数执行!setup简写方式1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <template > <div class ="setup-abbreviation" > <h2 > vue3 简写方式 </h2 > <div class ="name" > 姓名: {{name}}</div > <div class ="age" > 年龄: {{age}}</div > <div class ="emial" > 邮箱: {{email}}</div > <div class ="tel" > 电话: {{tel}}</div > <div class ="msg" > {{getMsg()}} </div > </div > </template >
1 2 3 4 5 6 7 8 9 10 11 <script setup > let name = "张三" let age = 18 let email = "1046665443@qq.com" let tel = "15121235122" let msg = "Welcome to Your setupAbbreviation App Components" function getMsg ( ){ return msg; } </script >
简写方式可以省略return值, 以及export default写法, 但是无法定义组件名 name!
定义组件名的方式需要借助vite插件 vite-plugin-vue-setup-extend! 使用此插件可以帮我们实现定义组件名name的功能!
安装vite-plugin-vue-setup-extend
1 npm i -D vite-plugin-vue-setup-extend
引入插件vite.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { fileURLToPath, URL } from 'node:url' import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import VitPluginVueSetupExtend from 'vite-plugin-vue-setup-extend' export default defineConfig ({ plugins : [vue (), VitPluginVueSetupExtend ()], resolve : { alias : { "@" : fileURLToPath (new URL ("./src" , import .meta .url )), }, }, });
配置组件name
1 <script setup name ="component-name" > </script >
ref reactive 响应式[ref]基本数据类型 or 对象类型注意: ref 支持 基本类型 和 对象类型!
作用: 定义响应式数据 语法: let xxx = ref('测试') 返回值: RefImpl实例对象,简称ref对象或ref,ref对象的value的属性是响应式的! 注意点:JS中操作数据需要xxx.value,template模版中不需要xxx.value对于let name = ref('张三') name不是响应式 name.value是响应式 如果ref类型数据存在reactive里,那么不需要通过.value去获取相应的值! 例如: reactive({a:1, b: ref(0)}) 1 2 3 let obj = reactive ({a :1 , b : ref (0 )})console .log ("obj.b" , obj.b )
[reactive]对象类型注意: reactive 仅支持 对象类型!
语法:对象: let xxx = reactive({brand: "xiaoke", color: "red"}) 数组: let xxx = reactive(['red','blue', 'green']) demo1 2 3 4 5 6 7 8 9 10 11 12 13 14 <template > <div class ="ref-reactive-demo" > <h2 > ref reactive 响应式</h2 > <p > name: {{ nameRef }}</p > <p > age: {{ ageRef }}</p > <p > email: {{ emailRef }}</p > <p > car: {{ car }}</p > <p > carList: {{ carList }}</p > <button @click ="modify('李四', 20, 'mjw1046665443@qq.com')" > 修改</button > <button @click ="modifyCar" > 修改car的颜色</button > <button @click ="removeCar(1)" > 删除car其中一项</button > </div > </template >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <script setup name ="RefAndReactive" > import { ref, reactive } from "vue" let nameRef = ref ("张三" ) let ageRef = ref (18 ) let emailRef = ref ("1046665443@qq.com" ) let car = reactive ({ brand : "xiaoke" , color : "blue" , type : "suv" }) let carList = reactive (["ford" , "bmw" , "audi" ]) function modify (name, age, email ) { console .log ("modify" , name, age, email) nameRef.value = name ageRef.value = age emailRef.value = email } function modifyCar ( ){ car.color = "green" } function removeCar (index = 0 ){ carList.splice (index, 1 ) } </script >
ref & reactiveref 可以定义基本数据类型响应式 也可以定义对象类型响应式, 但是reactive只能定义对象类型响应式!
1 2 3 4 5 6 7 8 9 10 11 12 13 import { ref, reactive } from "vue" let name = ref ("张三" )let obj = ref ({ brand : "xiaoke" , color : "blue" })let arr = ref (['red' , 'green' , 'yellow' ])obj.value .color = "red" ; arr.value [2 ] = "red" ; let obj2 = reactive ({ brand : "xiaoke" , color : "blue" });let arr = reactive (['red' , 'green' , 'yellow' ];obj2.color = "red" arr[2 ] = "red"
ref: 返回值为 RefImpl实例,需要通过.value形式去访问!
reactive: 返回值为 Proxy代理对象,不需要通过.value形式去访问!
注意事项ref-> 如果值为基本类型 或者浅层次应用类型 可以使用,反之深层次引用类型不适合使用 / refref-> 自动添加.value插件(vscode搜 Volor)然后设置找到Auto Insert: Dot Value 打勾即可,当敲打变量时会区分 ref自动.valuereactive -> 重新分配对象的时候会失去响应式,通过Object.assgin()处理即可!1 2 3 4 5 6 let obj = reactive ({ brand : "xiaoke" , color : "blue" });obj = Object .assign (obj, { color : "red" }); let obj = ref ({ brand : "xiaoke" , color : "blue" });obj.value = { color : "red" };
toRefs & toReftoRefsobject 对象结构时, 将结构的属性进行响应式! 把reactive定义的对象 转换为 ref对象!
1 2 3 let obj = reactive ({ brand : "xiaoke" , color : "blue" });let { brand } = obj;brand = "aaa" ;
通过 toRefs 处理非响应式数据,
1 2 3 4 import { reactive, toRefs } from 'vue' let obj = reactive ({ brand : "xiaoke" , color : "blue" });let { brand } = toRefs (obj);brand.value = "aaa" ;
let { brand } = obj; 等价于 let brand = obj.brand; obj.brand为响应式,当赋值与另一个变量时, brand变量不响应式!
let { brand } = toRefs(obj); 相当于将object中每个属性加了ref(obj.attr),因此brand.value即可响应式!
toReftoRef将单个object中的某个属性改为响应式!
1 2 3 let obj = reactive ({ brand : "xiaoke" , color : "blue" });let n1 = toRef (obj, 'brand' );n1.value ;
computed 计算属性1 2 3 4 5 6 7 8 9 10 11 <template > <div class ="computed-page" > <h2 > 计算属性</h2 > 姓: <input type ="text" v-model ="firstName" /> <br /> 名: <input type ="text" v-model ="lastName" /> <br /> {{ fullName }} <button @click ="modifyFullName" > 修改名称</button > </div > </template >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <script setup name ="Computed" > import { ref, computed } from "vue" ; let firstName = ref ("张" ) let lastName = ref ("三" ) let fullName = computed (() => { return firstName.value + "-" + lastName.value }) let fullName = computed ({ get ( ){ return firstName.value + "-" + lastName.value }, set (val ){ firstName.value = val.split ('-' )[0 ] lastName.value = val.split ('-' )[1 ] } }) function modifyFullName ( ){ fullName.value = "李-时" } </script >
watch 监听函数watch 只能监视以下几种情况!
ref和reactive定义的数据!函数返回的某个值 一个包含上述内容的数组 监听ref基本类型 和 对象类型1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <template > <div class ="watch-page" > <h2 > watch 监听 </h2 > <div class ="name" > 姓名: {{name}}</div > <div class ="age" > 年龄: {{age}}</div > <div class ="emial" > 邮箱: {{email}}</div > <div class ="tel" > 电话: {{tel}}</div > <div class ="car" > refCar: {{refCar}}</div > <button @click ="changeName" > 修改名称</button > <button @click ="changRefCar" > 修改汽车-ref</button > </div > </template >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <script setup name ="WatchPage" > import { reactive, ref, watch } from "vue" let name = ref ("张三" ) let refCar = ref ({type : "xiaoke" , color : "blue" }) let stopWatch = watch (name,(newValue, oldValue ) => { console .log ("基本类型监听" ,newValue, oldValue) if ( newValue == ??) { stopWatch () } }) let stopWatch2 = watch (refCar, (newValue, oldValue ) => { console .log ("ref对象类型监听" ,newValue, oldValue) }) function changeName ( ){ name.value += "~" } function changeCar ( ){ refCar.value = {type : "benchi" , color : "white" } } </script >
注意:
ref监听对象类型是监听的对象的引用地址! 若要监听对象内部属性需呀开启深度监视_deep!
若要修改ref对象类型中的属性,newValue和oldValue都是新值,因为都是同一个对象地址! 若要修改ref 整个定义的对象, newValue 为新值,oldValue 为旧值,因为不是同一个对象地址了!
1 2 3 4 5 6 7 import { watch } from "vue" watch (obj, ( newValue, oldValue ) => { console .log (newValue, oldValue) }, { deep : true , immediate : true })
deep: 深度监听, immediate: 立即执行
具体配置项请查看官方: vue3官方文档 watch
监听reactive对象类型reactive 只能为对象引用类型,且监听后,deep监听默认开启,无法取消默认深度监听!reactive 无法修改对象本身, 仅能修改对象属性!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <template > <div class ="watch-page" > <h2 > watch 监听 </h2 > <div class ="name" > 姓名: {{name}}</div > <div class ="age" > 年龄: {{age}}</div > <div class ="emial" > 邮箱: {{email}}</div > <div class ="tel" > 电话: {{tel}}</div > <div class ="car" > reactiveCar: {{refCar}}</div > <button @click ="changeCarColor" > 修改color</button > <button @click ="changRefCar" > 修改汽车-ref</button > </div > </template >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <script setup name ="WatchPage" > import { reactive, ref, watch } from "vue" let refCar = reactive ({type : "xiaoke" , color : "blue" }) let stopWatch2 = watch (refCar, (newValue, oldValue ) => { console .log ("ref对象类型监听" ,newValue, oldValue) }) function changeCarColor ( ){ refCar.color = "green" } function changeCar ( ){ Object .assign (refCar, {type : "benchi" , color : "white" }) } </script >
监视对象某个属性 监视ref和reactive定义的对象类型中的某个属性:
若属性值不是对象类型,需要写成函数形势! 若属性值是对象类型,也可以写成对象形式,也可以写成(推荐)函数形势! 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import { reactive, ref, watch } from "vue" let car = reactive ({type : "benchi" , color : "white" , characteristic : { size : "big" }})watch (() => car.type , (newValue, oldValue ) => {})watch (() => car.characteristic , (newValue, oldValue ) => {})watch (car.characteristic , (newValue, oldValue ) => {})
监视对象多个属性 监听对象多个属性时, 可以写成数组形式! 基本类型由函数形式, 对象类型可函数,可对象!
1 2 3 4 5 6 import { reactive, ref, watch } from "vue" let car = reactive ({type : "benchi" , color : "white" , characteristic : { size : "big" }})watch ([() => car.type , () => car.color , car.characteristic ], (newValue, oldValue ) => {})
watchEffect监视内部使用的响应数据watchEffect 监视回调函数内部使用的ref和reactive响应式数据!watchEffect 会立即执行一次!
1 2 3 4 5 6 7 8 9 10 <template > <div class ="watch-effect-page" > <h2 > watchEffect 监听 </h2 > <div > {{ zhangsan }}</div > <div > {{ lisi }}</div > <div > {{ person }}}</div > <button @click ="changeName" > 修改张三</button > <button @click ="addNewGames" > 修改john</button > </div > </template >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <script setup name ="WatchEffectPage" > import { reactive, ref, watchEffect } from "vue" ;let zhangsan = ref ("张三" )let lisi = ref ("lisi" )let person = reactive ({ name : "John" , age : 20 , hobby : { sport : "football" , music : "pop" , games : ['永结物件' ,'帕鲁' ] } }) watchEffect (()=> { console .log ("watchEffect" , zhangsan.value , person.hobby .games ) }) function changeName ( ){ zhangsan.value += "~" } function addNewGames ( ){ person.hobby .games .push ('飞车' ) } </script >
标签ref属性标签ref属性, 可以在html标签中使用, 也可以在component自定义组件中使用! 被标识了html元素的ref属性, 可以通过ref()获取到,返回结果为DOM对象! 被标识了component组件的ref属性, 可以通过ref()获取到,返回结果为component组件实例对象!
dom元素标记ref属性template
1 2 3 <template > <h2 ref ="titleRef" > watchEffect 监听 </h2 > </template >
script1 2 let titleRef = ref ()console .log ("titleRef" , titleRef.value )
component元素标记ref属性template
1 2 3 <template > <Person ref ="personRef" /> </template >
script1 2 let personRef = ref ()console .log ("personRef" , personRef.value )
获取ref标记的组件的数据 被ref所标记的component组件, 无法直接通过组件实例获取子组件的数据,需要通过子组件的暴露 defineExpose后,才可访问!
父组件template
1 2 3 <template > <Person ref ="personRef" /> </template >
script
1 2 let personRef = ref ()console .log ("personRef" , personRef.value )
子组件template
1 2 3 <template > <Person ref ="personRef" /> </template >
script1 2 3 4 5 6 let a = ref (0 )let b = ref ("111" )let c = ref ("dddd" )defineExpose ({a,b,c})
注意: ref="nameRef" 和 let nameRef = ref() 的变量名nameRef名称要对上!
属性传递定义属性传递需要使用defineProps defineEmits方法实现!
props向下传递defineProps
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 defineProps ({ lisi : [String , Number ], zhangsan : { type : String , default : "default" , required : false , validate : () => {} }, obj : { type : Object , default : () => { retrun {}} }, propF : { validator (value ) { return ['success' , 'warning' , 'danger' ].includes (value) } }, propG : { type : Function , default ( ) { return 'Default function' } } })
父组件
1 2 3 4 5 6 7 8 9 10 11 12 13 <template > <div class ="watch-effect-page" > <h2 ref ="titleRef" > watchEffect 监听 </h2 > <div > {{ zhangsan }}</div > <div > {{ lisi }}</div > <div > {{ person }}}</div > <button @click ="changeName" > 修改张三</button > <button @click ="addNewGames" > 修改john</button > <h2 > 子组件 </h2 > <props-page :lisi ="lisi" :person ="person" /> </div > </template >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <script setup name ="WatchEffectPage" > import PropsPage from "./PropsPage.vue" ;import { reactive, ref, watchEffect } from "vue" ;let zhangsan = ref ("张三" )let lisi = ref ("lisi" )let titleRef = ref ()let person = reactive ({ name : "John" , age : 20 , hobby : { sport : "football" , music : "pop" , games : ['永结物件' ,'帕鲁' ] } }) watchEffect (()=> { console .log ("titleRef" , titleRef.value ) console .log ("watchEffect" , zhangsan.value , person.hobby .games ) }) function changeName ( ){ zhangsan.value += "~" } function addNewGames ( ){ person.hobby .games .push ('飞车' ) } </script >
子组件
1 2 3 4 5 6 7 8 <template > <div class ="props-page" > <div > {{ zhangsan }}</div > <div > {{ lisi }}</div > <div > {{ person.name }}</div > <div > {{ list }}</div > </div > </template >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <script setup > import { onMounted, withDefaults } from "vue" let props = defineProps ({ zhangsan : { type : String , default : "default" }, list : { type : Array , default : () => [{a : "default" }] }, person : { type : Object , default : () => { return {}} }, lisi : { type : String , default : "lisi默认值" } }) onMounted (() => { console .log ("onMounted" , props) }) </script >
emits向上传递defineEmits
1 2 3 let emit = defineEmits (["on-close" ,"on-show" ])emit ('on-close' , '要传递的参数' )
子组件
1 2 3 4 5 6 7 8 9 10 <template > <div class ="props-page" > <div > {{ zhangsan }}</div > <div > {{ lisi }}</div > <div > {{ person.name }}</div > <div > {{ list }}</div > <button @click ="show" > 向上传递 show</button > <button @click ="close" > 向上传递 close</button > </div > </template >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <script setup > import { onMounted, withDefaults } from "vue" let emit = defineEmits (["on-close" ,"on-show" ]) let props = defineProps ({ zhangsan : { type : String , default : "default" }, list : { type : Array , default : () => [{a : "default" }] }, person : { type : Object , default : () => { return {}} }, lisi : { type : String , default : "lisi默认值" } }) function show ( ){ console .log ("show emit" ) emit ("on-show" , "我是子组件传递的数据on-show~~~~" ) } function close ( ){ console .log ("close emit" ) emit ("on-close" , "我是子组件传递的数据on-close~~~~" ) } onMounted (() => { console .log ("onMounted" , props) }) </script >
父组件
1 2 3 4 5 6 7 8 9 10 11 12 13 <template > <div class ="watch-effect-page" > <h2 ref ="titleRef" > watchEffect 监听 </h2 > <div > {{ zhangsan }}</div > <div > {{ lisi }}</div > <div > {{ person }}}</div > <button @click ="changeName" > 修改张三</button > <button @click ="addNewGames" > 修改john</button > <div > 子组件传递的数据: {{ subMsg }}</div > <h2 > 子组件 </h2 > <props-page :lisi ="lisi" :person ="person" @on-show ="subComponentShow" @on-close ="subComponentClose" /> </div > </template >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <script setup name ="WatchEffectPage" > import PropsPage from "./PropsPage.vue" ;import { reactive, ref, watchEffect } from "vue" ;let zhangsan = ref ("张三" )let lisi = ref ("lisi" )let subMsg = ref ("" )let titleRef = ref ()let person = reactive ({ name : "John" , age : 20 , hobby : { sport : "football" , music : "pop" , games : ['永结物件' ,'帕鲁' ] } }) watchEffect (()=> { console .log ("titleRef" , titleRef.value ) console .log ("watchEffect" , zhangsan.value , person.hobby .games ) }) function subComponentShow (msg ) { console .log ("subComponentShow" , msg) subMsg.value = msg } function subComponentClose (msg ) { console .log ("subComponentClose" , msg) subMsg.value = msg } function changeName ( ){ zhangsan.value += "~" } function addNewGames ( ){ person.hobby .games .push ('飞车' ) } </script >
生命周期vue2
创建 (创建前 beforeCreate ) 创建后创建 (created) 挂载 (挂载前 beforeMount) 挂载后 mounted 更新 (更新前 beforeUpdate) 更新后 updated 销毁 (销毁前 beforeDestroy) 销毁后 destroyed
vue3
创建 (创建前 setup ) 创建后创建 (setup) 挂载 (挂载前 beforeMount) 挂载后 mounted 更新 (更新前 beforeUpdate) 更新后 updated 销毁 (销毁前 beforeDestroy) 销毁后 destroyed
vue2生命周期钩子函数 描述 beforeCreate在实例创建之前调用。在实例初始化过程中创建组件的data、computed、watch和methods属性之前调用。在beforeCreate钩子中,实例的属性还不存在,因此在this中不能访问实例属性。 created在实例创建之后调用。在实例初始化之后,数据观测(data observer) 和事件监听器(event listeners)已经绑定,$el 已经挂载完成,但是$nextTick 回调中访问的 DOM 节点还不存在。 beforeMount在实例挂载之前调用。在 mounted 钩子中,实例的 $el 已经存在,但子组件可能还没有挂载。在 beforeMount 钩子中,实例的 $el 还不存在。 mounted在实例挂在之后调用。 这时可以访问实例的 $el。 beforeUpdate状态更新之前执行函数,此时data中的状态值是最新的,但是界面上显示的数据还是旧的,因为还没有开始重新渲染DOM节点。 updated此时data中的状态值和界面上显示的数据都已经完成了更新,界面已经被重新渲染好了!beforeDestroy组件实例销毁之前调用! destroyed实例销毁后调用,Vue实例指示的所有东西都会解绑,所有的事件监听器都会被移除,所有的子实例也都会被销毁。组件已经被完全销毁,此时组建中所有data、methods、以及过滤器,指令等,都已经不可用了。
vue2生命周期 vue3生命周期钩子函数 描述 setup()开始创建组件之前,在beforeCreate和created之前执行。创建的是data和methodonBeforeMount()组件挂载到节点上之前执行的函数。 onMounted()组件挂载完成后执行的函数。 onBeforeUpdate()组件更新之前执行的函数。 onUpdated()组件更新完成之后执行的函数。 onBeforeUnmount()组件卸载之前执行的函数。 onUnmounted()组件卸载完成后执行的函数 onActivated()被包含在keep-alive中的组件,会多出两个生命周期钩子函数。被激活时执行。 onDeactivated()比如从 A 组件,切换到 B 组件,A 组件消失时执行。 onErrorCaptured()当捕获一个来自子孙组件的异常时激活钩子函数。
对比vue2 vue3 beforeCreatesetupcreatedsetupbeforeMountonBeforeMountmountedonMountedbeforeUpdateonBeforeUpdateupdatedonUpdatedbeforeDestroyonBeforeUnmountdestroyedonUnmountedactivatedonActivateddeactivatedonActivatederrorCapturedonErrorCaptured
HooksHooks 将单个vue组件中的功能进行分类提取!module 当一个组件业务繁多时将业务的每个功能点单独抽离成一个文件js or ts 类似于mixis混入效果!hooks文件中必须export一个function,其命名必须为useXXX!
看一段没有使用 hooks的代码
no hooks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <template > <div class ="hooks-page" > <h2 > Hooks Page</h2 > <div class ="sum" > {{ sum }}</div > <div class ="show-img" > <img v-for ="dog in dogs" :key ="dog" :src ="dog" /> </div > <button @click ="add" > 点我累加</button > <button @click ="addDogs" > 点我添加 dog</button > </div > </template > <script setup > import { reactive, ref } from 'vue' ; import axios from 'axios' ; let sum = ref (0 ) let dogs = reactive ([ "https://images.dog.ceo/breeds/pembroke/n02113023_4373.jpg" ]) function add ( ){ sum.value += 1 ; } async function addDogs ( ) { let { data } = await axios.get ("https://dog.ceo/api/breed/pembroke/images/random" ) dogs.push (data.message ) } </script > <style scoped > .hooks-page { padding : 15px ; background-color : #ddd ; margin-top : 10px ; } .hooks-page .show-img img { height : 100px ; margin-right : 12px ; } </style >
以上代码有两个功能点sum求和 和 显示 dogs 列表,两个互不相干的功能混合在一起,代码可读性差,可维护性差!
使用 hooks
新建文件夹src/hooks/ 分别新建文件useSum.js useDogs.js!
useSum.js
useSum.js 1 2 3 4 5 6 7 8 import { reactive, ref } from "vue" ;export const useSum = ( ) => { const sum = ref (0 ); function add ( ) { sum.value += 1 ; } return { sum, add }; }
useDogs.js
useDogs.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import { reactive, ref } from "vue" ;import axios from "axios" ;export const useDogs = ( ) => { let dogs = reactive ([ "https://images.dog.ceo/breeds/pembroke/n02113023_4373.jpg" , ]); async function addDogs ( ) { let { data } = await axios.get ( "https://dog.ceo/api/breed/pembroke/images/random" ); dogs.push (data.message ); } return { dogs, addDogs }; };
HooksPage.vue
HooksPage.vue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <template > <div class ="hooks-page" > <h2 > Hooks Page</h2 > <div class ="sum" > {{ sum }}</div > <div class ="show-img" > <img v-for ="dog in dogs" :key ="dog" :src ="dog" /> </div > <button @click ="add" > 点我累加</button > <button @click ="addDogs" > 点我添加 dog</button > </div > </template > <script setup > import { useSum } from '@/hooks/useSum' ; import { useDogs } from '@/hooks/useDogs' ; let { add, sum } = useSum (); let { addDogs, dogs } = useDogs (); </script > <style scoped > .hooks-page { padding : 15px ; background-color : #ddd ; margin-top : 10px ; } .hooks-page .show-img img { height : 100px ; margin-right : 12px ; } </style >
vue-routervue-router 路由器 导航, 通过routes配置路由规则,进行path路径匹配,进而在当前页面显示对应组件!
vue-router中的组件:<router-view> 显示当前路由匹配的组件<router-link :to='' class='active-class'> 跳转到指定路由
路由组件通常放在pages或views, 一般组件通常放在components中! 通过点击导航视觉上的消失, 默认是被卸载的, 需要的时候在去挂载! 引入vue-router 安装vue-router
1 npm install vue-router --save
创建@/router/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import { createRouter, createWebHistory } from "vue-router" ;const router = createRouter ({ history : createWebHistory (), routes : [ { path : "/" , name : "Home" , component : () => import ("@/views/base-func-demo/index.vue" ), }, { path : "/router-page" , name : "RouterPage" , component : () => import ("@/views/router-page-demo/index.vue" ), }, ], }); export default router;
其中 createRouter 为创建router实例,配置路由规则! createWebHistory 为创建router 模式[hash 模式 or history 模式]!
链接router-linkto 字符串写法
1 <router-link active-class ="active" to ="/home" > 主页</router-link >
to 对象写法
1 2 3 <router-link active-class ="active" :to ="{path: '/home'}" > 主页</router-link > <router-link active-class ="active" :to ="{name: 'Home'}" > 主页</router-link >
路由工作模式vue-router 路由工作模式分为hash 模式和 history 模式!
history模式http://127.0.0.1:5173/router-page
优点: URL更加美观,不带有#号! 缺点: 后期项目上线, 需要服务端配合处理路径问题,否则刷新会有404错误!
1 2 3 4 const router = createRouter ({ history : createWebHistory () })
后端处理: 例如
nginx 在配置文件中需要:
1 2 3 4 5 location / { root /root/gshop; index index.html; try_files $uri $uri / /index.html; }
hash模式http://127.0.0.1:5173/#/router-page
优点: 兼容性好,不需要服务端处理路径! 缺点: URL不美观,带有#号,且在SEO优化方面相对较差!
1 2 3 4 const router = createRouter ({ history : createWebHashHistory () })
命名路由name: “Home” 给路由起名字!
1 2 3 4 5 { path : "/" , name : "Home" , component : () => import ("@/views/base-func-demo/index.vue" ), },
嵌套路由嵌套路由是通过children 属性来配置的,它是一个数组[]!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 { path : "/routerPage" , name : "RouterPage" , component : () => import ("@/views/router-page-demo/index.vue" ), children : [ { path : "details" , name : "Details" , component : () => import ("@/views/router-page-demo/sub-router/Details.vue" ), }, { path : "about" , name : "About" , component : () => import ("@/views/router-page-demo/sub-router/About.vue" ), }, ], },
注意: children下 path属性无需加/! 访问路径: /routerPage/details or /routerPage/about!
路由传参queryquery 通过?号来传递参数, 例如: ?id=1! 例如: http://127.0.0.1:5173/routerPage/about?content=内容2
demo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 <template > <div class ="router-page-demo" > <center > <h2 > router-page-demo</h2 > </center > <div class ="navbar" > <router-link class ="nav-item" v-for ="item in navList" :to ="`${item.to}?content=${item.content}`" :key ="item.id" > {{ item.nav }}</router-link > </div > <div class ="content" > <div class ="content-title" > 子级路由显示位置</div > <router-view > </router-view > </div > </div > </template > <script setup > import { reactive } from 'vue' ; let navList = reactive ([ {id : "851783781278371" , nav : "子路由1" , content : "内容1" , to : "/routerPage/details" }, {id : "675178378127837" , nav : "子路由2" , content : "内容2" , to : "/routerPage/about" }, ]) </script > <style scoped > .navbar { height : 125px ; display : flex; justify-content : space-around; align-items : center; } .navbar .nav-item { width : 95px ; line-height : 45px ; border-radius : 15px ; text-decoration : none; display : inline-block; background-color :#ddd ; color : #000 ; text-align : center; } .content { width : 1675px ; height : 400px ; border : 1px solid #ddd ; margin : 0 auto; border-radius :10px ; padding : 15px ; } .content .content-title { text-align : center; font-size : 18px ; font-weight : bold; } </style >
怎么获取 query 参数呢?
1 2 3 4 5 import { useRoute } from 'vue-router' ;let route = useRoute ();let queryParams = route.query ;console .log ("about" , queryParams);
还有一种写法
{ path: item.to, query: {content: item.content}}
1 2 3 <router-link class ="nav-item" v-for ="item in navList" :to ="{ path: item.to, query: {content: item.content}}" :key ="item.id" > {{ item.nav }} </router-link >
paramsparams 通过:/号来传递参数, 例如: /id/1!
1 2 3 4 5 6 { path : "details/:content/:title" , name : "Details" , component : () => import ("@/views/router-page-demo/sub-router/Details.vue" ), },
1 2 3 4 5 6 7 let navList = reactive ([ {id : "851783781278371" , nav : "子路由1" , content : "内容1" , to : "/routerPage/details/内容传参/15123213" , name : "Details" }, {id : "675178378127837" , nav : "子路由2" , content : "内容2" , to : "/routerPage/about" , name : "About" }, ])
怎么获取 params 参数呢?
1 2 3 4 5 import { useRoute } from 'vue-router' ;let route = useRoute ();let params = route.params ;console .log ("details" , params);
还有一种写法, path 替换成 name
{ name: item.name, params: {content: item.content,title: "15123213"}}
1 2 3 4 5 6 7 8 <router-link class ="nav-item" v-for ="item in navList" :to ="{ name: item.name, params: {content: item.content, title: " 15123213 "}}" :key ="item.id" > {{ item.nav }} </router-link >
path: "details/:content/:title" 假如占位符title属性不想要了,可以这样做:path: "details/:content/:title?" 加问号即可!
路由规则props配置在路由规则中通过props: true 来达到组件之间传递数据的目的! 该属性[props] 在组件 Details 中defineProps(['content','title']) 收集所有params参数,通过props透传到组件props中!
路由规则
1 2 3 4 5 6 7 { path : "details/:content/:title" , name : "Details" , component : () => import ("@/views/router-page-demo/sub-router/Details.vue" ), props : true },
子组件中使用defineProps1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <template > <div class ="details" > <h3 > 我是子级路由_details</h3 > </div > </template > <script setup name ="Details" > let props = defineProps (['content' ,'title' ])console .log ("content, title" ,props.content , props.title )</script > <style scoped > </style >
自定义props函数 props可以为函数通过回调参数来特定返回props属性值!
1 2 3 4 5 6 7 8 9 { path : "details/:content/:title" , name : "Details" , component : () => import ("@/views/router-page-demo/sub-router/Details.vue" ), props ( route ){ return route.query } },
自定义props对象 1 2 3 4 5 6 7 8 9 10 { path : "details/:content/:title" , name : "Details" , component : () => import ("@/views/router-page-demo/sub-router/Details.vue" ), props : { a : 123123 , b : 13513451 } },
replace属性replace 加上该属性后,无法通过浏览器历史浏览功能返回上一页!
1 2 3 4 5 6 7 8 9 <router-link replace class ="nav-item" v-for ="item in navList" :to ="`${item.to}?content=${item.content}`" :key ="item.id" > {{ item.nav }} </router-link >
编程式路由就是以代码的形式来控制路由!
1 2 3 4 5 6 import { useRouter } from 'vue-router' ; let router = useRouter ();router.push ("/about" ); router.push ({path : "/about" , query : {}, params : {}});
push 跳转是有历史记录的,可以返回上一次路由,而replace 跳转是没有历史记录的,无法返回上一次路由!
路由重定向重定向就是当访问/时,自动跳转到/about!/routerPage 访问此路由时跳转到/routerPage/details!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 { path: "/routerPage" , name: "RouterPage" , component: () => import("@/views/router-page-demo/index.vue" ), redirect: "/routerPage/details" , children: [ { path: "details/:content/:title" , name: "Details" , component: () => import("@/views/router-page-demo/sub-router/Details.vue" ), props: true } , { path: "about" , name: "About" , component: () => import("@/views/router-page-demo/sub-router/About.vue" ), } , ] , } ,
pinia状态管理工具pinia是vue3的状态管理工具,可以轻松的在vue3中使用vuex!
引用站外地址
pinia文档
https://pinia.vuejs.org/zh/core-concepts/plugins.html#calling-subscribe-inside-plugins
引入pinia安装
在main.js中引入pinia
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import { createApp } from "vue" ;import App from "./App.vue" ;import router from "@/router" ;import { createPinia } from "pinia" ;const pinia = createPinia ();const app = createApp (App );app.use (router); app.use (pinia); app.config .compilerOptions .isCustomElement = (tag ) => { return tag.startsWith ("ion-" ) || ["center" ].includes (tag); }; app.mount ("#app" );
pinia基本使用下面以counter为例,先创建@/views/counter.vue
counter.vue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <template > <div class ="counter-page" > <h2 > Counter</h2 > <p > 当前计数: {{ countStore.count }}</p > <select v-model ="countStore.selected" > <option v-for ="i in 4" :key ="i" :value ="i" :label ="i" > </option > </select > <button @click ="counter" > 计数</button > </div > </template > <script setup name ="Counter" > import { useStoreCount } from "@/store/counter.js" ;import { ref } from "vue" ;let countStore = useStoreCount ();console .log (countStore.count );console .log (countStore.$state .count );function counter ( ){ countStore.count +=countStore.selected || 1 } </script > <style scoped > .counter-page { padding : 15px ; background-color : #ddd ; margin-bottom : 10px ; } </style >
创建一个@/store/counter.js
counter.js 1 2 3 4 5 6 7 8 9 10 import { defineStore } from "pinia" ;export const useStoreCount = defineStore ("counter" , { state ( ){ return { count : 0 , selected : 1 , }; } });
修改状态pinia的state是响应式的,所以可以直接修改state!
直接修改
1 2 3 import { useStoreCount } from "@/store/counter.js" ;let countStore = useStoreCount ();countStore.count +=1 ;
批量修改
1 2 3 4 5 6 7 import { useStoreCount } from "@/store/counter.js" ;let countStore = useStoreCount ();countStore.$patch({ count : 123 , selected : 21231 })
使用 action
在 store 中 加入actions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import { defineStore } from "pinia" ;export const useStoreCount = defineStore ("counter" , { actions : { increment ( ){ this .count ++ } }, state ( ){ return { count : 0 , selected : 1 , }; }, });
在counter.vue中调用actions定义都方法
1 2 3 4 5 6 7 import { useStoreCount } from "@/store/counter.js" ;let countStore = useStoreCount ();function counter ( ){ countStore.increment (); }
storeToRefsstoreToRefs可以把store中的state转换为ref! 当在组件中需要结构 store数据时,需要storeToRefs将结构单数据,变为ref响应式!
1 2 3 4 5 6 7 8 import { useStoreCount, storeToRefs } from "@/store/counter.js" ;let countStore = useStoreCount ();let { count } = storeToRefs (countStore);console .log ("count" , count)function counter ( ){ countStore.increment (); }
storeToRefs(countStore) 和 toRefs(countStore.$state) 是等价的! 不要尝试toRefs(countStore) 去获取state! 这样会把store中的不需要的属性也会转为ref!
gettersgetters是store中的计算属性! 可以对数据进行计算和处理!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import { defineStore } from "pinia" ;export const useStoreCount = defineStore ("counter" , { actions : { increment ( ){ this .count ++ } }, getters : { defaultSelected ( state ){ return this .selected * 10 ; } }, state ( ){ return { count : 0 , selected : 1 , }; }, });
使用的话getters从countStore结构中获取getters
1 2 3 4 5 6 7 8 import { useStoreCount, storeToRefs } from "@/store/counter.js" ;let countStore = useStoreCount ();let { count, defaultSelected } = storeToRefs (countStore);console .log ("count" , count)function counter ( ){ countStore.increment (); }
订阅$subscribe监听数据变化pinia可以使用$subscribe监听state的变化!
1 2 3 4 5 6 7 8 9 10 11 12 13 import { useStoreCount, storeToRefs } from "@/store/counter.js" ;let countStore = useStoreCount ();let { count, defaultSelected } = storeToRefs (countStore);countStore.$subscribe((mutate, state ) => { console .log ("变化了 state" , state); }) function counter ( ){ countStore.increment (); }
store组合是写法1 2 3 4 5 6 7 8 9 10 11 12 13 14 import { reactive, ref } from "vue" ;import { defineStore } from "pinia" ;export const useStoreCount = defineStore ("counter" , ()=> { let counter = ref (0 ) function incrementCounter ( ){ counter.value ++ } return { counter, incrementCounter } });
防止pinia数据刷新丢失安装插件
1 npm i pinia-plugin-persistedstate -D
在main.js中引入插件并挂载到pinia
1 2 3 4 5 6 7 8 9 import { createPinia } from "pinia" ;import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' const app = createApp (App );const pinia = createPinia ();pinia.use (piniaPluginPersistedstate); app.use (pinia);
使用
1 2 3 4 5 6 7 8 9 10 11 12 import { defineStore } from "pinia" ;export const useLoverTail = defineStore ("lover-tail" , { state ( ){ return { loverTails : [] }; }, persist : true });
组件属性传递 组件传递方式 传递方法 父传子父组件通过v-bind:attr形式 传递给子组件, 子组件通过 defineProps(['attr'])来接受传值! $parent可以在子组件中获取父组件的属性和方法! 子传父1. 子组件通过 defineEmits属性来发送父组件v-on:attr传递的属性! 2.父组件定义方法通过v-bind:fun,传递给子组件进行传参并调用!3. 子组件通过ref在子组件上打标记 并在子组件中借助 defineExpose() 将属性或方法暴露出去给父组件获取! 4. $refs可以获取所有子组件 祖孙夸传provide inject任意组件miit 事件发布订阅 插件
父传子子组件通过 defineProps属性来接受父组件v-bind:attr传递的属性! 子组件通过$parent属性来获取 父组件中的属性和方法,需要父组件提供 defineExpose({})来暴露内部属性和方法!
1 2 3 4 5 6 7 8 9 10 <template > <children ref ="children" :attr ="attr" /> </template > <script setup name ="index" > import { ref } from "vue" import Children from "./children.vue" ; let value = ref ("父组件的值" ) defineExpose ({value}) </script >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <template > <div > 父组件传递过来的属性 {{ attr }}</div > <button @click ="onClick($parent)" > 子组件点击事件</button > </template > <script setup name ="children" > import { defineProps } from "vue" ; let props = defineProps (['attr' ]) function onClick (parent ){ console .log ("parent" , parent) } </script >
子传父子组件通过 defineEmits属性来发送父组件v-on:attr传递的属性! 也可以 父组件通过定义方法 传递给子组件,子组件调用父组件所定义的方法传参给父组件! 通过ref在子组件上打标记 并在子组件中借助 defineExpose() 将属性或方法暴露出去给父组件获取! 也可以通过在父组件中使用 $refs 获取所有子组件的ref!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template > <children ref ="childrenRef" :attr ="attr" :setValue ="getChildrenValue" @onClick ="getChildrenValue" /> </template > <script setup name ="index" > import { ref, onMounted } from "vue" import Children from "./children.vue" ; let childrenRef = ref () function getChildrenValue ( value ){ console .log ("子组件传递过来的值" , value) } onMounted (() => { console .log (childrenRef.value .childValue ) }) </script >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <template > <div > 父组件传递过来的属性 {{ attr }}</div > <button @click ="setEmitValue" > 通过 emit 传递值给父组件</button > </template > <script setup name ="children" > import { defineProps, ref } from "vue" ; let childValue = ref ("子组件值" ) let props = defineProps (['attr' , "setValue" ]) let emits = defineEmits (['on-click' , 'on-chnage' ]) function setEmitValue ( ){ emits ("on-click" , childValue ) } defineExpose ({ childValue, attr : props.attr }) </script >
miit 任意组件通信miit 是一个体积非常小的插件事件订阅 事件触发!
安装mitt
创建文件@/utils/emitter.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import mitt from "mitt" ;const emitter = mitt ();emitter.on ("test1" , (val )=> { console .log ("test1触发调用" ); }) emitter.on ("test2" , (val ) => { console .log ("test2触发调用" ); }); setInterval (()=> { emitter.emit ("test1" , "参数" ); emitter.emit ("test2" , "参数" ); }, 1000 ) setTimeout (() => { emitter.all .clear (); }, 2000 ); export default emitter;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <template > <children ref ="childrenRef" /> </template > <script setup name ="index" > import { ref, onMounted } from "vue" import emitter from "@/utils/emitter.js" ; import Children from "./children.vue" ; let childrenRef = ref () onMounted (() => { emitter.emit ("zujian2" ,childrenRef.value .childValue ) }) </script >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <template > <div > 父组件传递过来的属性 {{ attr }}</div > <button @click ="setEmitValue" > 通过 emit 传递值给父组件</button > </template > <script setup name ="children" > import { defineProps, ref } from "vue" ; import emitter from "@/utils/emitter.js" ; let childValue = ref ("组件2值" ) emitter.on ("zujian2" , (val )=> { console .log ("组件1 给 组件2 传值了" , val) }) </script >
v-model 组件通信借助v-model双向绑定的特性,实现 父子组件之间的通信!
vue3v-model在html标签上
1 2 3 <input v-model = "modelValue" /> <input :value = "modelValue" @input = "modelValue = $event.target.value" />
v-model在自定义组件上
1 2 3 <AtGuiGuInput v-model ="modelValue" /> <AtGuiGuInput :modelValue ="modelValue" @update:modelValue ="modelValue = $event.target.value" />
AtGuiGuInput.vue
1 2 3 4 5 6 7 8 <template > <input :value ="modelValue" @input ="emit('update:modelValue', $event.target.value)" /> </template > <script setup > import { defineProps, defineEmit } from "vue" ; let props = defineProps (['modelValue' ]) let emit = defineEmit (['update:modelValue' ]) </script >
v-mode绑定多个
1 <AtGuiGuInput v-model:username ="username" v-model:password ="password" />
AtGuiGuInput.vue
1 2 3 4 5 6 7 8 9 10 <template > <input :value ="modelValue" @input ="emit('update:username', $event.target.value)" /> <input :value ="modelValue" @input ="emit('update:password', $event.target.value)" /> </template > <script setup > import { defineProps, defineEmit } from "vue" ; let props = defineProps (['username' , 'password' ]) let emit = defineEmit (['username' , 'password' ]) </script >
vue2vue2 AtGuiGuInput.vue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <template > <div > <input :value ="modelValue" @input ="$emit('getValue', $event.target.value)" /> </div > </template > <script > export default { name : "AtGuiGuInput" , model : { prop : "modelValue" , event : 'getValue' , }, data ( ){ return { } } } </script >
$attrs$attrs用于当前父组件,向当前子组件传递数据(祖-孙)!$attrs是一个对象,包含所有父组件所传递的数据!注意: $attrs会自动排除props中声明的属性(可以认为声明过的props被子组件自己消费了)
1 2 3 4 5 6 7 8 9 10 11 12 <template > <children ref ="childrenRef" :a ="a" :b ="b" :c ="c" :d ="d" /> </template > <script setup name ="index" > import { ref, onMounted } from "vue" import Children from "./children.vue" ; let a = ref (0 ) let b = ref (0 ) let c = ref (0 ) let d = ref (0 ) </script >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <template > <div > 父组件传递过来的属性 {{ attr }}</div > <grandchild v-bind ="$attrs" /> </template > <script setup name ="children" > import { useAttrs } from "vue" ; let childValue = ref ("子组件值" ) let attrs = useAttrs (); console .log ("$attrs" , attrs) </script >
$refs和$parent$refs用于获取所有子组件的ref属性值!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template > <child1 ref ="child1Ref" /> <child2 ref ="child2Ref" /> <button @click ="getAllComponent($refs)" > 点我获取所有子组件</button > </template > <script setup name ="children" > import { useAttrs } from "vue" ; function getAllComponent ( refs ){ console .log ("refs" , refs) for (const key in refs) { console .log ("refs" , refs[key]) } } </script >
$parent 获取$parent父组件内部的属性和方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <template > <children ref ="childrenRef" :a ="a" :b ="b" :c ="c" :d ="d" /> </template > <script setup name ="index" > import { ref, onMounted } from "vue" import Children from "./children.vue" ; let a = ref (0 ) let b = ref (0 ) let c = ref (0 ) let d = ref (0 ) defineExpose ({a, b, c, d}) </script >
1 2 3 4 5 6 7 8 9 10 11 12 13 <template > <div > 父组件传递过来的属性 {{ attr }}</div > <grandchild v-bind ="$attrs" /> <button @click ="getAllComponent($parent)" > 点我获取父组件内部属性和方法</button > </template > <script setup name ="children" > function getAllComponent ( parent ){ console .log ("parent" , parent.a ) } </script >
provide 和 inject依赖注入provide和inject用于父组件向子组件传递数据,并且子组件可以向孙组件传递数据!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <template > <children ref ="childrenRef" :a ="a" :b ="b" :c ="c" :d ="d" /> </template > <script setup name ="index" > import { ref, onMounted, provide } from "vue" import Children from "./children.vue" ; let money = ref (100 ) let car = reactive ({ brand : "奔驰" , price : "1200" }) provide ("money" , money) provide ("car" , car) provide ("car" , {money, car}) </script >
1 2 3 4 5 6 7 8 <template > <div > 祖父组件传递过来的属性 {{ attr }}</div > </template > <script setup name ="children" > import { inject } from "vue" let money = inject ("money" , "这是默认值" ) let car = inject ("car" ) </script >
provide提供的值 在inject中没有找到对应的模块, 那么inject可以通过第二个参数来设置默认值!
插槽 slot 插槽 就是在父组件中的子组件标签内部添加动态标签内容!
默认插槽父组件
1 2 3 <children-component > <div > 这里是插槽内容,会被渲染在子组件内部元素中!</div > </children-component >
子组件
1 2 3 <templte > <slot > 父组件中的传递的内容 会在此处显示!</slot > </template >
具名插槽当在不同位置使用插槽时, 可以使用具名插槽!`v-slot:title 只能在template和 component中使用! ·v-slot:title·简写方式 #title
父组件
1 2 3 4 <children-component > <template v-slot:title > 这里是标题</template > <template v-slot:body > 这里是内容</template > </children-component >
子组件
1 2 3 4 5 6 7 8 <templte > <div > <slot name ="title" > 父组件中的传递的内容 会在此处显示!</slot > <div class ="body" > <slot name ="body" > 父组件中的传递的内容 会在此处显示!</slot > </div > </div > </template >
作用域插槽就是在子组件中定义slot中,加入特定内部参数,提供父组件使用!
父组件
1 2 3 4 5 <children-component > <template v-slot:title > 这里是标题</template > <template #body ="{ games }" > 这里是内容{{ games }}</template > </children-component >
子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <templte > <div > <slot name ="title" > 父组件中的传递的内容 会在此处显示!</slot > <div class ="body" > <slot :games ="games" name ="body" > 父组件中的传递的内容 会在此处显示!</slot > </div > </div > </template > <script setup > import { reactive } from "vue" let games = reactive ([ {id : 5781738213 , name : "永结午间" }, {id : 4781738213 , name : "明日" } ]) </script >
组件传递方式组件关系传递方式父传子props v-model $refs 默认插槽 具名插槽子传父props emit v-model $parent 作用域插槽祖传孙 孙传祖$attrs [provide - inject]兄弟间 任意传mitt pinia
其他 APIshallowRef 和 shallowReactiveshallowRef 只针对浅层面的数据进行响应式!shallowReactive 只针对object 浅层面属性响应式 深层面属性不做响应!场景: 当业务中遇到 深层次比较大的 对象时 一些不关注的属性不需要进行响应式,只需要关注浅层面的数据是否发生改变!
shallowRef shallowReactive 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <script setup > import { ref, shallowRef } from "vue" let num = shallowRef (0 ); let person = shallowRef ({ name : "张三" }); function changeNum ( ){ num.value += 2 ; } function changeName ( ){ person.value .name = "李四" ; } function changePerson ( ){ person.value = { name : "李四" }; } </script >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <script setup > import { ref, shallowReactive } from "vue" let person = shallowReactive ({ name : "zhangsan" , age : 15 , hobbys : { games : ['永结物件' ,'明日' ] likeColor : "black" } }) function changePersonName ( ){ person.name = "lisi" } function changePersonAge ( ){ person.age = 16 } function changeLikeColor ( ){ person.hobbys .likeColor = "red" } </script >
readyonly 和 shallowReadyonlyreadonly 用于创建一个对象的深层次只读副本!readonly 只接受一个响应式属性 ref / reactive作为参数!shallowReadyonly 浅层次 只读副本! 只限制 对象第一层只读!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import { ref, reactive, readyonly, shallowReadyonly } from "vue" let sum = ref (0 ) let sum2 = readyonly (sum) let car = reactive ({ brand : "奔驰" , options : { price : "1200" , color : "黑色" , } }) let car2 = readyonly (car) let car3 = shallowReadyonly (car) car3.brand = "宝马" car3.options .price = "1300"
car2 和 car 之间是有关系的,当car可响应式属性发生改变时, car2 也会发生改变!
toRaw 和 markRawtoRaw 将响应式对象 ref 或 reactive 数据 变为原始数据!不会触发视图的更新!markRaw 被标记的对象不会被响应式处理`!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import { ref, reactive, toRaw } from "vue" let car = reactive ({ brand : "奔驰" , options : { price : "1200" , color : "黑色" , } }) let car2 = toRaw (car)
markRaw
1 2 3 4 import { ref, reactive, toRaw, markRaw } from "vue" let car = markRaw ({ brand : "奔驰" })let car2 = reactive (car)
customRef自定义refcustomRef 可以自己定义ref响应式数据!customRef 类似于computed计算属性,监控数据变化,触发视图更新的过程中可以进行一些处理!
customRef 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <template > <div class ="custom-ref" > <div class ="msg" > {{ msg }}</div > <input type ="text" v-model ="msg" /> </div > </template > <script setup > import { ref, customRef } from "vue" ; let initailValue = "Hello Vue 3" let timer = null ; let msg = customRef ((track, trigger )=> { return { get : () => { track (); return initailValue }, set : (value ) => { clearTimeout (timer); timer = setTimeout (() => { initailValue = value; trigger (); }, 2000 ) } } }) </script > <style scoped > .custom-ref { padding : 15px ; background-color : #ddd ; margin-top : 10px ; } </style >
teleport传送门teleport 可以将元素传送到指定的DOM节点!teleport 即使你的 dom 元素 在某个节点位置,通过teleport to=""配置后,显示将在指定的节点中显示!id选择器 class选择器 标签选择器
1 2 3 <teleport to ="body" > <div > 我是传送门,我会将以下内容挂在到body中!</div > </teleport >
<Teleport> 挂载时,传送的 to 目标必须已经存在于 DOM 中。理想情况下,这应该是整个 Vue 应用 DOM 树外部的一个元素。如果目标元素也是由 Vue 渲染的,你需要确保在挂载 <Teleport>之前先挂载该元素。
suspense异步组件suspense 当组件内部使用到异步加载数据时,可以使用suspense!防止 异步组件 在异步加载数据之前 丢失问题!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <template > <div class ="container" > <h3 > 父组件</h3 > <Suspense > <template v-slot:default > <children :msg ="msg" > </children > </template > <template v-slot:fallback > <children :msg ="msg" > </children > </template > </Suspense > </div > </template > <script setup > import children from "./children.vue" import { Suspense } from "vue" </script >
1 2 3 4 5 6 7 8 9 10 <template > <div class ="msg" {{ data }}> </div > </template > <script setup > import { ref } from "vue" import axios from "axios" let msg = ref ("" ) let data = await axios.get ("https://api.uomg.com/api/rand.qinghua?format=json" ) </script >
vue3全局对象app.component - 全局组件注册app.config - 全局配置对象app.directive - 全局指令app.mount - ``app.unmountapp.use