关于 Vue 中 JSX 的最佳实践

63 篇文章 0 订阅
9 篇文章 0 订阅

在绝大多数情况下,Vue 推荐使用模板语法来创建应用。然而在某些使用场景下,我们真的需要用到 JavaScript 完全的编程能力。这时 JSX 就派上用场了。

什么是 JSX?

JSX 是一种 JavaScript 拓展语法,全称是 JavaScript and XML,用于声明 UI 组件的结构,最初由 React 发明引入,但现在已成为许多 JavaScript 框架的一部分,包括 Vue 2、Vue 3。

与传统 Vue 提倡的模板语法不一样,JSX 稍微改变了 Vue 的一般开发习惯,取消了模板语法,可以让我们像编写 JavaScript 一样去写页面,具备了 JavaScript 的灵活性同时又具备了 HTML 的语义化和直观性。

什么时候要用 JSX

在一些特定场景下,使用 JSX 去编写页面会比使用模板语法简单且逻辑更清晰,可以使页面渲染逻辑与判断逻辑更好的绑定在一起。在对于一些代码量较少但重复性较高的片段,使用 JSX 可以做到更好的代码复用。

场景一

有这样一个组件,需要通过传入的 Level 去生成不同级别的标题,如果使用模板语法,那可能会需要这样写:

 

vue

复制代码

<template> <h1 v-if="level === 1">{{ title }}</h1> <h2 v-if="level === 2">{{ title }}</h2> <h3 v-if="level === 3">{{ title }}</h3> </template>

在这里对于同一个内容的片段来说,只是标题的级别不一样导致需要重复写了部分代码,如果使用 JSX 来写的话,效果是这样的:

 

javascript

复制代码

const LevelTag = ({ level }) => { const Tag = `h${level}` return <Tag>123</Tag> } return <LevelTag level={level} />

从这个例子就可以很清楚的看出来 JSX 的优势在哪里,不过这里的写法是 Vue3 才能支持,Vue2 因不支持函数组件,在写法上略有不同,后面会详细说明如何编写。

场景二

在项目中,如果很多页面都需要使用表格,且每个表格的表头、单元格内容各不相同,如表头可能需要添加 ToolTips、单元格需要添加 Tag 或 Input 等等。在封装表格组件时,往往需要针对不同的场景编写不同的列规则以应对不同的情况。

 

vue

复制代码

<template> <el-table :data="dataSource"> <el-table-column prop="date" label="Date" width="180" /> <el-table-column label="Date" width="180"> <template #default="scope"> <div style="display: flex; align-items: center"> <el-icon><timer /></el-icon> <span style="margin-left: 10px">{{ scope.row.date }}</span> <div> </template> </el-table-column> <!-- more --> </el-table> </template>

那有没有办法可以封装一个通用表格组件一劳永逸,只需要通过配置就可以动态控制每一列的显示规则呢?模板语法封装或许会有点难度,但如果使用 JSX 的话,这个问题很容易就得到解决了。下面我将一步步实现这一个组件。

如何使用 JSX

Vue3 的 JSX 写法网上一搜一大堆,下面我主要针对 Vue2 中 JSX 的写法进行讲解。

基础用法

在 Vue2 中只在 render 中编写的 JSX 代码才能被正确渲染(Vue2.7 使用 defineComponent 在 setup 中写也可以),如果 templaterender 同时存在则 template 的优先级更高,在 render 中也可以拿到 this,变量使用 { } 包裹使用。

 

javascript

复制代码

<script> export default { name: 'Home', props: { name: { type: String, default: '' } } render () { return <div>{ this.name }</div> } } </script>

插槽

插槽父组件用法和模板语法一致,但子组件用法略有不同。

父组件

 

javascript

复制代码

<script> export default { name: 'Home', render () { return ( <Child> <div slot="header">芝士头部</div> <div>芝士内容</div> <div slot="footer">芝士底部</div> </Child> ) } } </script>

子组件

 

javascript

复制代码

<script> export default { name: 'Child', render () { return ( <div> <div class="header"> { this.$slots.header } </div> <div class="content"> { this.$slots.default } </div> <div class="footer"> { this.$slots.footer } </div> </div> ) } } </script>

