Vue 3 Composition API,你真的需要吗?

Vue 团队宣布,Vue 框架的第 3 版将于 2020 年第一季度发布。

新框架有望带来许多进步,其中大部分针对框架核心本身,同时保持我们已经习惯的构建 Vue 应用程序的标准语法。本质上,代码库被重写,框架被改变。

随着 JavaScript 的新特性,我们抓住了重写的机会。此外,他们将开发人员的反馈和想法纳入框架,并显示出来。结果是一个性能更高的框架。

开发人员将注意到框架性能的改进,而无需深入研究源代码来调整它。当您发现这为您节省了多少心痛时,请稍后感谢他们!

以下是 Vue 3 中将发生变化的事情的简要总结:

  • 虚拟 DOM 重写以获得更好的性能,并改进了 TypeScript 支持。
  • 暴露的反应性 API。
  • 时间切片支持。
  • 静态树木吊装。
  • 优化插槽世代。
  • 单态调用。

我们将专注于新的 Vue 3 组合 API。这个 API 是完全可选的,但是,您可以轻松地集成它,并在您的应用程序中从中受益。

Vue 组件/构建它们的方法

新的 Composition API 是 Vue 在 Vue 3 中构建组件的方法。使用 Component Options API 一直是 Vue 2 中采用的构建组件的传统方式。

组件有几个选项,开发人员可以使用这些选项在组件内实现特定功能。通过几个选项,开发人员可以在组件内使用和实现某个功能。

组件选项 API / 传统方法

例如,您的应用程序具有显示产品列表并允许用户对其执行创建读取更新删除 (CRUD) 操作的功能。

在 Vue 2 组件中实现此功能的一种可能方法是定义以下脚本代码:


<template>
  <div>Products</div>
</template>

<script>
export default {
  data() {
    return {
      products: [],
      product: null,
    };
  },
  methods: {
    createProduct() {
      // create product
    },
    updateProduct() {
      // update product
    },
    deleteProduct() {
      // delete product
    },
  },
};
</script>

组件选项 API 是指 Vue 2 组件提供的工件,以帮助您在应用程序中实现特定功能。

数据选项用于定义要显示的产品项目数组,以及用于跟踪选定产品或新产品的单个产品对象。

方法选项用于定义您需要在组件中使用的任何自定义函数或方法。还有其他选项,例如computedwatch等。

这是在 Vue 2 中构建组件的传统方式。

这种方法适用于小型甚至中型应用程序。当应用程序增长到几千个组件时,问题就会出现。通常,您的整个应用程序不会仅由几个组件构建。最好的方法是用更小的组件组​​成你的应用程序。这在测试、可读性和代码维护方面有很多好处。

换句话说,随着更多功能的实现,组件变得复杂且难以跟踪。此外,Options API 对代码重用和共享有限制。共享功能必须在您的组件中一遍又一遍地重复。

组件选项 API 本身给开发人员带来了困惑。这样想;如果你想构建一个单一的功能,你必须将它分散在不同的选项中(道具、数据、手表等等)。随着组件的大小和功能的增长,功能分散在组件内的选项中。

热的一团糟!

组件选项 API 和 Mixins

组件选项 API 不促进代码重用或共享。这在使用它时增加了它的复杂性。

减轻复杂性的一种解决方案是在mixins的帮助下构建组件。

我们仍然必须处理特性的实现分布在多个选项中的事实。但是,现在有了 mixins,您可以通过在 mixins 中使用更多代码重用和共享通用功能来增强您的组件。

Vue mixins 允许你在 mixin 文件中重构你的通用代码。然后,您可以将此 mixin 导入任何需要使用此通用代码的组件。

这是朝着正确方向迈出的一步,解决了 Vue 2 应用程序中的代码重用问题,但这并不是全部。为什么?

mixin 遵循 Composition Options API 的相同标准。让我们在使用 mixins 之前重构显示的组件:


