響應式變量 ref, reactive, toRefs
上篇咱介紹了 CompositionAPI, 其核心思想是直接在函數作用域内定義響應式狀态變量,并将從多個函數中得到的狀态組合起來處理複雜問題.
然後初介紹了 setup 函數的作用, 即其是在 created 執行個體完全初始化之前調用的, 是以不能用
this
, 它的主要作用就可以是管理我們接下來要介紹的 API. 在項目中通常扮演一個"任務排程" 的角色, 對整個單頁面的邏輯起一個核心排程的作用.
響應式變量
首先, 注意上面這裡有個關鍵詞叫
響應式狀态變量
那我們先用一個案例來認識該現象.
<!DOCTYPE html>
<html lang="en">
<head>
<title>響應式變量</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="root"></div>
<script>
const app = Vue.createApp({
template: `<div>{{name}}</div>`,
setup (props, context) {
let name = "youge"
// 延時器的作用是 2秒後, 将 name 的值 改為 "cj"
setTimeout(() => {
name = 'cj'
}, 2000);
return { name }
}
})
const vm = app.mount('#root')
</script>
</body>
</html>
經過 2 秒後, 發現頁面的資料并沒有改變 !, 這就引入了一個響應式變量的概念.
Vue3 是使用 Proxy, 它可以劫持整個data對象,然後遞歸傳回屬性的值的代理即可實作響應式;但是它的相容性不是很好;
Vue2 是使用 Object.defineProperty,它隻能劫持對象的屬性,是以它需要深度周遊data中的每個屬性,這種方式對于數組很不友好,而且對象觀測後,新增的屬性就不是響應式的,不過可以用Vue.set()來添加新的屬性;
總之結論就是在 vue 中, dom 的更新由資料驅動, 将資料進行 Proxy 封裝, 當資料變化時, 會自動觸發模闆 dom 的更新啦.
ref
像 ref, reactive 他們都是将一個 vue 将資料變量封裝為響應式變量的方法啦 ( ref 的底層也是 reactive) .
在使用中呢, ref 用來處理基礎類型的資料 (number, string, null, boolean, undefined) 将其變為響應式.
<!DOCTYPE html>
<html lang="en">
<head>
<title>ref</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="root"></div>
<script>
const app = Vue.createApp({
// 當時 ref 時, 這裡不需要寫成 name.value, 直接 name
template: `<div>{{name}}</div>`,
setup (props, context) {
const { ref } = Vue
// proxy: 将 'youge' 封裝成 proxy({value: 'youge'}) 的響應式
let name = ref("youge")
// 延時器的作用是 2秒後, 将 name 的值 改為 "cj"
setTimeout(() => {
// ref 底層也是 reactive, 其實是一個對象
name.value = 'cj'
}, 2000);
return { name }
}
})
const vm = app.mount('#root')
</script>
</body>
</html>
再來一波關鍵點的複述, 這個蠻重要的其實, 就先不探究原理, 重在使用輪子哈:
// 1. 通過結構的方式從 Vue 中引入 ref
// 2. 響應變量 = ref(基礎類型變量) 即完成包裝
// 3. 通過 響應變量.value = 'xxx' 即完成資料變更
// 4. setup 直接 return 響應變量, 模闆便可用, 而不用 .value 的方式
const { ref } = Vue
let name = ref("youge")
name.value = 'newValue'
`<div>{{name}}</div>`,
reactive
同 ref 一樣的作用 ( ref 基于 reactive) 用來将引用類型資料 ( object, array, function ... ) 等給封裝為響應式變量啦.
當然普通類型也是可以的, 就 reactive 的适普性會更強哦. 還是同上的例子, 我們用 reactive 來改寫一波:
<!DOCTYPE html>
<html lang="en">
<head>
<title>reactive</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="root"></div>
<script>
const app = Vue.createApp({
template: `
<div>{{nameObj}}</div>
<div>{{nameObj.name}}</div>
`,
setup (props, context) {
const { reactive } = Vue
// proxy: 将 { name: 'youge' } 封裝成 proxy({name: 'youge'}) 的響應式
let nameObj = reactive({ name: 'youge' })
setTimeout(() => {
nameObj.name = 'cj'
}, 2000);
return { nameObj }
}
})
const vm = app.mount('#root')
</script>
</body>
</html>
當然數組也是一樣的啦.
setup (props, context) {
const { reactive } = Vue
let arr = reactive(['a', 'b', 'c'])
setTimeout(() => {
arr[1] = 'cj'
}, 2000);
return { arr }
}
這樣通過 ref 和 reactive 将資料封裝為響應式變量,則就可以代替掉原來的 data ( ) 方法啦. 這個會更加通用和友善維護的哦.
readonly
即對響應式變量進行 "隻讀" 的限定哈.
setup (props, context) {
const { reactive, readonly } = Vue
let arr = reactive(['a', 'b', 'c'])
// 隻要用了 readonly 就不能修改啦, 會警告的
const copyArr = readonly(arr)
setTimeout(() => {
arr[1] = 'cj'
copyArr[0] = 666
}, 2000);
return { arr, copyArr }
}
toRefs
它的作用其實就是将咱響應式變量中的值, 通過結構的方式擷取到時也是一個響應式變量的值, 可以被模闆直接引用, 就不用在模闆中寫類似 xxxObj.xxx 的寫法啦.
一句話: 将 reactive 的資料轉為 ref 資料, 也是響應式的啦.
在本例中, proxy( { name: 'youge' }) 中的值給再包裝為 { name: proxy( { value: 'youge' })} 這樣.
<!DOCTYPE html>
<html lang="en">
<head>
<title>toRefs</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="root"></div>
<script>
const app = Vue.createApp({
template: `
<div>{{name}} : {{age}}</div>
`,
setup(props, context) {
const { reactive, toRefs } = Vue
let dataObj = reactive({ name: 'youge', age: 18 })
setTimeout(() => {
dataObj.name = 'cj'
dataObj.age = 26
}, 2000);
// toRefs: proxy({name: 'cj', age: 18}) 轉為:
// name: proxy({value: 'cj'}),
// age: proxy({value: 18})
// 然後直接來一波結構指派
const { name, age } = toRefs(dataObj)
return { name, age }
}
})
const vm = app.mount('#root')
</script>
</body>
</html>
小結
- ref 和 reactive 都是對 js 資料類型進行響應式的封裝, ref 針對基礎類型, reactive 針對引用類型
- ref 原理: proxy: 将 'youge' 封裝成 proxy({value: 'youge'}) 的響應式
- reactive 原理: proxy: 将 { name: 'youge' } 封裝成 proxy({name: 'youge'}) 的響應式
- toRefs 的作用: 将 proxy({name: 'cj' }) 轉化為 { name: proxy({ value: 'cj' })}