响应式API

  响应式对象能通过被读取收集依赖名单,当响应式对象发生变化时依照名单向所有的依赖发布更新公告,依赖收到公告更新,从而实现依赖随响应式对象更新而更新。
  vue3创建响应式对象的两个最主要函数为reactive与ref。其它创建响应式对象的函数有computed、toRef、toRefs。侦听响应式对象的函数有watch、watcheffect。还有其它操作响应式对象的函数unref、isRef、toRaw等vue3响应式数据的相关函数

http://www.webkaka.com/tutorial/js/2022/0513145/

响应式函数 reactive()

使用reactive函数包装一个对象

<template>
    <h1 v-text="state.count"></h1>
    <h1 v-text="stat[0]"></h1>
<button @click="ee">我来改变</button>
</template>
<script setup>
import { reactive } from 'vue'
const state = reactive({ count: 0 })     //创建对像要用({  })包裹
const stat = reactive([ 0 ])            //创建数组要用([  ])包裹
 function ee(){state.count++
               stat[0]++}
 </script>
1、reactive函数的书写规范:reactive(参数)

基本类型: Number,Boolen,null,String,Underfined 存放在栈内存中,数据长度是固定的。
引用类型: 对象、数组 值存在堆内存中,数据长度是变化的(同时有栈内存中有一个指针指向这个Object的)
reactive参数类型: 引用类型、变量型,不能是基本类型。
对象参数用{}括起,可以是空,如:reactive({})
数组参数用[]括起,可以是空,如:reactive([])
变量型参数: 引用类型变量不用括起,基本类型变量要用{}或[]括起。

2、reactive内部原理

reactive会用Proxy代理把参数包装成一个Proxy对象。
Proxy代理创建Handler处理集与Target对象目标,Handler处理集封装get与set,深层监测Target对象,产生DOM响应。

3、与变量型参数的关系
  • 相互响应: 当参数是引用型变量时,两者都使用同一指针,引用同一Object,两者之间相互响应。
  • 相互响应消失: 当参数重新赋值为基本类型时,变量型参数由堆内存指针改为栈内存固定地址,两者不再引用同一Object,两者之间相互响应消失。
  • 不全等: 两者引用同一Object,值相等,但reactive变量是个Proxy包装的对象(拥有Handler处理集与Target对象,而变量型参数只是个普通对象,reactive变量有DOM响应而变量型参数无DOM响应。
  • 被动更新DOM响应失效 因相互响应引起的reactive变量被动更新,并不会被Handler处理集监测到,虽然值改变了,但DOM并不会更新。如下:

    <template>
    <h1 >{{obj}}</h1>
    <h1 >{{react}}</h1>
    <button @click="ee">新变量主动更新</button> 
    <button @click="er">被动更新</button> 
    </template>
    <script setup>
    import { reactive} from 'vue';
    let obj ={rrr:'pppp',ttt:'loik'}
    const react=reactive(obj)
     function ee(){react.rrr='我变了';console.log(react);console.log(obj)}
     function er(){obj.rrr='我变了';console.log(react);console.log(obj)}
    </script>
4、深层包装

Proxy包装的对象Handler处理集监测整个Target对象,Target对象的子子孙孙也具有响应性。
向上直系触动: 深层属性变更会向上触动直系DOM,凡此属性的直系DOM都会更新, 如下:

<template>
  <h1 >{{react}}+'我是他爷会更新吗'</h1>
  <h1 >{{react.ttt}}+'我是他爸会更新吗'</h1>
  <h1 >{{react.ttt.eee}}</h1>
  <button @click="ee">改变自已</button> 
</template>
<script setup>
import {reactive} from 'vue';
let obj ={ttt:{eee:'我是本人'}}
const react=reactive(obj)
   function ee(){react.ttt.eee='我变'}
</script>
5、reactive赋值给其它变量
const react=reactive({ttt:"我是响应性变量"})
const obj=react
  • 赋值层全等: 新变量赋值层与reactive源全等,使用同一指针,引用同一Proxy对象,两者相互响应,赋值层也具有Proxy对象的DOM响应性。
    赋值层: reactive源变量赋值位置为赋值层,如下:
    如obj=react,obj为赋值层;
    obj={react},obj.react为赋值层;
    obj={ooo:react},obj.ooo为赋值层。
  • 失去相互响应与DOM响应: 当赋值层重新赋值为基本类型时,Proxy对象消失,两者之间不再相互响应,DOM响应性也会消失。因此切记在同层赋值基本类型,只可在同层子属性赋基本类型。如下:

    <template>
    <h1 >{{react}}</h1>
    <h1 >{{obj.react}}</h1>
    <button @click="ee">子层赋值基本类型</button> 
    <button @click="ef">同层赋值基本类型</button> 
    </template>
     <script setup>
     import {ref,reactive}from 'vue'
     var react=reactive({ttt:"我是响应性变量"})
     var obj={react}
     function ee(){obj.react.ttt='子层赋值基本类型';console.log(react)}
     function ef(){obj.react='同层赋值基本类型';console.log(react)}
    </script>

    一个奇怪问题:如下,向上直系触动失灵,如果将const obj={react}改为var obj={react},向上直系触动​有效。不知是否是因为const 声明的为常量,不能修改所以不能响应。附上const与var区别,以后再研究看看。

    <template>
     <h1 >{{react}}</h1>
     <h1 >{{obj.react}}</h1>
     <h1 >{{obj}}</h1>
    <button @click="ee">会更新吗</button> 
    </template>
    <script setup>
    import {ref,reactive}from 'vue'
    const react=reactive({ttt:"我是响应性变量"})
    const obj={react}
    function ee(){react.ttt='我会改变';console.log(obj)}
    </script>

    变量包裹赋值: 当变量被{}包裹赋值时,原变量名转化为新变量的子属性名;当变量被[]包裹赋值时,原变量转化为key值为0的项。如下

    <template>
    </template>
    <script setup>
     var react='我是值'
     var obj=react   
     var ob={react} 
     var o=[react]
    console.log(obj,ob.react,o[0])
    </script>
6、失去DOM响应性

不能直接对reactive() 包装的变量赋基本类型值或重新赋值一个新对象,原Proxy对象消失,失去DOM响应性。只能对子属性赋值(Proxy对象子属性)。如下:直接对react赋值失去响应性。

<template>
  <h1 >{{react}}</h1>
<button @click="ee">改变代理</button> 
</template>
<script setup>
import { reactive} from 'vue';
var react=reactive({ppp:'pppp'})
    function ee(){console.log(react); react={ppp:'pppp'};react.ppp='他';console.log(react)}
  </script>

从后打印可看出区别,不直接赋值前打印是Proxy(Object) {ppp: 'pppp'};赋基本类型值后打印的是{ppp: '他'},失去了Proxy代理标识。

7、解构

解构(对象型): 将对象型变量的子属性转化为一个同名的新变量,解构后的新变量与源子属性同名且放在{}内。如下:

var obj=reactive({count: {wew:'uu'} })
var {wew}=obj.count

解构(数组型): 将数组型变量的子项转化为一个新变量,解构后的新变量可随意命名且放在[]内。如下:

var obj=reactive(['count','uu'} ])
var [wew,uiu]=obj

