zoukankan      html  css  js  c++  java
  • 了解 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 中),则会有非常友好的智能提示。

  • 相关阅读:
    jquery proxy && delegate 的比较
    最近开发问题记录
    调研雅黑字体在各浏览器的显示
    我对浮动的认识(一)
    我对浮动的理解和认识(二)
    跨浏览器事件的其他问题(小记)
    web数据交互方式
    闪光字的处理
    IE hasLayout的问题总结
    文字内容溢出用点点点(...)省略号
  • 原文地址:https://www.cnblogs.com/fehoney/p/14600036.html
Copyright © 2011-2022 走看看