vue3的單組件的編寫(三)【響應式 API 之 toRef 與 toRefs】

響應式 API 之 toRef 與 toRefs

前面講了 ref 和 reactive 這兩種響應式API ,為了方便開發者使用,vue3 還出了兩個用來 reactive 轉換為 ref 的API,分別是 toRef 和 toRefs 。

🌈什么是toRef 與 toRefs

這兩個API看拼寫能猜到,toRef 轉換一個ref,toRefs 是轉換所有 ref 。轉換后將得到新的變量,并且新變量和原來的變量可以保持同步更新。

API作用
toRef創建一個新的 Ref 變量,轉換 Reactive 對象的某個字段為 Ref 變量
toRefs創建一個新的對象,它的每個字段都是 Reactive 對象各個字段的 Ref 變量

光看概念可能不容易理解,來看下面的例子,先聲明一個 reactive 變量:

interface Member {id: numbername: string
}
const userInfo : Member = reactive({id: 1 ,name: "張珊珊"
})

🌈為什么要進行轉換

為什么要出這么兩個 API ,官方文檔沒有特別說明,不過經過筆者在業務中的一些實際使用感受,可知道一些使用理由。

refreactive 在使用的過程中,各自都有不方便的地方:

ref API 雖然在 <template /> 里使用起來方便,但是在 <script /> 里進行讀取 / 賦值的時候,要一直記得加上 .value ,否則 BUG 就來了。

reactive API 雖然在使用的時候,因為知道它本身是一個對象,所以不會忘記通過 foo.bar 這樣的格式去操作,但是在 <template /> 渲染的時候,又因此不得不每次都使用 foo.bar 的格式去渲染。

那么有沒有辦法,既可以在編寫 <script /> 的時候不容易出錯,在寫 <template /> 的時候又比較簡單呢?

于是, toReftoRefs 因此誕生。

🌈toRef的使用

🚀API的TS類型和基本用法
// `toRef` API 的 TS 類型
function toRef<T extends object, K extends keyof T>(object: T,key: K,defaultValue?: T[K]
): ToRef<T[K]>// `toRef` API 的返回值的 TS 類型
type ToRef<T> = T extends Ref ? T : Ref<T>

通過接收兩個必傳的參數(第一個是 reactive 對象, 第二個是要轉換的 key ),返回一個 Ref 變量,在適當的時候也可以傳遞第三個參數,為該變量設置默認值。

以上面聲明好的 userInfo 為例子,如果想要轉換 name 字段為 Ref 變量,需要:

const name = toRef(userInfo , 'name')
console.log(name.value)

等號左側的 name 變量此時是一個 Ref 變量,這里因為 TypeScript 可以對其自動推導,因此聲明時可以省略 TS 類型的顯式指定,實際上該變量的類型是 Ref<string>

所以之后在讀取和賦值時,就需要使用 name.value 來操作,在重新賦值時會同時更新 nameuserInfo.name 的值:

// 修改前先查看初始值
const name = toRef(userInfo, 'name')
console.log(name.value) // Petter
console.log(userInfo.name) // Petter// 修改 Ref 變量的值,兩者同步更新
name.value = 'Tom'
console.log(name.value) // Tom
console.log(userInfo.name) // Tomty// 修改 Reactive 對象上該屬性的值,兩者也是同步更新
userInfo.name = 'Jerry'
console.log(name.value) // Jerry
console.log(userInfo.name) // Jerry

這個 API 也可以接收一個 Reactive 數組,此時第二個參數應該傳入數組的下標:

// 這一次聲明的是數組
const words = reactive(['a', 'b', 'c'])// 通過下標 `0` 轉換第一個 item
const a = toRef(words, 0)
console.log(a.value) // a
console.log(words[0]) // a// 通過下標 `2` 轉換第三個 item
const c = toRef(words, 2)
console.log(c.value) // c
console.log(words[2]) // c
🚀基本用法-完整代碼和演示
<script lang="ts">
import { defineComponent, toRef, reactive, toRefs } from "vue"
interface Member {id: numbername: string
}
const userInfo: Member = reactive({id: 1,name: "張珊珊"
})export default defineComponent({setup() {// 在這里聲明數據,或者編寫函數并在這里執行它const name = toRef(userInfo, 'name');function editRefName() {name.value = '陳圓圓'}function editReactiveName() {userInfo.name = '裴南葦'}return {name,userInfo,editRefName,editReactiveName,}},
})</script><template><div><h2> ref: {{ name }}</h2><h2> reactive: {{ userInfo.name }}</h2><button @click="editRefName()">修改ref的name</button><br><button @click="editReactiveName()">修改reactive 的name</button></div>
</template><style scoped></style>

代碼分別顯示了 ref 和 reactive 的兩種響應式的值的顯示。又分別控制了,修改兩個值。下面的演示結果可以看出,toRef 轉換后將得到新的變量,并且新變量和原來的變量可以保持同步更新。

演示動圖

🚀設置默認值

如果 Reactive 對象上有一個屬性本身沒有初始值,也可以傳遞第三個參數進行設置(默認值僅對 Ref 變量有效,即此次不會同步):

interface Member {id: numbername: stringage?: number
}const userInfo: Member = reactive({id: 2,name: "Tony"
})// 此時為了避免程序運行錯誤,可以指定一個初始值
// 但初始值僅對 Ref 變量有效,不會影響 Reactive 字段的值
const age = toRef( userInfo , age , 18)
console.log(age.value)  // 18
console.log(userInfo.age) // undefined// 除非重新賦值,才會使兩者同時更新
age.value = 25
console.log(age.value)  // 25
console.log(userInfo.age) // 25

數組也是同理,對于可能不存在的下標,可以傳入默認值避免項目的邏輯代碼出現問題:

const words = reactive(['a','b','c'])
//當下標對應的值不存在時,返回 ”undefined“
const d = toRef(words,3)
console.log(d.value) // undefined
console.log(words[3]) // undefined//設置了默認后,會僅對 Ref 變量有效
cosnt e = toRef(words,4,'e')
console.log(e.value) // e
console.log(words[4]) // undefined
🚀其他用法

這個 API 還有一個特殊用法,但不建議在 TypeScript 里使用。

toRef 的過程中,如果使用了原對象上面不存在的 key ,那么定義出來的 Ref 變量的 .value 值將會是 undefined

// 眾所周知, Petter 是沒有女朋友的
const girlfriend = toRef(userInfo, 'girlfriend')
console.log(girlfriend.value) // undefined
console.log(userInfo.girlfriend) // undefined// 此時 Reactive 對象上只有兩個 Key
console.log(Object.keys(userInfo)) // ['id', 'name']

如果對這個不存在的 key 的 Ref 變量進行賦值,那么原來的 Reactive 對象也會同步增加這個 key,其值也會同步更新。

// 賦值后,不僅 Ref 變量得到了 `Marry` , Reactive 對象也得到了 `Marry`
girlfriend.value = 'Marry'
console.log(girlfriend.value) // 'Marry'
console.log(userInfo.girlfriend) // 'Marry'// 此時 Reactive 對象上有了三個 Key
console.log(Object.keys(userInfo)) // ['id', 'name', 'girlfriend']

為什么強調不要在 TypeScript 里使用呢?因為在編譯時,無法通過 TypeScript 的類型檢查:

? npm run build> hello-vue3@0.0.0 build
> vue-tsc --noEmit && vite buildsrc/views/home.vue:37:40 - error TS2345: Argument of type '"girlfriend"'
is not assignable to parameter of type 'keyof Member'.37     const girlfriend = toRef(userInfo, 'girlfriend')~~~~~~~~~~~~src/views/home.vue:39:26 - error TS2339: Property 'girlfriend' does not exist
on type 'Member'.39     console.log(userInfo.girlfriend) // undefined~~~~~~~~~~src/views/home.vue:45:26 - error TS2339: Property 'girlfriend' does not exist
on type 'Member'.45     console.log(userInfo.girlfriend) // 'Marry'~~~~~~~~~~Found 3 errors in the same file, starting at: src/views/home.vue:37

如果不得不使用這種情況,可以考慮使用 any 類型:

// 將該類型直接指定為 `any`
type Member = any
// 當然一般都是 `const userInfo: any`// 或者保持接口類型的情況下,允許任意鍵值
interface Member {[key: string]: any
}// 使用 `Record` 也是同理
type Member = Record<string, any>

但筆者還是更推薦保持良好的類型聲明習慣,盡量避免這種用法。

🌈使用 toRefs

在了解了 toRef API 之后,來看看 toRefs 的用法。

🚀API 類型和基本用法

先看看它的 TS 類型:

function toRefs<T extends object>(object: T
): {[K in keyof T]: ToRef<T[K]>
}type ToRef = T extends Ref ? T : Ref<T>

與 toRef 不同, toRefs 只接收了一個參數:reactive 變量。

interface Member {id: numbername: string
}// 聲明一個 Reactive 變量
const userInfo: Member = reactive({id: 1,name: 'Petter',
})// 傳給 `toRefs` 作為入參
const userInfoRefs = toRefs(userInfo)

此時這個新的 userInfoRefs 變量,它的 TS 類型就不再是 Member 了,而應該是:

// 導入 `toRefs` API 的類型
import type { ToRefs } from 'vue'// 上下文代碼省略...// 將原來的類型傳給 API 的類型
const userInfoRefs: ToRefs<Member> = toRefs(userInfo)

也可以重新編寫一個新的類型來指定它,因為每個字段都是與原來關聯的 Ref 變量,所以也可以這樣聲明:

// 導入 `ref` API 的類型
import type { Ref } from 'vue'// 上下文代碼省略...// 新聲明的類型每個字段都是一個 Ref 變量的類型
interface MemberRefs {id: Ref<number>name: Ref<string>
}// 使用新的類型進行聲明
const userInfoRefs: MemberRefs = toRefs(userInfo)

當然實際上日常使用時并不需要手動指定其類型, TypeScript 會自動推導,可以節約非常多的開發工作量。

toRef API 一樣,這個 API 也是可以對數組進行轉換:

const words = reactive(['a', 'b', 'c'])
const wordsRefs = toRefs(words)

此時新數組的類型是 Ref<string>[] ,不再是原來的 string[] 類型。

🚀解構與賦值

轉換后的 Reactive 對象或數組支持 ES6 的解構,并且不會失去響應性,因為解構后的每一個變量都具備響應性。

// 為了提高開發效率,可以直接將 Ref 變量直接解構出來使用
const { name } = toRefs(userInfo)
console.log(name.value) // Petter// 此時對解構出來的變量重新賦值,原來的變量也可以同步更新
name.value = 'Tom'
console.log(name.value) // Tom
console.log(userInfo.name) // Tom

這一點和直接解構 Reactive 變量有非常大的不同,直接解構 Reactive 變量,得到的是一個普通的變量,不再具備響應性。

這個功能在使用 Hooks 函數非常好用(在 Vue 3 里也叫可組合函數, Composable Functions ),還是以一個計算器函數為例,這一次將其修改為內部有一個 Reactive 的數據狀態中心,在函數返回時解構為多個 Ref 變量:

import { reactive, toRefs } from 'vue'// 聲明 `useCalculator` 數據狀態類型
interface CalculatorState {// 這是要用來計算操作的數據num: number// 這是每次計算時要增加的幅度step: number
}// 聲明一個 “使用計算器” 的函數
function useCalculator() {// 通過數據狀態中心的形式,集中管理內部變量const state: CalculatorState = reactive({num: 0,step: 10,})// 功能函數也是通過數據中心變量去調用function add() {state.num += state.step}return {...toRefs(state),add,}
}

這樣在調用 useCalculator 函數時,可以通過解構直接獲取到 Ref 變量,不需要再進行額外的轉換工作。

// 解構出來的 `num` 和 `step` 都是 Ref 變量
const { num, step, add } = useCalculator()
console.log(num.value) // 0
console.log(step.value) // 10// 調用計算器的方法,數據也是會得到響應式更新
add()
console.log(num.value) // 10

🌈什么場景下比較適合使用它們

從便利性和可維護性來說,最好只在功能單一、代碼量少的組件里使用,比如一個表單組件,通常表單的數據都放在一個對象里。

當然也可以把所有的數據都定義到一個 data 里,再去 data 里面取值,但是沒有必要為了轉換而轉換,否則不如使用 Options API 風格。

🌈在業務中的具體運用

繼續使用上文一直在使用的 userInfo 來當案例,以一個用戶信息表的小 demo 做個演示。

<script /> 部分:

  1. 先用 reactive 定義一個源數據,所有的數據更新,都是修改這個對象對應的值,按照對象的寫法維護數據
  2. 再通過 toRefs 定義一個給 <template /> 使用的對象,這樣可以得到一個每個字段都是 Ref 變量的新對象
  3. return 的時候,對步驟 2 里的 toRefs 對象進行解構,這樣導出去就是各個字段對應的 Ref 變量,而不是一整個對象
import { defineComponent, reactive, toRefs } from 'vue'interface Member {id: numbername: stringage: numbergender: string
}export default defineComponent({setup() {// 定義一個 reactive 對象const userInfo = reactive({id: 1,name: 'Petter',age: 18,gender: 'male',})// 定義一個新的對象,它本身不具備響應性,但是它的字段全部是 Ref 變量const userInfoRefs = toRefs(userInfo)// 在 2s 后更新 `userInfo`setTimeout(() => {userInfo.id = 2userInfo.name = 'Tom'userInfo.age = 20}, 2000)// 在這里解構 `toRefs` 對象才能繼續保持響應性return {...userInfoRefs,}},
})

<template /> 部分:

由于 return 出來的都是 Ref 變量,所以在模板里可以直接使用 userInfo 各個字段的 key ,不再需要寫很長的 userInfo.name 了。

<template><ul class="user-info"><li class="item"><span class="key">ID:</span><span class="value">{{ id }}</span></li><li class="item"><span class="key">name:</span><span class="value">{{ name }}</span></li><li class="item"><span class="key">age:</span><span class="value">{{ age }}</span></li><li class="item"><span class="key">gender:</span><span class="value">{{ gender }}</span></li></ul>
</template>

🌈需要注意的問題

請注意是否有相同命名的變量存在,比如上面在 return<template /> 使用時,在解構 userInfoRefs 的時候已經包含了一個 name 字段,此時如果還有一個單獨的變量也叫 name ,就會出現渲染上的數據顯示問題。

此時它們在 <template /> 里哪個會生效,取決于誰排在后面,因為 return 出去的其實是一個對象,在對象里,如果存在相同的 key ,則后面的會覆蓋前面的。

下面這種情況,會以單獨的 name 為渲染數據:

return {...userInfoRefs,name,
}

而下面這種情況,則是以 userInfoRefs 里的 name 為渲染數據:

return {name,...userInfoRefs,
}

所以當決定使用 toReftoRefs API 的時候,請注意這個特殊情況!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/163503.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/163503.shtml
英文地址,請注明出處:http://en.pswp.cn/news/163503.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

css漸變詳解(重復性線性漸變、徑向漸變、重復性徑向漸變的使用)

目錄 線性漸變 重復性線性漸變 徑向漸變 重復性徑向漸變的使用 線性漸變 線性漸變是向下、向上、向左、向右、對角方向的顏色漸變。 其語法格式為&#xff1a; background-image: linear-gradient(side-or-corner|angle, linear-color-stop); 參數說明如下&#xff1a; …

物聯網網關在智慧農業行業的應用案例

物聯網網關在智慧農業行業的應用案例 隨著科技的發展和普及&#xff0c;智慧農業已經成為了農業領域的一個重要趨勢。在智慧農業中&#xff0c;物聯網網關是一個非常重要的組成部分&#xff0c;它能夠實現對農業設備和環境的實時監控和控制&#xff0c;從而提高農業生產效率和…

服務器不備案的影響

服務器不備案的影響 不備案&#xff0c;不能解析域名。 但凡你的域名綁定到的是國內地址&#xff0c;你不備案&#xff0c;這個域名解析未來就可能會失效。 &#xff08;你借用的其它網站的子域名情況除外&#xff0c;因為他們的網站本身主域名有可能已經備案。&#xff09; …

Linux 安裝顯卡驅動

Linux 安裝顯卡驅動

scrapy框架流程

1、Scrapy從Spider子類中提取start_url,然后構造為request請求對象 2、將request請求對象傳遞給爬蟲中間件 3、將request請求對象傳遞給Scrapy引擎&#xff08;核心代碼&#xff09; 4、將request請求對象傳遞給調度器&#xff08;它負責對多個request安排&#xff0c;好比交…

Python計算DICOM圖像兩點真實距離

Python計算DICOM圖像兩點真實距離 對比測量結果圖Code對比測量結果圖 DICOM閱讀器(小賽看看)測量結果 python測量結果 Code import numpy as np import cv2 import math import pydicom from pydicom.pixel_data_handlers.util import convert_color_spaceds = pydicom.dc…

《第一行代碼:Android》第三版-2.5.1類與對象

本文主要是創建了一個類&#xff0c;后續的很多例子都和本程序有關。 /*** You can edit, run, and share this code.* play.kotlinlang.org*/ fun main() {println("Hello, world!!!") val pPerson()p.name"Jack"p.age19p.eat() } class Person{var …

高通Camera HAL3: CamX、Chi-CDK要點

目錄 一、概述 二、目錄 三、CamX組件之前的關系 一、概述 高通CamX架構是高通實現的相機HAL3架構&#xff0c;被各OEM廠商廣泛采用。 二、目錄 代碼位于vendor/qcom/proprietary下&#xff1a; camx&#xff1a;通用功能性接口的代碼實現集合chi-cdk&#xff1a;可定制化…

如何正確接入API接口通過淘寶商品ID和sku ID獲取到淘寶商品SKU信息接口,可獲取sku價格,sku銷量,sku圖片及sku庫存參數等

接入API接口的正確方式可能因API的具體要求而有所不同&#xff0c;但一般來說&#xff0c;以下是一些通用的步驟&#xff1a; 獲取API文檔&#xff1a;API文檔通常包括API的請求方式、請求參數、響應格式等信息。您需要仔細閱讀文檔&#xff0c;了解API的具體要求和使用方式。…

MDK AC5和AC6是什么?在KEIL5中添加和選擇ARMCC版本

前言 看視頻有UP主提到“AC5”“AC6”這樣的詞&#xff0c;一開始有些不理解&#xff0c;原來他說的是ARMCC版本。 keil自帶的是ARMCC5&#xff0c;由于ARMCC5已經停止維護了&#xff0c;很多開發者會選擇ARMCC6。 在維護公司“成年往事”項目可能就會遇到新KEIL舊版本編譯器…

springboot動態加載驅動

DynamicDriverUtil: package com.zy.fastdync.demos.web;import java.io.File; import java.net.URL; import java.net.URLClassLoader; import java.sql.*; import java.util.Properties; import java.util.logging.Logger;public class DynamicDriverUtil {// 動態加載jdbc驅…

玻色量子“揭秘”之可滿足性問題(SAT)與QUBO建模

? 摘要&#xff1a;布爾可滿足性問題&#xff08;Boolean Satisfiability Problem&#xff0c;簡稱SAT問題&#xff09;是邏輯學和計算機科學中的一個問題&#xff0c;它的目的是確定是否存在一種解釋&#xff0c;使給定的布爾公式成立。換句話說&#xff0c;它詢問給定布爾公…

SpringBoot面試之SpringBoot自動裝配原理

SpringBoot自動裝配原理 背景 最近因為各種原因&#xff0c;我又重新加入到了找工作的大軍當中。昨天在面試的時候與面試官聊到我們項目都是基于SpringBoot開發的&#xff0c;然后面試官就順口問了句&#xff1a;”SpringBoot項目會引入許多的starter&#xff0c;比如&#x…

前端 計算機基礎篇 ( 二 )

文章目錄 websockt及原理ipv4和ipv6的區別線程和進程的區別cdn原理緩存所涉及的http狀態碼緩存的時候設置 no-store和no-cache和max-age0這幾個有什么區別token一般存放在哪兒怎么設置強緩存和協商緩存強緩存&#xff1a;1. 使用 Cache-Control 頭字段&#xff1a; 協商緩存&am…

C++復制構造函數中的對象形參只能是引用的形式

這是一個簡單的復制構造函數的應用&#xff1a; #include <bits/stdc.h> using namespace std;class A { public:A() {cout << "創建對象&#xff08;默認構造函數&#xff09;\n";}A(A & a) { // 最好是&#xff1a;const A & acout << …

C語言打字游戲案例

#include <stdio.h> #include <stdlib.h> #include <time.h>int main() {// 設置隨機數種子srand((unsigned int)time(NULL));char c[201] { 0 }; // 加上一個 \0結束符位置// 產生隨機數for (int i 0; i < 20; i){c[i] rand() % 26 a;}printf("…

Flutter和Android的混合跳轉

1、項目特點 項目是Flutter作為主工程&#xff0c;將Android module或SDK作為模塊嵌入到flutter中&#xff0c;與通常所熟悉的Android&#xff08;或iOS&#xff09;工程將flutter 為module嵌入到工程中有所不同。 2、業務需求 任意界面間的跳轉&#xff0c;不管是flutter頁…

工作中死循環害死人

背景&#xff1a;研發的一段代碼&#xff0c;循環一直沒有跳出&#xff0c;導致其他依賴邏輯有問題&#xff0c;生產事故導致9萬左右數據不正常。 這里while&#xff08;true&#xff09;真的不要輕易用 &#xff0c;后來研發改動限制mysql的id切分步長&#xff0c;控制不會有數…

去大連發展還是去蘇州

公司要搬到蘇州&#xff0c;你是跟隨公司去蘇州發展&#xff0c;還是留在大連另尋出路&#xff1f;

【RtpRtcp】1: webrtc m79:audio的ChannelReceive 創建并使用

m79中,RtpRtcp::Create 的調用很少 不知道誰負責創建ChannelReceiveclass ChannelReceive : public ChannelReceiveInterface,public MediaTransportAudioSinkInterface {接收編碼后的音頻幀:接收rtcp包: