【AICon】 如何构建高效的 RAG 系统?RAG 技术在实际应用中遇到的挑战及应对策略?>>> 了解详情
写点什么

拥抱 Vue 3 系列之 JSX 语法

  • 2021-04-06
  • 本文字数:5269 字

    阅读完需:约 17 分钟

拥抱 Vue 3 系列之 JSX 语法

写在前面


这是该系列文章的第一篇,后续会持续更新,覆盖 Vue 3 生态常用库。JSX 是一个小众群体使用开发方式,第一篇以  JSX 为切入点,目标是让大多数开发 Vue 的同学也对 JSX 有一定的认知,在用 Vue 开发复杂应用时,也能有更加灵活的方式。


比如当开始写一个只能通过 level prop 动态生成标题 (heading) 的组件时,你可能很快想到这样实现:


<script type="text/x-template" id="anchored-heading-template">  <h1 v-if="level === 1">    <slot></slot>  </h1>  <h2 v-else-if="level === 2">    <slot></slot>  </h2>  <h3 v-else-if="level === 3">    <slot></slot>  </h3></script>
复制代码


这里用模板并不是最好的选择,在每一个级别的标题中重复书写了 <slot></slot>,不够优雅。

如果尝试用 JSX 来写,代码就会变得简单很多。


const App = {  render() {    const tag = `h${this.level}`    return <tag>{this.$slots.default}</tag>  }}
复制代码


看过 Ant Design Vue  源码 (下面简称为 antdv) 的同学应该知道, antdv 的底层是基于 JSX 来实现的,也是 Vue 生态中使用 JSX 的深度用户。antd 为了尽快的兼容 Vue 3,和 Vue 官方展开合作,于是一起开发了 @ant-design-vue/babel-plugin-jsx 。


Vue JSX 简介


对于使用 React 的开发者来说,JSX 再熟悉不过了,但是如果你是一个 Vue 的重度用户,可能对 JSX 不是特别熟悉,甚至听到有同学说没有 template 的 Vue 项目没有灵魂。


先来看下面一段代码:


const el = <div>Vue 3</div>;
复制代码


这段代码既不是 HTML 也不是字符串,被称之为 JSX,是 JavaScript 的扩展语法。JSX 可能会使人联想到模板语法,但是它具备 Javascript 的完全变成能力。


看到这里可能会有疑问,不少同学可能会以为 JSX 是 React 中特有的,其实不然。大多数同学都知道,我们平常在 .vue 文件中开发的代码,实际上会被 vue-loader 处理,但可能少数同学去看过我们手把手写出的代码,会变编译成啥样。有兴趣的同学可以戳这个地址来看下。vue-template-explorer  (因为众所周知的原因,可能访问略慢)


<div id="app">{{ msg }}</div>
复制代码


function render() {  with(this) {    return _c('div', {      attrs: {        "id": "app"      }    }, [_v(_s(msg))])  }}
复制代码


观察上述代码我们发现,到运行阶段实际上都是 render 函数在执行。Vue 推荐在绝大多数情况下使用 template 来创建你的 HTML。然而在一些场景中,就需要使用 render 函数,它比 template 更加灵活。


写过 render 函数的同学可能深有体会,书写复杂的 render 函数异常痛苦,而且难以维护,非常容易被称之为 “祖传代码”。好在 2.0 的官方提供了一个 Babel 插件,可以将更接近于模板语法的 JSX 转译成 JavaScript。



使用过 React 的同学对于如何写 JSX 语法一定非常熟悉了,然而,Vue 2 中 的 JSX 写法和 React 还是有一些略微的区别。React 中所有传递的数据都挂在顶层。


const App = <A className="x" style={style} onChange={onChange} />
复制代码


Vue 2 中,仅仅属性就有三种:组件属性 props,普通 html 属性 attrs,DOM 属性 domProps。想要更多了解如何在 Vue 2 中写 JSX 语法,可以看这篇,在 Vue 中使用 JSX 的正确姿势 。


Vue 3 中对 JSX 带来的改变


  • 属性传递


Vue 3 中,属性这块的传递和 React 类似,意味这不需要再传递 props,attrs 这些属性。


// before{  class: ['foo', 'bar'],  style: { color: 'red' },  attrs: { id: 'foo' },  domProps: { innerHTML: '' },  on: { click: foo },  key: 'foo'}
// after{  class: ['foo', 'bar'],  style: { color: 'red' },  id: 'foo',  innerHTML: '',  onClick: foo,  key: 'foo'}
复制代码


  • 指令改版


