了解 Vue 的 Compsition API

在这篇文章中,我将讲讲 Vue 的 Composition API 为什么比之前的 Options API 要好,以及它是如何工作的。

Options API 有什么问题

首先,这里不是要大家放弃 Options API,如果你觉得 Options API 还不错,已经习惯了,就继续使用它。但我希望你能明白为什么 Composition API 是一种更好的选择。

当我刚开始接触 Vue 时,我喜欢它用匿名对象来构建组件的方式。简单易学,也很自然。但随着使用的时间越长,就会遇到一些很奇怪的问题。比如下面这段代码:

export default {
  mounted: async function () {
    this.load().then(function () {
      this.name = 'Foo'
    })
  },
}

这里的第二个this的问题我想很多人都遇到过。在不调试的情况下,很难知道两个this为什么不一样。我们只知道要解决这个问题,反正得用箭头函数,例如:

mounted: async function () {
  this.load().then(() => {
    this.name = 'Foo';
  });
}

此外,从data函数返回成员后,Vue 会包装它们(通常使用Proxy),以便能够监听对象的修改。但如果你尝试直接替换对象(比如数组),就可能监听不到对象修改了。比如:

export default {
  data: () => {
    return {
      items: [],
    }
  },
  mounted: async function () {
    this.load().then((result) => {
      this.items = result.data
    })
  },
  methods: {
    doSomeChange() {
      this.items[0].foo = 'foo'
    },
  },
}

这是 Vue 最常见的问题之一。尽管可以很简单地解决这些问题,但毕竟给学习 Vue 工作原理造成了障碍。

最后,Options API 很难实现组件间的功能共享,为了解决这个问题,Vue 使用了mixins的概念。例如,你可以创建一个 mixin 来扩展组件,如下所示:

export default {
  data: function () {
    return { items: [] };
  },

  methods: {
    ...
  }
}

mixin 看起来很像 Options API。它只是对对象进行合并,以允许你添加到特定组件上:

import dataService from "./dataServiceMixin";
export default {
  mixins: [dataService],
  ...

mixin 最大问题是名称冲突。

鉴于 Options API 这些不足,Composition API 诞生了。

了解 Composition API

现在来介绍一下 Composition API。Vue 2 也可以使用 Composition API。首先,你需要引入 Composition API 库:

> npm i -s @vue/composition-api

要启用 Composition API,你只需在main.js/ts中注册一下:

import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api'
Vue.use(VueCompositionAPI)

让我们来写一个 Composition API 组件。首先,组件仍然是匿名对象:

export default {
  setup() {
    return {}
  },
}

Composition API 的关键就是这个setup方法,所有所需的数据都从setup中返回。这非常类似于 Options API 的data方法:

export default {
  setup() {
    const name = 'Foo'
    return { name }
  },
}

与其只在return中赋值,不如在setup里面用一个局部变量创建。为什么要这样做呢?因为 JavaScript 的闭包。让我们扩展一下,再写一个函数。

export default {
  setup() {
    const name = 'Foo'

    function save() {
      alert(`Name: ${name}`)
    }
    return { name, save }
  },
}

该函数可以访问name,因为它们在同一个作用域内。这就是 Composition API 的神奇之处。没有魔术对象,只有 JavaScript。这个模式可以支持更复杂的场景,更灵活,这一切的基础只是闭包,仅此而已。

在这个例子中name是永远不会改变的。为了让 Vue 能够处理绑定,它需要知道name的变化。在这个例子中,如果你在save()中修改了代码来改变名称,它不会反映在 UI 中:

export default {
  setup() {
    const name = 'Foo'

    function save() {
      name = name + ' saved'
      alert(`Name: ${name}`)
    }
    return { name, save }
  },
}

响应式

要使对象具有响应式,Vue 提供了两种方法:refreactive包装器。

你可以将name包装成一个ref对象:

import { ref } from '@vue/composition-api'
export default {
  setup() {
    const name = ref('Foo')

    function save() {
      name.value = name.value + ' saved'
      alert(`Name: ${name.value}`)
    }
    return { name, save }
  },
}

这是一个简单的响应式,ref 对象的 value 属性才是实际的值。

这与简单对象可以很好地配合,但是具有自身状态的对象(例如类或数组),对值的更改是不够的。你真正需要的是使用一个 proxy 函数以便监听该对象内部发生的任何更改。

import { ref, reactive } from '@vue/composition-api'
export default {
  setup() {
    const name = ref('Foo')
    const items = reactive([])

    function save() {
      // Change Array
      items.splice(0, items.length)
      name.value = name.value + ' saved'
      alert(`Name: ${name.value}`)
    }
    return { name, save, items }
  },
}

在此示例中,使用了splice更改数组,Vue 需要了解此更改。你可以通过使用另一个称为reactive的包装器包装该对象(在本例中为数组)来实现。

Composition API 中的 reactive 包装器与 Vue2 的 Vue.observable 包装器相同。

ref 和 react 之间的区别在于,reactive 用 proxy 包装对象,而 ref 是简单的值包装器。

因此,你不需要将返回的 reactive 对象再包装,不需要像 ref 对象一样通过 value 属性访问其值。

一旦有了 ref 和 reactive 对象,就可以观察更改。有两种方法可以做到这一点:watchwatchEffect。watch 允许你监听单个对象的更改:

 setup() {
    const name = ref("Shawn");
    watch(() => name, (before, after) => {
        console.log("name changes");
    });
    return { name };
},

watch函数有两个参数:将数据返回到watch的回调以及发生更改时要调用的回调。它提供两个参数分别是修改前的值before和修改后的值after

或者,你可以使用watchEffect 监听 reactive 对象的任何更改。它只需要一个回调:

watchEffect(() => {
  console.log('Ch..ch...ch...changes.')
})

组合组件

有时候你希望把一个已有组件的功能组合到另一个组件,以便代码重用和数据交互。当组合组件时,Composition API 只是使用作用域和闭包解决问题。例如,你可以创建一个简单的 service 服务:

import axios from 'axios'

export let items = []

export async function load() {
  let result = await axios.get(API_URL)
  items.splice(0, items.length, ...result.data)
}

export function removeItem(item) {
  let index = items.indexOf(item)
  if (index > -1) {
    items.splice(index, 1)
  }
}

然后你可以按需将需要的功能(对象或函数) import 到你的组件中:

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

import { load, items, removeItem } from './dataService'

export default {
  setup() {
    const name = ref('Foo')

    function save() {
      alert(`Name: ${this.name}`)
    }

    return {
      load, // from import
      removeItem, // from import
      name,
      save,
      items, // from import
    }
  },
}

你可以使用 Composition API 更明显式地组合你的组件。接下来我们来谈谈组件的使用。

使用 Props

在 Composition API 中定义 Props 的方式与 Options API 相同:

export default {
  name: 'WaitCursor',
  props: {
    message: {
      type: String,
      required: false,
    },
    isBusy: {
      type: Boolean,
      required: false,
    },
  },
  setup() {},
}

你可以通过向setup添加可选参数来访问 Props:

setup(props) {
    watch(() => props.isBusy, (b,a) => {
        console.log(`isBusy Changed`);
    });
}

此外,你可以添加第二个参数,可以访问emitslotsattrs对象,和 Options API 上 this 指针上的对象对应:

setup(props, context) {
    watch(() => props.isBusy, (b,a) => context.emit("busy-changed", a));
}

使用组件

在 Vue 中有两种使用组件的方法。你可以全局注册组件(用作通用组件),以便可以在任何地方使用它们:

import WaitCursor from './components/WaitCursor'
Vue.component('wait-cursor', WaitCursor)

通常,你可以将组件添加到正在使用的特定组件中。在 Composition API 中,与 Options API 相同:

import WaitCursor from './components/waitCursor'
import store from './store'
import { computed } from '@vue/composition-api'

export default {
  components: {
    WaitCursor, // Use Component
  },
  setup() {
    const isBusy = computed(() => store.state.isBusy)
    return { isBusy }
  },
}

在 Composition API 中指定了组件后,就可以使用它:

<div>
  <WaitCursor message="Loading..." :isBusy="isBusy"></WaitCursor>
  <div class="row">
    <div class="col">
      <App></App>
    </div>
  </div>
</div>

使用组件的方式与 Options API 中的方式没有什么不同。

在 Vue 3 中使用 Composition API

如果你使用的是 Vue 3,则无需单独引用 Composition API。Vue 3 已经集成好了。该@vue/composition-api库仅用于 Vue 2 项目。Vue 3 的真正变化是,当你需要导入 Composition API 时,只需要直接从 Vue 获取它们:

import {
  ref,
  reactive,
  onMounted,
  watch,
  watchEffect,
  //from "@vue/composition-api";
} from 'vue'

其他一切都一样。只需从“ vue”导入即可。在 Vue 3 中,使用 Composition API 只是简单一些,因为它是默认行为。

Vue 3 的主要目标之一是改善 TypeScript 体验,所有内容都有类型库。但是要增加类型安全性,你必须进行一些小的更改才能使用 TypeScript。在创建组件时,需使用defineComponent

import { defineComponent, reactive, onMounted, ref } from "vue";
import Customer from "@/models/Customer";

export default defineComponent({
    name: "App",
    setup() {
      const customers = reactive([] as Array<Customer>);
      const customer = ref(new Customer());
      const isBusy = ref(false);
      const message = ref("");
      return {
        customers, customer, isBusy, message
      }
    }
});

在这些示例中,变量被推断为类型实例(例如,Customers对象是Reactive<Customer[]>类型的实例)。此外,使用强类型可以降低传递错误数据的机会。当然,如果你使用 TypeScript(尤其是在 Visual Studio 或 VS Code 中),则会有非常友好的智能提示。

posted @ 2021-03-31 09:40  前端小蜜蜂  阅读(735)  评论(0编辑  收藏  举报