<template>
  <div>Products</div>
</template>

<script>
import ProductsMixin from "@/mixins/Products.mixin";

export default {
  mixins: [ProductsMixin],
};
</script>

组件中的几乎所有代码都已被剥离。

ProductsMixin现在被导入到组件中。为了让组件知道并使用这个 mixin,你将 mixin 添加到mixins选项数组中。

ProductsMixin 看起来像这样:


export default {
  data() {
    return {
      products: [],
      product: null
    };
  },
  methods: {
    createProduct() {
      // create product
    },
    updateProduct() {
      // update product
    },
    deleteProduct() {
      // delete product
    }
  }
};

mixin 导出一个默认的 ES6 对象,该对象使用组件选项 API 来布局其代码。看着一个 mixin 文件让我想起了一个 Vue 组件。相同的结构和组织!

我已经从组件内部删除了数据方法选项,并将它们放在 mixin 中。

在运行时,Vue 框架将合并组件代码和 mixin 代码,以生成具有来自两个源的选项的单个组件。

您可以将此 mixin 导入应用程序中需要提供 Products CRUD 操作的其他组件。

使用 mixin 是有代价的:

  • 命名冲突。
  • 组件选项 API 的复杂性是继承的。
  • 工具的复杂性。使用 mixin,您必须在使用前打开 mixin 文件并检查方法或数据属性的名称。自动合并发生在运行时的幕后。因此,没有办法在 mixins 字段的组件内部拥有智能。

使用 Vue mixin 时要记住的事情:

  • 生命周期钩子首先针对 mixin 运行,然后针对组件运行。
  • 来自组件和 mixin 的选项将在运行时合并。
  • 当这些对象(方法、数据等)中存在冲突的键时,组件选项将优先

使用组合 API

组合 API引入了一种构建组件并在其中实现功能的新方法。

让我们看看前面提到的带有 mixin 示例的组件如何转换为 Composition API 方法。

首先,让我们看一下Products组件:


<template>
  <div>
    <div
      v-for="p in products"
      :key="p.id"
    >Product Name: {
    
    { p.name }} - Product Price: ${
    
    { p.price }}</div>
    <div>
      <button @click="createAProduct">Create New</button>
    </div>
  </div>
</template>

<script>
import { useProducts } from "@/cmp-functions/Products";

export default {
  name: "Products",
  setup() {
    return {
      ...useProducts()
    };
  },
  methods: {
    createAProduct() {
      this.createProduct({
        id: Math.floor(Math.random() * 10) + new Date().getTime(),
        name: "New Product",
        price: Math.floor(Math.random() * 1000).toString()
      });
    }
  }
};
</script>

该组件最引人注目的部分是setup()函数。它是由新的 Vue 3 组合 API 添加的。Vue 意识到了这一点,并且会在创建组件对象之前运行它。因此,这解释了为什么引用组件本身的对象this在函数内部不可用。

在此函数中,您可以定义代码所需的数据属性、计算方法、监视方法、普通方法和任何其他实用方法。它应该公开并返回一个包含所有公共方法和数据属性的对象。所谓公开,是指您希望 Vue 组件本身共享和使用的任何内容。

在我们的例子中,该函数通过传播useProducts()函数返回一个对象。在 Vue 3 组合 API 术语中,useProducts()是一个组合函数,并返回一个对象。

setup()函数返回一个对象,该对象包含函数返回的所有数据属性和方法useProducts()

useProducts()函数在文件中定义/src/cmp-functions/Products.js如下:


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

export const useProducts = () => {
  const products = ref([]);

  const createProduct = ({ id, name, price }) => {
    products.value.push({ id, name, price });
  };

  const updateProduct = ({ id, name, price }) => {
    const itemIndex = products.value.findIndex(p => p.id === id);

    if (itemIndex < 0) {
      return;
    }

    products.value.splice(itemIndex, 1, { id, name, price });
  };

  const deleteProduct = id => {
    const itemIndex = products.value.findIndex(p => p.id === id);

    if (itemIndex < 0) {
      return;
    }

    products.value.splice(itemIndex, 1);
  };

  return {
    createProduct,
    updateProduct,
    deleteProduct,
    products
  };
};

