碎片化编写代码使得复杂项目难以维护
Composition API能将同一逻辑的代码收集到一起

setup

我们需要一个可以实际使用composition API的地方,这个地方就是setup函数
setup在组件创建之前(在声明周期beforeCreated之前)执行,接受props和context作为参数,通过返回值暴露给外部使用

  1. const app = Vue.createApp({
  2. template: `
  3. <div @click="handleClick">{{name}}</div>
  4. `,
  5. mounted(){
  6. console.log(this.$options) // 获取当前实例的属性
  7. },
  8. setup(){
  9. return {
  10. name: 'jack',
  11. handleClick: ()=>{console.log('hello')}
  12. }
  13. }
  14. })

ref

下面的代码中,如果let count = 0, 当count改变时vue感知不到count的变化,而使用ref时,该对象就变成高度响应式
原理:通过proxy对数据进行封装变成proxy({value: 0})响应式引用,当数据变化时,触发模板等内容的更新
ref 只处理基础类型的数据

  1. const app = Vue.createApp({
  2. template: `
  3. <div @click="handleClick">{{count}}</div>
  4. `,
  5. setup(){
  6. const {ref} = Vue
  7. const count = ref(0)
  8. const handleClick = () => {count.value = count.value + 1;}
  9. return {
  10. count,
  11. handleClick,
  12. }
  13. }
  14. })

在template中,vue会将ref进行转化,不需要.value

reactive

与ref类型,reactive应用与非基础类型(对象,数组)
通过proxy对数据进行封装,使其变成响应式

  1. const app = Vue.createApp({
  2. template: `
  3. <div @click="handleClick">{{user.name}}</div>
  4. `,
  5. setup(){
  6. const {reactive} = Vue
  7. const user = reactive({name: 'jack'})
  8. const handleClick = () => {user.name = 'rose'}
  9. return {
  10. user,
  11. handleClick,
  12. }
  13. }
  14. })

有了ref和reactive后,就可以代替data了

readonly

接受一个对象 (响应式或纯对象) 或 ref 并返回原始对象的只读代理。只读代理是深层的:任何被访问的嵌套 property 也是只读的。

  1. const original = reactive({ count: 0 })
  2. const copy = readonly(original)
  3. watchEffect(() => {
  4. // 用于响应性追踪
  5. console.log(copy.count)
  6. })
  7. // 变更 original 会触发依赖于副本的侦听器
  8. original.count++
  9. // 变更副本将失败并导致警告
  10. copy.count++ // 警告!

toRefs

为响应式对象的每个property创建ref(proxy), 以使响应式对象解构后仍为响应式
如果要为单个property创建ref,应使用toRef

  1. const state = reactive({
  2. foo: 1,
  3. bar: 2
  4. })
  5. const stateAsRefs = toRefs(state)
  6. /*
  7. stateAsRefs 的类型:
  8. {
  9. foo: Ref<number>,
  10. bar: Ref<number>
  11. }
  12. */
  13. // ref 和原始 property 已经“链接”起来了
  14. state.foo++
  15. console.log(stateAsRefs.foo.value) // 2
  16. stateAsRefs.foo.value++
  17. console.log(state.foo) // 3
  1. const app = Vue.createApp({
  2. template: `
  3. <div @click="handleClick">{{name}}</div>
  4. `,
  5. setup(){
  6. const {reactive, toRefs} = Vue
  7. const user = reactive({name: 'jack'})
  8. const {name} = toRefs(user)
  9. const handleClick = () => {name.value = 'rose'}
  10. return {
  11. name,
  12. handleClick,
  13. }
  14. }
  15. })

toRef

  1. const app = Vue.createApp({
  2. template: `
  3. <div @click="handleClick">{{name}}</div>
  4. `,
  5. setup(){
  6. const {reactive, toRef} = Vue
  7. const user = reactive({name: 'jack'})
  8. const name = toRef(user, 'name')
  9. const handleClick = () => {name.value = 'rose'}
  10. return {
  11. name,
  12. handleClick,
  13. }
  14. }
  15. })

context

setup的第二个参数是context, 这是一个普通的javascript对象,可用于替代this.$attrs, this.$slots, this.$emit

  1. // MyBook.vue
  2. export default {
  3. setup(props, context) {
  4. // Attribute (非响应式对象,等同于 $attrs)
  5. console.log(context.attrs)
  6. // 插槽 (非响应式对象,等同于 $slots)
  7. console.log(context.slots)
  8. // 触发事件 (方法,等同于 $emit)
  9. console.log(context.emit)
  10. // 暴露公共 property (函数)
  11. console.log(context.expose)
  12. }
  13. }

