reactive()
ref()
reactive()ref()key 為 'value',value 為資料reactive 取得 Proxy 物件後放到 value 屬性下ref 資料傳進去 reactive() 會自動被 unwrap(取出 value 值),除非 ref 資料為響應式陣列或 Maplet objReactive = reactive({ name: "obj" });
console.log(`重新賦值前 objReactive`, objReactive);
objReactive = { name: "new-obj" };
console.log(`重新賦值後 objReactive`, objReactive);

Object.assign()什麼情境下會想對 reactive 物件重新賦值?
舉個例子,我們定義了一個 reactive 物件,用來儲存使用者的資料,從 API 拿到新的使用者資料後,想要整筆替換掉,但仍要維持響應性。
請看底下錯誤使用範例:
const user = reactive({})
async function getUsers() {
try {
const response = await axios.get(url, config)
//使用者資料現在在 data 裡
const { data } = response
//想要把 data 裝進 user 裡
//用這個方法會失去響應性!
user = data
} catch (error) {
console.log(error)
}
}
這時候就可以用 Object.assign()!
Object.assign()被用來複製一個或多個物件自身所有可數的屬性到另一個目標物件。回傳的值為該目標物件。MDN - Object.assign()
透過 Object.assign 可以將新物件上的屬性,全部透過 Proxy 物件,複製一份,寫入原物件下,避免了重新賦值,維持了響應性。
上面為情境示意,用底下來的程式碼來實驗看看:
const objReactive = reactive({ name: "obj" });
console.log(`原本的`, objReactive);
Object.assign(objReactive, { name: "new-obj" });
console.log(`透過 Object.assign 修改的`, objReactive);

這個方法有一個缺陷,透過 Object.assign 這個方法,是根據新物件去新增、修改原物件(透過 Proxy),所以,他不會比對並刪除舊有、新無的屬性。
也就是說,如果原物件有 type 屬性,但新物件沒有,透過 Object.assign 更新資料後,這個 type 屬性還是會留在原物件上。
const objReactive = reactive({ name: "obj", type: "Proxy" });
console.log(`原本的`, objReactive);
Object.assign(objReactive, { name: "new-obj" });
console.log(`透過 Object.assign 修改的`, objReactive);

屬性值只要脫離 Proxy,就失去響應性
const objReactive = reactive({ name: "obj" });
//會失去響應性,name1 拿到的是單純的字串
let { name: name1 } = objReactive;
console.log(`name1: ${name1}, ${typeof name1}`);
//會失去響應性,name2 拿到的是單純的字串
let name2 = objReactive.name;
console.log(`name2: ${name2}, ${typeof name2}`);

RefImpl),參數內容裝在該物件的value 屬性下RefImpl.value做重新賦值,因為每次要對 .value 重新賦值都會觸發 RefImpl 的 setter 處理響應性。const objectRef = ref({ count: 0 });
console.log(`前`, objectRef);
objectRef.value = { count: 1 };
console.log(`後`, objectRef);
*圖片說明:依然是那個 RefImpl,不過底下 value 屬性對應的值(物件參照)已經改變了。
在 template 中使用 ref 資料
value 值),所以不用加 .value
ref 值不是 top-level binding,依然可以成功讀取,但 template 中使用的值必須是最終計算所得的值,也就是不能用非 top-level binding 在模板中做運算。const object = { foo: ref(1) }
//放到 template 中
{{ obj.foo }} //不用運算可以正常顯示「1」
const object = { foo: ref(1) }
//把 foo 變數變成 top-level binding
const { foo } = object
//放到 template 中
//要對物件底下的 ref 做運算,需要把 ref 變成 top-level binding
{{ object.foo + 1 }} //會顯示「[Object Object] 1」
{{ foo + 1 }} //會顯示「2」
註:top-level binding 指的是這個 scope 內宣告在最外層的變數。
ref() 透過創造回傳的 reference (RefImpl),來維持響應性,而不是資料本身,所以基本型別才能透過 ref() 來達成響應。
value 從 ref 物件中解構出來存到變數中,拿到的是當下的純值,這個變數沒有響應性。const numberRef = ref(11);
const { value } = numberRef;
numberRef.value = 101;
//value 變數沒有響應性
console.log(value); //印出 11
當傳入的資料為物件,ref() 會用 reactive() 取得對應的 Proxy 物件,這時候對 objectRef.value 的做操作限制就跟上面的 reactive() 提到的限制相同。
理解兩者的達成響應方式的差異後,兩個不同的用法理論上會很好記~
| reactive | ref | |
|---|---|---|
| 接收參數 | 物件型別 | 都可以 |
| 回傳 | Proxy 物件 | RefImpl 物件 |
| 取值 | 同普通物件 | .value |
兩者最大的差異是可以接收的資料型態,基礎型別一定要使用 ref:
reactive:物件型別ref:所有型別還有,在處理物件型別資料的響應時:
reactive() 重新賦值就變成一般物件了,(可以考慮改用 Object.assign 處理,但是要小心原有屬性不會被刪除,而是根據新物件去新增、修改)RefImpl.value 重新賦值時,setter 內會透過 reactive 將新物件轉為 Proxy,依然可以透過 RefImpl 這個參照讀寫 value 內的新物件,但要注意資料的參照已經不一樣了。所以網路上有流傳一種說法是,ref 用到底就對了!反而比較不會出錯!
還真的是這樣!
聽起來有點聳動,繼續看進一步說明:
就我目前的使用經驗歸納下來~
沒有 ref 做不到但 reactive 做得到的情境
只有reactive 做不到,但 ref 做得到的情境,目前想到的情境有二:
要說 ref 的缺點...
就是在 <script> 中每次取 ref 物件的值,都需要 .value 才能拿到,資料一多起來,說實話是有點阿雜 ಠ_ಠ
總之,還是建議新手多嘗試踩坑,才會知道什麼情境下適合使用哪一個 API!
註:上面這段是由開發者角度去看,對 Vue 3 框架來說,reactive() 當然是必須的,實際上,所有非基本型別的響應處理,Vue 3 都是透過 reactive() 來處理。
能用統一的reactiveApi還是用統一的![]()
ref在有些情況,如template中會自動解構出.value,其他情況又要手動,容易造成代碼更難讀。
如果非基本型別&不需要重新賦值,用 reactive 的確更乾淨俐落~!