碎片化编写代码使得复杂项目难以维护
Composition API能将同一逻辑的代码收集到一起
setup
我们需要一个可以实际使用composition API的地方,这个地方就是setup函数
setup在组件创建之前(在声明周期beforeCreated之前)执行,接受props和context作为参数,通过返回值暴露给外部使用
const app = Vue.createApp({
template: `
<div @click="handleClick">{{name}}</div>
`,
mounted(){
console.log(this.$options) // 获取当前实例的属性
},
setup(){
return {
name: 'jack',
handleClick: ()=>{console.log('hello')}
}
}
})
ref
下面的代码中,如果let count = 0, 当count改变时vue感知不到count的变化,而使用ref时,该对象就变成高度响应式
原理:通过proxy对数据进行封装变成proxy({value: 0})响应式引用,当数据变化时,触发模板等内容的更新
ref 只处理基础类型的数据
const app = Vue.createApp({
template: `
<div @click="handleClick">{{count}}</div>
`,
setup(){
const {ref} = Vue
const count = ref(0)
const handleClick = () => {count.value = count.value + 1;}
return {
count,
handleClick,
}
}
})
在template中,vue会将ref进行转化,不需要.value
reactive
与ref类型,reactive应用与非基础类型(对象,数组)
通过proxy对数据进行封装,使其变成响应式
const app = Vue.createApp({
template: `
<div @click="handleClick">{{user.name}}</div>
`,
setup(){
const {reactive} = Vue
const user = reactive({name: 'jack'})
const handleClick = () => {user.name = 'rose'}
return {
user,
handleClick,
}
}
})
有了ref和reactive后,就可以代替data了
readonly
接受一个对象 (响应式或纯对象) 或 ref 并返回原始对象的只读代理。只读代理是深层的:任何被访问的嵌套 property 也是只读的。
const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
// 用于响应性追踪
console.log(copy.count)
})
// 变更 original 会触发依赖于副本的侦听器
original.count++
// 变更副本将失败并导致警告
copy.count++ // 警告!
toRefs
为响应式对象的每个property创建ref(proxy), 以使响应式对象解构后仍为响应式
如果要为单个property创建ref,应使用toRef
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state)
/*
stateAsRefs 的类型:
{
foo: Ref<number>,
bar: Ref<number>
}
*/
// ref 和原始 property 已经“链接”起来了
state.foo++
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
const app = Vue.createApp({
template: `
<div @click="handleClick">{{name}}</div>
`,
setup(){
const {reactive, toRefs} = Vue
const user = reactive({name: 'jack'})
const {name} = toRefs(user)
const handleClick = () => {name.value = 'rose'}
return {
name,
handleClick,
}
}
})
toRef
const app = Vue.createApp({
template: `
<div @click="handleClick">{{name}}</div>
`,
setup(){
const {reactive, toRef} = Vue
const user = reactive({name: 'jack'})
const name = toRef(user, 'name')
const handleClick = () => {name.value = 'rose'}
return {
name,
handleClick,
}
}
})
context
setup的第二个参数是context, 这是一个普通的javascript对象,可用于替代this.$attrs, this.$slots, this.$emit
// MyBook.vue
export default {
setup(props, context) {
// Attribute (非响应式对象,等同于 $attrs)
console.log(context.attrs)
// 插槽 (非响应式对象,等同于 $slots)
console.log(context.slots)
// 触发事件 (方法,等同于 $emit)
console.log(context.emit)
// 暴露公共 property (函数)
console.log(context.expose)
}
}
解构
// MyBook.vue
export default {
setup(props, { attrs, slots, emit, expose }) {
...
}
}
setup封装
如果将所有的数据和逻辑都写在setup里,会显得很混乱,应将负责各自功能的数据和逻辑独立封装
<script >
const listRelativeEffect = () => {
const {reactive} = Vue
const list = reactive([])
const addToList = (value) => {
console.log(value)
list.push(value)
}
return {list, addToList}
}
const inputRelativeEffect = () => {
const {ref} = Vue
const inputValue = ref('')
const updateInputValue = (e) =>{
inputValue.value = e.target.value
}
return {
inputValue,updateInputValue
}
}
const app = Vue.createApp({
template: `
<div>
<input :value="inputValue" @input="updateInputValue"/>
<button @click="()=>addToList(inputValue)">提交</button>
</div>
<ul>
<li v-for="item of list" :key="item">{{item}}</li>
</ul>
`,
setup(){
const {list, addToList} = listRelativeEffect()
const {inputValue, updateInputValue} = inputRelativeEffect()
return {
inputValue,
list,
updateInputValue,
addToList
}
}
})
app.mount("#root")
</script>
computed
const {ref, computed } = Vue
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 错误
getter/setter写法
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
watch
官方文档
watch有惰性
侦听单一源
// 侦听一个 getter
const state = reactive({ count: 0 })
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
)
// 直接侦听一个 ref
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})
侦听多个源
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
/* ... */
})
watchEffect
立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
- 没有惰性,立即执行
- 不需要传递侦听内容,自动感知依赖变化
- 不能获取之前的值 ```javascript const count = ref(0)
watchEffect(() => console.log(count.value)) // -> logs 0
setTimeout(() => { count.value++ // -> logs 1 }, 100)
<a name="p1TiE"></a>
### 停止侦听
watch和watchEffect都能停止侦听
```javascript
const stop = watchEffect(()=>{
// ...
setTimeout(()=>{
stop()
},5000)
})
生命周期钩子
provide/inject
const app = Vue.createApp({
setup(){
const {ref, provide, readonly} = Vue
const count = ref(0)
const addCount = () => {
count.value += 1
}
provide('count', readonly(count))
provide('addCount', addCount)
},
template:`<my-component />`
})
app.component('my-component', {
setup(){
const {inject} = Vue
const count = inject('count', 0)
const addCount = inject('addCount')
return {count, addCount}
},
template:`
<div @click="addCount">{{count}}</div>
`
})
ref
使用composition API获取真实DOM节点
- 生命ref为null
与属性ref绑定, 等价于this.$refs
const app = Vue.createApp({
setup(){
const {ref, onMounted} = Vue
const hello = ref(null)
onMounted(()=>{
console.log(hello.value)
})
return {hello}
},
template:`<div ref="hello">hello world</div>`
})