解构

  1. // MyBook.vue
  2. export default {
  3. setup(props, { attrs, slots, emit, expose }) {
  4. ...
  5. }
  6. }

setup封装

如果将所有的数据和逻辑都写在setup里,会显得很混乱,应将负责各自功能的数据和逻辑独立封装

  1. <script >
  2. const listRelativeEffect = () => {
  3. const {reactive} = Vue
  4. const list = reactive([])
  5. const addToList = (value) => {
  6. console.log(value)
  7. list.push(value)
  8. }
  9. return {list, addToList}
  10. }
  11. const inputRelativeEffect = () => {
  12. const {ref} = Vue
  13. const inputValue = ref('')
  14. const updateInputValue = (e) =>{
  15. inputValue.value = e.target.value
  16. }
  17. return {
  18. inputValue,updateInputValue
  19. }
  20. }
  21. const app = Vue.createApp({
  22. template: `
  23. <div>
  24. <input :value="inputValue" @input="updateInputValue"/>
  25. <button @click="()=>addToList(inputValue)">提交</button>
  26. </div>
  27. <ul>
  28. <li v-for="item of list" :key="item">{{item}}</li>
  29. </ul>
  30. `,
  31. setup(){
  32. const {list, addToList} = listRelativeEffect()
  33. const {inputValue, updateInputValue} = inputRelativeEffect()
  34. return {
  35. inputValue,
  36. list,
  37. updateInputValue,
  38. addToList
  39. }
  40. }
  41. })
  42. app.mount("#root")
  43. </script>

computed

  1. const {ref, computed } = Vue
  2. const count = ref(1)
  3. const plusOne = computed(() => count.value + 1)
  4. console.log(plusOne.value) // 2
  5. plusOne.value++ // 错误

getter/setter写法

  1. const count = ref(1)
  2. const plusOne = computed({
  3. get: () => count.value + 1,
  4. set: val => {
  5. count.value = val - 1
  6. }
  7. })
  8. plusOne.value = 1
  9. console.log(count.value) // 0

watch

官方文档
watch有惰性
侦听单一源

  1. // 侦听一个 getter
  2. const state = reactive({ count: 0 })
  3. watch(
  4. () => state.count,
  5. (count, prevCount) => {
  6. /* ... */
  7. }
  8. )
  9. // 直接侦听一个 ref
  10. const count = ref(0)
  11. watch(count, (count, prevCount) => {
  12. /* ... */
  13. })

侦听多个源

  1. watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  2. /* ... */
  3. })

watchEffect

立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。

  • 没有惰性,立即执行
  • 不需要传递侦听内容,自动感知依赖变化
  • 不能获取之前的值 ```javascript const count = ref(0)

watchEffect(() => console.log(count.value)) // -> logs 0

setTimeout(() => { count.value++ // -> logs 1 }, 100)

  1. <a name="p1TiE"></a>
  2. ### 停止侦听
  3. watch和watchEffect都能停止侦听
  4. ```javascript
  5. const stop = watchEffect(()=>{
  6. // ...
  7. setTimeout(()=>{
  8. stop()
  9. },5000)
  10. })

生命周期钩子

官方文档

provide/inject

官方文档

  1. const app = Vue.createApp({
  2. setup(){
  3. const {ref, provide, readonly} = Vue
  4. const count = ref(0)
  5. const addCount = () => {
  6. count.value += 1
  7. }
  8. provide('count', readonly(count))
  9. provide('addCount', addCount)
  10. },
  11. template:`<my-component />`
  12. })
  13. app.component('my-component', {
  14. setup(){
  15. const {inject} = Vue
  16. const count = inject('count', 0)
  17. const addCount = inject('addCount')
  18. return {count, addCount}
  19. },
  20. template:`
  21. <div @click="addCount">{{count}}</div>
  22. `
  23. })

ref

使用composition API获取真实DOM节点

  • 生命ref为null
  • 与属性ref绑定, 等价于this.$refs

    1. const app = Vue.createApp({
    2. setup(){
    3. const {ref, onMounted} = Vue
    4. const hello = ref(null)
    5. onMounted(()=>{
    6. console.log(hello.value)
    7. })
    8. return {hello}
    9. },
    10. template:`<div ref="hello">hello world</div>`
    11. })