Vue3 composition api 创建应用实例 | setup函数 | reactive unref ref toRefs | computer函数 | 监听函数

Vue3 composition api

创建一个Vue实例

  1. 创建一个Vue应用实例createApp方法
  2. 挂载应用实例.mount()

每个 Vue 应用都是通过 createApp 函数创建一个新的 Vue应用实例

createApp() :创建应用实例

  • 参数是根组件对象,该对象用于配置根组件。当挂载应用时,该组件被用作渲染的起点。
  • 返回了一个有mount方法的应用实例对象。
  • 函数内部完成了2个任务①创建app应用实例 ②重写app.mount方法
import {
    
     createApp } from 'vue'
import App from'./App.vue' //引入根组件
//const app = createApp({
    
    
  /* 根组件选项 */
//})
const app = createApp(App)
app.mount('#app')//将ID为app的节点挂载到Vue上

/*
vue2 写法
import Vue from 'vue'; 
new Vue({
	render: h => h(App)
}).$mount('#app')
*/

Vue.use 原理

作用:通过Vue.use()安装插件

  • Vue.use()方法至少传入一个参数,该参数类型必须是Object或Function。
    • 如果是Object则这个Object需要定义一个install方法。
    • 如果是Function这个函数就被当作install方法。

Vue.use()执行时相当于执行install方法,同一个插件多次调用,该插件只会被安装一次。

1.上述app是Vue的一个实例,app.use()用法等同于Vue.use、
2.第一个参数是插件本身,可选的第二个参数是要传递给插件的选项。

setup函数

setup()用于配合组合式API,为用户提供一个地方,用于建立组合逻辑、创建响应式数据、创建通用函数、注册声明周期钩子等能力。

  • 返回值有两种
    • 返回一个函数,该函数将作为组件的render函数
    • 返回一个对象,将对象中包含的数据暴露给模板使用。其中对象中的属性、方法可以在模板中直接使用。
  • 参数为(props,context)
    • 第一个参数接受一个响应式的props,这个props指向的是外部的props。如果没有定义props选项setup中的第一个参数将为undifined
      -第二个参数提供了一个上下文对象context,context作为上下文取代this,但是context中只有emitattrs,和slots

说明

  • setup不能是async函数,因为返回值不是return对象了,而是promise,模板看不见promise对象中的属性
  • 在整个生命周期中,setup函数只会被挂载一次。在beforeCreate之前执行一次,thisundefined(setup调用时机是在props解析完毕,组件实例创建之前执行),所以setup中无法获取组件的实例。
  • 这种写法会自动将所有顶级变量、函数,均会自动暴露给模板(template)使用
//返回一个模板
<template>
 {
    
    {
    
    name}}
 {
    
    {
    
    say()}}
</template>

<script>
export default {
    
    
  name: 'App',
  setup() {
    
    //这里定义的数据不是响应式数据
    //数据
    let name="ranan";
    //方法
    function say() {
    
    
      console.log(`hello${
      
      name}`);
    }
    return {
    
    name,say}
  }
}
</script>

//返回一个渲染函数
<template></template>

<script>
import {
    
    h}  from 'vue' //createElement函数
export default {
    
    
  name: 'App',
  setup() {
    
       
    //渲染函数要把h函数调用的返回值返回
    //return ()=>{return h('h1','ranan')}
    return ()=> h('h1','ranan')
  }
}
</script>

script setup

Vue3.2 (vue2.7也支持)中只需要在 script 标签上加上 setup 属性

说明
1.组件在编译的过程中代码运行的上下文是在 setup() 函数中
2.无需 return,在template 可直接使用。

//语法糖
<script setup="props, context" lang="ts">
 context.attrs
 context.slots
 context.emit 
<script>
//同时支持结构语法
<script setup="props, { emit }" lang="ts">
<script>

3.在 script setup 中,引入的组件可以直接使用,不用通过components进行注册,并且无法指定当前组件的名字,会自动以文件名为主,也就是不用再写name属性了。

