天天看点

Vue3 组合式API

组合式 API 基础
(就是将一个组件中的不同逻辑分离开来,放在不同的js文件中,再引入,使用者不需要知道怎么实现,只需要知道提供了哪些功能)
  • setup 组件选项
  • 新的 setup 选项在组件创建之前执行,一旦 props 被解析,就将作为组合式 API 的入口。
  • setup 中避免使用

    this

    , setup的调用 在

    data

    methods

    computed

    解析之前
  • setup 选项是一个接收

    props

    context

    的函数
  • 将setup 返回的函数 暴露给 组件的其余部分 (计算属性、方法、生命周期)
  • 带 ref 的响应式变量
    • 在 Vue 3.0 中,我们可以通过一个新的 ref 函数使任何响应式变量在任何地方起作用
    • toRefs(props)

      props

      属性的响应式引用
import { ref } from 'vue'
   const counter = ref(0)
           
  • ref 接收参数并将其包裹在一个带有 value property 的对象中返回,然后可以使用该 property 访问或更改响应式变量的值:
import { ref } from 'vue'

    const counter = ref(0)

    console.log(counter) // { value: 0 }
    console.log(counter.value) // 0
           
  • 在 setup 内注册生命周期钩子
  • 为了使组合式 API 的功能和选项式 API 一样完整,我们还需要一种在 setup 中注册生命周期钩子的方法。
  • 这些函数接受一个回调,当钩子被组件调用时,该回调将被执行。
import {ref, onMounted} from 'vue'
    setup(){
        const count = ref(0);
        const getCount = function(){
            return count + 6;
        }
        onMounted(getCount);
        return {
            count,
            getCount
        }
    }
           
  • watch 响应式更改
  • 接收三个参数
    • 一个想要侦听的响应式引用或 getter 函数
    • 一个回调
    • 可选的配置选项
import { ref, watch } from 'vue'
    const counter = ref(0)
    watch(counter, (newValue, oldValue) => {
        console.log('The new counter value is: ' + counter.value)
    })
           
  • 独立的 computed 属性
  • 与 ref 和 watch 类似,也可以使用从 Vue 导入的 computed 函数在 Vue 组件外部创建计算属性。
import { onMounted , ref , toRefs , computed , watch } from 'vue'
    setup(props){
        // 将 name 变成 props name 属性的引用
        const { name } = toRefs(props);
        // 将 username 变成响应式的 
        const username = ref('');
        // 方法
        const getUserName = function(){
            username.value ='ch' + name;
        }
        // 生命周期函数 
        onMounted(getUserName);
        // 监听 username 变化 触发 getUserName方法
        watch(username,getUserName);
        // 计算属性
        const enName = computed(()=>'en' + name.value)

        return {
            username,
            enName,
            getUserName
        }
    }
           
Setup
  • 接收两个参数
    • props
    • context
  • props
  • props 是响应式的

    (不要使用 ES6解构 会去掉它的响应式)

  • 如果需要解构

    prop

    ,可以在

    setup

    函数中使用

    toRefs

    函数来完成此操作:
import { toRefs } from 'vue';

    setup(props){
        // name 不再是响应式
        // let { name } = props;
        // 这里的name是响应式的
        let { name } = toRefs(props);
    }
           
  • 对于非必填(可选)的props 不可以使用 toRefs() 结构
  • toRefs 将不会为 title(可选参数) 创建一个 ref ,需要使用 toRef 替代它
import { toRefs , toRef } from 'vue';

    setup(props){
        let { name } = toRefs(props);
        // title 非必填(可选)
        const title = toRef(props, 'title')
    }
           
  • Context
  • 对象格式,暴露组件的三个属性
  • Context

    不是响应式的可以使用 ES6 安全解构
export default {
  setup(props, { attrs, slots, emit }) {
    ...
  }
}
           
  • attrs

    : Attribute 非响应式对象
  • slots

    : 插槽 非响应式对象
  • emit

    : 触发事件 方法
  • attrs

    slots

    是有状态的对象,它们总是会随组件本身的更新而更新。这意味着你应该避免对它们进行解构,并始终以 attrs.x 或 slots.x 的方式引用 property。 请注意,与 props 不同,attrs 和 slots 是非响应式的。如果你打算根据 attrs 或 slots 更改应用副作用,那么应该在 onUpdated 生命周期钩子中执行此操作
  • 访问组件的 property
    • setup

      的执行是在 组件创建之前,无法访问

      methonds

      data

      computed

    • setup

      可以访问

      props

      context(attrs,slots,emit)

  • 结合模板使用
    • 如果 setup 返回一个对象,那么该对象的 property 以及传递给 setup 的 props 参数中的 property(原来就可以访问到 props) 就都可以在模板中访问到
    • 注意,从

      setup

      返回的

      refs

      在模板中访问时是被自动浅解包的,因此不应在模板中使用

      .value

  • 使用渲染函数
    • setup

      还可以返回一个渲染函数,该函数可以直接使用在同一作用域中声明的响应式状态

    reactive

    返回对象的响应式副本
import { h , reactive  } form 'vue'
    setup(){
        const img = reactive({ src:"https:xxxxxx" });
        return () => h('img', { src:img.src })
    }
           
  • 使用 this
    • setup

      this

      不是该活跃实例的引用,因为他是在解析其它组件选项之前被调用的
