@vue/composition-api功能介绍

前言

@vue/composition-api 是通过一个插件的方式,为 Vue2(2.7自带,2.6及以下可用) 提供类似 Vue3 composition API 的函数式编程能力。它的实现思路主要有:

1、提供组合式函数,在函数内部追踪响应性依赖。

2、将组合产生的响应式状态保存到组件实例上,并在渲染期间还原。

3、重写组件的生命周期钩子,在钩子函数中恢复组合函数的上下文环境。

通过 setup() 函数作为入口,在其执行期间运行各响应式 API 追踪依赖,形成响应式状态,然后保存到实例上;在生命周期钩子中变更执行上下文,再次执行组合函数来恢复响应式状态,从而达到类似 Vue3 的编程体验。

为什么需要CompositionAPI

传统的Vue2 OptionAPI

我们以todolist为例,看一个基于vue2的OptionAPI的案例:

<template>
  <div class="hello">
    <h1>{
   
   { msg }}</h1>
    <input
      class="add-todo"
      v-focus
      type="text"
      placeholder="add something"
      v-model="newTodo"
      @keyup.enter="addTodo"
    />
    <div v-for="todo in todoList" :key="todo.id" class="todo-row">
      <input type="checkbox" v-model="todo.completed" />
      <div v-if="!todo.editing" @dblclick="editTodo(todo)">
        {
   
   { todo.title }}
      </div>
      <input
        v-else
        type="text"
        v-model="todo.title"
        @blue="doneEdit(todo)"
        @keyup.enter="doneEdit(todo)"
        @keyup.esc="cancelEdit(todo)"
      />
    </div>
  </div>
</template>

<script lang="ts">
import Vue from "vue";
import {
      
      
  reactive,
  ref,
  computed,
  onMounted,
  ComputedRef,
} from "@vue/composition-api";

type Todo = {
      
      
  id: number;
  completed: boolean;
  editing: boolean;
  title: string;
};
export default Vue.extend({
      
      
  name: "HelloWorld",
  props: {
      
      
    msg: String,
  },
  mounted() {
      
      
    console.warn(`component mounted..`);
  },
  data() {
      
      
    return {
      
      
      todoList: [
        {
      
      
          id: 1,
          title: "hello",
          completed: false,
          editing: false,
        },
        {
      
      
          id: 2,
          title: "world",
          completed: false,
          editing: false,
        },
      ],
      newTodo: undefined as undefined | string,
    };
  },
  computed: {
      
      
    getLatestTodoId(): number {
      
      
      const lastTodo: Todo = this.todoList[this.todoList.length - 1];
      return lastTodo.id;
    },
  },

  methods: {
      
      
    addTodo() {
      
      
      if (this.newTodo === undefined) return;
      this.todoList.push({
      
      
        id: this.getLatestTodoId + 1,
        title: this.newTodo,
        completed: false,
        editing: false,
      });
    },

    editTodo(todo: Todo) {
      
      
      todo.editing = !todo.editing;
    },

    cancelEdit(todo: Todo) {
      
      
      const editingTodo: Todo | undefined = this.todoList.find(
        (todo: Todo) => todo.editing === true
      );
      if (editingTodo === undefined) return;
      editingTodo.editing = false;
    },

    doneEdit(todo: Todo) {
      
      
      const editingTodo: Todo | undefined = this.todoList.find(
        (todo: Todo) => todo.editing === true
      );
      if (editingTodo === undefined) return;
      editingTodo.title = todo.title;
      editingTodo.editing = false;
    },
  },
  directives: {
      
      
    focus: {
      
      
      inserted(el) {
      
      
        el.focus();
      },
    },
  },
});
</script>

改良的Vue2 setup CompositionAPI

改造成用@vue/composition-api的案例:

<template>
  <div class="hello">
    <section style="margin-bottom:32px;">
      <h1 v-if="show">{
   
   { msg }}</h1>
      <button @click="toggle">Toggle above to hide</button>
    </section>
    <input
      class="add-todo"
      v-focus
      type="text"
      placeholder="add something"
      v-model="state.newTodo"
      @keyup.enter="addTodo"
    />
    <div v-for="todo in state.todoList" :key="todo.id" class="todo-row">
      <input type="checkbox" v-model="todo.completed" />
      <div v-if="!todo.editing" @dblclick="editTodo(todo)">
        {
   
   { todo.title }}
      </div>
      <input
        v-else
        type="text"
        v-model="todo.title"
        @blur="doneEdit(todo)"
        @keyup.enter="doneEdit(todo)"
        @keyup.esc="cancelEdit(todo)"
        v-focus
      />
    </div>
  </div>
</template>

<script lang="ts">
import Vue from "vue";

import {
      
       totoListLogic } from "@/compositions/todoListLogic";
import {
      
       toggleLogic } from "@/compositions/toggleLogic";

export default Vue.extend({
      
      
  name: "HelloWorld",
  props: {
      
      
    msg: String,
  },
  setup() {
      
      
    // todo business logic
    const {
      
      
      state,
      getLatestTodoId,
      addTodo,
      editTodo,
      cancelEdit,
      doneEdit,
    } = totoListLogic();

    // toggle business logic
    const {
      
       show, toggle } = toggleLogic();

    return {
      
      
      state,
      addTodo,
      editTodo,
      doneEdit,
      cancelEdit,
      show,
      toggle,
    };
  },
  directives: {
      
      
    focus: {
      
      
      inserted(el) {
      
      
        el.focus();
      },
    },
  },
});
</script>

以及对应的compositions函数:

toggleLogic.ts

import {
    
     ref } from "@vue/composition-api";

export const toggleLogic = () => {
    
    
  const show = ref(true);
  const toggle = () => {
    
    
    show.value = !show.value;
  };
  return {
    
     show, toggle };
};

todoListLogic.ts

import {
    
     reactive, computed, ComputedRef } from "@vue/composition-api";

type Todo = {
    
    
  id: number;
  completed: boolean;
  editing: boolean;
  title: string;
};
export const totoListLogic = () => {
    
    
  const state = reactive({
    
    
    todoList: [
      {
    
    
        id: 1,
        title: "hello",
        completed: false,
        editing: false,
      },
      {
    
    
        id: 2,
        title: "world",
        completed: false,
        editing: false,
      },
    ],
    newTodo: undefined as undefined | string,
  });

  const getLatestTodoId: ComputedRef<number> = computed(
    (): number => {
    
    
      const lastTodo: Todo = state.todoList[state.todoList.length - 1];
      return lastTodo.id;
    }
  );

  function addTodo() {
    
    
    if (state.newTodo === undefined) return;
    state.todoList.push({
    
    
      id: getLatestTodoId.value + 1,
      title: state.newTodo,
      completed: false,
      editing: false,
    });
  }

  function editTodo(todo: Todo) {
    
    
    todo.editing = !todo.editing;
  }

  function cancelEdit(todo: Todo) {
    
    
    todo.editing = false;
  }

  function doneEdit(todo: Todo) {
    
    
    const editingTodo: Todo | undefined = state.todoList.find(
      (todo: Todo) => todo.editing === true
    );
    if (editingTodo === undefined) return;
    editingTodo.title = todo.title;
    editingTodo.editing = false;
  }
  return {
    
     state, getLatestTodoId, addTodo, editTodo, cancelEdit, doneEdit };
};

优势对比

Composition API 相比于 Options API 的优势主要有:

1. 更好的逻辑复用性

- 通过组合函数抽取逻辑,更方便的在组件内外复用
- 更好地代码分割,按功能拆分成更小的逻辑块

2. 更好的代码组织

- 将同一功能的代码收敛在一个函数中,提高内聚性
- 减少组件中的选项混杂,提高可读性

3. 更好的类型推导

- 组合式函数可以利用 Typescript 进行更精确的类型定义
- 提高代码可读性和可维护性

4. 更优雅的处理逻辑抽取

- 将复杂逻辑抽取为可重用的函数,组件只关注业务级代码
- 避免复杂的 mixins 和 HOC 嵌套

5. 更灵活的逻辑复用粒度

