ref
接收一个参数值并返回一个响应式且可以改变的ref对象.ref对象拥有一个指向内布置的单一属性.value
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) //
如果传入ref的是一个对象,将调用reactive
方法进行深层响应转换
- 模板中访问
当ref作为渲染上下文的属性返回(即在setup()
返回的对象中)并在模板中使用时,他会自动 解套,无需再模板内额外书写.value
:
<template>
<div>{{ count }}</div>
</template>
<script>
export default {
setup() {
return {
count: ref(0),
}
},
}
</script>
- 作为响应式对象的属性访问
当ref作为reactive对象的property被访问或修改是,也将自动解套value值,其行为类似普通属性: ```javascript const count = ref(0) const state = reactive({ count, })
console.log(state.count) // 0
state.count = 1 console.log(count.value) // 1
- 注意如果讲一个新的ref分配给现有的ref,将替换旧的ref:
```javascript
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
console.log(count.value) // 1
- 注意当嵌套在reactive
object
中时,ref才会解套.从Array
或者Map
等原生集合类中访问ref时,不会自动解套: ```javascript const arr = reactive([ref(0)]) // 这里需要 .value console.log(arr[0].value)
const map = reactive(new Map([[‘foo’, ref(0)]])) // 这里需要 .value console.log(map.get(‘foo’).value)
<a name="7476383b"></a>
#### `computed`
传入一个getter函数,返回一个默认不可手动修改的ref对象.
```javascript
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 错误!
或者传入一个拥有get
和set
函数的对象,创建一个可手动修改的计算状态.
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
Reactive
接收一个普通对象然后返回该普通对象的响应式代理.等同于2.x的Vue.observable()
const obj=reative({cout:0})
响应式转换是”深层的”:会影响对象内部所有嵌套的属性.基于ES2.15的Proxy实现,返回的代理对象不等于原始对象.
Computed
传入一个getter函数,返回一个默认不可手动修改的ref对象.
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 错误!
或者传入一个拥有get
和set
函数的对象,创建一个可手动修改的计算状态.
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
Readonly
传入一个对象(响应式或普通)或ref,返回一个原始对象的只读代理.一个只读代理是”深层的”,对象内部任何嵌套的属性也都是制度的.
const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
// 依赖追踪
console.log(copy.count)
})
// original 上的修改会触发 copy 上的侦听
original.count++
// 无法修改 copy 并会被警告
copy.count++ // warning!
Watcheffect
立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更是重新运行该函数.
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> 打印出 0
setTimeout(() => {
count.value++
// -> 打印出 1
}, 100)
停止侦听
当watchEffect
在组件的setup()
函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止.
const stop = watchEffect(() => {
/* ... */
})
// 之后
stop()
清除副作用
有时副作用函数会执行一些一步的副作用,这些响应需要在其失效时清除(即完成之前状态已改变了).可以侦听副作用传入的函数可以接受一个onInvalidate
函数作入参,用来注册清理失效时的回调.当一下情况发生时,这个失效回调会被触发:
- 副作用即将重新执行时
- 侦听器被停止(如果在
setup()
或生命周期钩子函数中使用了watchEffect
,则在卸载组件时)
watchEffect((onInvalidate) => {
const token = performAsyncOperation(id.value)
onInvalidate(() => {
//每一次被触发的时候都会先执行onInvalidate内部逻辑,然后执行onInvalidate外部的逻辑
// id 改变时 或 停止侦听时
// 取消之前的异步操作
token.cancel()//这是一个形参函数 这是你的逻辑叫做cancle只是为了很好地去比喻
})
})
//示例
<template>
<div>
<input type="text"
v-model="keyword">
</div>
</template>
<script>
import { ref, watchEffect } from '@vue/composition-api'
export default {
setup() {
const keyword = ref('')
const asyncPrint = val => {
return setTimeout(() => {
console.log('user input: ', val)
}, 1000)
}
watchEffect(
onInvalidate => {
const timer = asyncPrint(keyword.value)
onInvalidate(() => clearTimeout(timer))
console.log('keyword change: ', keyword.value)
},
{
flush: 'post' // 默认'post',同步'sync','pre'组件更新之前
}
)
return {
keyword
}
}
}
// 实现对用户输入“防抖”效果
</script>
我们之所以是通过传入一个函数去注册失效回调,而不是从回调返回他(如ReactuseEffect
中的方式),是因为返回值对于异步错误处理很重要
在执行数据请求时,副作用函数往往是一个异步函数:
const data = ref(null)
watchEffect(async () => {
data.value = await fetchData(props.id)
})
我们知道异步函数都会隐式地返回一个Promise,但是清理函数必须要在Promise被resolve之前被注册.另外,Vue依赖这个返回的Promise来自动处理Promise链上的潜在错误.
Setup
setup
函数是一个新的组件选项.作为在组件内使用CompositionAPi的入口点.
- 调用时机
创建组件实例,然后初始化props
,紧接着就调用setup
函数.从生命周期钩子的视角来看,他会在beforeCreate
钩子之前被调用
- 模板中使用
如果setup
返回一个对象,则对象的属性将会被合并到组件模板的渲染上下文:
<template>
<div>{{ count }} {{ object.foo }}</div>
</template>
<script>
import { ref, reactive } from 'vue'
export default {
setup() {
const count = ref(0)
const object = reactive({ foo: 'bar' })
// 暴露给模板
return {
count,
object,
}
},
}
</script>
注意setup
返回的ref在模板中会自动解开,不需要写.value
.
- 渲染函数/jsx中使用
setup
也可以返回一个函数,函数中也能使用当前setup
函数作用域中的响应式数据:
import { h, ref, reactive } from 'vue'
export default {
setup() {
const count = ref(0)
const object = reactive({ foo: 'bar' })
return () => h('div', [count.value, object.foo])
},
}
- 参数
export default {
props: {
name: String,
},
setup(props) {
console.log(props.name)
},
}
注意props
对象是响应式的,watchEffect
或者watch
会观察
和响应props
的更新
export default {
props: {
name: String,
},
setup(props) {
watchEffect(() => {
console.log(`name is: ` + props.name)
})
},
}
然而不要结构props
对象,那样会使其失去响应性:
export default {
props: {
name: String,
},
setup({ name }) {
watchEffect(() => {
console.log(`name is: ` + name) // Will not be reactive!
})
},
}
在开发过程中,props
对象对用户空间代码是不可变的(用户代码尝试修改props
时会触发警告)
第二个参数提供了一个上下文对象,从原来2.x中this
选择性地暴露了一些property
const MyComponent = {
setup(props, context) {
context.attrs
context.slots
context.emit
},
}
attrs
和slots
都是内部组件实例上对应项的代理,可以确保在更新后仍然是最新值.都可以结构没无需担心后面访问到过期的值:
这里的attrs 相当于2.x的$attrs;相关资料说明 包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind=”$attrs” 传入内部组件——在创建高级别的组件时非常有用。
const MyComponent = {
setup(props, { attrs }) {
// 一个可能之后回调用的签名
function onClick() {
console.log(attrs.foo) // 一定是最新的引用,没有丢失响应性
}
},
}
处于一些原因将props
作为第一个参数,而不是包含在上下文中:
组件使用
props
的场景更多,有时候甚至只使用props
- 将
props
独立出来作为第一个参数,可以让TypeScript 对props
单独做类型推导,不会和上下文中的其他属性相混淆.这也使得setup
.render
和其他适用了TSX的函数式组件的签名保持一致.
- 将
this
的用法this
在setup()
中不可用.由于setup()
在解析2.x选项前被调用,setup()
中的this
将与2.x选项中的this
完全不同.同时在setup()
和2.x选项中使用this
时将造成混乱.在setup()
中避免这种情况的另一个原因是:这对初学者来学,混淆这两种情况的this
是非常常见的错误:setup() {
function onClick() {
this // 这里 `this` 与你期望的不一样!
}
}
打印的this结果
这里是vue3的this 提示: 为了获得传递为
setup()
参数的类型推断,需要使用defineComponent
(自我理解:这是使用typescript
的操作)