目录
5、响应式数据判断函数、Fragmen、Teleport、Suspense
1、与vue2对比
//入口文件与Vue2的区别
main.js
//引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
import { createApp } from 'vue'
import App from './App.vue'
//创建应用实例对象——app(类似于之前Vue2中的vm,但app比vm更“轻”)
const app = createApp(App)
//挂载,没有$
app.mount('#app')
【X组件】
<template>
<!-- Vue3组件中的模板结构可以没有根标签 -->
</template>
/* vue3.x Composition API 的优势 我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。
vue2.x 使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。
*/
2、setup、ref、reactive
/* setup 组件中所用到的:数据、方法等等,均要配置在setup中。其中方法的调用可以在方法的定义之前;setup可以自动追加对象属性
在beforeCreate之前被调用一次,setup里this为undefined,所以setup里不用this
setup函数的两种返回值:
1. 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性
使用了async修饰,若、则返回一个Promise实例,但需要Suspense和异步组件的配合
2. 若返回一个渲染函数:则可以自定义渲染内容。(了解)
setup(props,context)的参数
- props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
- context:上下文对象
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs
- slots: 收到的插槽内容, 相当于 this.$slots
- emit: 分发自定义事件的函数, 相当于 this.$emit
*/
【App】
<template>
<A @enevtName1="methodName1" propname1="xxx" propname2="yyy">
<template v-slot:slotName1>
<span>插槽内容</span>
</template>
</A>
</template>
<script>
import A from './components/A'
export default {
name: 'App',
components:{A},
setup(){
function methodName1(value){
alert(`你好啊,你触发了eventName事件,我收到的参数是:${value}!`)
}
return {
methodName1
}
}
}</script>
【A】
<template>
...
<slot name="slotName1"></slot>
<button @click="test">测试触发一下A组件的eventName1事件</button>
</template>
<script>
import {reactive} from 'vue'
export default {
name: 'A',
props:['propname1','propname2'],
emits:['eventName1'],//和Vue2一样不声明也可以用,但会弹出警告。
setup(props,context){
// console.log('---setup---',context.attrs) //相当与Vue2中的$attrs
// console.log('---setup---',context.emit) //相当与Vue2中的$emit
//console.log('---setup---',context.slots) //插槽
let letname1 = reactive({
xxx : yyy
})
function test(){
context.emit('eventName1',letname1)
}
return {
letname1,
test
}}</script>
/* ref函数 写在setup(){}中,定义一个响应式的数据
const xxx = ref(initValue)
会封装ref函数的参数内容 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)。
数据类型封装为一个refImpl,对象类型封装为Proxy【对象类型封装的还是reactive,逃不掉的】
-对于数据类型的initValue,xxx.value取出其存储的数据,
-而对于对象类型的initValue,xxx.value是取出的是借助reactive生成的Proxy对象,而reactive生成的数据自动开启深度监视
获取数据:
- JS中操作数据: xxx.value
- 模板中读取数据: 不需要.value,直接:<div>{{xxx}}</div> */
【App组件】
import {ref} from 'vue'
export default {
name: 'App',
setup(){
let letname1 = ref('value1')
let letname2 = ref(18)
let letname3 = ref({
propname1:'value1',
propname2:'value2',
})
function methodName1(){}
//返回一个对象(常用)
return {
letname1,letname2,letname3,methodName1
}
}
JS中操作数据: letname1.value
letname3.value.propname1
/* reactive: 写在setup(){}中,定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)=====
可以随意的往reactive定义的响应式对象中增删改查对象的属性。 */
【App组件】】
import {reactive} from 'vue'
export default {
name: 'App',
setup(){
let letname1 = reactive({
propname1:'value1',
propobjname1:{
propname1:'value1',
},
propname2:['抽烟','喝酒','烫头']
})
let letname2=reactive(['111','222','333'])
return {letname1,letname2}
}}
JS中操作数据: letname1.propname1
letname1.propobjname1.propname1
letname1.propname2[0]
letname2[0]
3、computed、watch、watchEffect
/*computed 写在setup(){}中,内容从一个Vue配置项成为了一个函数computed({}),可写多个,要有return,============
*/
【A组件】
<template>
姓:<input type="text" v-model="letname1.propname1">
<br>
名:<input type="text" v-model="letname1.propname2">
<br>
<span>全名:{{letname1.propname3}}</span>
<br>
全名:<input type="text" v-model="letname1.propname3">
</template>
<script>
import {reactive,computed} from 'vue'
export default {
name: 'A',
setup(){
let letname1 = reactive({
propname1:'张',
propname2:'三'
})
//计算属性——简写(没有考虑计算属性被修改的情况)
/* letname1.propname3 = computed(()=>{//如果letname1没有propname3属性,则直接向对象中添加此属性
return letname1.propname1 + '-' + letname1.propname2
}) */
//计算属性——完整写法(考虑读和写)
letname1.propname3 = computed({//如果letname1没有propname3属性,则直接向对象中添加此属性
get(){
return letname1.propname1 + '-' + letname1.propname2
},
set(value){//value为<input>框填写的文本
const nameArr = value.split('-')
letname1.propname1 = nameArr[0]
letname1.propname2 = nameArr[1]
}
})
return {letname1}}
/* watch:写在setup(){}中, 内容从一个Vue配置项成为了一个函数watch(),可写多个==================================
watch([监视数据1,监视数据2],
(newValue,oldValue)=>{...},
{immediate:true,deep:true}) */
问题:
- 监视reactive定义的响应式数据(对象)时:oldValue无法正确获取、且强制开启了深度监视(deep配置失效)。
- 监视reactive定义的响应式数据(对象)中某个属性时:oldValue正常获取,deep配置有效但没必要。
//解决:需要哪个属性的oldValue就把这个属性单独摘出来,用ref函数定义,而不用reactive,
-情况1:监视reactive所定义的一个响应式对象时
watch(obj1,(newValue,oldValue)=>{...},{deep:false}) //oldValue无法正确获取,此处的deep配置无效
-情况2:监视reactive所定义的一个响应式数据中的某些数据类型属性
【监视 对象的某一数据类型的属性时,要把watch的第一个参数写成函数,返回值为要监视的对象的属性】
watch(
[()=>obj1.prop1,()=>obj1.prop2] ,
(newValue,oldValue)=>
{...}
) //oldValue可以获取
-情况3:监视reactive所定义的一个响应式数据中的对象属性
watch(()=>obj1.propobjname1,
(newValue,oldValue)=>{...},
{deep:true})//oldValue无法正确获取
//此处由于监视的是reactive定义的对象中的某个属性,且属性为对象,所以deep配置有效
-情况4:监视ref所定义的一个响应式数据
-情况5:监视ref所定义的一个响应式对象数据
【A】
import {ref,reactive,watch} from 'vue'
export default {
name: 'A',
setup(){
let letname1 = ref('xxx')
let letObjName1 = ref({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})
watch(letname1,(newValue,oldValue)=>{//不能监视单纯的字符串xxx,只能监视封装好的letname1
console.log('letname1的值变化了',newValue,oldValue)
})
watch(letObjName1,(newValue,oldValue)=>{
console.log('letObjName1的值变化了',newValue,oldValue)
},{deep:true})
/*或者
watch(letObjName1.value,(newValue,oldValue)=>{
console.log('letObjName1的值变化了',newValue,oldValue)
}) */
return {
letname1,
letObjName1
}
}}
/* watchEffect:智能监视,写在setup(){}中
- watch的套路是:既要指明监视的属性,也要指明监视的回调。
- watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
- watchEffect有点像computed:
- 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
- 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。 */
【A】
import {ref,reactive,watch,watchEffect} from 'vue'
export default {
name: 'A',
setup(){
let letname1 = ref('xxx')
let letObjName1 = ref({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})
watchEffect(()=>{
const x1 = letname1.value
const x2 = letObjName1.job.j1.salary
console.log('watchEffect所指定的回调执行了')
})
return {
letname1,
letObjName1
}
}
4、hook、toRef、shallowReactive 与 shallowRef、readonly 与 shallowReadonly、toRaw与 markRaw、customRef、provide 与 inject
/*自定义hook函数
- 什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。
- 类似于vue2.x中的mixin。
- 自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。
建立一个hooks文件夹,里面存放要服用的代码,通过导入导出来获取
*/
/* toRef: 如果直接取出ref中的数据进行操作,取出的只是单纯的数据,会断开与ref的连接。即操作取出的数据时,原ref中的数据不变。
应用: 要将响应式对象中的某个属性单独提供给外部使用时,且不丢失响应性。
- 作用:不丢失响应性,创建一个 ref 对象,其value值指向另一个对象中的某个属性。这样就与原ref产生了连接。
可以用来为源响应式对象上的某个 property 新创建一个 ref。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。 */
const state = reactive({
foo: 1,
bar: 2
})
const fooRef = toRef(state, 'foo')
fooRef.value++
console.log(state.foo) // 2
state.foo++
console.log(fooRef.value) // 3
当你要将 prop 的 ref 传递给复合函数时,toRef 很有用:
export default {
setup(props) {
useSomeFeature(toRef(props, 'foo'))
}
}
//即使源 property 不存在,toRef 也会返回一个可用的 ref。
/* toRefs
将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref。
当从组合式函数返回响应式对象时,toRefs 非常有用,这样消费组件就可以在不丢失响应性的情况下对返回的对象进行解构/展开:*/
function useFeatureX() {
const state = reactive({
foo: 1,
bar: 2
})
return toRefs(state)或者直接解构 return ...toRefs(state)
}
export default {
setup() {
// 可以在不失去响应性的情况下解构
const { foo, bar } = useFeatureX()
return {
foo,
bar
}
}
}
/*shallowReactive 与 shallowRef
- shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
- shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。
- 什么时候使用?
- 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。 */
【A】
import {ref,reactive,toRef,toRefs,shallowReactive,shallowRef} from 'vue'
export default {
name: 'A',
setup(){
let person = shallowReactive({ //只考虑第一层数据的响应式
//let person = reactive({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})
let x = shallowRef({y:0})
return {
x,
person,
...toRefs(person)
}
}
}
/*readonly 与 shallowReadonly
- readonly: 让一个响应式数据变为只读的(深只读)。
- shallowReadonly:让一个响应式数据变为只读的(浅只读,内层的还是响应式的)。
- 应用场景: 不希望数据被修改时。 */
import {ref,reactive,toRefs,readonly,shallowReadonly} from 'vue'
export default {
name: 'A',
setup(){
let sum = ref(0)
let person = reactive({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})
person = readonly(person)//设置完后覆盖原数据,也可以不覆盖
// person = shallowReadonly(person) person里的salary还是响应式的
return {
sum,
...toRefs(person)
}
}
/* toRaw markRaw
- toRaw:
- 作用:将一个由reactive生成的响应式对象转为普通对象。ref的行不通。
- 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
- markRaw:
- 作用:标记一个对象,使其永远不会再成为响应式对象。
- 应用场景:
1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。
2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。 */
import {toRefs,toRaw,markRaw} from 'vue'
...
setup(){
const o1 = toRaw(reactiveObj1)
obj1.prop1 = reactiveObj2//如果obj1没有prop1属性,则给其追加这个属性。
obj1.prop1 = markRaw(reactiveObj2)
return {
o1,
obj1,//如果obj1本来没有属性prop1是setup追加的属性,则需要把原对象也返回出去,否则会报错没有prop1属性
...toRefs(obj1)
}
}
/* customRef
- 作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。
- 实现防抖效果: */
setup() {
//自定义一个ref——名为:myRef
function myRef(value,delay){
let timer
return customRef((track,trigger)=>{//使用customRef
return {
get(){
console.log(`有人从myRef这个容器中读取数据了,我把${value}给他了`)
track() //通知Vue追踪value的变化(提前和get商量一下,让他认为这个value是有用的)
return value
},
set(newValue){
console.log(`有人把myRef这个容器中数据改为了:${newValue}`)
clearTimeout(timer)//关闭上一个定时器
timer = setTimeout(()=>{
value = newValue
trigger() //通知Vue去重新解析模板
},delay)
},
}
})
}
// let keyWord = ref('hello') //使用Vue提供的ref
let keyWord = myRef('hello',500) //使用程序员自定义的ref
return {keyWord}
}
/* provide 与 inject
- 作用:实现祖与后代组件间通信
- 套路:父组件有一个 `provide` 选项来提供数据,后代组件有一个 `inject` 选项来开始使用这些数据
- 具体写法:
- provide('defSendName',data)
- const/let defReceiveName1 = inject('defSendName1')
*/
1. 祖组件中:
setup(){
let car = reactive({name:'奔驰',price:'40万'})
provide('car',car)
}
2. 后代组件中:
setup(props,context){
const car = inject('car')
return {car}
}
5、响应式数据判断函数、Fragmen、Teleport、Suspense
/* 响应式数据的判断
- isRef(): 检查一个值是否为一个 ref 对象
- isReactive(): 检查一个对象是否是由 `reactive` 创建的响应式代理
- isReadonly(): 检查一个对象是否是由 `readonly` 创建的只读代理
- isProxy(): 检查一个对象是否是由 `reactive` 或者 `readonly` 方法创建的代理 */
//Fragment组件
- 在Vue2中: 组件必须有一个根标签
- 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
- 好处: 减少标签层级, 减小内存占用
/* Teleport
-什么是Teleport?—— `Teleport` 是一种能够将我们的组件html结构移动到指定位置的技术。
<teleport to="元素名 / #idName"> ... </teleport>
*/
【A】
<template>
<div>
<button @click="isShow = true">点我弹个窗</button>
<teleport to="body">
<div v-if="isShow" class="mask">
<div class="dialog">
<h3>我是一个弹窗</h3>
<h4>一些内容</h4>
<h4>一些内容</h4>
<h4>一些内容</h4>
<button @click="isShow = false">关闭弹窗</button>
</div>
</div>
</teleport>
</div>
</template>
<script>
import {ref} from 'vue'
export default {
name:'A',
setup(){
let isShow = ref(false)
return {isShow}
}
}
</script>
<style>
.mask{//遮罩,弹窗出来后.mask里的内容为半透明,点不了
position: absolute;
top: 0;bottom: 0;left: 0;right: 0;
background-color: rgba(0, 0, 0, 0.5);
}
.dialog{//调整弹窗的位置
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
text-align: center;
width: 300px;
height: 300px;
background-color: green;
}
</style>
/* Suspense 其 API 可能随时会发生变动
- 等待异步组件时渲染一些额外内容,让应用有更好的用户体验
*/
【App】
<template>
<div class="app">
<Suspense><!--内有两个插槽-->
<template v-slot:default><!--展示真正要展示的组件-->
<A/>
</template>
<template v-slot:fallback><!--在还没有展示出来前,的展示内容-->
<h3>稍等,加载中...</h3>
</template>
</Suspense>
</div>
</template>
<script>
// import A from './components/A'//静态引入 所有组件都渲染完,一起出现在页面
import {defineAsyncComponent} from 'vue' //动态异步引入 如果异步引入的组件渲染比较慢,页面先显示着点,不等他
const A = defineAsyncComponent(()=>import('./components/A'))
export default {
name:'App',
components:{A},
}
</script>
【A】
<template>
<div class="a">
<h3>A</h3>
{{sum}}
</div>
</template>
<script>
import {ref} from 'vue'
export default {
name:'A',
async setup(){
let sum = ref(111)
let p = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve({sum})
},3000)
})
return await p
}
}
</script>
6、其他