Options API的弊端:
vue2中使用的时Options API,其一大特点就是在对应的属性中编写对应的功能模块;比如data定义数据、methods中定义方法、computed中定义计算属性、watch中监听属性改变,也包括生命 周期钩子。
但是这种代码有一个弊端:
当我们实现某一功能时,这个功能对应的代码逻辑会被拆分到各个属性中,阅读困难,当我们处理单个逻辑关注点时,需要不断的跳到相应的代码块中。compositions API 的出现解决了这个大组件逻辑分散的问题。
setup是composition API的入口,该选项在组件被创建之前执行。
1.官方提示:setup内部并未绑定this。
setup主要有两个参数: props 和context。
1.setup
函数中的第一个参数是 props
。正如在一个标准组件中所期望的那样,setup
函数中的 props
是响应式的,当传入新的 prop 时,它将被更新。
<script>
import {toRefs} from 'vue'
export default {
props: {
title: String
},
setup(props) {
console.log(props.title)
//官方提示props不能使用ES6解构,他会影响prop的双向绑定
//所以要解构的话要使用内置方法toRefs
const { title } = toRefs(props)
console.log(title.value)
}
}
</script>
2.传递给 setup
函数的第二个参数是 context
。``context是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着你可以安全地对
context` 使用 ES6 解构。
context有四个属性:
setup的返回值可以在模板template中被使用,也就是说我们可以通过setup的返回值来替代data选项。
setup函数中定义的变量是非响应式的。data中的数据之所以时响应式的,是因为Vue内部将数据交给了reactive()方法。
注意:reactive API 对传入的类型有限制,只能传入对象或数组类型。另外reactive会对深层refs进行解包,所以我们不用写demo.age.value。
ref接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象具有指向内部值的单个 property .value
。
也就是说因为ref API 内部返回的是一个ref对象我们需要解包使用,template中vue会帮助我们浅层解包(仅一层),所以在其他地方调用时 我们需要通过 xxx.value的形式调用。
另外ref API对传入的数据类型无限制。
接受一个对象 (响应式或纯对象) 或 ref 并返回原始对象的只读代理。只读代理是深层的:任何被访问的嵌套 property 也是只读的。与 reactive
一样,如果任何 property 使用了 ref
,当它通过代理访问时,则被自动解包:
作用:当我们需要给其他组件传递数据,又不希望它们修改属性时,可以使用readonly。
详情查看官网
检查对象是否是由 reactive
或 readonly
创建的 proxy。
检查对象是否是由 reactive创建的响应式代理。如果该代理是 readonly 建的,但包裹了由 reactive 创建的另一个代理,它也会返回 true;
import { reactive, isReactive, readonly } from 'vue'
export default {
setup() {
const state = reactive({
name: 'John'
})
// 从普通对象创建的只读 proxy
const plain = readonly({
name: 'Mary'
})
console.log(isReactive(plain)) // -> false
// 从响应式 proxy 创建的只读 proxy
const stateCopy = readonly(state)
console.log(isReactive(stateCopy)) // -> true
}
}
检查对象是否是由 readonly
创建的只读代理。
返回 reactive
或 readonly
代理的原始对象。这是一个“逃生舱”,可用于临时读取数据而无需承担代理访问/跟踪的开销,也可用于写入数据而避免触发更改。不建议保留对原始对象的持久引用。请谨慎使用。
const foo = {}
const reactiveFoo = reactive(foo)
console.log(toRaw(reactiveFoo) === foo) // true
创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (深层还是原生对象)。
const state = shallowReactive({
foo: 1,
nested: {
bar: 2
}
})
// 改变 state 本身的性质是响应式的
state.foo++
// ...但是不转换嵌套对象
isReactive(state.nested) // false
state.nested.bar++ // 非响应式
创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换(深层还是可读、可写的)。
import { shallowReadonly } from "vue";
export default {
const state = shallowReadonly({
foo: 1,
nested: {
bar: 2
}
})
// 改变 state 本身的 property 将失败
state.foo++
// ...但适用于嵌套对象
isReadonly(state.nested) // false
state.nested.bar++ // 适用
}
在setup中,如果我们使用ES6的解构语法,对reactive返回的对象进行解构获取值,那么之后无论是修改结构后的变量,还是修改reactive 返回的state对象,数据都不再是响应式的。
const state = reactive({
foo: 1,
age:20
})
const {foo,age} =state
console.log(isReactive(foo)) //false
所以官方推出toRefs和toRef来解决该问题:
toRef和toRefs功能相同。作用场景不同:toRefs是给每一个属性添加ref,而toRef是给某一个属性添加ref。
作用场景:当我们需要去获取一个ref引用中的value,就可以使用unref方法。如果参数是一个ref,则返回内部.value值 ,否则返回参数本身
//其实就是下列式子的语法糖
val = isRef(val) ? val.value : val
作用:判断值是否是一个ref对象。
创建一个跟踪自身 .value
变化的 ref,但不会使其值也变成响应式的。
const foo = shallowRef({})
// 改变 ref 的值是响应式的
foo.value = {}
// 但是这个值不会被转换。
isReactive(foo.value) // false
手动执行与 shallowRef
关联的任何作用 (effect),换句话说就是手动解决shallowRef的副作用。
创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 track
和 trigger
函数作为参数,并且应该返回一个带有 get
和 set
的对象。
场景:对双向绑定的数据进行节流或者防抖操作等,如搜索功能 输入的过程不可能一直搜索,应该是多久没操作的情况下才调用搜索提示。
后期项目会贡献代码。
composition API中的computed的写法与之前不同:
写法一:接受一个 getter 函数,并根据 getter 的返回值返回一个不可变的响应式 ref 对象。
写法二:接受一个具有 get
和 set
函数的对象,用来创建可写的 ref 对象。
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: val => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0
在Composition API中,我们可以使用watchEffect和watch来完成响应式数据的侦听。
watchEffect:用于自动收集响应式数据的依赖。
watch:需要手动指定侦听的数据源
首先watchEffevct传入的函数会被立即执行一次,并且在执行的过程中收集依赖,当收集的依赖发生变化时,watchEffect传入的函数才会再次执行。
setup() {
const count = ref(0);
// 立即执行一次
watchEffect(() => console.log(count.value));
// -> logs 0
setTimeout(() => {
count.value++;
// -> logs 1
}, 1000);
return {};
},
停止侦听
当 watchEffect
在组件的 setup() 函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。举个例子:当我们监听点击次数,当超过20次时 ,手动停止侦听。
const stop = watchEffect(() => {
/* ... */
})
// later 停止侦听
stop()
watchEffect清除副作用
有时副作用函数会执行一些异步的副作用,这些响应需要在其失效时清除 (即完成之前状态已改变了) 。所以侦听副作用传入的函数可以接收一个 onInvalidate
函数作入参,用来注册清理失效时的回调。当以下情况发生时,这个失效回调会被触发:
setup()
或生命周期钩子函数中使用了 watchEffect
,则在组件卸载时)例如:我们需求当数据修改时重新请求接口,但是如果频繁修改数据时,我们需要使用清除副作用来清除上一次请求。
watchEffect(onInvalidate => {
const token = performAsyncOperation(id.value)
//副作用函数通常时一个异步函数
//当副作用即将重新执行 或者 侦听器被停止 时会执行该函数传入的回调函数
onInvalidate(() => {
// id has changed or watcher is stopped.
// invalidate previously pending async operation
token.cancel()
})
})
如下情况:组件会在DOM挂载前更新一次,当DOM挂载后,watchEffect侦听到数据的改变,从而更新第二次。
1.watchEffect参数二 {flush:“post”}:
watch的API 和vue2中基本一致,用于侦听特定的数据源。
与watchEffect的不同:
侦听器数据源可以是一个具有返回值的 getter 函数,也可以直接是一个 ref。
// 侦听一个 getter
const state = reactive({ count: 0 })
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
)
// 直接侦听一个 ref
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})
侦听器还可以使用数组以同时侦听多个源:
有时候我们要侦听一个响应式数组或者对象,那么可以使用一个getter函数,并且对可响应对象进行解构。
//setup中一个生命周期可以使用多次,方便之后hook的抽取
setup(){
onMounted(()=>{
xxxxx
}),
onMounted(()=>{
xxxxx
}),
}