compositions

入门 todo

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

使用场景 vue2+setup 函数+@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>

对照传统 vue2 的 optionsAPI 版本:

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

通过getCurrentInstance来容错

笔记片段:通过getCurrentInstance实现容错

在Vue.js的Composition API中,getCurrentInstance是一个用于获取当前Vue实例的函数。这个函数可以在我们编写自定义钩子或工具函数时,帮助我们实现容错机制。

当我们调用getCurrentInstance时,如果当前有活动的Vue实例,它将返回该实例。然而,如果没有活动的实例,它将返回null。我们可以利用这个特性来检查当前是否存在Vue实例,并据此决定如何执行代码。

通过判断getCurrentInstance的返回值,我们可以在自定义函数中实现容错逻辑。例如,如果我们想在Vue实例存在时使用onMounted钩子,而在没有实例时直接执行函数,可以这样编写代码:

import { onMounted, getCurrentInstance } from 'vue'  
  
function tryOnUnmounted(fn) {
  if (getCurrentInstance()) onUnmounted(fn)
}

function tryOnMounted(fn) {
  if (getCurrentInstance()) onMounted(fn)
  else fn()
}

在上述代码中,tryOnMounted函数通过调用getCurrentInstance来获取当前Vue实例。如果存在实例,则使用onMounted将传入的函数fn注册为挂载钩子。如果没有实例,则直接执行函数fn。

这种容错写法可以帮助我们在不同情况下保证代码的正常执行,无论是否存在Vue实例。它提高了代码的适应性和健壮性,使我们的自定义函数更加可靠。