首先从包中导入ref函数。@vue/composition-api它包装任何值或对象,并使其具有反应性,因此如果它的值发生变化,Vue 会知道它的存在,并相应地更新 UI。

useProducts()函数是一个普通的 JavaScript 函数(在我们的例子中是箭头函数)。它返回一个对象。

在函数内部,我们使用空数组的初始值定义products反应数据属性。

如果不使用ref([]),Vue 将无法检测到数组的更改,并且 UI 无法更新。

其余函数createProduct()updateProduct()deleteProduct()只是用于处理产品上的 CRUD 操作的 JavaScript 函数。

注意products.value与响应式数据属性交互时的使用。此语法仅在组合函数内部是必需的。在 Vue 组件内部使用数据属性时,无论是数据集还是获取数据,您都可以引用数据属性的名称,而无需使用.value语法。此语法仅在组合函数内部使用。

最后,useProducts()函数返回一个对象,其中包含要公开的数据属性和方法列表,并且可供 Vue 组件使用。

组合函数将数据属性和方法的白名单列表返回给 Vue 组件。它可以根据需要定义形成私有数据属性和方法,而无需将它们暴露给 Vue 组件。您公开运行您正在构建的功能所需的任何内容。

您可以将代码从函数内部移动到组件内部的useProducts()函数体setup()。这是绝对合法的。但是,从代码重用的角度来看,建议将单个组合函数移出到它自己的 JavaScript 文件中。

现在回到 Vue 组件,该setup()函数返回与组合函数返回的数据属性和方法相同的列表。Vue 组件将这些属性和方法视为在组件本身上定义的。在组件模板内,您可以绑定到setup()函数内定义的数据属性和方法。

人们不禁会注意到 Composition API 带来的诸多优势,例如:

组合函数或setup()函数不遵循组件选项 API。因此,一项功能可以实现为单个组合函数,甚至可以实现为函数内部的一个块setup()。不再需要在组件选项之间传播功能实现。您可以将数据属性、私有方法、公共方法、计算属性、监视方法等放在一起。

对于任何普通的 JavaScript 开发人员来说,组合函数看起来都非常熟悉。它没有什么特别之处。只是一个普通的 JavaScript 函数。工具体验得到了改善。现在,您可以准确指定从任何组合函数返回的内容。这与混合选项与组件选项合并时在运行时发生的自动魔术进行比较。

更好、更清晰的代码重用和共享。现在,每个功能都在其自己的组合函数或 JavaScript 文件中实现。

演示

现在您已经了解了新的 Vue 组合 API 的理论,让我们看看如何开发一个包含两个视图的基本 Vue 2 应用程序:

第一个是“下载图像”视图,允许用户查看和下载图像文件。第二个是下载 PDF视图,允许用户查看和下载 PDF 文件。

我将首先按照传统的 Vue 组件选项 API 方法构建这个应用程序。然后,我将增强此方法以利用 Mixins 进行代码重用。最后,我将转换这个应用程序以使用新的 Vue Composition API。

首先从以下 GitHub存储库[email protected]:bhaidar/vue3-composition-api.git克隆应用程序源代码。

完成后,切换到dev * 分支,并运行以下命令以启动应用程序。


npm i
npm run serve

应用导航栏允许您在两个可用视图之间切换。

传统的 Vue 组件/选项 API

包含重复组件代码的完整解决方案可以在feat/repeated-inside-components分支中找到

为了构建下载文件功能,我将分别在两个视图中实现此功能。这两个组件的实现将是相似的,因此,我将只向您展示其中一种实现。

将文件内容替换为DownloadPdf.vue以下内容:


