根據Vue官方 RFC文檔來看,Vue3借鑑React Hook的思想,進行了更新。將 2.x 中與組件邏輯相關的選項以 API 函數的形式從新設計。我在看完RFC文檔以後,也是感嘆Vue3,真香!接下來和你們一塊兒分享一下,Vue3的新API的用法,以及對比Vue2.x的優點。vue
import { value, computed, watch, onMounted } from 'vue'
const App = {
template: `
<div>
<span>count is {{ count }}</span>
<span>plusOne is {{ plusOne }}</span>
<button @click="increment">count++</button>
</div>
`,
setup() {
// reactive state
const count = value(0)
// computed state
const plusOne = computed(() => count.value + 1)
// method
const increment = () => { count.value++ }
// watch
watch(() => count.value * 2, val => {
console.log(`count * 2 is ${val}`)
})
// lifecycle
onMounted(() => {
console.log(`mounted`)
})
// expose bindings on render context
return {
count,
plusOne,
increment
}
}
}
複製代碼
setup是一個新的組件選項,也是其餘API的入口。setup是在一個組件實例被建立時,初始化了 props 以後調用,其實就是取代了Vue2.x的careted和beforeCreate。它接收porps做爲參數。react
const MyComponent = {
props: {
name: String
},
setup(props) {
console.log(props.name)
return { msg: `hello ${props.name}!`
}
}
template: `<div>{{ msg }}</div>`}複製代碼
setup返回一個對象,對象中的屬性講直接暴露給模板渲染的上下文。若是你不return,那麼渲染的上下文將沒法捕獲到你定義的屬性。而在Vue2.x中,你定義的屬性都會被Vue內部無條件暴露給模板渲染上下文。從性能上來講,你將能夠有選擇性地暴露你須要渲染的數據。數組
reactive函數接收一個對象做爲參數,返回這個對象的響應式代理,等價Vue2.x的Vue.observable()promise
用法:bash
setup() {
let reactiveData = reactive({name: 'lin', age: 20})
return {
reactiveData
}
}複製代碼
等價於Vue2.x:app
data(){
return {
reactiveData :{name: 'lin', age: 20} }
}複製代碼
對比Vue2.x的observable(),只要是定義在this上的數據,都將進行監聽,雖然給咱們帶來的便利,可是在大型項目上來講,性能開銷就很大了。Vue3.0以後再也不將主動監聽全部的數據,而是將選擇權給你,只有經過reactive函數包裝過的數據,纔有被Vue監聽。框架
ref接收一個原始值,返回一個包裝對象,包裝對象具備.value屬性。經過.value訪問這個值。dom
import {ref} from vue
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1複製代碼
在渲染上下文中使用,Vue幫你自動展開,無須用.value訪問。
異步
<template>
<div>{{ count }}</div>
</template>
<script>
export default {
setup() {
return {
count: ref(0)
}
}
}
</script>複製代碼
若是做爲對象訪問,ref定義的值將自動展開,不用.value訪問。async
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 不用state.count.value
state.count = 1
console.log(count.value) // 做爲值任然須要經過.value訪問複製代碼
檢查一個對象是被ref包裝過的對象
const unwrapped = isRef(foo) ? foo.value : foo複製代碼
其實ref至關於reactive的小弟,ref背後也是經過reactive實現的,惟一的區別是ref返回的是包裝對象
const count = ref(0) 等價 const count = reactive({value:0})複製代碼
關於什麼是包裝對象,若是不懂的,請看這裏
咱們知道在 JavaScript 中,原始值類型如 string 和 number 是隻有值,沒有引用的。若是在一個函數中返回一個字符串變量,接收到這個字符串的代碼只會得到一個值,是沒法追蹤原始變量後續的變化的。所以,包裝對象的意義就在於提供一個讓咱們可以在函數之間以引用的方式傳遞任意類型值的容器。這有點像 React Hooks 中的 useRef
—— 但不一樣的是 Vue 的包裝對象同時仍是響應式的數據源。有了這樣的容器,咱們就能夠在封裝了邏輯的組合函數中將狀態以引用的方式傳回給組件。組件負責展現(追蹤依賴),組合函數負責管理狀態(觸發更新):相似某act的自定義Hook
setup() {
const valueA = useLogicA() // valueA 可能被 useLogicA() 內部的代碼修改從而觸發更新
const valueB = useLogicB()
return {
valueA,
valueB
}
}複製代碼
ref和reactive須要注意的點:
在setup函數中,若是經過結構返回ref和reactive,那麼在模板渲染上下文中,獲取不到他們的響應式變化。由於解構他們就意味着copy了他們的引用。因此儘可能不要用解構去返回一個你指望響應式的數據
var App = { template: ` <div class="container"> <div> {{name1}} {{name2}} </div> <button @click="add1"> add count</button> </div>`, setup() { const name1 = ref({name1:'我是name'}) const name2 = reactive({name2:'aa'}) const add1 = () => { console.log(name1.value.name1 = 'test') console.log(name2.name2 = 'test') } return { count, add1, ...pop,...name1.value,...name2 } }}複製代碼
若是你非要經過解構來使用,你可使用toRefs()來使用
return{
...toRefs({name:'name'})
}複製代碼
props對比Vue2.x主要要注意的地方
props
對象是響應式的 —— 它能夠被看成數據源去觀測,當後續 props 發生變更時它也會被框架內部同步更新。但對於用戶代碼來講,它是不可修改的(會致使警告)。 interface IProps{
name:string
}
const MyComponent = {
setup(props:IProps) {
return {
msg: `hello ${props.name}!`
}
},
template: `<div>{{ msg }}</div>`
}複製代碼
計算值的行爲跟計算屬性 (computed property) 同樣:只有當依賴變化的時候它纔會被從新計算。類型某act的useCallback useMemo
computed()
返回的是一個包裝對象,它能夠和普通的包裝對象同樣在 setup()
中被返回 ,也同樣會在渲染上下文中被自動展開。
computed能夠傳兩種參數
第一種:直接傳一個函數,返回你所依賴的值的計算結果,這個值是個包裝對象,默認狀況下,若是用戶試圖去修改一個只讀包裝對象,會觸發警告,說白了就是你只能get沒法set
第二種:傳一個對象,對象包含get函數和set函數。
總的來講這兩點和Vue2.x相同。
import {computed,reactive} from vue
setup(){
const count = reactive({count:0})
//第一種
const computedCount1 = computed(()=>count.count++})
//第二種
const computedCount2 = computed({
get: () => count.value + 1, set: val => { count.value = val - 1 }
computedCount2.value = 1
console.log(computedCount1.value) // 0
})
}複製代碼
惟一不一樣的是,3.0中,computed 被抽成一個API,直接從vue中獲取,而Vue2.x中,computed是一個對象,在對象中定義一個個computed
Vue2.x
var vm = new Vue({
data: { a: 1 },
computed: {
// 僅讀取
aDouble: function () {
return this.a * 2
},
// 讀取和設置
aPlus: {
get: function () {
return this.a + 1
},
set: function (v) {
this.a = v - 1
}
}
}
})
Vue3.0
import {ref,computed} from Vue
setup(){
const a = ref(0)
const b = ref(1)
const a_computed = computed(()=>a.value++)複製代碼
const b_computed = computed({
get(){return a.value},
set(val){return a.value+val }
)
}複製代碼
接收一個ref或者reactive包裝對象,返回一個只讀的響應式對象。
const original = reactive({ count: 0 })
const copy = readonly(original)
watch(() => {
// works for reactivity tracking
console.log(copy.count)
})
// mutating original will trigger watchers relying on the copy
original.count++
// mutating the copy will fail and result in a warning
copy.count++ // warning!複製代碼
watch()
API 提供了基於觀察狀態的變化來執行反作用的能力。
watch()
接收的第一個參數被稱做 「數據源」,它能夠是:
第二個參數是回調函數。回調函數只有當數據源發生變更時纔會被觸發:
這裏有一些須要注意的點:
1.若是你不傳數據源,只傳一個回調函數,Vue會被動監聽你回調函數中用到的每個響應式數據。
2.若是你不傳數據源,回調函數參數中,沒有監聽函數的當前值和變化前一次的值
const count = ref(0)
const count1 = ref(1)
watch(() => console.log(count.value)) //監聽count
watch(()=>{
console.log(count.value)
console.log(count1.value)
}) //監聽count和count1複製代碼
const value = ref(0) watch((newValue,oldValue)=>value.value,() => {
//監聽Value
console.log(value.value, 'value') })
複製代碼
這裏須要注意的點是:若是你監聽reactive包裝的數據,不能用這種方法,由於reactive返回的不是一個包裝對象。你能夠用第一種方法
const count = reactive({count:0})
watch(()=>count.count,()=>{....})
const value = ref(0)
watch(value,() => { //監聽Value
console.log(value.value, 'value')
})複製代碼
這種狀況下,任意一個數據源的變化都會觸發回調,同時回調會接收到包含對應值的數組做爲參數:
const count = ref(0)
const test = ref(0)
watch([value,count,()=>test.value],([newValue,newCount,newTest],[oldValue,oldCount,oldTest]) => { console.log(value.value, 'value') })
複製代碼
watch自動連接到組件的生命週期,在組件卸載的時候自動中止watch。不一樣的是,Vue3.0的watch函數返回一箇中止watch的函數,供你手動中止watch
const value = ref(0)
const stop = watch(value,(val,old)=>{.....})
stop()
watch(value, (val, oldVal, onCleanup) => {
const token = setTimeout(() => console.log(val, '我更新了'), 2000)
onCleanup(() => {
// id 發生了變化,或是 watcher 即將被中止.
// 取消還未完成的異步操做。
console.log('我是清理函數')
clearTimeout(token)
})
})複製代碼
這是由於,咱們可能這麼寫watch:
watch(value, async (val, oldVal, onCleanup) => { const token = await setTimeout(() => console.log(val, '我更新了'), 2000) onCleanup(() => { // id 發生了變化,或是 watcher 即將被中止. // 取消還未完成的異步操做。 console.log('我是清理函數') clearTimeout(token) }) })複製代碼
async函數隱性地返回一個promise,這樣的狀況下,咱們是沒法返回一個須要被馬上註冊的清理函數的
默認狀況下,watch會在組件更新以後調用,若是你想在組件更新前調用,你能夠傳第三個參數,
第三個參數是個對象,有幾個選項
flush 表示回調調用時機
post 默認值,在組件更新以後
pre 組件更新以前
sync 同步調用
deep 深度監聽
類型: boolean default :false
watch(
() => state.foo,
foo => console.log('foo is ' + foo),
{ flush: 'post', // default, fire after renderer flush
flush: 'pre', // fire right before renderer flush
flush: 'sync' // fire synchronously
})複製代碼
和Vue2.x行爲一致,都是對對象的深度監聽
const count1 = reactive({count:{count:0}}) watch(()=>count1.count, (val,oldVal)=>{ console.log(count1,"count1") },{ deep:true }) const add1 = ()=>{ count1.count.count = Math.random() }複製代碼
Lazy - 和Vue2.ximmediate
正好相反
type:Boolean, default:false
const count1 = reactive({count:{count:0}}) watch(()=>count1.count, (val,oldVal)=>{ console.log(count1,"count1") },{ lazy:true })複製代碼
onTrack和 onTrigger
debugger鉤子函數,分別在依賴追蹤和依賴發生變化時調用。