reactive
如果想為在setup中定義的數據提供響應式
的特性奔害,那么我們可以使用reactive函數
:
那么這是什么原因呢兴使?為什么就可以變成響應式的呢?
- 這是因為當我們使用reactive函數處理我們的數據之后,數據再次被使用時就會進行依賴收集蝉揍;
- 當數據發(fā)生改變時绪抛,所有收集到的依賴都是進行對應的響應式操作(比如更新界面)泰偿;
- 事實上,我們編寫的data選項归露,也是在內部交給了reactive函數將其編程響應式對象的熙卡;
<template>
<div>
<div>數量:{{ state.count }}</div>
<button @click="increment">+1</button>
</div>
</template>
<script>
import { reactive } from "vue";
export default {
setup() {
//reactive函數把對象參數處理過后杖刷,返回一個響應式對象,響應式對象內部的key的值都是響應式的
const state = reactive({
count: 100,
});
const increment = () => {
state.count++;
console.log(state.count);
};
return {
state,
increment,
};
},
};
</script>
<style lang="scss" scoped></style>
reactive函數把對象參數處理過后,返回一個響應式對象,
響應式對象內部的key的值都是響應式的
缺陷:響應式變量被存放到對象中驳癌,不管在template還是在setup中使用都需要從對象中獲取
reactive函數的參數類型
reactive API對傳入的類型
是有限制的滑燃,它要求我們必須傳入的是一個對象
或者數組
類型:
-
如果我們傳入一個基本數據類型(String、Number颓鲜、Boolean)會報一個警告表窘;image.png
ref API
ref函數 會返回一個可變的響應式對象ref對象
,該對象作為一個 響應式的引用 維護著它內部的值甜滨,這就是ref名稱的來源乐严;
- 它內部的值是在ref對象的
value
屬性中被維護的;
這里有兩個注意事項:
- 在template模板中使用ref對象的值時衣摩,Vue會自動幫助我們進行
解包
操作昂验,所以我們并不需要在模板中通過ref.value
的方式使用,直接使用ref對象
就行 - 但是在 setup 函數內部,它依然是一個 ref引用既琴, 所以對其進行操作時占婉,我們依然需要使用 ref.value的方式;
template模板中的對ref對象的解包是一個淺層的解包
- 如果包裹著ref對象的是普通對象呛梆,vue不會自動幫我們把ref對象解包
- 如果包裹著ref對象的是reactive函數返回的可響應式對象锐涯,vue依然會自動幫我們把ref對象解包
<template>
<div>
<!-- 在template模板中使用ref對象,vue會自動幫我們解包
不需要再去value屬性中獲取值 -->
<div>數量:{{ count }}</div>
<!-- template中的解包是淺層的解包填物,
如果包裹響應式變量的對象是普通對象纹腌,vue不會自動幫我們解包 -->
<div>數量:{{ info.count.value }}</div>
<!-- 如果包裹響應式變量的對象是reactive函數返回的對象,
vue會自動幫我們解包滞磺,不需要通過state.count.value獲取 -->
<div>數量:{{ state.count }}</div>
<button @click="increment">+1</button>
</div>
</template>
<script>
import { ref, reactive } from "vue";
export default {
setup() {
//ref函數返回一個ref對象升薯,參數存儲在ref對象的value屬性中
//ref對象是一個可響應式對象
const count = ref(100);
const info = { count }; //info是一個普通對象,此對象的count屬性的值是count這個ref對象
const state = reactive({
//state是一個響應式對象击困,此對象的屬性count的值count這個ref對象
count,
});
const increment = () => {
count.value++; //ref對象的值存儲在value屬性中
console.log(count.value);
};
return {
count,
info,
state,
increment,
};
},
};
</script>
<style lang="scss" scoped></style>
readonly
我們通過reactive
或者ref
可以獲取到一個響應式的對象涎劈,但是某些情況下,我們傳入給其他地方(組件)的這個響應式對象希望在另外一個地方(組件)被使用
阅茶,但是不能被修改
蛛枚,這個時候如何防止這種情況的出現呢?
Vue3為我們提供了readonly
的方法脸哀;
- readonly會返回原生對象的只讀代理(也就是它依然是一個Proxy蹦浦,這是一個proxy的set方法被劫持,并且不
能對其進行修改)撞蜂;
在開發(fā)中常見的readonly方法會傳入三個類型的參數:
- 類型一:普通對象盲镶;
- 類型二:reactive返回的對象;
- 類型三:ref的對象蝌诡;
readonly的使用
在readonly的使用過程中溉贿,有如下規(guī)則:
- readonly返回的對象都是不允許修改的;
- 但是經過readonly處理的原來的對象是允許被修改的浦旱;
- 比如 const info = readonly(obj)宇色,info對象是不允許被修改的;
- 當obj被修改時颁湖,readonly返回的info對象也會被修改宣蠕;
- 但是我們不能去修改readonly返回的對象info;
- 其實本質上就是readonly返回的對象的setter方法被劫持了而已爷狈;
readonly的使用場景
在我們傳遞給其他組件數據時植影,往往希望其他組件使用我們傳遞的內容,但是不允許它們修改時涎永,就可以使用readonly了思币;
需求:父組件App.vue傳遞給子組件Home.vue的響應式變量鹿响,在父組件內部修改了響應式變量的值,子組件中的數據要會響應式更新谷饿,但是子組件不能修改通過屬性從父組件接收到的響應式變量的值
App.vue
<template>
<div>
<button @click="update">app:修改</button>
<home :info="readonlyInfo2" :name="readonlyInfo3" />
</div>
</template>
<script>
import { ref, reactive, readonly } from "vue";
import Home from "./Home.vue";
export default {
name: "App",
components: {
Home,
},
setup() {
// 1.info1為一個普通對象
const info = { name: "why" };
const readonlyInfo1 = readonly(info);
// 2.info2為一個reactive返回的響應式對象
//reactive對象被readonly函數轉換后的返回值的用法和之前的reactive對象一樣惶我,只是不能被修改
const info2 = reactive({ name: "why" });
const readonlyInfo2 = readonly(info2);
// 3.info3是一個ref對象
//ref對象被readonly函數轉換后的返回值的用法和之前的ref對象一樣,只是不能被修改
//依然可以在template中自動解包
const info3 = ref("why");
const readonlyInfo3 = readonly(info3);
const update = () => {
info.name = "lily";
info2.name = "kobe";
info3.value = "curry";
};
// 所以傳給子組件時可以把readonlyInfo2或readonlyInfo3傳給子組件
//在子組件內部只能使用博投,但不能修改readonlyInfo2或readonlyInfo3中的屬性的值
//但是在當前組件可以通過info2或info3修改屬性的值绸贡,子組件中也會響應式更新
return {
readonlyInfo2,
readonlyInfo3,
info2,
info3,
update,
};
},
};
</script>
<style></style>
Home.vue
<template>
<div>
<div>名字: {{ name }}</div>
<div>信息: {{ info.name }}</div>
<button @click="updateName">修改名字</button>
<button @click="updateInfo">修改信息</button>
</div>
</template>
<script>
export default {
props: {
info: {
type: Object,
required: true,
},
name: {
type: String,
required: true,
},
},
setup(props) {
const updateName = () => {
props.name = "coder"; //報錯
};
const updateInfo = () => {
props.info.name = "coder"; //報錯
};
return {
updateName,
updateInfo,
};
},
};
</script>
<style lang="scss" scoped></style>
所以傳給子組件時可以把readonlyInfo2或readonlyInfo3只讀變量傳給子組件
在子組件內部只能使用,但不能修改readonlyInfo2中的屬性的值或readonlyInfo3的值
但是在當前組件可以通過info2或info3修改屬性的值毅哗,子組件中也會響應式更新
Reactive判斷的API
isProxy
- 檢查對象是否是由 reactive 或 readonly創(chuàng)建的 proxy听怕。
isReactive
- 檢查對象是否是由 reactive創(chuàng)建的響應式代理:
- 如果該代理是 readonly 建的,但包裹了由 reactive 創(chuàng)建的另一個代理虑绵,它也會返回 true尿瞭;
isReadonly
- 檢查對象是否是由 readonly 創(chuàng)建的只讀代理。
toRaw
- 返回 reactive 或 readonly 代理的原始對象(不建議保留對原始對象的持久引用翅睛。請謹慎使用)声搁。
shallowReactive
- 創(chuàng)建一個響應式代理,它跟蹤其自身 property 的響應性捕发,但不執(zhí)行嵌套對象的深層響應式轉換 (深層還是原生對象)疏旨。
shallowReadonly
- 創(chuàng)建一個 proxy,使其自身的 property 為只讀扎酷,但不執(zhí)行嵌套對象的深度只讀轉換(深層還是可讀檐涝、可寫的)。
案例
<template>
<div>
<div>reactive: {{ info.friend.name }}</div>
<div>shallowReactive: {{ info1.friend.name }}</div>
<button @click="updateInfo">修改info</button>
<button @click="updateInfo1">修改info1</button>
</div>
</template>
<script>
import {
reactive,
readonly,
shallowReadonly,
isProxy,
isReactive,
isReadonly,
shallowReactive,
} from "vue";
export default {
name: "App",
setup() {
//reactive創(chuàng)建的reactive對象霞玄,不管內嵌多少層都是響應式的骤铃,即inof.friend.name值改變拉岁,頁面也會響應式更新
const info = reactive({ name: "why", age: 18, friend: { name: "kobe" } });
//shallowReactive創(chuàng)建的reactive對象坷剧,只有其自身的屬性是響應式的,內嵌對象的屬性不是響應式的喊暖,
//即inof.friend.name值改變惫企,頁面不會響應式更新
const info1 = shallowReactive({
name: "why",
age: 18,
friend: { name: "kobe" },
});
const updateInfo = () => {
info.friend.name = "curry";
console.log("info", info.friend.name);
};
const updateInfo1 = () => {
info1.friend.name = "curry";
console.log("info1", info1.friend.name);
};
//readonlyInfo.name和readonlyInfo.friend.name都不可以被修改
const readonlyInfo = readonly(info);
//shallowReadonlyInfo.friend.name可以被修改,shallowReadonlyInfo.name不能被修改
const shallowReadonlyInfo = shallowReadonly(info);
const message = readonly({ name: "蘋果", price: 18 });
console.log(isProxy(message)); //true
console.log(isProxy(info)); //true
console.log(isProxy(readonlyInfo)); //true
console.log(isReactive(info)); //true
console.log(isReactive(readonlyInfo)); //true
console.log(isReactive(info.friend)); //true
console.log(isReactive(info1.friend)); //false
console.log(isReadonly(readonlyInfo)); //true
return {
info,
info1,
updateInfo,
updateInfo1,
};
},
};
</script>
<style></style>
toRefs
如果我們使用ES6的解構
語法陵叽,對reactive返回的對象state進行解構獲取值
狞尔,解構出來的變量name, age
,只是普通的值
,和原reactive對象state沒有關系,
- 修改reactive返回的state對象的屬性name,age的值巩掺,不會影響之前已經解構出的name, age變量偏序,
- 修改變量name和age的值,也不會影響原reactive返回的state對象
- 在template模板中使用變量name, age,當變量name, age值改變時胖替,頁面中數據不會響應式更新
const info = reactive({ name: "why", age: 18 });
let { name, age } = info;
那么有沒有辦法讓我們解構出來的屬性是響應式的呢研儒?
- Vue為我們提供了一個toRefs的函數豫缨,可以將reactive返回的對象中的屬性都轉成ref;
- 那么我們再次進行解構出來的 name 和 age 本身都是 ref對象端朵;
- 這種做法相當于已經在state.name和ref.value之間建立了 鏈接好芭,任何一個修改都會引起另外一個變化;
- 也就是修改name.value值冲呢,state.name也會發(fā)生改變舍败,
- 修改state.name的值,name.value也會發(fā)生改變
- 如果在template模板中使用了變量name, age敬拓,name.value和age.value的值改變邻薯,頁面中的數據也會響應式更新
- toRefs函數的參數必須是
reactive對象
const info = reactive({ name: "why", age: 18 });
let { name, age } = toRefs(info);
案例1:使用es6解構出的變量為普通的值:
解構出的變量的值是普通的值,不是響應式變量乘凸,值修改弛说,頁面中的數據不會響應式更新,與原reactive也沒有關系
<template>
<div>
<h1>名字:{{ name }},年齡:{{ age }}</h1>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年齡</button>
</div>
</template>
<script>
import { reactive } from "vue";
export default {
name: "App",
setup() {
const info = reactive({ name: "why", age: 18 });
//通過此種對象解構的方式翰意,獲取的name,age變量的值木人,只是普通的值,因為不是ref對象或reactive對象冀偶,所以不具備響應式 ,
//修改其值醒第,頁面也不會重新渲染
//相當于下面寫法:name = 'why' age = 18
let { name, age } = info;
const changeName = () => {
info.name = "kobe"; //修改info.name,name不會發(fā)生改變
console.log(info.name, name); //kobe why
};
const changeAge = () => {
age++; //修改age,頁面中的數據也不會響應式更新进鸠, info.age不會發(fā)生改變稠曼,
console.log(info.age, age); //18 19
};
return {
name,
age,
changeAge,
changeName,
};
},
};
</script>
<style></style>
案例2:使用ref創(chuàng)建ref對象, 參數為reactive對象的某個屬性的值:
ref函數只是獲取了reactive對象的某個屬性的值,ref函數返回的ref對象和原reactive對象沒有其他關系客年,互不影響
<template>
<div>
<h1>名字:{{ name }},年齡:{{ age }}</h1>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年齡</button>
</div>
</template>
<script>
import { reactive, ref, toRefs } from "vue";
export default {
name: "App",
setup() {
const info = reactive({ name: "why", age: 18 });
//下面這種方式霞幅,變量age和name獲取的只是info對應屬性的值,
//修改變量age和name的value量瓜,或者修改info的name,age屬性的值司恳,都不會影響對方
const name = ref(info.name);
const age = ref(info.age);
const changeName = () => {
info.name = "kobe"; //修改info.name,name.value不會發(fā)生改變
console.log(info.name, name.value); //kobe why
};
const changeAge = () => {
age.value++; //修改age,頁面中的數據會響應式更新绍傲, info.age不發(fā)生改變扔傅,
console.log(info.age, age.value); //18 19
};
return {
name,
age,
changeAge,
changeName,
};
},
};
</script>
<style></style>
案例3:使用toRefs解構出的變量為ref對象:
使用toRefs會將reactive返回的對象中的屬性都轉成ref,
從info解構出的變量都是ref對象烫饼,且跟原reactive對象的對應屬性建立了鏈接猎塞,一個變量,另一個也會改變
<template>
<div>
<h1>名字:{{ name }},年齡:{{ age }}</h1>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年齡</button>
</div>
</template>
<script>
import { reactive, ref, toRefs } from "vue";
export default {
name: "App",
setup() {
const info = reactive({ name: "why", age: 18 });
// 使用toRefs會將reactive返回的對象中的屬性都轉成ref杠纵,
//從info解構出的變量都是ref對象荠耽,且根原reactive對象的對應屬性建立了鏈接,一個變量比藻,另一個也會改變
let { name, age } = toRefs(info);
const changeName = () => {
info.name = "kobe"; //修改info.name铝量,name.value會發(fā)生改變
console.log(info.name, name.value); //kobe kobe
};
const changeAge = () => {
age.value++; //修改age,頁面中的數據會響應式更新伊履, info.age也會發(fā)生改變,
console.log(info.age, age.value); //19 19
};
return {
name,
age,
changeAge,
changeName,
};
},
};
</script>
<style></style>
toRef
如果我們只希望轉換一個reactive對象中的屬性為ref, 那么可以使用toRef的方法:
使用toRef函數返回一個ref對象款违,和原reactive對象中對應屬性建立了鏈接唐瀑,一個改變,另一個也改變
<template>
<div>
<h1>名字:{{ name }},年齡:{{ age }}</h1>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年齡</button>
</div>
</template>
<script>
import { reactive, toRef } from "vue";
export default {
name: "App",
setup() {
const info = reactive({ name: "why", age: 18 });
let { age } = info; //變量age的值為普通的值
// 從info解構name變量轉換為ref對象插爹,
//ref對象name和原reactive對象info的屬性name建立了鏈接哄辣,一個改變,另一個也改變
let name = toRef(info, "name");
const changeName = () => {
info.name = "kobe"; //修改info.name赠尾,name.value會發(fā)生改變力穗,頁面中的數據會響應式更新
console.log(info.name, name.value); //kobe kobe
};
const changeAge = () => {
age++; //age值改變,頁面中的數據不會響應式更新气嫁,不會引起info.age值的改變
console.log(age, info.age); //19 18
};
return {
name,
age,
changeAge,
changeName,
};
},
};
</script>
<style></style>
ref其他的API
unref
- 如果我們想要獲取一個ref引用中的value当窗,那么也可以通過unref方法:
- 如果參數是一個 ref,則返回內部值寸宵,否則返回參數本身崖面;
- 這是 val = isRef(val) ? val.value : val 的語法糖函數;
isRef
- 判斷值是否是一個ref對象梯影。
shallowRef
- 創(chuàng)建一個淺層的ref對象巫员;
triggerRef
- 手動觸發(fā)和 shallowRef 相關聯(lián)的副作用:
案例:ref函數創(chuàng)建的深層響應式對象和shallowRef創(chuàng)建的淺層響應式對象的區(qū)別
ref函數返回的ref對象,是深層響應的甲棍,其內嵌的對象的屬性發(fā)生改變简识,頁面中的數據也會響應式
shallowRef創(chuàng)建ref對象為淺層響應,
- 即只有其ref.value的值發(fā)生改變感猛,頁面才會響應式更新七扰,
- ref.value.name發(fā)生改變,頁面不會響應式更新更新
<template>
<div>
<h1>info:{{ info.name }}</h1>
<h1>info1:{{ info1.name }}</h1>
<button @click="changeInfoName">修改info名字</button>
<button @click="changeInfo1Name">修改info1名字</button>
</div>
</template>
<script>
import { ref, shallowRef, triggerRef } from "vue";
export default {
name: "App",
setup() {
//ref函數返回的ref對象陪白,是深層響應的颈走,
//其內嵌的對象的屬性發(fā)生改變,頁面中的數據也會響應式更新
const info = ref({
name: "why",
age: 18,
});
//shallowRef創(chuàng)建ref對象為淺層響應拷泽,
//即只有其ref.value的值發(fā)生改變疫鹊,頁面才會響應式更新袖瞻,
//ref.value.name發(fā)生改變司致,頁面不會響應式更新
const info1 = shallowRef({
name: "why",
age: 18,
});
const changeInfoName = () => {
info.value.name = "curry";
console.log(info.value.name);
};
const changeInfo1Name = () => {
info.value.name = "curry";
console.log(info.value.name);
};
return {
info,
info1,
changeInfoName,
changeInfo1Name,
};
},
};
</script>
<style></style>
triggerRef的用法
如果希望淺層響應ref對象的內嵌對象的值改變時,頁面上的數據響應式更新聋迎,
- 需要使用triggerRef函數手動觸發(fā)淺層ref對象相關的副作用脂矫,
- 參數為要觸發(fā)副作用淺層ref對象
<template>
<div>
<h1>info:{{ info.name }}</h1>
<button @click="changeInfoName">修改info名字</button>
</div>
</template>
<script>
import { shallowRef, triggerRef } from "vue";
export default {
name: "App",
setup() {
//shallowRef創(chuàng)建ref對象為淺層響應,
//即只有其ref.value的值發(fā)生改變霉晕,頁面才會響應式更新庭再,
//ref.value.name發(fā)生改變捞奕,頁面不會響應式更新
const info = shallowRef({
name: "why",
age: 18,
});
const changeInfoName = () => {
info.value.name = "curry";
console.log(info.value.name);
//如果希望info.value.name值改變時,頁面上的數據響應式更新拄轻,
//需要使用triggerRef函數手動觸發(fā)淺層ref對象相關的副作用颅围,
//參數為要觸發(fā)副作用淺層ref對象
triggerRef(info);
};
return {
info,
changeInfoName,
};
},
};
</script>
<style></style>
案例:unref和isRef的用法
<template>
<div></div>
</template>
<script>
import { ref, isRef, unref } from "vue";
export default {
name: "App",
setup() {
const info = ref({
name: "why",
age: 18,
});
const value = isRef(info) ? info.value : info;
//unref是上面用法的語法糖
const value1 = unref(info);
console.log(value, value1);
return {};
},
};
</script>
<style></style>
customRef
創(chuàng)建一個自定義的ref,并對其依賴項跟蹤
和更新觸發(fā)
進行顯示控制
:
- customRef函數的參數為一個
工廠函數
恨搓,此工廠函數有兩
個參數院促,每個參數都是一個函數
- 第一個參數是
追蹤函數track
, 你可以通過調用track函數斧抱,來決定什么時候收集依賴
- 第二個參數是
觸發(fā)函數trigger
常拓,你可以通過調用trigger函數,來決定什么時候觸發(fā)所有的依賴
去更新數據
- 第一個參數是
- 通過上面兩個函數辉浦,你可以決定什么時候收集依賴弄抬,什么時候觸發(fā)更新
- 工廠函數需要返回一個
有get和set的對象
這里我們使用一個的案例:
- 對雙向綁定的屬性進行debounce(節(jié)流)的操作;
./hooks/useDebounceRef.js
import {
customRef
} from 'vue'
export default function (value) {
let timer = null
//customRef函數的參數為一個工廠函數宪郊,此工廠函數有兩個參數掂恕,每個參數都是一個函數
//第一個參數是追蹤函數track, 你可以通過調用track函數弛槐,來決定什么時候收集依賴
//第二個參數是觸發(fā)函數trigger竹海,你可以通過調用trigger函數,來決定什么時候觸發(fā)所有的依賴去更新數據
//通過上面兩個函數丐黄,你可以決定什么時候收集依賴斋配,什么時候觸發(fā)更新
//工廠函數需要返回一個有get和set的對象
return customRef((track, trigger) => {
return {
get() {
track() //在獲取值的時候收集依賴
return value
},
set(newVal) {
clearTimeout(timer)
timer = setTimeout(() => {
value = newVal
trigger() //在更新值后的1s后觸發(fā)更新
}, 1000)
}
}
})
}
App.vue
<template>
<div>
<input v-model="message" />
<h1>{{message}}</h1>
</div>
</template>
<script>
import debounceRef from './hook/useDebounceRef.js'
export default {
setup() {
let message = debounceRef('hello')
return {
message
}
}
}
</script>
<style lang="scss" scoped>
</style>
此文檔主要內容來源于王紅元老師的vue3+ts視頻教程