Vue 3 把大多数全局 API 和 内部 helper 移到了 ES 模块中导出(譬如 v-model、transition、teleport),从而使得 Vue 3 在增加了很多新特性之后,基线的体积反而小了。


v-modelv-show 这些 API 全部通过模块导出的方式来引入


基线体积:无法舍弃的代码体积


我们来看一段非常简单的代码 <input v-model="x" />,在 Vue 2 和 Vue 3 中的编译结果有何不同


// beforefunction render() {  with(this) {    return _c('input', {      directives: [{        name: "model",        rawName: "v-model",        value: (x),        expression: "x"      }],      domProps: {        "value": (x)      },      on: {        "input": function ($event) {          if ($event.target.composing) return;          x = $event.target.value        }      }    })  }}
复制代码


// afterimport { vModelText as _vModelText, createVNode as _createVNode, withDirectives as _withDirectives, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache) {  return _withDirectives((_openBlock(), _createBlock("input", {    "onUpdate:modelValue": $event => (_ctx.x = $event)  }, null, 8 /* PROPS */, ["onUpdate:modelValue"])), [    [_vModelText, _ctx.x]  ])}
复制代码


可以看到在 Vue 3 中,对各个 API 做了更加细致的拆分,理想状态下,用户可以在构建时利用摇树优化 (tree-shaking) 去掉框架中不需要的特性,只保留自己用到的特性。模版编译器会生成适合做 tree-shaking 的代码,不需要使用者去关心如何去做,这部分的改动同样需要在 JSX 写法中实现。


模板编译器中增加了 PatchFlag,在 JSX 的编译过程同样也做了处理,性能会有提升,但是考虑到 JSX 的灵活性,做了一些兼容处理,该功能还在测试阶段。


从 Vue 2 到 Vue 3 的过渡


Vue 3 虽然引入了一部分破坏性的更新,但对于绝大多数 Vue 2 的 API 还是兼容的。那么同样的,我们也要尽可能让使用 JSX 的用户通过最小的成本升级到 Vue 3,这是一个核心的目标。写这篇文章的时候,antdv 已经使用 @ant-design-vue/babel-plugin-jsx 重构了大约 70% 的功能,预计会在 Vue 3 正式版之前发布测试版,大概率会是东半球最快兼容 Vue 3 的企业级组件库。

Vue 3 JSX 的 API 设计


  • 函数式组件

const App = () => <div>Vue 3 JSX</div>
复制代码
  • 普通组件

const App = {  render() {    return <div>Vue 3.0</div>  }}
复制代码


const App = defineComponent(() => {  const count = ref(0);
  const inc = () => {    count.value++;  };
  return () => (    <div onClick={inc}>      {count.value}    </div>  )})
复制代码
  • Fragment

