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
的確更乾淨俐落~!