速来报名!AICon北京站鸿蒙专场~ 了解详情
写点什么

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

评论

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

五分钟快速掌握Maven的核心概念

田维常

maven

写作感悟之无从下笔

JiangX

写作 28天写作

SpringCloud 微服务实现数据权限控制

Barry的异想世界

SpringCloud SpringBoot 2 权限认证 数据权限

没人告诉过你更复杂的缓存穿透怎么解决

艾小仙

架构

回顾2020年那些“领域第一本”,每一本都强烈推荐!

博文视点Broadview

握草,这些研发事故30%我都干过!

小傅哥

Java 小傅哥 28天写作 线上事故 系统秒杀

IndexedDB详解

程序那些事

大前端 程序那些事 indexedDB webtech 浏览器技术

Java 并发编程之 JMM & volatile 详解

vivo互联网技术

Java volatile JMM 指令重排序

DAPP软件开发|DAPP系统APP开发

系统开发

网络请求是如何发送出去的

kof11321

网络

【JS】防止浏览器控制台被直接查看(1)

德育处主任

JavaScript 大前端 js 28天写作

从标准到开发,解读基于MOF的应用模型管理

华为云开发者联盟

模型 ROMA 应用模型 mof

人脸识别门禁系统搭建,智慧小区实施方案

t13823115967

智慧平安小区平台开发

智能停车管理系统搭建,智慧小区智能化解决方案

t13823115967

智慧小区

十八般武艺玩转GaussDB(DWS)性能调优:SQL改写

华为云开发者联盟

数据库 sql 性能优化 GaussDB 算子

一名分布式存储工程师的技能树是怎样的?

焱融科技

分布式 存储 分布式存储 分布式文件

什么是ReadWriteMany?

焱融科技

Kubernetes 云原生 存储 焱融科技 持久化存储

微软开源WebUI自动化测试神器Playwright​​​​​​​

软测小生

微软 自动化测试 playwright webUI Web自动化测试

我是如何用几十个小时完成自己的3个flag

Sandy

没想到,学习带给我最宝贵的东西是底气

Sandy

SpringCloud 从入门到精通 04---支付模块 02

Felix

上链DAPP软件开发|上链DAPP系统APP开发

系统开发

开发效率提升15倍!批流融合实时平台在好未来的应用实践

Apache Flink

流计算 fink

STM32标准库开发实战指南

华为云开发者联盟

SMT32处理器 stm32 内核 寄存器

软件测试--数据库基础知识

测试人生路

数据库 软件测试

Java 程序经验小结:剖析方法重载

后台技术汇

28天写作

【吐血整理】超全golang面试题合集+golang学习指南+golang知识图谱+成长路线 一份涵盖大部分golang程序员所需要掌握的核心知识

小白debug

面试 新手指南 编程之路 职业成长 Go 语言

架构师 3 期 3 班 -week7- 作业

zbest

作业 week7

创业失败启示录|校园微生活(故事篇1)

阿萌

创业 28天写作 创业失败启示录 青城

springboot多模块配置问题

原来不悔

springboot Spring Frame

拍乐云语音聊天室SDK,助力非洲版陌陌“Mochat”打造粉丝经济

拍乐云Pano

音视频 RTC 语音聊天室 出海社交 社交泛娱乐

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