javascript 您所在的位置:网站首页 vue3兼容性 javascript

javascript

#javascript| 来源: 网络整理| 查看: 265

作为Vue的死忠粉,日常必损React,没想到这次会把矛头对准Vue3。原本对Vue3新版本期望满满,体验后却落得一腔失望,Vue3的保守让人感到非常可惜!

本文介绍Vue3的基础使用、Vue3的设计理念、组合api实现原理、还有个人的思考,其中可能会有不妥和狭隘的地方,欢迎大家评论指正。

前言

未了解Vue3的同学可以通过Vite工具快速搭建Vue3开发环境,参考《Vue3文档》接入开发。

我们可以在《Vue3组合式API征求意见稿》中略窥Vue3的设计思路、技术选型的判断过程。其中《为什么不支持class?》、《和React-Hook的相比》的分析值得一看。

下文所有提到template的用法,均指Vue文件的HTML模板相关用法。

Vue3实际体验

为了了解Vue3的用法,下面通过Vue3新特性实现了计时效果,应该能覆盖绝大多数的应用场景:

jsxTypeScriptprops组合API生命周期import {ref, defineComponent, onMounted, onBeforeUnmount} from "vue"; export interface PropsType { title: string; } export default defineComponent({ props: { title: String }, setup: (props) => { let time = ref((new Date()).toLocaleString()); let timerForDate: number; onMounted(() => { timerForDate = window.setInterval(() => { time.value = (new Date()).toLocaleString() }, 1e3); }); onBeforeUnmount(() => { if (timerForDate) { clearInterval(timerForDate) } }); let renderDate = () => { return (

当前时间:{time.value}

) } return () => (

{props.title}

{renderDate()} ) } })

编写demo的过程中,逐渐深入了解Vue3新特性,Vue2和Vue3新老版本之间的界限也变得模糊,很难说这是好消息还是坏消息,下面是我个人的一些困惑。

Vue3的割裂感jsx复杂化

vue3的jsx虽然已经在vue2基础上进行了优化,但还是存在一定的复杂语法,具体语法可以参考官方文档:

A, foo: () => B }} />

相比React简单明了的jsx语法,Vue却在jsx中保留指令、修饰器、插槽等复杂语法,大大增加入门门槛,而这些语法只是为了解决template局限性所存在的,并不需要存在于jsx中:

指令(directive)

指令100%是为template服务的,无一例外

插槽 & 作用域插槽 (scoped slots)

插槽的引入完全是为了对标jsx的功能所实现的。作用域插槽是vue2版本”中途”引入的功能,为了解决表格单元格数据渲染之类场景,以往通过formatter方法实现渲染逻辑,只能通过html字符串拼接的方式实现Dom插入,无法实现单元格和页面数据联动,所以引入了作用域插槽的概念。涉及template的功能都无法很好的接入TypeScript中,包括这里的插槽。

修饰器(Modifiers)

由于在template内编写复杂语句存在一定的难度,代码要抽离到JS代码中,修饰器可以降低这里成本。

我们熟悉jsx都知道,jsx并不存在上面的问题,也并不需要上面的语法特性。

如果非要接入这些特性,实际上更好的方案是,用更加“函数式”的方式来引入,比如修饰器(Modifiers)更好的写法:

import {throlle, prevent} from "@vue/modifiers" {})} /> {})} />

比如用jsx函数式代替插槽:

{ return (copyright. {data.title}); }} />

函数式能大大地减少jsx复杂语法,降低jsx接入门槛,减少反复查阅文档的时间。

我个人的建议是,业务侧如果要引入Vue3,应该完全舍弃template的用法,同时在jsx中,减少甚至禁用插槽、修饰符等功能。

插槽很难禁用,是因为生态决定业务侧无法禁用插槽等功能,下文会提到#为什么Vue2不是问题,到了Vue3变成了问题?#函数组件和状态组件的隔离

Vue3中明确地将纯函数和状态组件(stateful component)区分开来,简单来说,Vue3中的纯函数无法拥有状态(stateless):

import {ref} from "vue"; export default () => { let count = ref(0); const inc = () => { count.value++; }; return ( 纯函数无状态,无法计数: { count.value } ) }

