【ArchSummit】如何通过AIOps推动可量化的业务价值增长和效率提升?>>> 了解详情
写点什么

Vue.js 3:面向未来的编程

  • 2019-11-10
  • 本文字数:4612 字

    阅读完需:约 15 分钟

Vue.js 3:面向未来的编程

如果你对 Vue.js 感兴趣,那很可能知道这个框架的第 3 个版本即将发布(如果你在未来读这篇文章,我希望本文还具有意义)。这个新版本正在积极开发中,但是所有可能的功能都可以在单独的 RFC(request for comments,即意见征求)处查看:https://github.com/vuejs/rfcs。其中一项特性function-api,将极大地改变开发 Vue 应用程序的方式。


本文主要面向具有基本的 JavaScript 和 Vue 背景知识的人。


开篇之前:使用 Bit 来封装 Vue 组件以及它们的依赖和配置。通过更好的代码复用、更简单的维护和更少的开销来构建真正的模块应用。(译者注,这里是对 Bit 平台的推广)

当前的 API 有什么问题?

最好的方法是在一个例子中展示所有问题。因此,我们可以想象,我们需要实现一个组件,这个组件应该获取某个用户的数据并根据滚动偏移显示加载状态和顶栏。下面是最终结果:



你还可以点击链接查看在线演示。


跨组件抽取一些逻辑进行复用是一种好习惯。使用 Vue 2.x 版本的当前 API,有许多常见的模式,最著名的是:


  • Mixins(通过 mixins 选项)

  • 高阶组件(HOC)


因此,我们可以将滚动跟踪逻辑转移到一个 mixin,并将数据获取逻辑转移到一个高阶组件。典型的 Vue 实现如下。


滚动 mixin:


const scrollMixin = {    data() {        return {            pageOffset: 0        }    },    mounted() {        window.addEventListener('scroll', this.update)    },    destroyed() {        window.removeEventListener('scroll', this.update)    },    methods: {        update() {            this.pageOffset = window.pageYOffset        }    }}
复制代码


其中,我们增加了scroll事件监听,跟踪页面偏移并将值保存到pageOffset属性。


高阶组件如下:


import { fetchUserPosts } from '@/api'
const withPostsHOC = WrappedComponent => ({ props: WrappedComponent.props, data() { return { postsIsLoading: false, fetchedPosts: [] } }, watch: { id: { handler: 'fetchPosts', immediate: true } }, methods: { async fetchPosts() { this.postsIsLoading = true this.fetchedPosts = await fetchUserPosts(this.id) this.postsIsLoading = false } }, computed: { postsCount() { return this.fetchedPosts.length } }, render(h) { return h(WrappedComponent, { props: { ...this.$props, isLoading: this.postsIsLoading, posts: this.fetchedPosts, count: this.postsCount } }) }})
复制代码


其中,isLoadingposts分别针对加载状态和发布数据进行初始化。为了获取新id的数据,fetchPosts方法会在创建实例和props.id每次变化之后触发。


这并不是一个完整的高阶组件实现,但是针对这个例子,已经足够了。这里,我们只是包装了目标组件并传递原始属性以及数据请求相关的属性。


目标组件如下:


// ...<script>export default {    name: 'PostsPage',    mixins: [scrollMixin],    props: {        id: Number,        isLoading: Boolean,        posts: Array,        count: Number    }}</script>// ...
复制代码


为了获取指定 props,应该把它包装在创建的高阶组件中:


const PostsPage = withPostsHOC(PostsPage)
复制代码


包含模版和样式的完整组件链接在此

1.命名空间冲突

假设我们需要在我们的组件中增加update方法:


// ...<script>export default {    name: 'PostsPage',    mixins: [scrollMixin],    props: {        id: Number,        isLoading: Boolean,        posts: Array,        count: Number    },    methods: {        update() {            console.log('some update logic here')        }    }}</script>// ...
复制代码


如果你重新打开页面并滚动,顶栏不会再显示。这都是由于 mixin 的update方法的重写。这对于高阶组件也适用。如果你将数据域从fetchedPosts改为posts


