概述

Vue 2.x 及以前的高阶组件的组织形式或多或少都会面临一些问题,特别是在需要处理重复逻辑的项目中,一旦开发者组织项目结构组织得不好,组件代码极有可能被人诟病为“胶水代码”。而在 Vue 2.x 及之前的版本,解决此类问题的办法大致是下面的方案:

  • mixin
  • 函数式组件
  • slots

笔者维护的项目也需要处理大量复用逻辑,在这之前,笔者一直尝试使用mixin的方式来实现组件的复用。有些问题也一直会对开发者和维护者造成困惑,如一个组件同时mixin多个组件,很难分清对应的属性或方法写在哪个mixin里。其次,mixin的命名空间冲突也可能造成问题。难以保证不同的mixin不用到同一个属性名。为此,官方团队提出函数式写法的意见征求稿,也就是RFC:Function-based component API。使用函数式的写法,可以做到更灵活地复用组件,开发者在组织高阶组件时,不必在组件组织上考虑复用,可以更好地把精力集中在功能本身的开发上。

注:本文只是笔者使用vue-function-api提前体验 Vue Function API ,而这个 API 只是 Vue 3.0 的 RFC,而并非与最终 Vue 3.x API 一致。发布后可能有不一致的地方。

在 Vue 2.x 中使用

要想提前在Vue 2.x中体验 Vue Function API ,需要引入vue-function-api,基本引入方式如下:

import Vue from 'vue';
import { plugin as VueFunctionApiPlugin } from 'vue-function-api';Vue.use(VueFunctionApiPlugin);

基本组件示例

先来看一个基本的例子:

<template><div><span>count is {{ count }}</span><span>plusOne is {{ plusOne }}</span><button @click="increment">count++</button></div>
</template><script>
import Vue from 'vue';
import { value, computed, watch, onMounted } from 'vue-function-api';export default {setup(props, context) {// reactive stateconst count = value(0);// computed stateconst plusOne = computed(() => count.value + 1);// methodconst increment = () => {count.value++;};// watchwatch(() => count.value * 2,val => {console.log(`count * 2 is ${val}`);});// lifecycleonMounted(() => {console.log(`mounted`);});// expose bindings on render contextreturn {count,plusOne,increment,};},
};
</script>

详解

setup

setup函数是Vue Function API 构建的函数式写法的主逻辑,当组件被创建时,就会被调用,函数接受两个参数,分别是父级组件传入的props和当前组件的上下文context。看下面这个例子,可以知道在context中可以获取到下列属性值

const MyComponent = {props: {name: String},setup(props, context) {console.log(props.name);// context.attrs// context.slots// context.refs// context.emit// context.parent// context.root}
}

value & state

value函数创建一个包装对象,它包含一个响应式属性value

那么为何要使用value呢,因为在JavaScript中,基本类型并没有引用,为了保证属性是响应式的,只能借助包装对象来实现,这样做的好处是组件状态会以引用的方式保存下来,从而可以被在setup中调用的不同的模块的函数以参数的形式传递,既能复用逻辑,又能方便地实现响应式。

直接获取包装对象的值必须使用.value,但是,如果包装对象作为另一个响应式对象的属性,Vue内部会通过proxy来自动展开包装对象。同时,在模板渲染的上下文中,也会被自动展开。

import { state, value } from 'vue-function-api';
const MyComponent = {setup() {const count = value(0);const obj = state({count,});console.log(obj.count) // 作为另一个响应式对象的属性,会被自动展开obj.count++ // 作为另一个响应式对象的属性,会被自动展开count.value++ // 直接获取响应式对象,必须使用.valuereturn {count,};},template: `<button @click="count++">{{ count }}</button>`,
};

如果某一个状态不需要在不同函数中被响应式修改,可以通过state创建响应式对象,这个state创建的响应式对象并不是包装对象,不需要使用.value来取值。

watch & computed

watchcomputed的基本概念与 Vue 2.x 的watchcomputed一致,watch可以用于追踪状态变化来执行一些后续操作,computed用于计算属性,用于依赖属性发生变化进行重新计算。

computed返回一个只读的包装对象,和普通包装对象一样可以被setup函数返回,这样就可以在模板上下文中使用computed属性。可以接受两个参数,第一个参数返回当前的计算属性值,当传递第二个参数时,computed是可写的。

import { value, computed } from 'vue-function-api';const count = value(0);
const countPlusOne = computed(() => count.value + 1);console.log(countPlusOne.value); // 1count.value++;
console.log(countPlusOne.value); // 2// 可写的计算属性值
const writableComputed = computed(// read() => count.value + 1,// writeval => {count.value = val - 1;},
);

watch第一个参数和computed类似,返回被监听的包装对象属性值,不过另外需要传递两个参数:第二个参数是回调函数,当数据源发生变化时触发回调函数,第三个参数是options。其默认行为与 Vue 2.x 有所不同:

  • lazy:是否会在组件创建时就调用一次回调函数,与 Vue 2.x 相反,lazy默认是false,默认会在组件创建时调用一次。
  • deep:与 Vue 2.x 的 deep 一致
  • flush:有三个可选值,分别为 'post'(在渲染后,即nextTick后才调用回调函数),'pre'(在渲染前,即nextTick前调用回调函数),'sync'(同步触发)。默认值为'post'。
// double 是一个计算包装对象
const double = computed(() => count.value * 2);watch(double, value => {console.log('double the count is: ', value);
}); // -> double the count is: 0count.value++; // -> double the count is: 2

watch多个被包装对象属性时,参数均可以通过数组的方式进行传递,同时,与 Vue 2.x 的vm.$watch一样,watch返回取消监听的函数:

const stop = watch([valueA, () => valueB.value],([a, b], [prevA, prevB]) => {console.log(`a is: ${a}`);console.log(`b is: ${b}`);}
);stop();

注意:在RFC:Function-based component API初稿中,有提到effect-cleanup,是用于清理一些特殊情况的副作用的,目前已经在提案中被取消了。

生命周期

所有现有的生命周期都有对应的钩子函数,通过onXXX的形式创建,但有一点不同的是,destoryed钩子函数需要使用unmounted代替:

import { onMounted, onUpdated, onUnmounted } from 'vue-function-api';const MyComponent = {setup() {onMounted(() => {console.log('mounted!');});onUpdated(() => {console.log('updated!');});// destroyed 调整为 unmountedonUnmounted(() => {console.log('unmounted!');});},
};

一些思考

上面的详解部分,主要抽取的是 Vue Function API 的常见部分,并非RFC:Function-based component API的全部,例如其中的依赖注入,TypeScript类型推导等优势,在这里,由于篇幅有限,想要了解更多的朋友,可以点开RFC:Function-based component API查看。个人也在Function-based component API讨论区看到了更多地一些意见:

  • 由于底层设计,在setup取不到组件实例this的问题,这个问题在笔者尝试体验时也遇到了,期待正式发布的 Vue 3.x 能够改进这个问题。

  • 对于基本类型的值必须使用包装对象的问题:在 RFC 讨论区,为了同时保证TypeScript类型推导、复用性和保留Vue的数据监听,包装属性必须使用.value来取值是讨论最激烈的

  • 关于包装对象valuestate方法命名不清晰可能导致开发者误导等问题,已经在Amendment proposal to Function-based Component API这个提议中展开了讨论:

  • setup() {const state = reactive({count: 0,});const double = computed(() => state.count * 2);function increment() {state.count++;}return {...toBindings(state), // retains reactivity on mutations made to `state`double,increment,};
    }
  • 引入reactive API 和 binding API,其中reactive API 类似于 state API , binding API 类似于 value API。
  • 之前使用的方法名state在 Vue 2.x 中可能被用作组件状态对象,导致变量命名空间的冲突问题,团队认为将state API 更名为 reactive 更为优雅。开发者能够写出const state = ... ,然后通过state.xxxx这种方式来获取组件状态,这样也相对而言自然一些。
  • value方法用于封装基本类型时,确实会出现不够优雅的.value的情况,开发者可能会在直接对包装对象取值时忘记使用.value,修正方案提出的 reactive API,其含义是创建响应式对象,初始化状态state就使用reactive创建,可保留每项属性的gettersetter,这么做既满足类型推导,也可以保留响应式引用,从而可在不同模块中共享状态值的引用。
  • reactive可能导致下面的问题,需要引入binding API。 解决,如使用reactive创建的响应式对象,对其使用拓展运算符...时,则会丢失对象的gettersetter,提供toBindings方法能够保留状态的响应式。

当然,目前 Vue Function API 还处在讨论阶段,Vue 3.0 还处在开发阶段,还是期待下半年 Vue 3.0 的初版问世吧,希望能给我们带来更多的惊喜。


阅读目录(置顶)(长期更新计算机领域知识)https://blog.csdn.net/weixin_43392489/article/details/102380691

阅读目录(置顶)(长期更新计算机领域知识)https://blog.csdn.net/weixin_43392489/article/details/102380882

第十六期:Vue 3.0 前瞻,体验 Vue Function API相关推荐

  1. 未雨绸缪,迎接运维新时代—— Tech Neo第十六期技术沙龙

    运维发展历程与工业革命异曲同工,工业的三次革命分别是机械化.电气化与信息化,运维则是原始手工.脚本与自动化工具.那么工业4.0悄然来临的今天,智能化又将会给运维带来哪些影响?坦白讲,AIOps是新概念 ...

  2. android仿秒拍源码,你所不知道的程序员 程序员其实真的很…【Bus Weekly】三十六期...

    原标题:你所不知道的程序员 程序员其实真的很-[Bus Weekly]三十六期 快,点击蓝色"字体"关注这个公众号,一起涨姿势- 现如今,程序员在中国的科技 圈可以说已经达到了举足 ...

  3. 【移动战略说·第十六期】创业者面临的机遇与挑战(杭州站)

    经济高速发展的时代,消费者需求越来越高,创业政策的放宽,大量谋求改变命运的创业者涌入市场,创业早已不稀奇.但随着全民创业的趋势不断发展,创业市场竞争日趋激烈,创业似乎越来越难了.但即便如此,越来越多的 ...

  4. 【悟空云课堂】第二十六期:通过错误消息导致的信息暴露(CWE-209:Generation of Error Message Containing Sensitive Information)

    关注公众号"中科天齐软件安全中心"(id:woocoom),一起涨知识! 该栏目为中科天齐全新规划的悟空云课堂,每周五下午18:00准时上线,旨在科普软件安全相关知识,助力企业有效 ...

  5. html5诗歌作业,《品味诗词群》总第七十六期作业

    <品味诗词群>总第七十六期作业 老物件总能给人以无限的怀想.本期作业以<收录机>为主题,以图题诗.韵部不限,诗词均可,要求不变,期待精彩! 品味诗词雅韵,传承华夏文明. 七绝 ...

  6. 鲁大师电动车智能化测评报告第十六期:破局者与搅局者之争

    鲁大师第十六期智能化电动车测评排行榜数据来源于鲁大师智慧实验室,测评的车型均为市面上主流品牌的主流车型.截止目前,鲁大师智能化电动车测评的车型高达80余种,且还在不断增加和丰富中. 1.测评依据 鲁大 ...

  7. ipv4v6双栈技术_【第二十六期】IPv6系列应用篇——数据中心IPv4/IPv6双栈架构探讨...

    背景 2017年,工业和信息化部发布了<推进互联网协议第六版(IPv6)规模部署行动计划>的通知,从国家层面推动下一代IP技术--IPv6的普及和应用.目标到2020年末,IPv6活跃用户 ...

  8. 第三十六期:学 Java 网络爬虫,需要哪些基础知识?

    说起网络爬虫,大家想起的估计都是 Python ,诚然爬虫已经是 Python 的代名词之一,相比 Java 来说就要逊色不少.有不少人都不知道 Java 可以做网络爬虫,其实 Java 也能做网络爬 ...

  9. 极乐技术周报(第十六期)

    2019独角兽企业重金招聘Python工程师标准>>> 几年前的真事,项目组 review 同事代码,notepad 打开 web.config 看数据库连接串,其中写着 " ...

最新文章

  1. halcon的算子清点: Chapter 6 图标对象操作(Graphics)
  2. ViBe背景建模算法
  3. php-7.1.0,PHP 7.4.0 Alpha 1 v7.4.0 官方最新版
  4. leetcode 54. 螺旋矩阵(递归)
  5. 综述 | 基于特征的视觉同步定位和建图
  6. api接口文档生成工具apipost
  7. 大厂面试快问快答,10分钟搞定MySQL夺命20问,你都能接住吗?
  8. 中断系统与定时/计数计时器
  9. 【520表白】C语言开发《浪漫流星雨》表白程序,源码来了!
  10. JavaScript ArrayBuffer浅析
  11. pythonocc 如何把TopoDS_Edge转换成Geom_Curve
  12. iOS系统神奇app,别以为你穿了马甲我就找不到你了
  13. 什么是JSON。如何使用JSON。
  14. 【机器人】谐波减速器和RV减速器的自我理解
  15. 达观AI+知识图谱技术在数字档案馆建设中的探索
  16. 验证码--数字和英文
  17. MATLAB中如何用对数方式显示图形坐标?
  18. 被动信息收集----指纹识别(CMS识别)
  19. 【真】华为云计算HCIE实验-FCD环境搭建
  20. [渝粤教育] 郑州升达经贸管理学院 投融资决策分析 参考 资料

热门文章

  1. CDU集训代码:基础算法和数据结构2
  2. 得到WebService应用程序的路径
  3. 流程管理软件如何适应变化
  4. subversion安装与配置备忘录
  5. 共享一些变态的签名,希望不太OLD
  6. html网页和cgi程序编程,CGI 编程方式学习
  7. 精通java益处_你真的精通Java吗?
  8. android系统耗电量大待机,安卓手机耗电快有什么解决办法吗 安卓手机待机耗电量大怎么办...
  9. php print div,JavaScrip实现PHP print_r的数功能(三种方法)
  10. 【电路补习笔记】4、二极管的参数与选型