<template>
  <div class="download-pdf">
     <DownloadFileButton link="Download Pdf File" @download-file-btn="downloadPdf('dummy.pdf')" />
      <embed src="/assets/dummy.pdf" type="application/pdf">
  </div>
</template>

<script>
import axios from '@/http-common.js';
import DownloadFileButton from '@/components/DownloadFileButton.vue';

export default {
  data() {
    return {
      status: {
        showSpinner: false,
        showSuccess: false,
        showErrors: false,
      },
    };
  },
  components: {
    DownloadFileButton,
  },
  methods: {
    downloadPdf(fileName) {
      this.status = { ...this.status, showSpinner: true };

      axios.get(`/assets/${fileName}`, {
        responseType: 'arraybuffer',
        headers: {
          Accept: 'application/pdf',
        },
      }).then((response) => {
        this.status = { ...this.status, showSpinner: false, showSuccess: true };

        const arrayBufferView = new Uint8Array(response.data);
        const blob = new Blob([arrayBufferView], {
          type: 'application/pdf',
        });
        const urlCreator = window.URL || window.webkitURL;
        const fileUrl = urlCreator.createObjectURL(blob);
        const fileLink = document.createElement('a');
        fileLink.href = fileUrl;
        fileLink.setAttribute('download', `${this.randomNumber()}-${fileName}`);
        document.body.appendChild(fileLink);
        fileLink.click();
      }).catch(() => {
        this.status = { ...this.status, showSpinner: false, showErrors: true };
      });
    },
    randomNumber() {
      return Math.floor(Math.random() * 100);
    },
  },
};
</script>

该组件定义了一些数据选项来跟踪下载过程,并相应地显示反馈。

downloadPdf()方法利用axios HTTP Client 向服务器请求 PDF 文件。一旦文件内容可用,它会创建一个超链接元素,其 URL 指向下载文件的blob Url,并模拟链接上的单击事件,以便强制在浏览器中下载文件。

在现实生活中的示例中,您很可能拥有一个处理下载文件的后端 API。

DownloadImage.vue视图中重复相同的代码。重复相同的代码,没有任何代码重用或共享。

让我们看看如何通过引入 mixin 来改进这段代码。

在组件中使用 Mixin

完整的 mixins 解决方案可以在feat/using-mixins分支中找到

我现在将视图中重复的代码重构为单个 mixin 文件。在 path 下添加一个新的 mixin /src/mixins/DownloadFile.mixin.js。将以下内容放入此新文件中:


import axios from '@/http-common.js';

export default {
  data() {
    return {
      status: {
        spinner: false,
        sucess: false,
        errors: null,
      },
    };
  },
  computed: {
    showSpinner() {
      return this.status.spinner;
    },
    showSuccess() {
      return this.status.success;
    },
    showErrors() {
      return this.status.errors;
    },
  },
  methods: {
    async downloadFile(fileName, contentType) {
      this.status = { ...this.status, spinner: true };

      axios.get(`/assets/${fileName}`, {
        responseType: 'arraybuffer',
        headers: {
          Accept: contentType,
        },
      }).then(value => new Promise(resolve => setTimeout(resolve, 2000, value)))
        .then((response) => {
          const blobResults = this.getBlobResults(response.data, contentType);
          const blobFileUrl = this.createBlobFileUrl(blobResults);
          const fileLink = this.generateFileLink(fileName, blobFileUrl);

          this.status = { ...this.status, spinner: false, success: true };

          // Download file
          fileLink.click();
        }).catch((err) => {
          this.status = { ...this.status, spinner: false, errors: err };
        });
    },
    createBlobFileUrl(blob) {
      const urlCreator = window.URL || window.webkitURL;
      return urlCreator.createObjectURL(blob);
    },
    generateFileLink(fileName, blobUrl) {
      const fileLink = document.createElement('a');

      fileLink.href = blobUrl;
      fileLink.setAttribute('download', `${this.randomNumber()}-${fileName}`);

      document.body.appendChild(fileLink);

      return fileLink;
    },
    getBlobResults(fileContent, contentType) {
      const arrayBufferView = new Uint8Array(fileContent);
      return new Blob([arrayBufferView], {
        type: contentType,
      });
    },
    randomNumber() {
      return Math.floor(Math.random() * 100);
    },
    wait(ms, value) {
      return new Promise(resolve => setTimeout(resolve, ms, value));
    },
  },
};