详见数组解构
被解构的reactive变量的 子属性或子项 为赋值层,上例解构等同于如下:
var wew=obj.count.wew
var wew=obj[0];var uiu=obj[1]
Proxy继承与非继承: 解构出来的新变量是引用型时变量继承Proxy代理包装,新变量与赋值层相互响应,拥有Proxy对象的DOM响应性;解构出来的新变量是基本型时变量不继承Proxy代理包装,新变量与赋值层不相互响应,无Proxy对象不具DOM响应性。

8、调用书写规范

调用时会自动解包Proxy,因此在逻辑部、模版部不需要额外的解包直接使用。

响应式函数 ref()

使用ref函数包装一个对象

<template>
 <h1 v-text="state"></h1>
    <h1 v-text="state.count"></h1>
    <h1 v-text="stat[0]"></h1>
    <h1 v-text="sta"></h1>
<button @click="ee">我来改变</button>
</template>
<script setup>
import { ref } from 'vue'
const state = ref({ count: 0 })     //创建对像要用({  })包裹
const stat = ref([ 0 ])            //创建数组要用([  ])包裹
const sta = ref(0)                //参数可以是基本类型
 function ee(){state.value.count++
               stat.value[0]++
               sta.value++
state.val=999}
 </script>
1、ref函数的书写规范:ref(参数)

ref参数类型: 引用类型、基本类型、变量型。
对象型参数用{}括起,可以是空,如:ref({})
数组型参数用[]括起,可以是空,如:ref([])
基本类型参数不用括起,可以是空,如:ref()
与reactive曲别: 参数可以使用基本类型;基本类型变量不用括起(变量包裹:当包裹变量时,变量名会转化为属性名。调用时要额外加上此变量名,数组要额外加上[0],书写繁杂,所以最好是不括起)。

2、ref内部原理

ref会用RefImpl把参数包装成一个RefImpl对象。
基本型参数: 包装基本型参数时,RefImpl对象创建value属性存储参数,对外暴露value值;创建prototype原型集,内含get value与set value函数,监测value值负责DOM响应。
引用型参数: 包装引用型参数时,RefImpl内Proxy把参数包装成一个名为value的Proxy对象,对外暴露value;Proxy代理创建Handler处理集与Target对象目标,Handler处理集封装get与set,深层监测Target对象,产生DOM响应。

3、与变量型参数的关系

相互响应: 当参数是引用型变量时,引用型参数与新变量内的value对象使用同一指针,引用同一Object,两者之间相互响应。
不相互响应: 当参数是基本类型变量时,两者各用栈内存固定地址栈内存固定地址,不相互响应。
相互响应消失: 当引用型参数重新赋值为基本类型时,由堆内存指针改为栈内存固定地址,两者不再引用同一Object,两者之间相互响应消失。
被动更新DOM响应失效: 因相互响应引起的ref变量被动更新,并不会被Handler处理集监测到,虽然值改变了,但DOM并不会更新。
与reactive曲别: reactive是变量Proxy代理包装,变量与参数相互响应;而ref是变量下的value对象Proxy代理包装,value对象​与参数相互响应。

