Composition API
shallowReactive
- 只处理了对象内最外层属性的响应式(也就是浅响应式)
shallowRef
- 只处理了value的响应式, 不进行对象的reactive处理
shallowrReadonly
- 只保证最外层属性为只读
<template>
<h2>shallowReactive & shallowRef</h2>
<h3>shallowReactive : 只处理了对象内最外层属性的响应式(也就是浅响应式)
shallowRef: 只处理了value的响应式, 不进行对象的reactive处理</h3>
<hr>
<h3>m1: {{m1}}</h3>
<h3>m2: {{m2}}</h3>
<h3>m3: {{m3}}</h3>
<h3>m4: {{m4}}</h3>
<hr>
<button @click="update">更新</button>
<hr>
<h2>readonly & shallowrReadonly</h2>
<h3>state: {{state}}</h3>
<button @click="update1">更新</button>
</template>
<script lang='ts'>
import {defineComponent, reactive, ref, shallowReactive, shallowRef, readonly, shallowReadonly} from 'vue'
export default defineComponent({
name: 'App',
setup(){
const m1 = reactive({
name: '鸣人1',
car: {
name: 'bwm',
color: 'white'
}
})
const m2 = shallowReactive({
name: '鸣人2',
car: {
name: 'bwm',
color: 'red'
}
})
const m3 = ref({
name: '鸣人3',
car: {
name: 'bwm',
color: 'black'
}
})
const m4 = shallowRef({
name: '鸣人4',
car: {
name: 'bwm',
color: 'green'
}
})
const update = ()=>{
// m1.car.name += '111'
// m2.car.color += '222'
// m2.name += '2' // 若此注释打开,则上面的 m2.car.color 可以执行
// m3.value.name += '333'
m4.value.car.color += '444'
console.log(m3, m4);
}
//
const state = reactive({
name: '佐助',
car: {
name: '玛莎',
color: 'yellow'
}
})
// const state2 = readonly(state)
const state3 = shallowReadonly(state)
const update1 = ()=>{
// state2.car.name += "拉蒂" // error
state3.car.name += "拉蒂"
// state3.name += 'lal' // error
}
return {
m1,
m2,
m3,
m4,
update,
state,
update1
}
}
})
</script>
toRaw
- 代理对象变成普通对象
markRaw
- 标记一个对象,使其永远不会转换为代理
toRef
- 为源响应式对象上的某个属性创建一个 ref对象, 二者内部操作的是同一个数据值, 更新时二者是同步的
- 区别ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响
- 应用: 当要将某个prop 的 ref 传递给复合函数时,toRef 很有用
// App.vue
<template>
<h2>toRaw & markRaw</h2>
<h3>state: {{state}}</h3>
<hr>
<button @click="testToRaw">test_toRaw</button>
<button @click="testMarkRaw">test_markRaw</button>
<hr>
<h2> toRef </h2>
<h3>state3: {{state3}}</h3>
<h3>age: {{age}}</h3>
<h3>money: {{money}}</h3>
<button @click="upt">upt</button>
<Child :money="money"/>
<!-- <Child :age="age.value"/> -->
</template>
<script lang='ts'>
import {defineComponent, toRaw, reactive, markRaw, toRef, ref} from 'vue'
import Child from './components/Child.vue'
interface UserInfo{
name: string;
age: number;
likes?: string[];
}
export default defineComponent({
name: 'App',
components: {
Child
},
setup(){
const state = reactive<UserInfo>({
name: 'lz',
age: 20
})
const testToRaw = ()=>{
// 代理对象变成普通对象
const user1 = toRaw(state)
user1.name += '---'
console.log('toRaw', user1);
}
const testMarkRaw = ()=>{
// state.likes = ['吃饭', '睡觉']
// state.likes[0] += '---'
// console.log(state); // Proxy对象
// 标记一个对象(这里是state.likes属性),使其永远不会转换为代理
state.likes = markRaw(['吃饭', '睡觉'])
// 使用定时器测试不能转换了
setInterval(()=>{
if (state.likes) {
state.likes[0] += '111'
console.log('定时器~~~');
}
}, 1000)
console.log(state);
console.log(state.likes);
}
// toRef
const state3 = reactive({
age: 10,
money: 99
})
const age = toRef(state3, 'age')
const money = ref(state3.money)// 拷贝了一份新的数据值单独操作, 更新时相互不影响
console.log(age);
console.log(money);
const upt = () =>{
// state3.age++
// age.value++ // 和 state3.age++ 效果一样
money.value++ // 不同步, state3 里面的 money 不会变
}
return {
state,
testToRaw,
testMarkRaw,
state3,
age,
money,
upt
}
}
})
</script>
// Child.vue
<template>
<h2> toRef 子组件 </h2>
<h3>money: {{money}}</h3>
<h3>length: {{length}}</h3>
</template>
<script lang='ts'>
import {defineComponent, Ref, computed, toRef} from 'vue'
function useLength(age: Ref) {
return computed(()=>{
return age.value.toString().length
})
}
export default defineComponent({
name: 'Child',
props: {
money: {
type: Number,
require: true
}
},
setup(props){
const length = useLength(toRef(props, 'money'))
return {
length
}
}
})
</script>
CustomRef
- 用于定义一个 ref,可以显式的控制依赖追踪和触发响应,接受一个工厂函数
- 两个参数:用于追踪的 track 和 用于触发响应的 trigger,返回一个带有 get 和 set 属性的对象。
- 箭头函数使用泛型: ES箭头函数使用泛型。
<template>
<h2> CustomRef 自定义ref</h2>
<input v-model="keyword" placeholder="搜索关键字"/>
<p>{{keyword}}</p>
</template>
<script lang='ts'>
import {defineComponent, customRef} from 'vue'
// const useDebouncedRef = <T extends unknown>(value: T,delay = 200)=>{
const useDebouncedRef = <T, > (value: T,delay = 200) => {
return customRef((track, trigger)=>{
// 准备一个存储定时器id的变量
let timeId: number
return {
get(){
// 告诉Vue追踪数据
track()
return value
},
set(newValue: T){
// 清理定时器
clearTimeout(timeId)
value = newValue
// 开启新的定时器
setTimeout(() => {
// 告诉Vue更新界面
trigger()
}, delay);
}
}
})
}
export default defineComponent({
name: 'App',
setup(){
// const keyword = ref('abc')
// 防抖函数
const keyword = useDebouncedRef('abc', 1000)
return {
keyword,
}
}
})
</script>
provide & inject
- 提供依赖注入,功能类似 2.x 的provide/inject
- 实现跨层级组件(祖孙)间通信
// App.vue(父级组件)
<template>
<h2>provide 与 inject</h2>
<hr>
<p>当前颜色:{{color}}</p>
<button @click="color='red'">红</button>
<button @click="color='green'">绿</button>
<button @click="color='blue'">蓝</button>
<hr>
<Son />
</template>
<script lang='ts'>
import {defineComponent,ref,provide} from 'vue'
import Son from './components/Son.vue'
export default defineComponent({
name: 'App',
components: {
Son
},
setup(){
const color = ref('red')
// 提供
provide('yanse', color)
return {
color
}
}
})
</script>
// Son.vue(儿子组件)
<template>
<h2>我是Son组件</h2>
<h3 :style="{color}">我是儿子组件: {{color}}</h3>
<hr>
<grand-son />
</template>
<script lang='ts'>
import {defineComponent, inject} from 'vue'
import GrandSon from './components/GrandSon.vue'
export default defineComponent({
name: 'Son',
components: {
GrandSon
},
setup(){
const color = inject('yanse')
return {
color
}
}
})
</script>
// GrandSon.vue(孙子组件)
<template>
<h2>我是GrandSon组件</h2>
<h3 :style="{color}">孙子组件: {{color}}</h3>
</template>
<script lang='ts'>
import {defineComponent, inject} from 'vue'
export default defineComponent({
name: 'GrandSon',
setup(){
// 注入
const color = inject('yanse')
return {
color
}
}
})
</script>
响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactiv: 检查一个对象是否是由 reactive 创建的响应式代理
- isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
- isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理
<template>
<h2>响应式数据的判断</h2>
<hr>
</template>
<script lang='ts'>
import {defineComponent, isRef, ref, isReactive, reactive, isReadonly, readonly, isProxy} from 'vue'
export default defineComponent({
name: 'App',
setup(){
console.log(isRef(ref({})));
console.log(isReactive(reactive({})));
console.log(isReadonly(readonly({})));
console.log(isProxy(readonly({})));
console.log(isProxy(reactive({})));
return {}
}
})
</script>
Vue3的新组件
Fragment (片段)组件
- 在Vue2中: 组件必须有一个根标签,在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个
Fragment
虚拟元素中- 好处: 减少标签层级, 减小内存占用
teleport (瞬移)组件
- 提供了一种干净的方法,让组件的
html模板
在父组件界面外的特定标签(很可能是body)下插入显示Suspense (不确定的)组件
- 允许我们的应用程序在等待异步组件时渲染一些后备内容
- 好处: 创建一个平滑的用户体验
// App.vue
<template>
<h3>新组件</h3>
<hr>
<!-- <Dialog /> -->
<Suspense>
<!-- <template v-slot:default> -->
<template #default>
<!-- 异步组件 -->
<!-- <AsyncCom /> -->
<AsyncAddress />
</template>
<template v-slot:fallback>
<!-- 准备的加载内容 -->
<h2>loading...</h2>
</template>
</Suspense>
</template>
<script lang='ts'>
import {defineComponent} from 'vue'
// 引入组件:动态引入和静态引入
// import Dialog from './Dialog.vue'
// import AsyncCom from './AsyncCom.vue'
// vue2中动态引入组件: (vue3中不支持这种)
// const AsyncCom = ()=>import('./AsyncCom.vue')
// vue3中动态引入组件:
// const AsyncCom = defineAsyncComponent(()=>import('./AsyncCom.vue'))
import AsyncAddress from './AsyncAddress.vue'
export default defineComponent({
name: 'App',
components: {
// Dialog,
// AsyncCom,
AsyncAddress
}
})
</script>
// Dialog.vue
<template>
<h2>这是对话框组件</h2>
<button @click="dialogOpen=true">Open dialog</button>
<teleport to="body">
<div v-if="dialogOpen" class="modal">
<div>
I'm a teleported modal!
(My parent is "body")
<button @click="dialogOpen = false">
Close
</button>
</div>
<div>
test flex-direction
</div>
</div>
</teleport>
</template>
<script lang='ts'>
import {defineComponent, ref} from 'vue'
export default defineComponent({
name: 'Dialog',
setup () {
const dialogOpen = ref(false)
return {
dialogOpen
}
}
})
</script>
<style>
.modal {
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
background-color: rgba(39, 37, 37, 0.5);
display: flex;
flex-direction: column-reverse;
align-items: center;
justify-content: center;
}
.modal div {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: rgb(171, 226, 243);
width: 300px;
height: 300px;
padding: 5px;
}
</style>
// AsyncCom.vue
<template>
<h2>Suspense(不确定的)子组件</h2>
<h3>{{msg}}</h3>
</template>
<script lang='ts'>
import { defineComponent } from "vue";
export default defineComponent({
name: " ",
setup() {
return new Promise(resolve => {
setTimeout(() => {
resolve({
msg: "what ?"
});
}, 1000);
});
}
});
</script>
// AsyncAddress.vue
<template>
<h2>异步请求</h2>
<h3>{{data}}}</h3>
</template>
<script lang='ts'>
import { defineComponent } from "vue";
import axios from "axios";
export default defineComponent({
name: " ",
// setup(){ // Promise.then()方式
// return axios.get('/data/address.json').then((res)=>{
// return {
// data: res.data
// }
// })
// }
async setup() { // async await方式
const rst = await axios.get("/data/address.json");
return {
data: rst.data
};
}
});
</script>
// public/data/address.json
{
"id": 1,
"address": "河北省石家庄",
"distance": "10000m"
}