Vue3 - Composition API (组合式API - 其余部分)

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"
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值