@vue/composition-api function introduction

foreword

@vue/composition-api provides functional programming capabilities similar to Vue3 composition API for Vue2 (included with 2.7, available for 2.6 and below) through a plug-in. Its implementation ideas mainly include:

1. Provide a combined function to track responsive dependencies inside the function.

2. Save the responsive state generated by the combination to the component instance and restore it during rendering.

3. Rewrite the life cycle hook of the component, and restore the context of the composite function in the hook function.

Use the setup() function as the entry point to run each responsive API to track dependencies during its execution, form a responsive state, and then save it to the instance; change the execution context in the life cycle hook, and execute the combined function again to restore the responsive state. So as to achieve a programming experience similar to Vue3.

Why do you need Composition API

Traditional Vue2 OptionAPI

Let's take todolist as an example to see a case of OptionAPI based on vue2:

<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>

Improved Vue2 setup Composition API

The case of transforming into @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>

And the corresponding compositions function:

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 };
};

Comparison of advantages

The main advantages of Composition API over Options API are:

1. 更好的逻辑复用性

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

2. 更好的代码组织

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

3. 更好的类型推导

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

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

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

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

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

6. 更直观的响应式编程

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

7. 更优雅的增量采用

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

Generally speaking, Composition API has great advantages for the maintenance and expansion of large and complex applications, and can design a clearer and more flexible code structure.

How to use @vue/composition-api

Install

Vue2.7 or above comes with it, no installation is required; below vue2.7, install it manually npm install @vue/composition-api, and install the plug-in:

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

Vue.use(VueCompositionAPI)

Subsequent articles will introduce its principle.

API introduction

The following introduces several common APIs, and subsequent articles will introduce their principles one by one:

ref

The ref function can convert an ordinary value into responsive data. It returns a mutable ref object, and a .value attribute is mounted on the object. We can read or modify the value of ref through this .value attribute.

Examples are as follows:

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

const count = ref(0)

console.log(count.value) // 0

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

Ref is like a container, which "boxes" basic types of data like Number, String, etc., making it a responsive data source.

reactive

reactive is used to convert an object into responsive data. It returns a proxy object, and all property access and changes in the original object will be converted into responsive ones.

Examples are as follows:

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

const obj = reactive({
    
     count: 0 })

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

reactive is more suitable for scenarios where all the states of a component are defined in one object, and it can convert the entire object state into a responsive one at a time.

computed

The computed function is used to create a computed property that is cached and lazily executed based on dependencies.

Examples are as follows:

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 avoids repeated execution of functions and automatically tracks dependencies.

watch

The watch function is used to listen to a specific data source and perform side effects in the callback function.

Examples are as follows:

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

const count = ref(0)

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

count.value++ // 触发watcher

It acts like a listener that sees changes in count and reacts.

toRefs

toRefs can convert the properties of a reactive object into ref form.

Examples are as follows:

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

const state = reactive({
    
    
  count: 0
})

const {
    
     count } = toRefs(state)

console.log(count.value) // 0

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

This makes it easy to destructure a reactive object while maintaining the reactive nature. It should be noted that the reactive object is not available for template by default, and can only be converted to ref.

setup

setup is the entry point of the combined API, where all the combined functions are executed.

Examples are as follows:

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

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

    return {
    
    
      count,
      obj
    }
  }  
}

life cycle

The combined API provides a series of hook functions corresponding to the component life cycle, which are used to register life cycle hooks.

Examples are as follows:

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

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

There are the following hook functions:

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

Guess you like

Origin blog.csdn.net/mevicky/article/details/132040382