生命周期钩子
  • 通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子。
  • 接收一个回调函数,当钩子被组件调用的时候执行
setup(){
        onMounted(()=>{})
    }
           
Provide/Inject
  • Provide/Inject

  1. 需要先引入 import { provide , inject } form ‘vue’
  2. provide 接收两个参数
    1. name
    2. value
  3. inject 两个参数
    1. name
    2. 默认值(可选)
import { provide , inject  } form 'vue'
    // parent.vue
    setup(){
        provide('name','xiaohei');
        provide('addr',{city:'',town:''});
    }
    //child.vue
    setup(){
        let cname = inject('name','hah');
        let caddr = inject('addr');
    }
           
  • 响应性

    • 为了增加 provide 值和 inject 值之间的响应性,我们可以在 provide 值时使用 ref 或 reactive。
import { provide , ref , reactive  } form 'vue';
    // parent.vue
    setup(){
        let name = ref('xiaohei');
        let addr = reactive({city:'',town:''});
        provide('name',name);
        provide('addr',addr);
    }
           
  • 修改响应式 property

    • 修改响应式 属性的 时候,最好是在定义响应式的组件内进行修改
    • 有时我们需要在注入组件的内部修改

      inject

      中的值,可以通过

      provide

      一个方法来修改这个值
import { provide , ref , reactive } form 'vue';
    // parent.vue 
    setup(){
        let age = ref(1);
        let addr = reactive({city:'',town:''});
        const updateAge = function(){
            age.value +=1;
        }
        provide('name',name);
        provide('addr',addr);
        provide('updateAge',updateAge);
    }
    // child.vue
    setup(){
        let age = inject('age');
        let addr = inject('addr');
        const updateAge = inject('updateAge');
        return{
            age,
            addr,
            updateAge
        }
    },
    methods:{
        update(){
            this.updateAge();
        }
    }
           
  • 不允许 inject 的组件 修改 provide 传递的属性 添加

    readonly

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

与 reactive 一样,如果任何 property 使用了 ref,当它通过代理访问时,则被自动解包:

import { readonly , provide , reactive } from 'vue'
    setup(){
        let age = ref(1);
        let work = reactive({money:'',addr:''}) ;
        provide('age' , readonly(age)); // 只读属性
        provide('work' , readonly(work)); // 只读属性
    }
           
模板引用
  • 在使用组合式 API 时,响应式引用和模板引用的概念是统一的。为了获得对模板内元素或组件实例的引用,我们可以像往常一样声明 ref 并从 setup() 返回:
<template> 
  <div ref="root">This is a root element</div>
</template>
<script>
    import { ref, onMounted } from 'vue'
    export default {
        setup() {
            const root = ref(null)

            onMounted(() => {
                // DOM元素将在初始渲染后分配给ref
                console.log(root.value) // <div>这是根元素</div>
            })
      return {
        root
      }
    }
  }
</script>
           
  • 这里我们在渲染上下文中暴露

    root

    ,并通过

    ref="root"

    ,将其绑定到 div 作为其

    ref

    。在

    虚拟 DOM

    补丁算法中,如果

    VNode

    ref

    键对应于渲染上下文中的

    ref

    ,则

    VNode

    的相应元素或组件实例将被分配给该

    ref

    的值。这是在虚拟 DOM 挂载/打补丁过程中执行的,因此模板引用只会在初始渲染之后获得赋值。

    大致意思就是 模板里面声明的 ref='root' 如果与下文中暴露的 ref 所赋值的 变量名一样,就会把这个模板中的ref 与下文中的那个ref赋值的变量关联起来

  • JSX 中的用法

export default {
  setup() {
    const root = ref(null)

    return () =>
      h('div', {
        ref: root
      })

    // with JSX
    return () => <div ref={root} />
  }
}
           
  • v-for 中的用法

<template>
  <div v-for="(item, i) in list" :ref="el => { if (el) divs[i] = el }">
    {{ item }}
  </div>
</template>

<script>
  import { ref, reactive, onBeforeUpdate } from 'vue'

  export default {
    setup() {
      const list = reactive([1, 2, 3])
      const divs = ref([])

      // 确保在每次更新之前重置ref
      onBeforeUpdate(() => {
        divs.value = []
      })

      return {
        list,
        divs
      }
    }
  }
</script>
           
  • 侦听模板引用

    • 但与生命周期钩子的一个关键区别是,

      watch()

      watchEffect()

      在 DOM 挂载或更新之前运行副作用,所以当侦听器运行时,模板引用还未被更新。
    • 因此,使用模板引用的侦听器应该用

      flush: 'post'

      选项来定义,这将在

      DOM

      更新后运行副作用,确保模板引用与

      DOM

      保持同步,并引用正确的元素。
<template>
  <div ref="root">This is a root element</div>
</template>

<script>
  import { ref, watchEffect } from 'vue'

  export default {
    setup() {
      const root = ref(null)

      watchEffect(() => {
        console.log(root.value) // => <div></div>
      }, 
      {
        flush: 'post'
      })

      return {
        root
      }
    }
  }
</script>

           

继续阅读