Vue3知识汇总
|字数总计:5.5k|阅读时长:24分钟|阅读量:
基础知识
ref和reactive
ref
可以定义基本数据类型和对象类型,用ref
定义对象类型数据时,底层使用的其实就是reactive
reactive
只能定义对象类型数据,reactive
定义的对象不能直接赋值修改整个对象,否则会失去响应式
1 2 3 4
| const obj = reactive({ name: 'zs' }) obj.name = "ls" obj = { name: 'ls' } Object.assign(obj,{ name: 'ls' })
|
toRefs和toRef
作用:从响应式对象解构时,将数据转换为响应式的
toRefs
:解构时,批量将对象属性全转换为ref
响应式对象
toRef
:解构时,将对象中指定属性转换为ref响应式对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const obj = reactive({ name: 'zs', age: 15, sex: 'man' })
const { name, age } = obj
const { name,age } = toRefs(obj)
const oSex = toRef(obj, 'sex')
|
计算属性computed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const name = "zs"
const ccucc = computed(()=>{ return name + '123' })
const ccucc = computed({ get(){ return name + '123' }, set(val){ name.value = val } }) console.log(ccucc.value) ccucc.value = 'ls'
|
监听watch
watch
能监听以下四种数据:
ref
定义的数据
reactive
定义的数据
- 函数返回一个值(
getter
函数)
- 一个包含上述内容的数组
ref定义的基本数据类型
基本使用
1 2 3 4 5 6 7
| const name = ref("zs")
watch(name, (newVal,oldVal) => { console.log('新值:' + newVal) console.log('旧值:' + oldVal) })
|
1 2 3 4
| watch(name, (val) => { console.log('新值:' + val) })
|
停止监听
1 2 3 4 5 6 7 8
| const stopWatch = watch(name, (newVal,oldVal) => { console.log('新值:' + newVal) console.log('旧值:' + oldVal) if(newVal === 'ls'){ stopWatch() } })
|
ref定义的对象数据类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const obj = ref({ name: 'zs', age: 18 })
watch(obj, (newVal,oldVal) => { console.log('新值:' + newVal) console.log('旧值:' + oldVal) })
watch(obj, (newVal,oldVal) => { console.log('新值:' + newVal) console.log('旧值:' + oldVal) },{ deep:true })
|
新旧值出现相同原因
情况一:在修改某个属性时,新旧值返回一样。原因是对象地址没有变化,新值和旧值读取的都是一个值
即:obj.name
由zs
变成了ls
,watch监听到变化,新旧值都是从同一个内存地址的对象obj.name
拿取,所以返回的一样的值
情况二:若是修改整个对象,那么新旧值读取的将是两个不同的内存地址,所以返回的值是不一样,
即:oldVal
拿取的是修改前内存地址的对象,newVal
拿取的是现在修改后指向的内存地址值
reactive定义的数据监听
1 2 3 4 5 6 7 8 9 10 11
| const obj = reactive({ name: 'zs', age: 18 })
watch(obj, (newVal,oldVal) => { console.log('新值:' + newVal) console.log('旧值:' + oldVal) })
|
新旧值出现相同原因同上ref
reactive默认开启的深度监听,原来版本不支持手动关闭,但是在最新版支持手动关闭?未测试
监听对象中的某个属性
- 当只监听对象中某个基础数据类型属性时,需要以
getter
函数的形式完成监听某个值(即,一个返回值函数)
1 2 3 4 5 6 7 8 9 10 11 12
| const obj = reactive({ name: 'zs', age: 18, sp: { type: 'XL' } })
watch(()=> obj.name, (newVal,oldVal) => { console.log('新值:' + newVal) console.log('旧值:' + oldVal) })
|
- 当只监听对象中某个对象数据类型属性时,可以直接监听,也可写成函数返回值形式,但两者略有不同。
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
| const obj = reactive({ name: 'zs', age: 18, sp: { type: 'XL', money: 666 } })
watch(obj.sp, (newVal,oldVal) => { console.log('新值:' + newVal) console.log('旧值:' + oldVal) })
watch(() => obj.sp, (newVal,oldVal) => { console.log('新值:' + newVal) console.log('旧值:' + oldVal) })
watch(() => obj.sp, (newVal,oldVal) => { console.log('新值:' + newVal) console.log('旧值:' + oldVal) },{ deep:true })
|
监听多个不同数据
基于上述情况,按需求放到数组中即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const obj = reactive({ name: 'zs', age: 18, sp: { type: 'XL', money: 666 } })
watch([() => obj.name,() => obj.sp.type], (newVal,oldVal) => { console.log('新值:' + newVal) console.log('旧值:' + oldVal) },{ deep:true })
|
watchEffect
官方:立即运行一个函数,同时响应式的追踪其依赖,并在依赖更改时重新执行改函数
watch
和watchEffect
对比
- 都能监听响应式数据的变化,不同的是监听数据变化的方式不同
watch
:要明确指出监听的数据
watchEffect
:不用明确指出监听的数据(函数中用到哪些数据,那就监听哪些数据)
1 2 3 4 5 6 7 8 9 10
| const a = ref(0) const b = ref(55)
watchEffect(()=>{ if(a > 99){ console.log("a 大于 99 了") } })
|
标签ref属性
1 2 3 4 5 6
| <div ref="ccucc"></div>
<script> // 创建一个ccucc,存储ref标记的内容 let ccucc = ref() </script>
|
1 2 3 4 5 6
| <MyCard ref="ccucc"></MyCard>
<script> // 配合子组件中的defineExpose API 可以拿到子组件中的数据 let ccucc = ref() </script>
|
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
|
<MyCard a='zs' :b='list'></MyCard>
const list = reactive({ name: 'zs', age: 15 })
defineProps(['a','list'])
interface listModel { name: string, age: number } defineProps<{ list:listModel, a:string }>()
interface listModel { name: string, age: number } defineProps<{ list:listModel, a?:string }>()
import { withDefaults } from "vue"
interface listModel { name: string, age: number } withDefaults(defineProps<{ list: listModel; a?: string }>(), { list: () => ({ name: "ls", age: 18 }), a: "默认值" });
|
组件生命周期
总的概括为:创建、挂载、更新、销毁
beforeCreate
(创建前)、created
(创建完毕)
beforeMount
(挂载前)、mounted
(挂载完毕)
beforeUpdate
(更新前)、update
(更新完毕)
beforeDestroy
(销毁前)、destroy
(销毁完毕)
在setup中就已经完成创建
onBeforeMount
(挂载前)、onMounted
(挂载完毕)
onBeforeUpdate
(更新前)、onUpdated
(更新完毕)
onBeforeUnmount
(销毁前)、onUnmounted
(销毁完毕)
渲染阶段子组件优先于父组件
v-if
可以控制组件的销毁
自定义Hooks
本质:同一功能或相关功能和数据的抽离,组合式API
的体现
优点:功能清晰易维护功能,各个hooks中都可调用生命周期钩子
常用命名:通常use[Name].[ts/js]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| export default function(){ const c = 55 const sum = (a:number,b:number) =>{ return a + b + c } return { sum } }
import useSum from './useSum'
const { sum } = useSum()
console.log(sum(3,5))
|
组件通信
props
使用频率最高,父<=>
子
- 若父
=>
子:属性值是非函数
- 若父
<=
子:属性值是函数
father.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <template> <Child :name="name" :sendAge = "getAge"/> </template> <script setup lang="ts">
let name = ref('zs')
let age = ref()
// 儿子触发函数,拿到儿子的年龄 const getAge = (val:number) => { age.value = val } </script>
|
child.vue
1 2 3 4 5 6 7 8 9 10 11 12
| <template> <div> {{ name }} <button @click="sendAge(age)">把年龄给父亲</button> </div> </template> <script setup lang="ts"> let age = ref(99)
// 声明接收props defineProps(['name','sendAge']) </script>
|
自定义事件
父<=
子
- 自定义事件时命名官方推荐
keybab-case
的事件命名,非驼峰
father.vue
1 2 3 4 5 6 7 8 9 10 11 12 13
| <template> <Child @test-age="getAge(age)">把年龄给父亲</Child> </template> <script setup lang="ts">
let age = ref()
// 儿子触发函数,拿到儿子的年龄 const getAge = (val:number) => { age.value = val } </script>
|
child.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <div> {{ name }} <button @click="sendAge">把年龄给父亲</button> </div> </template> <script setup lang="ts">
let age = ref(99)
// 声明事件 const emit = defineEmits(['test-age'])
const sendAge = () => { // 触发事件 emit("test-age", age.value) } </script>
|
mitt
任意组件通信
src\utils\emitter.ts
1 2 3 4
| import mitt from 'mitt'
const emitter = mitt() export default emitter
|
main.ts
1
| import emitter from '@/utils/emitter'
|
1 2 3 4 5 6 7 8 9 10
| import emitter from '@/utils/emitter'
emitter.on('test',()=>{ ... })
emitter.emit('test')
emitter.off('test') emitter.all.clear()
|
father.vue
1 2 3 4
| <template> <Child1 @test-age="getAge(age)">把年龄给父亲</Child1> <Child2 @test-age="getAge(age)">把年龄给父亲</Child2> </template>
|
child1.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <template> <div> 收到child2的年龄 {{ age }} </div> </template> <script setup lang="ts"> import emitter from '@/utils/emitter' let age = ref() // emitter绑定send-age事件,只要事件触发就会接收数据 emitter.on('send-age',(val:number)=>{ age.value = val })
// 组件卸载时解绑事件 onUnmounted(() => { emitter.off('send-age') }) </script>
|
child2.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <template> <div> <button @click="toAge">把年龄给兄弟child1</button> <!-- 直接触发给值 --> <button @click="emitter.emit('send-age', age)">把年龄给兄弟child1</button> </div> </template> <script setup lang="ts"> import emitter from '@/utils/emitter' let age = ref(99)
const toAge = () =>{ // 触发,发送数据 emitter.emit('send-age', age.value) }
</script>
|
v-model
父<=>
子
father.vue
- 当
v-model
用在html
标签上,将实现双向绑定
下面两种写法,第一为v-model
,第二为底层实现
第二种写法中:value="name"
是页面到数据,@input="name = $event.target.value"
是数据到页面,即前者是页面数据变则绑定的数据就变。后者实现绑定的数据变则页面的数据就变,合着完成的就是双向绑定
1 2 3 4 5 6 7 8 9
| <template> <input type="text" v-model="name" /> <input type="text" :value="name" @input="name = (<HTMLInputElement>$event.target).value" /> </template> <script setup lang="ts">
let name = ref('zs')
</script>
|
father.vue
1 2 3 4 5
| <template> <Child1 v-model="name"></Child1> <!-- 上面完整写法 --> <Child1 :modelValue="name" @update:modelValue="$event"></Child1> </template>
|
update:modelValue
是一个事件名称,类似自定义事件,只是名称带有规范
$event:对于原生事件,$event是事件对象,有target属性。对于自定义事件,$event是触发事件时所传递的数据,无target属性
child1.vue
1 2 3 4 5 6 7 8 9 10 11 12
| <template> <input type="text" :value="modelValue" @input="changeInput" /> </template> <script setup lang="ts"> defineProps['modelValue'] const emit = defineEmits(['update:modelValue'])
const changeInput = (e)=>{ emit('update:modelValue',e.target.value) } </script>
|
生态相关
路由(vue-router
)
基础使用
安装路由 npm i vue-router
创建路由 src/router/index.ts
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
| import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
const routes: Array<RouteRecordRaw> = [ { path: "/", name: "HelloWorld", component: () => import("../components/HelloWorld.vue"), children: [] }, { path: "/Cesium", name: "Cesium", component: () => import("../components/Cesium.vue") }, { path: "/Openlayer", name: "Openlayer", component: () => import("../components/Openlayer.vue") } ];
const router = createRouter({ history: createWebHistory(), routes });
export default router;
|
1 2 3 4 5 6 7 8
| import { createApp } from "vue";
import router from './router'
const app = createApp(App);
app.use(router) app.mount("#app");
|
1 2 3 4 5 6 7
| <div> // active-class可定义当前激活的样式 <router-link to="/home" active-class='active'>首页</router-link> <router-link :to="{ name: 'about' }" active-class='active'>关于</router-link> <router-link :to="{ path: '/login' }" active-class='active'>登录页</router-link> <router-view /> </div>
|
上面为路由跳转方式的一种,另外一种函数形式调用useRouter().push()
active-class
可以设置激活状态下的样式
注意
路由组件:网页调用中通过路由来调取到页面中的称为路由组件。一般存放在项目中的views
或者pages
文件夹下。
一般组件:网页调用中通过标签引入的形式,来调取到页面中的称为一般组件。一般存放在项目中的component
文件夹下。
- 通过点击导航,从页面”消失”的路由组件,默认被卸载,需要的时候再会去挂载。
路由器工作模式
history
模式
优点:URL
更加美观,不带有#
,更接近传统的网站
缺点:服务器需要做路径处理,否则刷新会出现404
1 2 3
| const router = createRouter({ history: createWebHistory() })
|
1 2 3 4
| # nginx 配置处理路径 location / { try_files $uri $uri/ /index.html; }
|
hash
模式
优点:兼容性更好,因为不用服务端处理路径。
缺点:URL
会带有#
不美观,且在SEO
优化方面相对较差。
1 2 3
| const router = createRouter({ history: createWebHashHistory() })
|
- 后台一般采用
hash
,电商等官网类一般history
路由传参
传
1 2
| <RouterLink to="/home?id=55"> <RouterLink :to="{ path:'/home', query: { id: 55 }}">
|
接
1 2 3 4
| import { useRoute } from "vue-router" const route = useRoute()
console.log(route.query.id)
|
传
1 2 3 4 5 6 7 8
| { name: "home", path: "home/:id/:name?" }
<RouterLink to="/home/55/zs"> <RouterLink :to="{ name:'home', params: { id: 55, name: 'zs' }}">
|
接
1 2 3 4 5
| import { useRoute } from "vue-router" const route = useRoute()
console.log(route.params.id) console.log(route.params.name)
|
路由的props
通过配置路由props
参数,结合defineProps
实现路由传参
- 传递
params
参数,需要地址占位
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const routes: Array<RouteRecordRaw> = [ { path: "/", ... children: [ { name: "home", component: () => Home path: "home/:id/:name?", props: true } ] } ]
|
1 2 3 4 5
| <RouterLink :to="{ name:'home', params: { id: 55, name: 'zs' }}">
defineProps(["id","name"])
|
- 传递
query
参数,不需要地址占位,函数式写法返回路由中的query对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const routes: Array<RouteRecordRaw> = [ { path: "/", ... children: [ { name: "home", component: () => Home path: "home", props(route){ return route.query } } ] } ]
|
1 2 3 4 5
| <RouterLink :to="{ name:'home', query: { id: 55, name: 'zs' }}">
defineProps(["id","name"])
|
- 传递固定参数,对象式写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const routes: Array<RouteRecordRaw> = [ { path: "/", ... children: [ { name: "home", component: () => Home path: "home", props: { id: 55 } } ] } ]
|
1 2 3 4
| <RouterLink :to="{ name:'home' }">
defineProps(["id"])
|
使用路由的props模式传递参数实质是,普通defineProps
传递函数的逻辑,vue
将自动在路由组件上加上<Home :id="id" :name="name" />
,所以子组件用defineProps
取值就行。
路由replace
默认路由跳转为push
,相当于每跳转一下就会追加一个路由,点击返回就可回到上一次的路由
相反,replace
将替换原来路由,所以其无法返回上一个路由
1
| <RouterLink replace :to="{ name:'home', query: { id: 55, name: 'zs' }}">
|
编程式路由导航
通过一个路由的钩子函数进行跳转,其中router.push
中的参数写法和前面标签<router-link />
中的to
参数一样。
1 2 3 4 5 6 7
| import { useRouter } from "vue-router"
const router = useRouter()
router.push('/home')
router.replace('/home')
|
路由重定向
默认项目启动,会读取/
路由,若没有该路由地址,控制台会提示警告。一般采取一个重定向将某个路由组件放在/
路由下,再通过redirect
进行重定向过去。即自动跳转到目标路径下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const routes: Array<RouteRecordRaw> = [ { path: "/", redirect: "/home", }, { name: "home", component: () => Home path: "/home", props: { id: 55 } } ]
|
Pinia
集中式状态(数据)管理(redux
、vuex
、Pinia
)
基本使用
- 安装
pinia
- 创建
pinia
在src
创建一个store
文件夹,并新建一个index.ts
文件
1 2 3 4 5 6
| import { createPinia } from 'pinia'
const pinia = createPinia()
export default pinia
|
- 引入
pinia
main.ts
1 2 3 4 5 6 7 8
| import { createApp } from 'vue' import App from './App.vue'
import pinia from './store' const app = createApp(App)
app.use(pinia) app.mount('#app')
|
- 应用
规范
- 所有
pinia
相关内容放在src\store
目录下,各个仓库放在src\store\modules
下
- 文件命名通常与相关组件同名,如当前文件记录
home
组件相关数据,那通常命名为home.ts
或Home.ts
- 暴露对象时,通常采取
hooks
的方式,如useHomeStore
home.ts
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
| import { defineStore } from 'pinia'
const useHomeStore = defineStore('Home', { state: () => { return { name: "zs", age: 15 } }, getters:{ grow: state => state.sum + 1 , delAge(){ return this.sum - 1 } }, actions:{ editName(name:string){ this.name = name } } })
export default useHomeStore
|
home.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <template> <div> {{ homeStore.name }} <!-- getters计算值 --> {{ homeStore.grow }} </div> </template> <script setup lang="ts"> import { useHomeStore } from "@/store/home" const homeStore = useHomeStore() // 读值 console.log(homeStore.name)
</script> <style scoped lang="scss"></style>
|
pinia
数据修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { useHomeStore } from "@/store/home" const homeStore = useHomeStore()
homeStore.name = "ls"
homeStore.$patch({ name: 'ls', age: 18 })
homeStore.editName("ls")
|
storeToRefs
作用:解构store
中数据后,进行响应式转换
区别toRefs
:只会关注store
中的数据,不对方法起作用,而toRefs
会将store
中所有东西转换为响应式,会将所有方法进行转换。所以pinia
数据响应式转换用storeToRefs
1 2 3 4 5
| import { storeToRefs } from 'pinia' import { useHomeStore } from "@/store/home"
const homeStore = useHomeStore() const { name, age, grow } = storeToRefs(homeStore)
|
$subscribe
作用:监听store
中数据变化
1 2 3 4 5 6 7
| import { useHomeStore } from "@/store/home"
const homeStore = useHomeStore()
homeStore.$subscribe((mutate,state)=>{ console.log('homeStore中数据变化了') })
|
组合式写法
home.ts
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
| import { defineStore } from 'pinia' import { reactive, computed } from "vue"
const useHomeStore = defineStore('Home', { const homeList = reactive({ name: "zs", age: 15 }) const grow = computed(()=>{ return homeList.age + 1 }) const editName = (name:string) => { homeList.name = name } return { homeList, grow, editName } })
|
vue3开发
父子组件数据双向绑定
父组件
1
| <DataFormat v-model:modelValue="dataFormat"></DataFormat>
|
子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <template> <el-input v-model="inputValue" @input="handleInput"></el-input> </template>
<script setup lang="ts"> import { ref, watch } from 'vue'
const props = defineProps<{ modelValue: string }>()
const emit = defineEmits(['update:modelValue'])
const inputValue = ref(props.modelValue)
watch(inputValue, (newValue) => { emit('update:modelValue', newValue) })
const handleInput = (value: string) => { inputValue.value = value } </script>
|
1
| https://blog.csdn.net/haodian666/article/details/134672770
|
问题解答
vue3
中的setup
能否同vue2
中的 data、methods
同时存在?
可以同时存在,且setup的生命周期前于data和methods,所以在data中可以通过this访问到setup中定义的数据