一、 setup(语法糖)
1、基本使用
在vue3.2中不再需要进行return,当使用 <script setup> 的时候,任何在 <script setup> 声明的顶层的绑定 (包括声明的变量,函数声明,以及 import 引入的内容) 都可以在模板中直接使用,这是因为在setup函数中,所有的ES模板都被认为是暴露給上下文的值,并包含在setup()返回对象中。
要使用这个语法,需要将 setup 属性添加到 <script> 代码块上,示列:
<script setup>
import {ref} from 'vue'
let property = ref('alex');
</script>
<script setup> 中的代码会在每次组件实例被创建的时候执行。
2、自动注册
无需再通过components进行注册,可以直接引入使用。示列:
<template>
<child @getChild="getChild" :title="msg" />
</template>
<script setup>
import {ref} from 'vue'
//这里我们引入了子组件 child
import child from './child.vue'
</script>
3、组件通信
1)defineProps ----> [用来接收父组件传来的 props] 代码示列:
父组件代码:
<template>
<div>我是父组件----1</div>
<subassembly @getChili="getChili" :title="msg" />
</template>
<script setup>
import {ref} from 'vue'
import subassembly from './subassembly.vue'
//把值传递给子组件 ---> :title="msg" <Home @getChili="getChili" :title="msg" />
const msg = ref('父的值')
</script>
子组件代码:
<template>
<div>我是子组件----1</div>
<div style="color: red">{{props.title}}</div>
</template>
<script setup>
import {defineProps} from 'vue'
//接收父组件 传过来的值!
const props = defineProps({
title: {
type: String
}
});
//打印一下 接收父组件的值
console.log(props.title) //父的值
</script>
2)defineEmit ----> [子组件向父组件事件传递] 代码示列:
子组件代码:
<template>
<hr>
<div>我是子组件----2</div>
<button @click="toEmits">点击传到父组件</button>
</template>
<script setup>
import {defineEmits,ref} from 'vue'
//先定义一下子2 在发送值
const emits = defineEmits(['getChili']);
const toEmits = () => {
emits('getChili','子2的值')
}
</script>
父组件代码:
<template>
<div>我是父组件----2</div>
<div>{{data}}</div>
<subassembly @getChili="getChili" :title="msg" />
</template>
<script setup>
import {ref} from 'vue'
import subassembly from './subassembly.vue'
//空值接收 子组件2的传值
let data = ref(null)
const getChili = (e) => {
data.value = e
console.log(e) //子组件2的值
}
</script>
在标准组件写法里,子组件的数据都是默认隐式暴露给父组件的,但在script-setup模式下,所有数据只是默认return给template 使用,不会暴露到组件外,所以父组件是无法直接通过挂载ref 变量获取子组件的数据。如果要调用子组件的数据,需要先在子组件显示的暴露出来,才能够正确的拿到,这个操作,就是由defineExpose来完成。
defineExpose ----> [组件暴露出自己的属性] 代码示列:
子组件代码:
<template>
<div>我是子组件----3> {{ alex.stator }}</div>
</template>
<script setup>
import {ref, defineExpose, reactive} from 'vue'
let alex = reactive({
stator: 'alex',
age: 27
})
let alexNew = ref('alexNew');
console.log(alexNew)
defineExpose({
alex,
alexNew
})
</script>
父组件代码:
<template>
<button @click="shiEmots">获取暴露</button>
<subassembly ref="shield"/>
</template>
<script setup>
import subassembly from './subassembly.vue'
import {defineEmits,defineProps,ref} from 'vue'
const shield = ref()
const shiEmots = () =>{
//子组件接收暴露出来得值
console.log('接收reactive暴漏出来的值',shield.value.alex)
console.log('接收ref暴漏出来的值',shield.value.alexNew)
}
</script>
二、 hook函数
介绍:
- Vue3 的 hook函数 相当于 vue2 的 mixin, 不同在于 hooks 是函数
- Vue3 的 hook函数 可以帮助我们提高代码的复用性, 让我们能在不同的组件中都利用 hooks 函数 示列 :
2.1、首先我们需要创建一个hooks的文件 文件示列:
2.2、在hookds文件下,我们创建一个我们需要使用的.js文件 这里我们比如时usePoint.js
这里我们在usePoint里面写了一个获取鼠标点击位置的方法 代码示列:
import {reactive, onMounted, onBeforeUnmount} from 'vue'
export default function () {
//展示的数据 可以通过App.vue 界面去隐藏
let point = reactive({
x: 0,
y: 0
})
//获取鼠标点击事件
function savePonint(event) {
point.x = event.pageX
point.y = event.pageY
console.log(event.pageX, event.pageY)
}
//现实之后调用 挂载完毕
onMounted(() => {
window.addEventListener('click', savePonint)
})
//在隐藏之前调用 卸载之前
onBeforeUnmount(() => {
window.removeEventListener('click', savePonint)
})
return point
}
我们在组件中引入此文件 代码示列:
<template>
<h2>当前求和:{{ sum }}</h2>
<button @click="sum++">点我加一</button>
<hr>
<h2>当前鼠标点击坐标为:x:{{ point.x }},y:{{ point.y }}</h2>
</template>
<script>
import {ref} from 'vue'
//复用的usePoint
import usePoint from "../hooks/usePoint";
export default {
name: 'App',
setup() {
//数据
let sum = ref(0)
let point = usePoint()
return {sum,point}
},
}
</script>
结果展示:
总结:
新引入的setup语法糖的 目的是简化使用Composition API时冗长的模板代码。
而在组件中引入并使用自定义hook 自定义Hook的优势: 很清楚复用功能代码的来源, 更清楚易懂!
三、 Reactivity APIs
3.1 reactive
接收一个对象返回原对象的响应式proxy对象,是深度的代理
其实vue2.x也有一个Vue.observable()和其功能相同
isReactive
检测一个对象是否由reactive创建出来的对象
注意以下情况
const state = reactive({
name:'zs'
})
const test = readonly({
name:'zs'
})
const copyState = readonly(state)
isReactive(test) === false
isReactive(copyState) === true
shallowReactive
不会对嵌套的对象执行深度响应
3.2 ref
1.接收一个inner value返回一个reactive并且可变的ref对象
这个对象有唯一的一个.value属性指向inner value
2.如果接收一个对象,会默认调用reactive进行处理
3.如果reactive某个属性值是ref:
const state = reactive({
count:ref(1)
})
//访问和修改
state.count
4.如果传入的是个Array或者Map对象或者其他原生集合类型包含的ref,默认不会对ref自动展开
const arr = reactive([ref(0)])
const map = reactive(new Map([['foo',ref(0)]]))
//访问和修改
arr[0].value
map.get('foo').value
3.3 unref/isRef
如果参数是一个 ref,则返回内部值,否则返回参数本身。
IsRef检测值是否是一个 ref对象
即 unref === isRef(val) ? val.value : val
3.4 toRef
为一个reactive对象的某个属性新创建一个ref
这个新创建的ref对象会和源property进行响应式连接
经常用于只需要使用响应式对象的某个property的情况
const state = reactive({
foo: 1,
bar: 2
})
const fooRef = toRef(state, 'foo')
fooRef.value++
console.log(state.foo) // 2
3.5 toRefs
将响应式对象转为普通对象, 结果对象的每个property都指向原始对象相应property的ref
通常用于在不丢失响应式的前提下对返回的对象进行解构/展开
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state)
//stateAsRefs 的类型:
{
foo: Ref<number>,
bar: Ref<number>
}
//在template中可以直接使用foo / bar
return{
...toRefs(stateAsRefs)
}
3.6 customRef
本质是一个自定义的composition API,接收一个工厂函数
该函数接收track和trigger两个函数,返回一个带get和set的对象
track即追踪依赖,trigger即触发更新
通常用于图片的懒加载和防抖或节流
//使用customRef的实现debounce
function useDebounce(value,delay=200){
let t=null;
return customRef((track,trigger)=>{
return {
get(){
track();
return value
},
set(newVal){
clearTimeout(t)
t = setTimeout(()=>{
value = newVal
trigger()
},delay)
}
}
})
}
const text = useDebounce('',500)
return {
text
}
四、 computed
1.可以接收一个getter函数并返回一个不可更改的响应式对象
2.可以接收一个set和get函数的对象,创建一个可写的ref对象
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: val => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0
五、 readonly
接收一个对象(reactive或plain或ref)
返回一个只读的readonly的original源对象的proxy代理
六、 isReadonly
检测是否是由readonly创建的proxy代理的对象
七、 isProxy
检测是否是一个由reactive或readonly创建的proxy代理的对象
注意以下情况则为false
const state = new Proxy({
a:'属性a'
},{})
//原生Proxy代理的这种特殊情况
isProxy(state)===false
八、 watchEffect
- 默认在
onBeforeUpdate前被调用,若观测DOM的ref注册节点
需要在onMountedhook里面执行watchEffect
- 接收一个回调函数并响应式追踪其依赖(数据)
- 当依赖改变会
re-run - 返回一个
stop handle,调用可以明确停止其监听
在组件卸载时会自动停止
const count = ref(0)
setTimeout(() => {
count.value = 1
}, 1000)
const stop = watchEffect(async (onInvalidate) => {
onInvalidate(() => {
//在此处进行拦截来消除副作用 需要在Promise被resolve前被注册
//此外,Vue依赖这个返回的Promise来自动处理Promise链的潜在错误
console.log('onInvalidate is triggered')
})
console.log(count.value)
//在此处有可能是异步操作 为了取消异步操作的副作用(失败的回调)
//类似data.value = await fetchData(props.id)
},
{ //接收第二个参数 是个对象 对象内两个函数只有在开发模式下可以使用
onTrack(e){
console.log('trick') //追踪 一开始就会执行
},
onTrigger(e){
//通常在这里放置debugger 交互式的调试检查
console.log('trigger') //依赖 以来改变就会执行
},
flush:'post' //默认是`pre`在beforeUpdate执行,
//可以通过设置为`post`使其在beforeUpdate之后执行,但是肯定是在updated之前执行
})
setTimeout(() => {
stop()
console.log('watchEffect is closed')
}, 2000)
/**先后打印为:
* 0
* onInvalidate is triggered
* 1
* onInvalidate is triggered
* watchEffect is closed
**/
九、 watch
传入一个返回需要观测的明确的数据的回调(
getter的方式)传入一个执行副作用的回调
对比watchEffect:
- 可以懒执行副作用
- 更加明确了什么时候执行副作用函数
- 可以访问
newVal和oldVal- 同样是在
onBeforeUpdate前执行- 同样返回
stop具柄函数,可以调用手动关闭watch监听- 同样接收第三个回调,里面可以调用
onTrack和onTrigger回调watch可接受的回调和回调中常用的方法和属性如下
<template>
<h2>count: {{ count }}</h2>
<button @click="changeBtn" type="button">更新count</button>
</template>
<script lang="ts" setup>
const count = ref(1)
const changeBtn = () => {
count.value++
}
onBeforeUpdate(() => {
console.log('onBeforeUpdate') //点击后4
})
const stop = watch(
() => count.value,
(newVal, oldVal, onInvalidate) => {
console.log('newVal', newVal) //初始化 2 点击后 3
onInvalidate(() => {
console.log('onInvalidate', onInvalidate) //点击后 2
})
},
{
onTrack() {
console.log('onTrack') // 初始化 1
},
onTrigger() {
console.log('onTrigger') //点击后 1
},
immediate: true,
deep: true,
}
)
</script>
十、 toRaw
raw可以理解为不是reactive或readonly创建的proxy代理的原始对象
是一个逃生舱,用于临时读取数据不承担跟踪的开销
视图不进行更新的话,比如列表数据仅展示,可以使用此属性
十一、 markRaw
标记一个对象,此对象不会被转化成proxy代理对象,而会返回其本身
会给标记的对象添加一个_v_skip:true的属性,在响应式代理时会被跳过
在另外的响应式对象中的某个属性使用markRaw仍然是有效的
注意以下情况
//情况1
const foo = markRaw({
nested: {},
})
const bar = reactive({
nested: foo.nested,
})
console.log(foo.nested === bar.nested) //false 因为前者是原始对象,后者是Proxy代理对象
//情况2
const foo = markRaw({
nested: markRaw({
a: 1,
}),
})
const bar = reactive({
nested: foo.nested,
})
bar.nested.a++ //a:2 表明指向的仍是同一个内存对象
console.log(foo.nested === bar.nested) //true 表明markRaw也是表层标注
11.1 markRaw小结
markRaw和shallowXXX都是允许不参与深度reactive/readonly转换
通常见于Vue component object或第三方的class instance
当渲染庞大且不可变的数据图表跳过响应可以提高性能
即手动对嵌套层进行markRaw或者直接在最外层使用shallowXXX
十二、 vue3.x
生命周期钩子都是以onX为名称,同步使用在setup配置项中
下表包含如何在 setup () 内部调用生命周期钩子:
选项式 API Hook inside setupbeforeCreateNot needed* createdNot needed* beforeMountonBeforeMountmountedonMountedbeforeUpdateonBeforeUpdateupdatedonUpdatedbeforeUnmountonBeforeUnmountunmountedonUnmountederrorCapturedonErrorCapturedrenderTrackedonRenderTracked(dev环境使用)renderTriggeredonRenderTriggered(dev环境使用)activatedonActivateddeactivatedonDeactivated
onErrorCaptured表示在父组件里面可以捕获子组件的错误信息onRenderTracked追踪render的行为,通常在此debugger调试onRenderTriggered当re-render时触发onActivated被 keep-alive 缓存的组件激活时调用onDeactivated被 keep-alive 缓存的组件失活时调用
十三、 依赖注入
Provide/Inject
当你仅需要
祖级组件的某些数据
祖级组件不需知道哪些子组件使用了它provide的property子组件不需要知道
inject的property来自哪里可以使用这一对属性
处理响应式:
provide时使用computed进行处理数据;或者在定义数据的时候使用
ref或reactive修改响应式数据:
祖级组件直接provide一个方法给子组件,仍然在祖级组件进行操作如果不想让子组件修改的话,用
readonly进行数据修饰
十五、 节点引用
vue2.x中为了直接访问到子组件,可以通过ref属性给子组件或者DOM节点注册一个reference ID,可以在refs访问到
vue3.x的template中使用ref注册组件或者DOM节点时,在setup可以定义同名ref代理的变量,会指向同一个引用reference
如果一个
VNode ref的在渲染函数中和setup定义的ref的变量的key是对应一致的,虚拟节点对应的元素或组件实例会被指定为这个变量的值,并且在VDOM的挂载和更新进程中起作用,所以在初始化render之后才会设置这个值
v-for的情况下使用refs
<template>
<ul>
<li
v-for="(item, index) in students"
:key="index"
:ref="
(el) => {
if (el) lists[index] = el
}
"
>
{{ item.name }}
</li>
</ul>
</template>
const students = reactive([{ name: 'zs' }, { name: 'ls' }])
const lists = reactive([])
onMounted(() => {
console.log('lists', lists)
})
十六、 globalProperties
添加一个可以再应用的任何组件实例中访问的全局property
此属性命名优先级高于某个组件setup声明的变量
const app = createApp({})
app.config.globalProperties.$util = ()=>{}
//等同于vue2.x的
Vue.prototype.$util = () => {}
十七、 内置组件slot
使用非常频繁的内置组件,简单来说
父组件使用子组件的结构,但是子组件的内容是由父组件控制并传入
<slot>即为我们想要插入内容的占位符
十八、应用API:
18.1 directive
对视图上一些逻辑的抽离封装通常利用directive来操作
// 注册 (函数指令)
app.directive('my-directive', () => {
// 这将被作为 `mounted` 和 `updated` 调用
})
// 也可以把第二个参数写成对象写法
app.directive('my-directive', {
created(el, binding, vnode) {},
beforeMount(el, binding, vnode) {
el.style.background = binding.value
},
mounted() {},
beforeUpdate(a,b,c,prevNode) {
//第四个参数 prevNode 只在beforeUpdate和updated才有!
a.style.background = b.value
},
updated() {},
beforeUnmount() {
// 当指令绑定的元素 的父组件销毁前调用。
},
unmounted() {},// 当指令与元素解除绑定且父组件已销毁时调用。
})
// getter, 如果已注册,则返回指令定义
const myDirective = app.directive('my-directive')
18.2 nextTick
将回调推迟到下一个DOM更新周期之后执行
在更改了一些数据之后等待DOM更新的时机立即使用它