<template>
    <HelloWorld />
</template>

<script setup>
import HelloWorld from "./components/HelloWorld.vue";
</script>

script setup指定当前组件的名字

方法1:修改文件名,文件名=组件名
方法2:新建一个与script setup同级的script标签,在该标签内抛出name

<script setup lang="ts">
</script>

<script lang="ts">
export default {
    
    
  name: 'app-viewer'
}

方法3:使用插件unplugin-vue-define-options
插件安装方法

响应式数据

  • ref()使数据具有响应式
  • shallowRef()只处理基本数据类型的响应式, 不进行对象的响应式处理。
  • reactive()定义的深层次的对象响应式,内部基于Proxy实现,通过代理对象操作源对象内部数据。
  • shallowReactive()创建一个浅层次响应对象(一层响应)

ref函数 数据劫持defineProperty

ref() 使数据具有响应式,将值包装成RefImpl引用对象(Ref对象)返回

创建一个对象,对象的value属性设置为传入的参数,然后对对象做响应式处理,最后返回响应式对象

  • ref响应式的原理是使用数据劫持(第一层): 利用defineProperty中的gettersetter
  • 数据的获取使用属性名.value获取,但在模板中可以直接使用属性名(自动解包),模板中会自动属性名.value
<script setup lang="ts">
import {
    
    ref} from 'vue'
let count = ref(0) //生成一个0的响应式数据
console.log(count.value) //RefImpl{value:0....}
console.log(count) //0
count=10 //对0重新赋值,现在count不再是响应式数据
</script>

<template>
   <div>{
    
    {
    
    count }}</div>
</template>

在这里插入图片描述

ref函数的参数为对象

const 返回值obj = ref(参数对象) 返回值obj本身还是RefImpl对象,但是obj.value返回的是对参数对象进行Proxy代理的对象,通过对代理对象的操作间接操作源对象。

<script setup lang="ts">
import {
    
    ref} from 'vue'
/*
count是响应式的,name也是响应式的
const tmp = {name:ref("ranran")] name是响应式的,tmp不是响应式的
*/
const count = ref({
    
    name:"ranran"}) 
console.log(count) //返回一个RefImpl对象,对象的value值为传入的参数{value:{name:"ranran"}}
console.log(count.value) //Proxy(Object) {name: 'ranran'}
console.log(count.name) //注意这个是访问不到的,因为ref函数是创建一个对象,对象的value属性设置为传入的参数,然后对对象做响应式处理,最后返回响应式对象
</script>
<template>
   <div>{
    
    {
    
    count }}</div>
</template>

自动解包的细节

template标签里的自动解包问题:自动解包只会解包顶层属性顶层属性.value.xxx

const  obj = ref({
    
    
  name:"ranran"
})
const obj2 = {
    
    
  name:ref("biubiu")
}
</script>

<template>
  <div>
	//自动解包obj.value.name
    <div>{
    
    {
    
    obj.name }}</div>
    //obj2不是响应式数据,所以没有自动解包。错误写法
    <div>{
    
    {
    
    obj2.name }}</div>
    //正确写法
    <div>{
    
    {
    
    obj2.name.value}}</div>
  </div>
 
</template>

ref函数的源码

function ref(value) {
    
    
    return createRef(value, false); //创建ref对象,第一个参数时需要响应的数据,第二个参数是是否浅拷贝
}

function createRef(rawValue, shallow) {
    
     
    if (isRef(rawValue)) {
    
     
        return rawValue;
    }
    return new RefImpl(rawValue, shallow);
}

class RefImpl {
    
    
    constructor(value, _shallow) {
    
    
        this._shallow = _shallow;
        this.dep = undefined;
        this.__v_isRef = true;
        this._rawValue = _shallow ? value : toRaw(value);
        this._value = _shallow ? value : convert(value);
    }
    get value() {
    
    
        trackRefValue(this);
        return this._value;
    }
    set value(newVal) {
    
    
        newVal = this._shallow ? newVal : toRaw(newVal);
        if (hasChanged(newVal, this._rawValue)) {
    
    
            this._rawValue = newVal;
            this._value = this._shallow ? newVal : convert(newVal);
            triggerRefValue(this, newVal);
        }
    }
}