代码现在更加模块化,并分成更小且可读的函数。在这个 mixin 中定义了相同的数据属性。此外,还定义了一个新的通用方法downloadFile()以适应任何文件下载。

切换回DownloadPDF.vue视图,并通过粘贴更新组件:


<template>
  <div class="download-pdf">
     <DownloadFileButton link="Download Pdf File" @download-file-btn="downloadPdf('dummy.pdf')" />

     <div class="download-image__results">
        <span v-if="showSpinner" class="spinner">Downloading ...</span>
        <span v-if="showSuccess" class="success">File downloaded successfully!</span>
        <span v-if="showErrors" class="failure">File failed to download!</span>
      </div>

      <embed src="/assets/dummy.pdf" type="application/pdf">
  </div>
</template>

<script>
import DownloadFileMixin from '@/mixins/DownloadFile.mixin';
import DownloadFileButton from '@/components/DownloadFileButton.vue';

export default {
  mixins: [DownloadFileMixin],
  components: {
    DownloadFileButton,
  },
  methods: {
    downloadPdf(fileName) {
      this.downloadFile(fileName, 'application/pdf');
    },
  },
};
</script>

该组件现在更加简洁。它导入DownloadFile.mixin.js文件,并将其注入到 Vue 组件的 mixins 选项中。

现在显示一条消息,指示文件下载的开始、成功和失败阶段。

mixin 公开了一个方法,即downloadFile(). 组件调用此方法下载 PDF 文件。

最后,让我们改进一下代码,引入 Vue Composition API。

使用组合 API

完整的 mixins 解决方案可以在feat/composition-api分支找到

要开始使用 Vue 3 组合 API,您不必等到 Vue 3 发布。Vue 团队为任何 Vue 2 应用程序提供了 Composition API。

通过安装以下 NPM 包将 Composition API 添加到您的应用程序:


npm install --save @vue/composition-api

安装库后,转到main.jsapp 文件夹中的文件,并添加代码以告诉 Vue 使用此库或插件。


import Vue from 'vue';
import VueCompositionApi from '@vue/composition-api';
import App from './App.vue';
import router from './router';

Vue.config.productionTip = false;

Vue.use(VueCompositionApi);

new Vue({
  router,
  render: h => h(App),
}).$mount('#app');

就这样!现在您可以开始在您的应用程序中使用 Composition API。

让我们在 path 下添加一个新的组合函数/src/cmp-functions/download-file.js。将其内容替换为以下内容:


/* eslint-disable import/prefer-default-export */
import { ref, computed } from '@vue/composition-api';
import axios from '@/http-common.js';

