根据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钩子函数,分别在依赖追踪和依赖发生变化时调用。