unref()

如果参数是一个ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 的语法糖函数。

使用场景:当一个值可能是ref或者是ref之外的值

reactive函数 数据代理Proxy

const 代理对象 = reactive(源对象):定义一个对象的响应式代理,接受一个对象(数组),返回一个Proxy的实例对象。

  • reactive()定义的响应式数据是深层次的,内部基于Proxy实现,通过代理对象操作源对象内部数据。
  • shallowReactive()创建一个浅层次响应对象(一层响应)
<script setup lang="ts">
import {
    
    reactive} from 'vue'
const stu = reactive({
    
    name:"ranran"})
console.log(stu)
/*
{
    "name": "ranran"
}
*/
</script>

toRef(obj,prop) 创建一个ref对象

作用: toRef(响应式对象,该对象的属性)创建一个ref对象该ref对象value值指向参数对象中的某个属性(ref对象的value值改变,参数对象中的该属性也会改变,反之也会改变)
说明: 本质是引用,与原始数据有关联
应用场景: 要将响应式对象中的某个属性单独提供给外部使用

说明

  • 针对一个响应式对象的属性,如果用于普通对象(非响应式对象),返回的结果不具备响应式。
  • toRef返回值对象 Object 两者保持引用关系(一个变化另外一个也会变化)

在这里插入图片描述

toRefs(obj) 创建多个ref对象

toRefs(obj):返回一个和参数一致的普通对象,只不过属性的值都变成了ref对象

说明

  • toRefs对于新增的属性不会生效
  • 针对整个对象的所有属性,目标在于将响应式对象( reactive 封装)转换为普通对象。
 return{
    
    
 	...toRefs(person)
 }

1.ref的本质是拷贝,修改响应式数据,不会影响到原始数据,视图会更新
toRef、toRefs的本质是引用,修改响应式数据,会影响到原始数据,视图不会更新
2.toRef、toRefs不创造响应式,而是延续响应式。

副作用函数effect()

当一个数据发生变化时,effect函数自动重新执行,我们就称该数据是响应式的。(响应式数据发生变化时effect()会自动执行)
副作用函数effect() 的执行会直接或间接影响其他函数的执行。

//假设obj是一个响应式数据
const obj = {
    
    text:'xxx'}
function effect(){
    
    //effect函数
	console.log(obj.text);
}
//修改响应式数据
obj.text = 'yyy'
//手动模拟
effect()//响应式数据发生变化,自动执行effect()

reactive响应式原理

本质上是通过 Proxy 代理了数据对象的读写

  • 当我们访问代理对象时,会触发 getter 执行依赖收集
    • track():track()收集依赖,对象的每个属性都有自己的dep,将所有依赖于该属性的effect函数都收集起来,放在dep里。
  • 修改数据时,会触发 setter 派发通知。
    • trigger():trigger()通知依赖。将依赖搜集起来之后,只要变量一改变,就执行trigger()通知dep里所有依赖该变量的effect()执行,实现依赖变量的更新。
  1. 对象的每个属性都有自己的dep,将所有依赖于该属性的effect函数都收集起来,放在dep里。
  2. 每个对象会建立一个Map来存储各个属性和dep的对应关系,key为属性名,value为该属性的dep(使用Set来存储)。
  3. 使用WeakMap来存储多个对象的Map

在这里插入图片描述

  1. Proxy-Reflect实现自动收集和触发依赖track-trigger函数
//reactive 将对象变成响应式的
function reactive(target) {
    
    
    const handler = {
    
    
        get(target, key, receiver) {
    
    
            track(receiver, key) // 访问时收集依赖
            return Reflect.get(target, key, receiver)
        },
        set(target, key, value, receiver) {
    
    
            Reflect.set(target, key, value, receiver)
            trigger(receiver, key) // 设值时自动通知更新
        }
    }

    return new Proxy(target, handler)
}