const App = () => (  <>    <span>I'm</span>    <span>Fragment</span>  </>)
复制代码

Fragment 参考 React 的写法,尽可能写起来更加方便

  • Attributes/Props

const App = () => <input type="email" />
复制代码


const placeholderText = 'email'const App = () => (  <input    type="email"    placeholder={placeholderText}  />)
复制代码
  • 指令


建议在 JSX 中使用驼峰 (vModel),但是 v-model 也能用 v-show

const App = {  data() {    return { visible: true };  },  render() {    return <input vShow={this.visible} />;  },};
复制代码


修饰符:使用 (_) 代替 (.) (vModel_trim={this.test})


export default { data: () => ({   test: 'Hello World', }), render() {   return (     <>       <input type="text" vModel_trim={this.test} />       {this.test}     </>   ) },}
复制代码

自定义指令

const App = {  directives: { antRef },  setup() {    return () => (      <a        vAntRef={(ref) => { this.ref = ref; }}      />    );  },}
复制代码
  • 插槽

关于指令、插槽最终的 API 还在讨论中,有想法的可以去留言。Vue 3 JSX Design 


Vue 2 的 JSX 写法如何快速迁移到 Vue 3


由于 antdv 的底层基本上都是基于 JSX 来写的,想要快速迁移到 Vue 3,就必须有一个比较好的插件来支持,这也是为什么会有这个插件的原因。当然在实现过程中也踩了很多坑。


目前用法和 Vue 2 的语法大多数是一致的,为了帮助更快迁移,在插件中做了针对旧 VNode 格式的兼容层,这里只能兼容一部分写法,以及部分语法的兼容会增加运行时的性能开销,所以我们希望能够将我们的经验分享给大家,让大家少走弯路!


{  "plugins": ["@ant-design-vue/babel-plugin-jsx", { "transformOn": true, "compatibleProps": true }]}
复制代码
  • transformOn

针对 Vue 2 中 on: { click: xx } 写法的兼容,在运行时中会转为 onClick: xxx


  • compatibleProps

上文提到 Vue 3 对属性的传递做了变更,propsattrs 这些都不存在了,因此如果设置了这个属性为 true,在运行时也会被解构到第一层的属性中。


需要注意的一点,目前一旦开启这两个属性,在 createVNode 的第二个参数,都会包一个 compatibleProps 和 transformOn 方法,所以酌情开启这两个参数。对于使用 Vue 2 的 JSX 同学,如果没有使用到比较”不为人知“ 的 API 的情况下,都可以快速得迁移。


那么 antdv 又是如何做迁移的呢?考虑到 antdv 是个组件库,都包一层 compatibleProps 势必不太优雅,因此没有选择开启这个两个开关。这里插一句,目前 antdv 的迁移还在进行中,相关的进度都在这个 issue 里面:Vue 3 支持 (https://github.com/vueComponent/ant-design-vue/issues/1913),有兴趣的同学可以关注下,提一些 PR 过去。


对于 props 的迁移工作比较简单,如果你是直接通过标签的属性来传递,那么无须做更改。


<Modal visible={visible} />
复制代码


如果是通过对象来传递的属性,只需要把原有分散在 propsonattrs 中的值直接铺开即可。


 const vcUploadProps = {-  props: {-    ...this.$props,-   prefixCls,-    beforeUpload: this.reBeforeUpload,-  },-  on: {-    start: this.onStart,-    error: this.onError,-    progress: this.onProgress,-    success: this.onSuccess,-    reject: this.onReject,- },+  ...this.$props,+  prefixCls,+  beforeUpload: this.reBeforeUpload,+  onStart: this.onStart,+  onError: this.onError,+  onProgress: this.onProgress,+  onSuccess: this.onSuccess,+  onReject: this.onReject,+  ref: 'uploadRef',+  attrs: this.$attrs,+  ...this.$attrs,};
复制代码


但是关于 inheritAttrs 有个较为底层的变动,需要开发者根据实际情况去修改。什么是 inheritAttrs? (https://cn.vuejs.org/v2/api/index.html#inheritAttrs) 在 Vue 2 中,这个选项不影响 class 和 style 绑定,但是在 Vue 3 中会影响到。因此可能在属性的传递上,需要额外对这两个参数做处理。


在事件的处理上,我们建议在 props 中声明,这样对后续的开发更加易维护,可以很直观地从 props 看出我这个组件到底会传递哪些事件。值得一提的是,在 props 中声明的事件,也可以通过 emit 来触发。例如声明了 onClick 事件,仍然可以使用 emit('click')


Vue 3 对 context 的 API 也做了改动,一般如果不是复杂的组件,不会涉及到这个 API。这部分的改动可以看原先 Vue Compositon API 的相关文档,Dependency Injection (https://composition-api.vuejs.org/api.html#dependency-injection),注意一点,在 setup 中取不到 this


总结


如今有超过百万的开发人员使用 Vue,还有超百万的 React 开发者正在去使用 Vue 的路上。


虽然说 Vue 中 JSX 的开发方式是一个少数群里,但是 antdv 的使用用户也不是少数。为了让这部分用户可以快速体验到兼容 Vue 3 版本的组件库,因此在设计这个插件的时候,第一原则就是要最小的迁移和认知成本。


对于常年使用 template 的开发者来说,JSX 又何尝不是一片新的天空呢?开发者要与使用者共情,站在使用者的角度出发,设计出的工具大概率可能满足其需求。


距离 JSX 发布正式版本,还有一部分路要走。最后要感谢 Vue.js 官方团队,尤其是 @sodatea 大佬的信任。



头图:Unsplash

作者:天泽

原文:https://mp.weixin.qq.com/s/QoI9Jdb6phoFsZEjU-P6lw

原文:拥抱 Vue 3 系列之 JSX 语法

来源:政采云前端团队 - 微信公众号 [ID:Zoo-Team]

转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2021-04-06 20:067633

评论 1 条评论

发布
用户头像
支持双向绑定的React?
2021-04-29 17:54
回复
没有更多了
发现更多内容

面试官:try-catch放在循环体内还是循环体外,哪种效率更高?

做梦都在改BUG

pytest学习和使用3-对比unittest和pytest脚本在pycharm中运行的方式

Python 自动化测试 pytest

pytest学习和使用4-pytest和Unittest中setup、teardown等方法详解和使用(最全)

Python 自动化测试 unittest 测试框架 pytest

不可错过!Arm 、Intel 及阿里云等资深技术专家现场解读系统安全

OpenAnolis小助手

系统安全 Meetup 龙蜥社区 sig 机密计算

测试人软件测试技术沙龙——深入探讨一站式效能平台的演进历程

测试人

软件测试 自动化测试 测试开发

镜舟数据库荣获 CSDN 年度创新产品与解决方案!

镜舟科技

数据库

前端已死?后端已亡?这个概念真的成立吗?

这我可不懂

前端 低代码 JNPF

【分享】为什么我设计的PCB很少出错?

华秋PCB

工具 测试 电路 PCB PCB设计

软件工程高效学 | 软件的内涵与危机

TiAmo

软件工程 软件开发

2023中国儿童防敏市场发展洞察

易观分析

医疗 防敏 儿童

阿里云PAI-DeepRec CTR 模型性能优化天池大赛——获奖队伍技术分享

阿里云大数据AI技术

人工智能 深度学习 性能优化 模型 企业号 3 月 PK 榜

MySQL MVCC实现原理

得物技术

MySQL MVCC java

测试人社区技术沙龙——计算机视觉在App兼容性测试中的实践

测试人

软件测试 测试开发 测试开发自动化测试

龙蜥自动化平台 SysOM 2.1 热补丁中心介绍 | 第 74 期

OpenAnolis小助手

直播 系统运维 龙蜥大讲堂 SysOM 补丁

IPQ6010/QCA8081/QCN5052/QCN5022 MAXON MX-A6022-ME WiFi6 Industrial Wireless Access Point

MAXON

IPQ6010 QCN5052 QCN5022 QCA8081

镜舟数据库与 DataBuilder 完成兼容性认证,助力企业开展“极速统一”数据分析

镜舟科技

数据库

腾讯大神耗时三年,立足实际开发的巅峰之作,详解高并发程序设计

做梦都在改BUG

Java 程序设计 高并发

初学后端,如何做好表结构设计?

王中阳Go

Go golang 数据库 表结构 golang 面试

金三银四互联网大厂精选1160道Java面试题答案整理(2023最新版)

架构师之道

编程 程序员 java面试

ByteHouse:基于ClickHouse 的实时计算能力升级

字节跳动数据平台

大数据 云原生 flink 消费 kafka Clickhouse 企业号 3 月 PK 榜

镜舟科技荣获IT168年度技术卓越奖!

镜舟科技

数据库

NFTScan 与 DeBox 达成合作,双方在 NFT 社交数据层面展开合作

NFT Research

NFT

Git客户端工具:SourceTree中文激活版

真大的脸盆

git Mac Mac 软件 Git客户端

终于有人把Java面试高分Guide总结得如此系统,堪称傻瓜式笔记总结

采菊东篱下

java面试

吃透阿里2023版Java性能优化小册后,我让公司系统性能提升了200%

Java你猿哥

ssm Java工程师 Java性能优化 java

镜舟:打造行业顶级国产OLAP数据库

镜舟科技

好家伙!阿里最新SpringBoot进阶笔记涵盖了SpringBoot所有骚操作

Java你猿哥

Java Spring Boot 面经 SSM框架

跟着字节AB工具DataTester,5步开启一个实验

字节跳动数据平台

大数据 云服务 AB testing实战 ab测试 企业号 3 月 PK 榜

面试被怼:技术更新这么快,你还不懂响应式微服务就out了

做梦都在改BUG

Java spring 微服务 响应式

GaussDB(DWS)运维:导致SQL执行不下推的改写方案

华为云开发者联盟

数据库 后端 华为云 华为云开发者联盟 企业号 3 月 PK 榜

2022 OpenHarmony年度运营报告

OpenHarmony开发者

OpenHarmony

拥抱 Vue 3 系列之 JSX 语法_语言 & 开发_政采云前端团队_InfoQ精选文章