上面的demo中,点击按钮会给count+1,但Vue3中纯函数组件会重新渲染,Count的值永远为0。

如果要拥有状态(stateful),代码必须用defineComponent标识为状态组件:

import {ref, defineComponent} from "vue"; export default defineComponent(() => { let count = ref(0); const inc = () => { count.value++; }; // 注意!这里要返回一个函数 return () => ( { props.msg } stateful component 点击可以计数: { count.value } ) });

相比React的用法,Vue3这样的写法确实有点臃肿,我们可以先思考下Vue3的两点设计:

一、为什么要用defineComponent包裹?

为了支持TypeScript的类型检查defineComponent实际返回的是一个对象,上面的代码实现会通过setup字段返回。对象是为了兼容更多配置,比如inheritAttrs等区分无状态的纯函数,兼容Vue组件注册机制由于Decorator(装饰器)无法应用于纯函数,所以只能用函数包裹的方式

二、为什么不能直接返回jsx?

这个问题说白了,为什么setup函数类型必须是() => (() => JSX),而不能是() => JSX?

个人猜测,估计是Vue3用了一个讨巧的方案,在setup函数运行时:

作用域内的代码等同于初始化代码(beforeCreate/Created)返回的函数会被当做是”render“函数来看待。

这样就能拆分初始化和render函数,避免初始化代码重复执行,其次在”不会伤筋动骨“的情况下接入到Vue的实现中。

”伤筋动骨“是相对React来说,React的hook功能在低版本是不可能通过运行时兼容代码实现的。

但Vue3这个方案相比React,就一个小小的差别:返回闭包函数,技术难度就大大降低了。抛开Vue3,我们甚至可以通过低版本Vue2实现相同的用法和功能。

下面我弄了一个小demo,composition-api.js中编写了简单的组合API功能,在Vue2旧版本实现新版本的用法,可以进入codeSandbox查看效果:

// composition-api.js let currentComponent; export function ref(value) { let cComponent = currentComponent; let refKey = cComponent.refKey++; cComponent.$set(cComponent.refData, refKey, value); return { get value() { return cComponent.refData[refKey]; }, set value(value) { cComponent.refData[refKey] = value; } }; } function setComp(com) { currentComponent = com; } export function defineComponent(setup) { return { data() { return { refKey: 0, refData: {} }; }, created() { setComp(this); this.renderFunc = setup(); setComp(null); }, render(h) { return this.renderFunc(h); } }; } // app.js import { defineComponent, ref } from "./composition-api"; export default defineComponent(() => { let count = ref(0); const inc = () => { count.value++; }; return h => ( Vue2实现组合API+defineComponent 点击我!计数: {count.value} ); });

通过上述介绍,我们能充分了解到“Vue3如此设计”的理由,但让人难以接受的是,本应该在大版本的“大刀阔斧”革新,却变成了“小步走”。

这个场景下,Vue3和React对比会非常明显——把”纯函数“改成“状态组件”,需要改多少代码?

Vue3:

加入defineComponent记得把() => jsx改成() => () => jsx根据props interface新增props定义

React,只要一步操作:

引用useState。二次声明props

Vue3接入TypeScript后,props需要两份定义:

props interface 用于导出组件的传参类型,业务场景会用到vue3-props定义,由于vue中对props做了过滤和类型判断,必须加上这个定义才能让组件接收到父作用域传递的props属性;同时为了让调试插件(Vue-devtool)能显示出组件的props定义;// 1. 支持业务侧引入组件的参数类型 export interface PropsType { title: string; } export default defineComponent({ // 2. 这里必须定义,props会为空 props: { title:String, }, setup: (props) => { return () => (

{props.title}

) } })

二次声明会加大代码维护成本,同时二次定义也是没有实际意义的。在Vue官方仓库已经跟进了这个问题:https://github.com/vuejs/vue-...

解决方案?

我个人的想法是在编译阶段解决:

只写TypeScript interface,编译阶段把interface编译成props定义根据函数类型() => () => JSX判断,将纯函数转为组件:// 开发时 export interface PropsType { title: string; } export default function TitleComponent (props:PropsType) { return () => (