reactive与ref的对比


reactive ref
定义数据 对象类型数据 基本类型数据
如果定义对象,.value会借住reactive将对象转为代理对象
原理 Proxy实现响应式(数据代理)
通过Reflect操作源对象内部的属性
通过Object.defineProperty()的get和set来实现响应式(数据劫持)
使用 不需要使用.value 操作数据需要.value,模板中自动解包读取不需要.value

计算属性computed函数

computed属性是Vue3中的一个响应式计算属性(返回一个ref对象),根据其他响应式数据的变化(不会检测非响应式数据的的更新)而自动更新其自身的值。

import {
    
    computed} from 'vue'
setup(){
    
    
	//计算属性fullName -简写内部是函数形式,只符合被读取的情况
	let fullName = computed(()=>{
    
    
		return person
	})
	//计算属性-完整写法
	let fullName = computed({
    
    
		get(){
    
    },
		set(val){
    
    }
	})
}

computed属性的原理是使用了一个getter函数和一个setter函数来实现,computed的依赖数据(监听变化的数据)需要是响应式的

监听响应式数据的watch函数

watch() 用来侦听特定的数据源,并在回调函数中执行副作用。默认情况仅在侦听的源数据变更时才执行回调。

watch(data,function(),[option])

  • 监视的响应式数据
    • ref对象
    • 计算属性
    • getter函数:可以简单的理解为获取数据的一个函数,该函数是一个返回值的操作
    • reactive定义的属性,会把deep:true强制开启
    • 监听多个来源的数组
  • 该数据绑定的回调函数
  • vue2的配置参数:支持 deep、immediate 和 flush 选项。

vue2的写法复习

说明

  • 当监视的属性发生变化时,handler(newVal,oldVal)函数调用,初始化时不调用(可以设置)
  • 可以监视data和computed里的属性
  • Vue中的watch默认不监测对象内部值的改变(一层)
  • 配置deep:true可以检测对象内部值得改变(多层)
//写法1
new Vue({
    
    
   el:'#root',
   data:{
    
    
       firstName:'ran',
       lastName:'an'
    },
    watch:{
    
    
 		isHot:{
    
    //固定配置
 		    immediate:false; //默认false,初始化时调用handler函数
 			//当监视的属性发生变化时,handler调用
			handler(newVal,oldVal){
    
    }
			}
		}
    
});
//写法2
vm.$watch('isHot',{
    
    配置信息})