4、深层包装

表层响应性是由RefImpl下的prototype原型集内的get value与set value函数提供DOM响应性,深层由Proxy代理包装value对象下的get 与set,监测value对象的子子孙孙产生响应性;遵循向上直系触动原则。
与reactive曲别: reactive是用Proxy包装变量,监测变量的子属性及孙属性产生DOM响应。ref是用Proxy包装变量下的value对象,只监测value对象的子属性及孙属性产生DOM响应。

5、ref赋值给其它变量
  • 赋值层全等: 新变量赋值层与ref源全等,使用同一指针,引用同一RefImpl对象,两者相互响应。
  • 监测value产生DOM响应性: 赋值层与源ref变量同一RefImpl对象,赋值层也具有RefImpl对象的特性,对value值监测产生DOM响应或value对象内部Handler处理集监测value对象产生DOM响应。非value属性或value对象不监测无DOM响应性。如下:

    <template>
    <h1 >{{react}}</h1>
    <h1 >{{obj}}</h1>
     <button @click="ee">同层下的非value更新</button> 
     <button @click="ef">同层下的value更新</button> 
     </template>
     <script setup>
     import {ref,reactive}from 'vue'
     const react=ref("我是响应性变量")
     const obj=react
     function ee(){react.vlu='同层非value子属性改变';console.log(obj)}
     function ef(){react.value='同层value子属性改变'}
     </script>
  • 失去相互响应与DOM响应: 当赋值层重新赋值为基本类型时RefImpl对象消失,两者之间不再相互响应,DOM响应性也会消失。
  • 相互响应但DOM不响应:
    赋值层增加子属性时,RefImpl对象没有灭失,两者之间依旧相互响应,但增加子属性非value,不监测DOM不响应。
  • 解包: 新变量的赋值层与源ref指向同一RefImpl对象,使用时也是要解包。
    ref赋值给reactive变量时,解包特殊性:
    作为对象赋值——会自动解包,不需加value。
    作为数组赋值——不会自动解包,需加value。
    如下:

    <template>
    <h1 >{{react}}</h1>
    <h1 >{{obj}}</h1>
     <button @click="ee">加value</button> 
     <button @click="ef">不加value</button> 
     </template>
     <script setup>
     import {ref,reactive}from 'vue'
     const count = ref(0)
    const react = reactive({count})
    const obj = reactive([count])
    function ee(){console.log(react.count.value,obj[0].value)}
    function ef(){console.log(react.count,obj[0])}
     </script>
6、失去响应性

ref()的响应性是建立在对value属性的监测上,一切灭失value属性会失去响应性,或重新包装value对象(Proxy代理包装变为普通包装)也会失去响应性。

7、解构

Proxy继承与非继承: 解构出来的新变量是引用型时变量继承Proxy代理包装,新变量与赋值层相互响应,拥有Proxy对象的DOM响应性;解构出来的新变量是基本型时变量不继承Proxy代理包装,新变量与赋值层不相互响应,无Proxy对象不具DOM响应性。因此解构底层属性会失去DOM响应性。

8、调用

DOM响应值包装在value属性里,因此调用响应值时需要对value解包。
逻辑部可以调用所有属性,模版部只能调用value属性。
逻辑部调用value属性: 需要加.value解包。
模版部调用: 如果是顶级属性自动解包(不需要加.value解包)、非顶级属性且不需计算会自动解包、非顶级属性需计算需要加value解包。如下:

<template>
  <h1 >{{react}}</h1>
  <h1 >{{obj.react.value+1}}非顶级属性需计算</h1>
  <h1 >{{obj.react}}非顶级属性不需计算</h1>
  <h1 >{{obj}}顶级属性</h1>
 </template>
 <script setup>
 import {ref,reactive}from 'vue'
 const react=ref(0)
 const obj={react}
 </script>
9、用ref还是reactive

ref定义基础型数据、引用型数据(定义引用型数据时使用的也是reactive中的Proxy代理)
reactive只能定义引用型数据。
我懒好吧,ref字母少,一招通用ref。

10、连带更新效应

当有一个DOM元素更新时,其它非响应性DOM也会检查所包函的变量或属性的值,作出连带更新(需在Vue3.4版本以上)。如下:

<template>
  <h1 >{{uu}}</h1>
  <h1 >{{uuj.uuu}}</h1>
  <h1 >{{react}}</h1>
<button @click="ee">非响应性DOM更新</button> 
<button @click="er">响应性DOM更新</button> 
<button @click="eer">响应性变量更新但不在DOM上</button> 
</template>
<script setup>
import { ref} from 'vue';
var uu="我是非响应性对象"
var uuj={uuu:"我也是非响应性对象"}
var react=ref(1)
var reac=ref(1)
    function ee(){uu='我被更新了';uuj.uuu='我也被更新了'}
    function er(){react.value++}
     function eer(){reac.value++}
  </script>

注:不在DOM上的响应变量更新不会引起非响应DOM连带更新。


百分之一百零八
15 声望3 粉丝