封装通用表格组件

首先我们要先写好一个表格的基本结构,我这里直接使用原生的 table 标签去实现,可以按需要替换为 elm 或是 antd。

 

javascript

复制代码

<script> export default { name: 'CustomTable', render () { return ( <table style="table-layout: auto;"> <thead> <tr> <th></th> </tr> </thead> <tbody> <tr> <td></td> </tr> </tbody> </table> ) } } </script>

光有结构还不行,表格需要按照配置的表头和数据进行渲染,这边随意定义了一些数据结构作为参考。

 

vbnet

复制代码

const columns = [ { title: 'Name', dataIndex: 'name', key: 'name' }, { title: 'Age', dataIndex: 'age', key: 'age' }, { title: 'Address', dataIndex: 'address', key: 'address 1' }, { title: 'Long Column 1', dataIndex: 'address', key: 'address 2' } ] const dataSource = [ { name: 'John Brown', age: 32, address: 'New York No. 1 Lake Park, New York No. 1 Lake Park', }, { name: 'Jim Green', age: 42, address: 'London No. 2 Lake Park, London No. 2 Lake Park', }, { name: 'Joe Black', age: 32, address: 'Sydney No. 1 Lake Park, Sydney No. 1 Lake Park', } ]

现在表格结构和数据结构都定义好了,那要怎么根据 columnsdataSource 生成表头和列表呢?

在模板语法里,循环生成页面元素使用的是 v-for 指令,如果要使用 v-for 去生成的话,应该是这么写的

 

vue

复制代码

<template> <table style="table-layout: auto;"> <thead> <tr> <th v-for="column in columns" :key="column.key" >{{ column.title }}</th> </tr> </thead> <tbody> <tr v-for="(data, index) in dataSource" :key="index" > <td v-for="column in columns" :key="column.key" >{{ data[column.dataIndex] }}</td> </tr> </tbody> </table> </template>

但在 JSX 中不能使用 v-for,我们应该使用的是 map 去生成元素,所以上述代码在 JSX 中对应的写法是这样的

 

javascript

复制代码

<table style="table-layout: auto;"> <thead> <tr> { columns.map(column => <th key={ column.key }>{ column.title }</th>) } </tr> </thead> <tbody> { dataSource.map((data, index) => ( <tr key={ index }> { columns.map(column => <td key={ column.key }>{ data[column.dataIndex] }</td>) } </tr> )) } </tbody> </table>

写到这里可能会有小伙伴觉得,唉?这换成 JSX 写法好像也没多简洁啊,怎么写的还更复杂了呢? - 别急,现在要说的优化的地方才能最大程度的发挥出 JSX 的优势。

在上面的代码中,我们可以把比较复杂的部分抽离出来,比如 tbody 内的元素,改写成函数调用的形式返回对应结构

 

javascript

复制代码

const getTableCell = (data) => { return columns.map(column => <td key={ column.key }>{ data[column.dataIndex] }</td>) } const getTableRow = () => { return dataSource.map((data, index) => <tr key={ index }>{ getTableCell(data) }</tr>) }

把原本 tbody 内的元素替换为函数调用返回,不仅使代码更简洁了,在逻辑方面也更清晰了,不需要全部耦合在一个地方处理。

 

javascript

复制代码

<table style="table-layout: auto;"> <thead> <tr> { columns.map(column => <th key={ column.key }>{ column.title }</th>) } </tr> </thead> <tbody> { getTableRow() } </tbody> </table>

上面所示的是 Vue2 中的写法,如果在 Vue3 中,不需要通过函数调用的形式渲染元素,我们可以把 tbody 的部分写成函数组件,如下所示

 

javascript

复制代码

const TableBody = () => { return ( <tbody> { dataSource.map((data, index) => <tr key={ index }>{ getTableCell(data) }</tr>) } </tbody> ) }

而使用的时候可以直接写成组件形式渲染 <TableBody />。那如果 Vue2 中也想直接以组件形式而不是函数调用的形式去实现呢?