const withPostsHOC = WrappedComponent => ({    props: WrappedComponent.props, // ['posts', ...]    data() {        return {            postsIsLoading: false,            posts: [] // fetchedPosts -> posts        }    },    // ...
复制代码


你将会得到如下报错:



报错的原因是封装组件已经用名字posts指定了组件。

2.来源不明

如果一段时间之后,你决定在组件中使用另一个 mixin:


// ...<script>export default {    name: 'PostsPage',    mixins: [scrollMixin, mouseMixin],// ...
复制代码


你能明确说明pageOffset属性来自哪个 mixin 吗?或者换个场景,两个 mixin 都可以有一个同名属性(比如说yOffset),后一个 mixin 的属性将覆盖前一个 mixin 的属性。这并不好,并且会导致许多不可预见的代码缺陷。

3.性能

高阶组件的问题是,我们需要仅仅因为逻辑复用而牺牲性能去创建单独的组件实例。

让我们来“setup”

让我们来看看下个 Vue.js 版本将提供什么可选方案,以及我们将如何适用基于函数的 API 解决同样的问题。


由于 Vue 3 还没有发布,所以创建了辅助插件——vue-function-api。这个插件提供 Vue 3.x 到 Vue 2.x 的版本的函数 API,用于创建下一代 Vue 应用程序。


首先,你需要进行安装:


$ npm install vue-function-api
复制代码


然后通过Vue.use()进行显式设置:


import Vue from 'vue'import { plugin } from 'vue-function-api'
Vue.use(plugin)
复制代码


基于函数的 API 主要新增了一个新的组件选项——setup()。顾名思义,这里是我们使用新的 API 的功能来设置我们的组件逻辑的地方。因此,让我们实现一个功能来根据滚动偏移显示顶栏,基本组件示例如下:


// ...<script>export default {  setup(props) {    const pageOffset = 0    return {      pageOffset    }  }}</script>// ...
复制代码


注意,setup函数接收解析过的 props 对象作为它的首个参数,而且这个props对象是响应式的。我们也返回了一个包含pageOffset属性的对象来暴露给模版的渲染上下文。这个属性也变成响应式的,但是只关于渲染上下文。我们可以像往常一样在模版中使用它:


<div class="topbar" :class="{ open: pageOffset > 120 }">...</div>
复制代码


但是,这个属性在每次滚动事件中应该是变化的。为了实现这点,我们需要在这个组件被挂载时增加一个滚动事件监听器,而这个组件卸载时移除这个监听器。valueonMountedonUnmounted API 函数就是为了实现这些目标而存在:


// ...<script>import { value, onMounted, onUnmounted } from 'vue-function-api'export default {  setup(props) {    const pageOffset = value(0)    const update = () => {        pageOffset.value = window.pageYOffset    }        onMounted(() => window.addEventListener('scroll', update))    onUnmounted(() => window.removeEventListener('scroll', update))        return {      pageOffset    }  }}</script>// ...
复制代码


注意,在 Vue 2.x 版本中,所有生命周期 hooks 都有一个可以在setup()中使用的等效的onXXX函数。


你可能也注意到pageOffset变量包含一个单个的响应式属性:.value。我们需要使用这个包装过的属性,因为在 JavaScript 中像 numbers 和 strings 这样的原始值不是引用传递。值包装器为任何值类型提供了一种方式来传递可变的响应式的引用。


下面是pageOffset对象:



下一步是实现用户数据获取。和使用基于选项的 API 时一样,你可以使用基于函数的 API 来声明计算过的值和观察者:


// ...<script>import {    value,    watch,    computed,    onMounted,    onUnmounted} from 'vue-function-api'import { fetchUserPosts } from '@/api'export default {  setup(props) {    const pageOffset = value(0)    const isLoading = value(false)    const posts = value([])    const count = computed(() => posts.value.length)    const update = () => {      pageOffset.value = window.pageYOffset    }        onMounted(() => window.addEventListener('scroll', update))    onUnmounted(() => window.removeEventListener('scroll', update))        watch(      () => props.id,      async id => {        isLoading.value = true        posts.value = await fetchUserPosts(id)        isLoading.value = false      }    )        return {      isLoading,      pageOffset,      posts,      count    }  }}</script>// ...
复制代码


计算值类似 2.x 版本的计算属性:它只跟踪它的依赖,并且只在依赖改变时重新求值。传递给watch的第一个参数称为“源”,可以是如下之一:


  • 一个 getter 函数

  • 一个值包装器

  • 一个包含两个以上类型的数组


第二个参数是一个回调函数,只在从 getter 返回的值或值包装器改变时调用。


我们只是使用基于函数的 API 来实现目标组件。 下一步是使所有这些逻辑可复用。

解构

最有趣到部分是,为了复用部分逻辑的代码,我们只能将它抽取到一个组合函数并返回响应式状态:


// ...<script>import {    value,    watch,    computed,    onMounted,    onUnmounted} from 'vue-function-api'import { fetchUserPosts } from '@/api'function useScroll() {    const pageOffset = value(0)    const update = () => {        pageOffset.value = window.pageYOffset    }    onMounted(() => window.addEventListener('scroll', update))    onUnmounted(() => window.removeEventListener('scroll', update))    return { pageOffset }}function useFetchPosts(props) {    const isLoading = value(false)    const posts = value([])    watch(        () => props.id,        async id => {            isLoading.value = true            posts.value = await fetchUserPosts(id)            isLoading.value = false        }    )    return { isLoading, posts }}export default {    props: {        id: Number    },    setup(props) {        const { isLoading, posts } = useFetchPosts(props)        const count = computed(() => posts.value.length)        return {            ...useScroll(),            isLoading,            posts,            count        }    }}</script>// ...
复制代码


注意我们是如何使用useFetchPostsuseScroll函数来返回响应式属性的。这些函数可以被存储在单独的文件中,并且任何其它组件中使用。相较于基于选项的方案:


  • 暴露到模板的属性拥有清晰的来源,因为它们是组合函数返回的值;

  • 从组合函数返回的值是任意命名的,因此没有命名空间冲突;

  • 没有仅仅因为逻辑复用目的而创建的不必要的组件实例。


官方RFC页面还列举了许多其它好处。


本文用到的所有代码示例链接在此


你还可以在这个链接查看组件的在线示例。

结论

正如你所见,Vue 基于函数的 API 展示了一个干净灵活的方式来组合组件内部以及组件之间的逻辑,而没有任何基于选项的 API 的缺陷。想象一下,对于任何类型的项目——从小型到大型再到复杂的 Web 应用程序,组合函数会多么有用。


作者介绍


Taras Batenkov 主要关注 Web 前端和数据科学。


原文链接


https://blog.bitsrc.io/vue-js-3-future-oriented-programming-54dee797988b


2019-11-10 08:003025

评论

发布
暂无评论
发现更多内容

区块链永续合约交易所搭建,永续合约系统开发

中国云计算的云栖“坐标”

脑极体

测试

云龙

大作业

Geek_2e7dd7

LeetCode题解:1. 两数之和,Map+队列+双指针,JavaScript,详细注释

Lee Chen

大前端 LeetCode

直播风口,是什么在支撑教育、电商、泛娱乐等场景?

腾讯云音视频

腾讯云 音视频 云直播 点播

Http请求中如何保持状态?

架构师修行之路

非科班进大厂必备算法

我是程序员小贱

面试 算法

STL总结与常见面试题

C语言与CPP编程

c c++ 编程 编程语言 stl

前端必会的七种排序算法

执鸢者

算法 大前端

甲方日常 14

句子

Java 工作 随笔杂谈 日常

你还在手撕微服务?快试试 go-zero 的微服务自动生成

万俊峰Kevin

微服务 microservice go-zero Go 语言

SwiftGG 文档翻译笔记1-基础部分函数闭包

随想之乐观估计

云杉

Spring事务是如何应用到你的业务场景中的?

AI乔治

Java spring 架构 微服务 springboot

Spring 5 中文解析测试篇-WebTestClient

青年IT男

单元测试 Spring5

SpringBoot RabbitMQ消息队列的重试、超时、延时、死信队列

Barry的异想世界

RabbitMQ springboot 消息队列 死信队列 延时队列

【写作群星榜】9.1~9.13写作平台优秀作者 & 文章排名

InfoQ写作社区官方

写作平台 排行榜 文章

为什么区块链能成为全球贸易的助推器

CECBC

区块链 金融 国际贸易

2020英特尔大师挑战赛携手华硕ROG激战成都

E科讯

架构师课程大作业 知识图谱

杉松壁

测试

解决分布式session问题

架构师修行之路

分布式 架构设计 session

欧洲央行近期将决定是否建立官方数字货币

CECBC

数字货币 欧央行

RabbitMQ 重要概念介绍

hepingfly

Java RabbitMQ 消息队列 JMS

你需要开始做点什么,否则你会一直忙一直忙

老胡爱分享

学习 思维方式 行动派 随笔杂谈 拖延症

组合模式

纯纯

理财专题一

TCA

C/C++基础之sizeof使用

C语言与CPP编程

c c++ 编程 编程语言

Flink SQL CDC 上线!我们总结了 13 条生产实践经验

Apache Flink

flink

基于 Flink 的典型 ETL 场景实现方案

Apache Flink

flink

Vue.js 3:面向未来的编程_语言 & 开发_Taras Batenkov_InfoQ精选文章