【ArchSummit架构师峰会】探讨数据与人工智能相互驱动的关系>>> 了解详情
写点什么

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

评论

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

Angular tsconfig.json 文件里的 paths 用途

Jerry Wang

typescript 前端开发 angular SAP UI5 9月月更

SQL 嵌套 N 层太长太难写怎么办?

jiangxl

纠删码在实时视频流中的应用丨Dev for Dev 专栏

声网

音视频 人工智能’

NEO FANTASY:回合制策略游戏在ACGN文化与GameFi中的新探索 09-07

鳄鱼视界

云备份服务CBR

创意时空

面试突击81:什么是跨域问题?如何解决?

王磊

Java 面试

C++学习------clocale头文件的源码学习

桑榆

c++ 源码阅读 9月月更

每日一R「22」内存:堆与栈

Samson

学习笔记 ​Rust 9月月更

如何让百度搜索结果显示网站 logo

源字节1号

网站建设 网站开发

NFT开发公司带你了解目前NFT开发属于什么状态

开源直播系统源码

区块链 NFT 数字藏品

如何快速的部署一个静态页面到 Web3.0 上?5 分钟解密

掘金安东尼

前端 Web3.0 9月月更

「趣学前端」来逛逛数字博物馆

叶一一

小程序 前端 9月月更

Go vs Python,我该选哪一门语言?

宇宙之一粟

Python 编程语言 Go 语言 9月月更

当代用电行为大赏:有人心疼电费,有人靠屋顶光伏“理财”

白洞计划

mysql存储引擎

急需上岸的小谢

9月月更

SD-WAN网络编排原理

阿泽🧸

9月月更 网络编排

【文本检测与识别-白皮书】第一章:技术背景

合合技术团队

文字识别 文本 人工智能’

MySQL不同隔离级别,都使用了什么锁?

Java全栈架构师

Java MySQL 数据库 程序员 程序人生

构筑校园防线  “云资环”助力精准防控

神奇视野

Unity 关于低版本是否可以引用高版本构建内容的可行性验证

CoderZ

C# dll Unity3D 9月月更

Java进阶(九)正则表达式

No Silver Bullet

Java 正则表达式 9月月更

设计模式的艺术 第四章简单工厂设计模式练习(使用简单工厂模式设计一个可以创建不同几何形状(如圆形、方形和三角形等)的绘图工具,每个图形都具有绘制draw()和擦除erase()两个方法,要求在绘制不支持的几何图形时,提示UnSupportedShape)

代廉洁

设计模式的艺术

给我一起学jdbc之sql注入

楠羽

JDBC 笔记 9月月更

VUE 如何格式化数字

HoneyMoose

[Go WebSocket] 单房间的聊天室

HullQin

Go golang 后端 websocket 9月月更

2022-09-07:给你一个由正整数组成的数组 nums 。 数字序列的 最大公约数 定义为序列中所有整数的共有约数中的最大整数。 例如,序列 [4,6,16] 的最大公约数是 2 。 数组的一个

福大大架构师每日一题

算法 rust 福大大

微信小程序挖坑汇总

Shine

微信小程序

数据治理(十):Atlas案例演示

Lansonli

数据治理 9月月更

关于C语言结构体(struct),你不知道的用法?(初阶篇)

Albert Edison

指针 C语言 结构体 9月月更

概述大数据技术在智能运维中四大挑战

穿过生命散发芬芳

智能运维 9月月更

2022年中国新能源汽车用户体验指数(UEI)

易观分析

新能源汽车 UEI

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