Vue3 的写法不同,我们可以在组件内嵌套一个组件,不需要新建一个文件去实现一个新的组件就可以达到组件复用的效果

 

javascript

复制代码

<script> export default { name: 'CustomTable', render () { ... ... const TableBody = { render () { return ( <tbody> { dataSource.map((data, index) => <tr key={ index }>{ getTableCell(data) }</tr>) } </tbody> ) } } return ( <table style="table-layout: auto;"> <thead> <tr> { columns.map(column => <th key={ column.key }>{ column.title }</th>) } </tr> </thead> <TableBody /> </table> ) } } </script>

当然,如果不想在组件内嵌套使用的话,我们也是可以提出来的,代码结构如下所示

 

javascript

复制代码

<script> const TableBody = { props: { columns: { type: Array, required: true }, dataSource: { type: Array, required: true } }, render () { return ( <tbody> { dataSource.map((data, index) => <tr key={ index }>{ getTableCell(data) }</tr>) } </tbody> ) } } export default { name: 'CustomTable', render () { ... ... return ( <table style="table-layout: auto;"> ... ... <TableBody columns={columns} dataSource={dataSource} /> </table> ) } } </script>

上述实现,我们解决了代码复用的问题,但另一个痛点是不同的表格需要渲染的行和列各不相同,我们又不想每次写的时候都要重新写一堆各种情况的 table columns,那在 JSX 中又能怎么样解决呢?

首先,我们从上面的代码可以知道,页面元素是可以通过函数调用进行返回的,那我们是不是可以改造传入的 columns,使表格组件可以通过 columns 内传入的方法去生成我们需要的元素呢?

columns 的数据结构中,加入一个 render 字段,用于格式化单元格的内容,我这里约定了三个参数:

  1. text,当前单元格显示的文字
  2. record,当前行的数据源
  3. index,当前行的 index
 

javascript

复制代码

const columns = [ ... { title: 'Long Column 1', dataIndex: 'address', key: 'address 2', render: (text, record, index) => { return <span style="color: pink;">{ `${record.name}-${text}-${index}` }</span> } } ]

render 方法中,我们可以使用这三个参数渲染我们需要的内容,例如实现 Tooltips、Tag、可编辑行等等。方法定义好了,那实际需要怎么去使用这个方法渲染呢,其实也很简单,只需要在渲染单元格的代码中调用这个方法就没问题了(thead 同理)。

 

javascript

复制代码

const getTableCell = (data) => { return columns.map(column => <td key={ column.key }>{ column.render ? column.render(data[column.dataIndex], data, index) : data[column.dataIndex] }</td>) }

至此,基于 JSX 实现的通用表格组件就基本封装好了,内部使用的 columns 和 dataSource 可以修改为通过 props 传入到表格组件中,在父组件中不管是在 data 或是 computed 中定义的 columns 都可以在 JSX 组件中无痛使用,完整代码如下

 

javascript

复制代码

<script> export default { name: 'CustomTable', props: { columns: { type: Array, required: true }, dataSource: { type: Array, required: true } }, render () { const { columns, dataSource } = this const getTableCell = (data) => { return columns.map((column, index) => <td key={ column.key }>{ column.render ? column.render(data[column.dataIndex], data, index) : data[column.dataIndex] }</td>) } const getTableRow = () => { return dataSource.map((data, index) => <tr key={ index }>{ getTableCell(data) }</tr>) } return ( <table style="table-layout: auto;"> <thead> <tr> { columns.map(column => <th key={ column.key }>{ column.title }</th>) } </tr> </thead> <tbody> { getTableRow() } </tbody> </table> ) } } </script>

总结一下

还是那句话,在绝大多数的情况下模板语法都可以满足,但在某些特定场景下,比如:动态表格、递归生成组件、动态生成标签时等等。项目中模板和 JSX 的使用要权衡利弊,不能无脑使用其中一种,模板的插槽、指令和事件绑定等方式使页面开发更得心应手,但在复杂的交互场景,不得不承认 JSX 可以更灵活地处理各种条件和事件。

原文链接:https://juejin.cn/post/7375438737235132455
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值