一、Composition API
和Options API
有什么区别
1.Composition API
几乎是函数,会有更好的类型推断
2.对tree-shaking
友好,代码也更容易压缩
3.见不到this
的使用,减少了this
指向不明的情况
4.Vue2的Object.defineProperty
不能检测对象的添加和删除(通过set
和delete
的实例方法解决,在嵌套层级较深的情况下有性能问题),Vue3是通过proxy
监听整个对象,在getter
中去递归响应式
二、ref
和reactive
1.ref
可以使用于任何数据类型,而reactive
只适用于对象类型。
2.reactive
进行重新赋值一个对象的话会丢失响应式,解决办法为:使用Object.assign()
let 变量名=reactive({key:value,key:value})
Object.assign(变量名,{
key:value,
key:value,
...
key:value
3.ref
进行赋值时应该使用变量名.value = 新值
,而reactive
赋值时直接变量名=新值
4.ref
一般情况下可以适用于所有场景,reactive
适合使用于嵌套层级较深的对象类型中
三、组件通信
1.父子传参:通过使用porps
给在子组件中传递参数,子组件中直接使用宏defineProps
可以直接接受该值,就可以直接在结构上使用
<script setup>
import {ref} from 'vue'
let msg=ref('父组件给子组件传的值')
</script>
<template>
<div>
<div>父组件</div>
<Child :msg/>
</div>
</template>
----------------------------------------
<script setup>
defineprops(['msg'])
</script>
<template>
<div>
<div>子组件</div>
<div>父组件传的值:{{msg}}</div>
</div>
</template>
2.子父传参:(不使用defineEmits
)父组件创建一个函数handleGetChild
传递给子组件
子组件利用defineProps
接受该函数后,在事件中直接调用并传递值toy
父组件中该函数通过value接受到该值后,使用toy.value=value
可以直接接受值
<script setup>
import {ref} from 'vue'
let toy = ref(null)
function handleGetChild(value){
toy.value = value
//前面的toy是上面创建的,因为使用ref创建响应式所以要toy.value,后面的value是
接受到从子组件传递过来的值
}
</script>
<template>
<div>
<div>父组件</div>
<div>子组件给父组件的玩具:{{toy}}</div>
<Child :handleGetChild/>
</div>
</template>
-------------------------------------
<script setup>
import {ref} from 'vue'
let toy = ref('给父亲的玩具')
defineProps(['handleGetChild'])
</script>
<template>
<div>
<div>子组件</div>
<button @click='handleGetChild(toy)'>点我把玩具给父组件</button>
</div>
</template>
3.子父传参:(使用defineEmits
):子组件绑定事件A,然后使用defineEmits
声明需要发送给父组件的事件B,在绑定事件A中利用defineEmits
发送B事件及需要传递的数据,
父组件中接收该事件后接收该值
<template>
<div>
//子组件
玩具:{{toy}}
<button @click='handleSendFather'>点我发送给父组件</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
let toy = ref('奥特曼')
let emit = defineEmits(['handleSendToy'])
let handleSendFather = () =>{
emit('handleSendToy',toy.value)
}
</script>
-------------------------------------------------------------------
<template>
<div>
//父组件
子组件给的玩具:{{getChildToy}}
<Child @handleSendToy='handleGetToy'/>
</div>
</template>
<script setup>
import { ref } from 'vue'
let getChildToy = ref()
let handleGetToy = value=>{
getChildToy.value = value
}
</script>
4.mitt
兄弟传参:自定义hooks mitt
然后在两个组件中分别引入mitt
,
在需要发送数据的组件里使用mitt.emit('事件名',数据)
,
在需要接收的组件里使用mitt.on('事件名',value=>{接收的变量名.value=vlaue})
,
性能优化:记得在组件卸载时解绑该事件,在生命周期onUnmounted
中使用mitt.off,
onUnmounted(()=>{mitt.off('事件名')})
先自定义hooks:
import mitt from 'mitt'
const emitter = mitt()
export default emitter
<script setup>
import emitter from '../../utils/emitter'
import {ref} from 'vue'
let computer = ref(null)
//使用mitt接收数据
emitter.on('send-computer',value=>{
computer.value = value
})
//在组件卸载时解绑send-computer事件
onUnmounted(()=>{
emitter.off('send-computer')
})
</script>
<template>
<div>
<div>兄弟组件1</div>
<div>兄弟组件2发来的电脑:{{computer}}</div>
</div>
</template>
-----------------------------
<script setup>
import emitter from '../../utils/emitter'
import {ref} from 'vue'
let computer = ref('华硕')
</script>
<template>
<div>
<div>兄弟组件2</div>
//使用mitt发送数据
<button @click='emitter.emit('send-computer',computer)'></button>
</div>
</template>
5.父组件给孙组件传参:父组件使用provide('事件名',数据)
,孙组件用let 变量名= inject('事件名')
可以直接获取到对应的数据,同样父组件传递函数给后代组件,后代组件通过inject接收后传参,父组件中该函数就可以进行数据的更改
6.attrs
:父组件传递给后代组件中的值用defineProps
接收,如果传递了3个值,只接收了1个值,那么剩余的两个值会自动存储在$attrs
中
<div>
父组件
<Child :a :b :c/>
</div>
-------------
<script setup>
defineProps(['a'])
</script>
<div>
子组件
<GrandChild v-bind='$attrs' />
</div>
------------
<script setup>
defineProps(['b','c'])
</script>
<div>
孙组件
</div>
7.集中式状态管理器Pinia Vue3中的Pinia相对于Vue2中的Vuex简洁了不少:在store文件夹中创建Top.js
1.第一步在控制台中安装pinia yarn add pinia
2.第二步在main.js
中注入pinia
```
import {createPinia} from 'pinia'
const pinia = createPinia()
app.use(pinia)
```
3.第三步编写store
文件夹中的js文件
```
import {defineStore} from 'pinia'
import {computed ref} from 'vue'
export let useTopStore = defineStore('Top',()=>{
let sum = ref(6)
let bigSum = computed(()=>{sum.value*100})
return {sum,bigSum}
})
```
4.第四步使用引入useTopStore
,再对其包裹使用storeToRefs
然后解构赋值let {sum,bigSum} = storeToRefs(topStore)
最后如果要修改pinia
里的值只需要topStore.sum = sum.value
直接赋值即可
<script setup>
import {useTopStore} from '../../store/Top'
import {storeToRefs} from 'pinia'
let topStore = useTopStore()
let {sum,bigSum} = storeToRefs(topStore)
function handleChangeSum(){
sum.value +=1
topStore.sum = sum.value
}
</script>
<template>
<div>
{{sum}}
{{bigSum}}
<button @click='handleChangeSum'>修改sum</button>
</div>
</template>
四、watch和watchEffect
watch
要明确监视的数据
watch(['监视对象'],(newValue,oldValue)=>{})
watchEffect
用到哪些数据,就监听哪些数据
watchEffect(()=>{})
<script setup>
import { ref, watch, watchEffect } from 'vue'
let temp = ref(10)
function handleSumAdd() {
temp.value += 10;
}
let height = ref(0)
function handleHeightAdd() {
height.value += 10;
}
//watch要明确指出监视的数据
watch([temp,height],(newValue,oldValue)=>{
if(newValue[0]>=60||newValue[1]>=80){
console.log('给服务器发请求');
}
}}
//watchEffect不需要明确指出监视的数据(函数中用到哪些属性,那就监视哪些属性)
watchEffect(() => {
if (temp.value >= 60 || height.value >= 80) {
console.log('给服务器发请求');
}
})
</script>
五、插槽
1.具名插槽(默认插槽也有个名字:default),其中v-solt
的语法糖为#
父组件
<Child>
<template #content>
<div v-for="item in games" :key="item.id">{{ item.name }}</div>
</template>
<template #title>
<div>标题<div>
</template>
</Child>
--------------------------
子组件
<slot name='title'>标题</slot>
<slot name='content'>内容</slot>
2.作用域插槽:特点为数据存储在子组件中,结构在父组件中
父组件
<Child>
<!-- params是子组件中slot组件中传给父组件的所有props -->
<template #games="params">
<ul>
<li v-for="item in params.games" :key="item.id">{{ item.name }}</li>
</ul>
</template>
</Child>
-----------------------------
子组件
let games = ref([
{ id: 1, name: '原神' },
{ id: 2, name: '梦幻西游' },
{ id: 3, name: '绝区零' },
{ id: 4, name: '星穹铁道' }
])
<div>标题</div>
<solt :games name='games'>内容</solt>
六、生命周期
1.setup
2.挂载前onBeforeMount
3.挂载完毕onMounted
4.更新前onBeforeUpdate
5.更新完毕onUpdated
6.卸载前onBeforeUnmount
7.卸载完毕onUnmounted
//创建
console.log('创建');
//挂载前
onBeforeMount(() => {
console.log('挂载前');
})
//挂载完毕
onMounted(() => {
console.log('挂载完毕');
})
//更新前
onBeforeUpdate(() => {
console.log('更新前');
})
//更新完毕
onUpdated(() => {
console.log('更新完毕');
})
//卸载前
onBeforeUnmount(() => {
console.log('销毁前');
})
//卸载完毕
onUnmounted(() => {
console.log('销毁完毕');
})
七、路由Router
1.第一步安装路由依赖yarn add vue-router
2.第二步创建router
文件夹index.js
文件
3.第三步在index.js
文件中编写router
代码
import {createRouter,crateWebHistory} from 'vue-router'
//引入组件
let router = createRouter({
history:createWebHistory(),
routes:[
{
path:'/props',
component:Props
},
{
path:'/news',
component:News,
children:[
{
name:'detail',
path:'detail',
component:Detail
}
]
}
]
})
4.第四步在main.js
中注入
import router from './router'
app.use(router)
5.第五步在路由组件中编写代码
<script setup>
import{RouterLink,RouterView} from 'vue-router'
</script>
<template>
<RouterLink :to='{path:'/news/detail'}'>内容</RouterLink>
<RouterView />
</template>
6.路由跳转除<RouterLink to=''></RouterLink>
外还可以在script
中使用useRouter().push('/')
八、shallowRef
、readonly
、shallowReadonly
、toRaw
、markRaw
shallowRef
:浅引用只能修改第一层的数据
let sum = shallowRef(0)
function a(){
sum.value +=1
}
let person = shallowRef({
name:'joy',
age:18
})
function b(){
person.value= {
name:'ken',
age:19
}
}
//person.value或者sum.value属于第一层,person.value.name或者person.value.age属于第二层不能被修改
readonly
:只读
let sum = readonly(0)
function a(){
sum.value +=`1 //不会被修改
}
shallowReadonly
:浅只读
toRaw
:只能对reactive
使用,将一个响应式对象变为原始对象,以此来保证哪怕代码修改了,视图不会发生变化以此来保护变量不被修改,使用场景:将响应式数据传递给非响应式的库或框架时使用
markRaw
:标记一个对象为原始对象且永远不会被响应式处理
九、defineProps()
、defineEmits()
、defineExpose()
是什么
1.defineProps()
:父组件给子组件传值后,子组件通过defineProps()
进行接收
<script setup>
//父组件
import {ref} from 'vue'
let sum = ref(0)
<Child :sum />
</script>
-------------------------------
<script setup>
//子组件
defineProps(['sum'])
</script>
<template>
<div>{{sum}}</div>
</template>
2.defineEmits()
:在子组件中声明一个事件后触发事件,在父组件中通过该事件名监听该事件
<script setup>
//子组件
let emit = defineEmits(['event-name'])
</script>
<template>
<div>
<button @click='emit('event-name')'>触发事件</button>
</div>
</template>
------------------------------------------------------------
<script setup>
//父组件
let handleEvent = ()=>{
console.log('事件已触发')
}
</script>
<template>
<div>
<Child @event-name='handleEvent'/>
</div>
</template>
3.defineExpose()
:子组件通过defineExpose()
暴露属性与方法,父组件通过ref引用子组件,可以访问子组件已暴露的属性和方法
<script setup>
//子组件
import {ref} from 'vue'
let sum = ref(0)
function handleText(){
console.log('被父组件调用')
}
defineExpose({
sum,
handleText
})
</script>
---------------------------------
<script setup>
//父组件
import {ref} from 'vue'
let ChildRef = ref(null)
function handleChildMethod(){
ChildRef.value.handleText
}
</script>
<template>
<div>
<Child ref='ChildRef'/>
<button @click='handleChildMethod'>访问子组件的属性和方法</button>
</div>
</template>
十、Teleport
:将组件的HTML结构移动到指定位置
eg:
css中使用了position:fixed,又使用了filter:saturate(100%).即使用了定位以及滤镜,原本的视图定位由整个屏幕变成了父组件,这个时候使用Teleport可以解决模态框问题
<button @click="isShow = true">展示弹窗</button>
//to为移动到的目标位置
<Teleport to="body">
<div class="modal" v-show="isShow">
<div class="titles">我是弹窗的标题</div>
<div>我是弹窗的内容</div>
<button @click="isShow = false">关闭弹窗</button>
</div>
</Teleport>
十一、路由守卫
路由守卫看这篇帖子:Vue3路由守卫详解:掌握导航控制的6个核心钩子路由守卫是Vue Router的核心功能之一,它允许开发者在路由导航的不 - 掘金
十二 、v-cloak
:一个小小的性能优化
在最近的项目中,通常会在渲染出页面之前会出现以下原始模型标签
```
<iamge />
<view></view>
<div></div>
...
```
解决办法就是在页面的最外层标签上绑定一个v-cloak
就可以解决这个问题
eg:
<template v-cloak>
<view></view>
</template>
v-cloak
是Vue3的一个内置指令,它可以让样式会在 Vue
实例编译结束时,从绑定的 HTML
元素上被移除。
当网络较慢,网页还在加载 Vue.js
,而导致 Vue
来不及渲染,这时页面就会显示出 Vue
源代码。我们可以使用 v-cloak
指令来解决这一问题。