{props.title}

); }; // 编译后 export interface PropsType { title: string; } export default defineComponent({ name: "TitleCompnent", props: { title:String, }, setup: (props) => { return () => (

{props.title}

) } })

这样,开发时只需要统一维护一份props定义(即上面的interface),在编译过程中自动生成出Vue3的”props“定义并保留原来的interface,兼顾两者。

同样,vue3不支持的class也许变得可能:

由于传递给泛型参数的 interface 仅仅是类型标注,所以用户仍然需要在 this 上为 prop 的代理行为提供其运行时声明。这种二次声明是多余且笨拙的。为什么要抛弃template?

为了支持template功能,Vue需要附加很多特性,例如插槽(slots)、指令、修饰器、props定义、公共设置(inheritAttrs)等功能,即使template有这些特性的加持,还是无法和JSX媲美。Vue3保留template可以说得不偿失,1.造成开发的局限性;2.增加JSX的复杂度。

template其局限性在于:

无法书写复杂的语句,必须要抽离到方法或者计算属性中再引用无法进行TypeScript难以进行代码抽离和整合

template和jsx是冲突的,即template中无法渲染jsx

只在同一个文件重复的模板片段抽离难(小片段不可能都拆文件,只能重复的写)如果组件是template,无法通过props传入jsx进行渲染,必须用插槽

template用法已经脱离时代。

但让人揪心的是,早在Vue2时代,template的实现已经接入jsx:将template编译为jsx转换为虚拟dom(vdom)。尤大觉得template是比jsx更好的语法,业务场景应该用template来编写,但要加上许多特性让它达到jsx的通用性。在Vue3角度看,这是个非常矛盾的点。

template用法确实已经脱离时代,食之无味弃之要快,连鸡肋都算不上。

Vue的困局

Vue2时代的优势却变成Vue3的困局:

向下兼容(渐进式)运行时(runtime)覆盖所有功能

可能正是以上两点,让Vue3还坚守template、JSX支持不到位、在组合API上做出让步。

为什么Vue2不是问题,在Vue3变成问题?

Vue2是个大版本,不应该在大版本中引入破坏性的改造,所以必须要兼容template、指令等用法。而且在Vue2时代我们”各过各的“,你用template,我用jsx,互不影响。

但Vue3是个全新版本,主要目的之一是为了接入TypeScript,但为了老功能,让步了太多东西,革新做的不过彻底。

考虑到Vue3生态的问题,即使项目侧坚持JSX的用法,摒弃template相关功能,但Vue3的开源生态是不可控的。就算项目侧规范得再好,还是使用开源组件的,接入生态就难免插槽、指令等template的功能,终究是无法逃开jsx复杂语法、props interface缺失等等问题。

组合API是个好东西?

组合API是不是未来?是不是好的设计?这对Vue3来说都无关紧要了,因为Vue3已经舍弃了class方案,组合API成为Vue3接入TypeScript的唯一选择。

按React的尿性,React-Hook算不上什么高大上的东西,至于收益大不大,考虑到React的生命周期钩子常年混乱不堪烂挫差,最终收归到Hook的统一接口中,从这点来看,收益还是有的。

总结

上述Vue3缺点虽多,但Vue的优势还是要承认:

明确且清晰的生命周期,也是React同行衬托的好数据懒处理、数据监听,节省了很多的工作量,”框架能优化的事情在框架下解决“的绝妙案例计算属性(Computed)依赖收集的代码实现值得大家一看,非常的优雅和巧妙,值得大家一看。而且这个设计收归了数据更新的出口,大大避免了更新遗漏问题稳定且严谨的迭代,Vue双刃剑(渐进式+向下兼容),相比隔壁React在次版本号引入破坏性更新的尿性追求极致,参考为什么尤大放弃webpack稳定的生态,尤大介入建设绝大多数的Vue生态,理念同源,质量保证平衡功能和开发体验

Vue的优势非常突出,希望Vue3新版本正式推出前,着重提炼优势的特性,摒弃老旧功能,如果大步迈进向前趟,Vue3还是值得期待。

最后个人建议,在Vue3版本,无论是公共开源组件还是业务代码,不要使用template相关功能(包括插槽、指令、修饰符等),这样才能更好的接入到jsx+TypeScript中。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有