//简写
watch:{
    
    
	isHot(newVal,oldVal){
    
    }
}
vm.$watch('isHot',function(newVal,oldVal){
    
    //handler函数})

监听计算属性computed

import {
    
    ref,computed,watch} from 'vue'
const message = ref("ranran");
const newMessage = computed(() => {
    
    
  return message.value;
});
watch(newMessage, (newValue, oldValue) => {
    
    
});

监听getter函数

const x1 = ref(12);
const x2 = ref(13);
watch(
  () => x1.value + x2.value,//类似计算属性
  (newValue, oldValue) => {
    
    
  }
);

监听reactive定义的属性

监听reactive定义的属性,会强制开启deep:true深度监听(自动创建一个深层侦听器,deep配置无效),该响应式对象里面的任何属性发生变化,都会触发监听函数中的回调函数。

无法正确的获取oldValue,oldValue和newValue的值一样了。因为对象是引用类型,即使里面的属性发生了变化,但是对象的地址没有发生变化,oldValue和newValue指向了同一个地址。

import {
    
    reactive,watch} from 'vue'
let person= reactive({
    
    
	name:"ranna",
	age:18,
	job:{
    
    
		j1:{
    
    
			salary:20
		}
	}
  })
watch(person,(newValue,oldValue)=>{
    
     
})

watch 不能直接监听响应式对象的属性

1.watch 不能直接监听响应式对象的属性,相当于你直接向 watch 传递了一个非响应式的数字,然而 watch 只能监听响应式数据。

//错误写法
const number = reactive({
    
     count: 0 });
const countAdd = () => {
    
    
  number.count++;
};
watch(number.count, (newValue, oldValue) => {
    
    
});

解决办法: 使用 getter 函数的形式,采用这种情况可以获取到oldValue

watch(
  () => number.count,//返回的是基本数据类型
  (newValue, oldValue) => {
    
    
  }
);

2.特殊情况:Proxy是自动深度,Object按需深度
如果我们是使用的 getter()返回响应式对象的形式,那么响应式对象的属性值发生变化,是不会触发 watch 的回调函数的。

//自动开启deep
watch(person.job,(newValue,oldValue)=>{
    
     })
watch(()=>person.job,(newValue,oldValue)=>{
    
    },{
    
    deep:true})//返回的是一个obj,需要手动开启deep

推荐getter()监听响应式对象中的某一个属性

监听多个来源的数组

watch 还可以监听数组,前提是这个数组内部含有响应式数据。

let sum = ref(0)
let msg= ref("hello")
 watch([sum,msg],(newvalue,oldValue)=>{
    
    
	//此时newvalue和oldValue都是数组,里面的顺序和第一个参数的顺序一致
})

监听props属性

监听整个props

<script setup>
import {
    
     ref, watch } from 'vue';
const props = defineProps({
    
    
    listData: {
    
    
        type: Array,
        default: [],
    }
});
const childList = ref([])
watch(props, (nweProps)=>{
    
    
    childList.value = nweProps.listData
})
</script>

监听props中的具体属性

<script setup>
import {
    
     ref} from 'vue';
const appearState = ref(false);
const props = defineProps({
    
    
  show: {
    
    
    type: Boolean,
    default: () => false,
  },
});
watch(
  () => props.show,
  (newVal) => {
    
    
    appearState.value = newVal;
  },
);

监听器watchEffect()

watchEffect()也是一个监听器,第一个参数是回调函数,第二个参数是配置对象,该函数初始化时就会执行(从无到有)。在回调函数中,自动监听响应属性,当回调函数里面的响应数据发生变化,回调函数就会立即执行。

  • watch的套路是: 既要指明监视的属性,也要指明监视的回调
  • watchEffect的套路是: 不用指明监视哪个属性,监视的回调中用到哪个属性,就监视哪个属性。监视的数据发生变化时执行回调函数。
//一上来就会执行
watchEffect(()=>{
    
    
	
})

监听回调中的DOM | watchPostEffect()

监听器的回调函数中获取DOM,默认是获取更新前的DOM。

方法1:配置选项
想要在回调函数里面获取更新后的 DOM,只需要再给监听器多传递一个参数选项flush: 'post'

watch(source, callback, {
    
    
  flush: 'post'
})
watchEffect(callback, {
    
    
  flush: 'post'
})

方法2:watchPostEffect

watchPostEffect(() => {
    
    
  /* 在 Vue 更新后执行 */
})

手动销毁监听器

通常一个组件被销毁或者卸载后,监听器也会跟着停止。但一些特殊情况监听器依然存在,需要手动关闭,否则容易造成内存泄露。

//案例
<script setup>
import {
    
     watchEffect } from 'vue'
// 它会自动停止
watchEffect(() => {
    
    })
// 这个时候监听器没有与当前组件绑定,所以即使组件销毁了,监听器依然存在。
setTimeout(() => {
    
    
  watchEffect(() => {
    
    })
}, 100)
</script>

解决办法:利用一个变量接收监听器函数的返回值,返回值是一个关闭当前监听器的函数。

const unwatch = watchEffect(() => {
    
    })
// ...当该侦听器不再需要时
unwatch()

猜你喜欢

转载自blog.csdn.net/qq_41370833/article/details/131704075