- 可以将任意粒度的逻辑封装为组合函数进行复用
- 更灵活的逻辑 boundaries

6. 更直观的响应式编程

- 通过组合式函数内的响应式变量,更直观地表达响应性
- 避免模版和业务逻辑交织在一起

7. 更优雅的增量采用

- 可以与现有的 Options API 共存
- 逐步使用 Composition API 改造旧组件

总体来说,Composition API 对于大型复杂应用的维护和扩展有很大优势,能够设计出更清晰、灵活的代码结构。

怎么用@vue/composition-api

安装

vue2.7以上自带,不需要安装;vue2.7以下手动安装npm install @vue/composition-api,并安装插件:

import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api'

Vue.use(VueCompositionAPI)

后续文章会介绍其原理。

API介绍

下面介绍几个常见的API,后续文章会一一介绍其原理:

ref

ref函数可以把一个普通的值转成响应式的数据。它返回一个可变的ref对象,对象上挂载了一个.value属性,我们可以通过这个.value属性读取或者修改ref的值。

示例如下:

import {
    
     ref } from '@vue/composition-api'

const count = ref(0)

console.log(count.value) // 0

count.value++ 
console.log(count.value) // 1

ref很像是一个容器,它把基本类型的数据像Number、String等"装箱",让其成为响应式的数据源。

reactive

reactive用来把一个对象转换成响应式的数据。它返回的是一个代理对象,原对象中的所有属性访问和变更,都会转化成响应式的。

示例如下:

import {
    
     reactive } from '@vue/composition-api'

const obj = reactive({
    
     count: 0 })

obj.count++ // 响应式更新

reactive更适合组件的所有状态都定义在一个对象中的场景,它可以一次性把整个对象状态都转成响应式。

computed

computed函数用来创建一个计算属性,它根据依赖进行缓存和懒执行。

示例如下:

import {
    
     computed } from '@vue/composition-api'

const count = ref(1)

const doubled = computed(() => count.value * 2)

console.log(doubled.value) // 懒执行得到 2

count.value++ // 修改依赖

console.log(doubled.value) // 缓存得到 4

computed避免了重复执行函数,也自动追踪了依赖关系。

watch

watch函数用于侦听特定的数据源,并在回调函数中执行副作用。

示例如下:

import {
    
     ref, watch } from '@vue/composition-api'

const count = ref(0)

watch(count, (newCount, oldCount) => {
    
    
  console.log(`count变化:${
      
      oldCount} -> ${
      
      newCount}`) 
})

count.value++ // 触发watcher

它就像一个监听器,可以看到count的变化并作出反应。

toRefs

toRefs 可以把一个 reactive 对象的属性都转成 ref 形式。

示例如下:

import {
    
     reactive, toRefs } from '@vue/composition-api'

const state = reactive({
    
    
  count: 0
})

const {
    
     count } = toRefs(state)

console.log(count.value) // 0

count.value++ // 修改 ref 形式的值

这样可以方便地解构一个 reactive 对象,同时保持响应式特性。需要注意的是reactive对象,默认给template是不可用的,只有转为ref才行。

setup

setup 是组合式 API 的入口,所有的组合函数都在此执行。

示例如下:

import {
    
     ref, reactive } from '@vue/composition-api'

export default {
    
    
  setup() {
    
    
    const count = ref(0)
    const obj = reactive({
    
     foo: 'bar' })
    
    // ... 进行逻辑处理

    return {
    
    
      count,
      obj
    }
  }  
}

生命周期

组合式API提供了一系列与组件生命周期对应的钩子函数,用来注册生命周期钩子。

示例如下:

import {
    
     onMounted } from '@vue/composition-api'

export default {
    
    
  setup() {
    
    
    onMounted(() => {
    
    
      console.log('组件挂载了!')
    })
  }
}

有如下钩子函数:

onBeforeMount - created()
onMounted - mounted()
onBeforeUpdate - beforeUpdate()
onUpdated - updated()
onBeforeUnmount - beforeDestroy()
onUnmounted - destroyed()

猜你喜欢

转载自blog.csdn.net/mevicky/article/details/132040382