export const useDownloadFile = () => {
  const status = ref({
    spinner: false,
    success: false,
    errors: null,
  });

  const randomNumber = () => Math.floor(Math.random() * 100);

  const showSpinner = computed(() => status.spinner);

  const showSuccess = computed(() => status.success);

  const showErrors = computed(() => status.errors);

  const createBlobFileUrl = (blob) => {
    const urlCreator = window.URL || window.webkitURL;
    return urlCreator.createObjectURL(blob);
  };

  const generateFileLink = (fileName, blobUrl) => {
    const fileLink = document.createElement('a');

    fileLink.href = blobUrl;
    fileLink.setAttribute('download', `${randomNumber()}-${fileName}`);

    document.body.appendChild(fileLink);

    return fileLink;
  };

  const getBlobResults = (fileContent, contentType) => {
    const arrayBufferView = new Uint8Array(fileContent);
    return new Blob([arrayBufferView], {
      type: contentType,
    });
  };

  const downloadFile = async (fileName, contentType) => {
    status.value = { spinner: true, success: false, errors: null };

    axios.get(`/assets/${fileName}`, {
      responseType: 'arraybuffer',
      headers: {
        Accept: contentType,
      },
    }).then(value => new Promise(resolve => setTimeout(resolve, 2000, value)))
      .then((response) => {
        const blobResults = getBlobResults(response.data, contentType);
        const blobFileUrl = createBlobFileUrl(blobResults);
        const fileLink = generateFileLink(fileName, blobFileUrl);

        status.value = { spinner: false, success: true, errors: null };

        // Download file
        fileLink.click();
      }).catch((err) => {
        status.value = { spinner: false, success: false, errors: err};
      });
  };

  return {
    showSpinner, showSuccess, showErrors, downloadFile,
  };
};

这段代码你现在应该很熟悉了。您看到的唯一新事物是一些计算属性的定义。

您可以使用computed()函数在 Composition API 中定义一个新的计算属性。此函数接受返回值的回调函数。这是计算属性的值。正如您从 Vue 2 中了解的那样,它会跟踪对基础数据属性的任何更改,并相应地运行。

组合函数利用useDownloadFile()了几个私有函数。它仅公开 Vue 组件所需的内容,而不是公开所有实现。

这就是组合功能。

让我们回到DownloadPdf.vue视图来导入这个函数并使用它。将视图的内容替换为以下内容:


<template>
  <div class="download-pdf">
     <DownloadFileButton link="Download Pdf File"
      @download-file-btn="downloadFile('dummy.pdf', 'application/pdf')" />

     <div class="download-image__results">
        <span v-if="showSpinner" class="spinner">Downloading ...</span>
        <span v-if="showSuccess" class="success">File downloaded successfully!</span>
        <span v-if="showErrors" class="failure">File failed to download!</span>
      </div>

      <embed src="/assets/dummy.pdf" type="application/pdf">
  </div>
</template>

<script>
import { useDownloadFile } from '@/cmp-functions/download-file';
import DownloadFileButton from '@/components/DownloadFileButton.vue';

export default {
  components: {
    DownloadFileButton,
  },
  setup() {
    const {
      showSpinner, showSuccess, showErrors, downloadFile,
    } = useDownloadFile();

    return {
      showSpinner, showSuccess, showErrors, downloadFile,
    };
  },
};
</script>

该组件导入useDownloadFile()组合功能。它从组合函数中提取计算的属性和downloadFile()方法,并从函数内部返回它们setup()

要下载文件,组件调用该downloadFile(‘dummy.pdf’, ‘application/pdf’)方法,传递要下载的 PDF 文件的名称和文件的内容类型。为了显示下载进度,组件将 UI 绑定到由组合函数定义的计算属性。

结论

Vue 3 组合 API 是可选的!

我很确定您可以看到使用新的 Vue 3 组合 API 时的价值和好处。在我看来,最显着的增强是在单个组合函数中构建单个功能,而无需在 Vue 选项(选项 API)之间传播实现。

此外,新的工具体验和智能感知可以更轻松地查看您从组合函数中导入的内容,以及您向 Vue 组件公开的内容。这在编码时带来了令人难以置信的体验。

您是否开始在您的应用程序中使用新的 Composition API 将取决于您想要实现的目标。

我绝对建议在需要代码重用和共享的具有许多组件的大型应用程序中使用新 API!

同样,如果您厌倦了组件选项 API 以及通过将其分散到不同的可用选项来构建功能的概念,那么是时候开始使用这个新的 API 了!

祝你快乐!

猜你喜欢

转载自blog.csdn.net/qq_22182989/article/details/125396438