Vue 中从 template 到 jsx 语法指南

前言

欢迎关注同名公众号《熊的猫》,文章会同步更新,也可快速加入前端交流群!

大多数 Vue 开发者都习惯使用 template 模板语法 ,因为 template 模板语法 具有如下优点:

  • 熟悉的类 HTML 结构
    • 模板语法可以像 HTML 一样进行布局和设计,上手快、学习成本比低
  • 更简洁的写法
    • 例如,可以在模板中使用各种 修饰符 来达到简化编写代码的过程
  • 结构与逻辑分离
    • 元素结构和逻辑并没有杂糅在一起,因此结构上更简洁明了
  • 提供更好的性能
    • Vue3 中对模板语法在 编译阶段 进行的各种优化,使得其性能更好

但即便如此,在某些场景下还是不得不在 Vue 中使用 jsx 语法 实现需求, 例如开发 内部组件库 选择的编写形式就是 jsx 语法 等。

而对于习惯使用 template 模板语法 的开发者并不是轻易的就能转换到相应 jsx 语法 ,因此本文就列举一些 template 模板语法 中对应的 jsx 语法 应该怎么写。

从 template 到 jsx

插值表达式(文本插值)

template 语法

最基本的数据绑定形式就是 文本插值 ,它使用的是 Mustache 语法 (即 {{}} ),双大括号 中的内容最终 会将数据解释为纯文本

js 复制代码
<span>Message: {{ msg }}</span>

jsx 语法

而 JSX 中使用 文本插值 就从 双大括号 {{ }} 变成 单大括号 {}

js 复制代码
<span>Message: { msg }</span>

原始 HTML

template 语法

双大括号 会将数据解释为纯文本,而不是 HTML ,因此若想在模板中 插入 HTML ,我们需要使用 v-html 指令

js 复制代码
// html 字符内容
rawHtml = '<span>hello!</span>'

// 最终变成纯文本
<p>Using text interpolation: {{ rawHtml }}</p>

// 最终渲染为 html 结构
<div>Using v-html directive: <p v-html="rawHtml"></p></div>

jsx 语法

而在 jsx 语法 中就更直接了,我们直接通过 变量 的形式来写 html 结构 配合上 文本插值 {} 即可:

js 复制代码
// 最终变成纯文本
cosnt rawHtml = '<span>hello!</span>'
<p>Using text interpolation: { rawHtml }</p>

// 最终渲染为 html 结构
cosnt rawHtml = <span>hello!</span>
<div>Using v-html directive: { rawHtml }</div>

条件渲染

template 语法

在模板语法中和 条件渲染 相关的可以直接使用指令 v-if / v-show 来实现:

js 复制代码
<p v-show="isShow">hello world!</p>
<p v-show="!isShow">hello bros!</p>

<p v-if="isShow">hello world!</p>
<p v-if-else>hello bros!</p>

jsx 语法

而在 jsx 语法 中我们就不能使用 指令形式 了,取而代之的是 JavaScript 中的 if-else、&&、||、三元表达式 等形式:

if-else

js 复制代码
const content = (isShow) => {
    if(isShow){
      return <h1>hello world!</h1>
    }else{
      return <div>hello bros!</div>
    }
}

&&

js 复制代码
{ isShow && <div>hello world!</div> }
{ !isShow && <div>hello bros!</div> }

||

js 复制代码
const content1 = <h1>hello world!</h1>
const content2 = <h1>hello world!</h1>

<div>{ content1 || content2 } </div>

三元表达式

js 复制代码
const content1 = <h1>hello world!</h1>
const content2 = <h1>hello world!</h1>

<div>{ isShow ? content1 : content2 } </div>

列表渲染

tempalte 语法

template 模板 中可以通过 v-for 指令 来快速实现列表渲染:

js 复制代码
<ul>
  <li v-for="item in list" :key="item.key">{{ item.text }}</li>
</ul>

jsx 语法

jsx 语法 中通常是使用 Array.prototype.map() 方法来实现列表渲染,原因就在于这个遍历数组的方法返回值也是数组:

js 复制代码
<ul>
  {
    list.map((item) => (
      <li key={item.key}>{ item.text }</li>
    ))
  }
</ul>

style 外部样式

template 语法

.vue 文件 中可以通过 <style> 标签来 编写样式 或 导入外部样式 ,还可以直接通过设置 scope 实现局部样式:

js 复制代码
<script setup lang="ts">
import { ref } from "vue";
import ChcekBox from "./components/CheckBox";

const checkResult = ref(false);
</script>

<template>
  <ChcekBox v-model="checkResult">选项</ChcekBox>
</template>

<style scope>
@import './index.less';
</style>

jsx 语法

而在一个 .jsx / .tsx 文件中由于不存在 <style> 元素,因此无法通过其来编写或导入样式,或者通过 scope 实现局部样式,可通过如下方式导入:

  • 直接 import 导入

    js 复制代码
    import { defineComponent, ref } from 'vue'
    import './index.less'
    
    export default defineComponent({})
  • 通过 CSS Module 导入

    js 复制代码
    import { defineComponent, ref } from 'vue'
    import styleModule from './index.module.less'
    
    export default defineComponent({
        setup() {
            return () => (
                <label class={styleModule.abs}>hello world!</label>
             )
    })

事件绑定

tempalte 语法

在模板语法中绑定事件可以使用 v-on(简写 @) 来实现,并且可以在模板中 直接传递参数 给目标事件,也可以配合使用 事件修饰符,支持内联事件等等。

绑定处理函数

js 复制代码
<!-- 方法处理函数 --> 
<button v-on:click="doThis"></button>

<!-- 缩写 --> 
<button @click="doThis"></button>

使用修饰符

js 复制代码
<!-- 链式调用修饰符 -->
<button @click.stop.prevent="doThis"></button>

传递参数

js 复制代码
<!-- 传参 --> 
<button @click="doThis($event, params)"></button>

内联事件

js 复制代码
<!-- 传参 --> 
<button @click="count++"></button>

jsx 语法

上述写法在 jsx 语法 中的对应写法具体如下:

绑定处理函数

需要使用 on + [eventName] 的形式来绑定事件,可使用 或 不使用 驼峰形式 ,但当使用 typescript 时建议使驼峰形式,否则会有提示:

js 复制代码
<!-- 驼峰 --> 
<button onClick={doThis}></button>

<!-- 非驼峰 --> 
<button onclick={doThis}></button>

使用修饰符

jsx 语法 中不能直接使用 .stop 形式的事件修饰符,需要通过 withModifiers 函数来实现,其支持 事件和按键修饰符

js 复制代码
<!-- withModifiers  -->
<button onClick={withModifiers(doThis, ['prevent'])}></button>

传递参数

jsx 语法 中不能像在模板中使用 handleAction($event, params) 的方式来实现传参,因为这种写法在 jsx 中属于调用,因此相当于把函数返回值作为事件绑定到目标元素上,大多数情况下会抛出异常(即返回值不一定为函数):

  • 使用 bind 实现传参

    js 复制代码
    const bindEvent = doThis.bind(tarrget)
    
    <!-- 传参 -->
    <button onClick={bindEvent}></button>
  • 使用 箭头函数 实现传参

    js 复制代码
    <!-- 传参 -->
    <button onClick={ (parms) => bindEvent(parms)}></button>

内联事件

jsx 语法 并不支持内联事件的写法,因此可以使用箭头函数来包裹:

js 复制代码
<!-- 方式一 -->
<button onClick={ () => count++ }></button>

<!-- 方式一 -->
const addCount = () => count++
<button onClick={ addCount }></button>

双向绑定 v-model

template 语法

v-model 可以在组件上使用以实现 双向绑定

  • 原生元素 上使用 v-model 会被编译为 value 属性input 事件
  • 组件 上使用 v-model 会被编译为 modelValue 属性update:modelValue 事件
  • 支持自定义 v-model 绑定的 属性名事件名
js 复制代码
// 常见表单
<input v-model="searchText" />

// 自定义组件
<CustomInput v-model="searchText" />

// 自定义 v-model 名
<MyComponent v-model:title="bookTitle" />

jsx 语法

正常绑定

js 复制代码
<CustomInput v-model={searchText} />

自定义名称

jsx 语法 中不存在类似 v-model:title 的命名形式,因此我们给 v-model 一个数组,如 [title, 'titleAlias']

  • 数组的第一个参数就是要绑定的
  • 数组的第二个参数就是要绑定的 自定义名称
js 复制代码
<Custom v-model={[title, 'titleAlias']} />

slot 插槽

template 语法

在模板语法中可以 <slot> 元素来定义 插槽出口 ,用于标示父元素提供的 插槽内容 的渲染位置:

默认插槽

js 复制代码
<button class="fancy-btn">
  <slot></slot> <!-- 插槽出口 -->
</button>

具名插槽

js 复制代码
<button class="fancy-btn">
  <slot name="content"></slot> <!-- 插槽出口 -->
</button>

动态插槽

js 复制代码
<div> 
    <!-- 动态插槽 --> 
    <template v-slot:[dynamicSlotName]> ... </template> 

    <!-- 缩写为 --> 
    <template #[dynamicSlotName]> ... </template> </base-layout>
</div> 

作用域插槽

js 复制代码
<!-- MyComponent 模板 --> 
<div>
    <slot :text="greetingMessage" :count="1"></slot> 
</div>

<!-- 父组件模板 --> 
<MyComponent v-slot="slotProps"> 
    {{ slotProps.text }} {{ slotProps.count }} 
</MyComponent>

jsx 语法

由于 jsx 语法 中不存在 <slot> 元素,因此只能通过如下方式来渲染插槽内容:

  • 从 SetupContext 中获取

    js 复制代码
    defineComponent({
        setup(props, { slots }) {
            return <div>
                { slots.default && slots.default() }
                { slots.nameSlot && slots.nameSlot() }
            </div>
        }
    })
  • 使用 useSlot() 方法

    js 复制代码
    import { defineComponent, renderSlots } from 'vue'
    
    defineComponent({
        setup(props, context) {
            const slots = useSlots();
            
            return <div>
                { renderSlot(slots, 'default') }
            </div>
        }
    })
  • 使用 renderSlot() 方法

    js 复制代码
    import { defineComponent, renderSlot } from 'vue'
    
    defineComponent({
        setup(props, { slots }) {
            return <div>
                { renderSlot(slots, 'default') }
            </div>
        }
    })
  • 使用 Scoped Slots 作用域插槽

    js 复制代码
    <!-- MyComponent 模板 --> 
    defineComponent({
        setup(props, { slots }) {
            const slotParams = { name: 'hello' };
            
            return <div>
                { slots.default && slots.default(slotParams) }
            </div>
        }
    })
    
    <!-- 父组件模板 -->
    defineComponent({
        setup(props, { slots }) {
            
            return <div>
                <MyComponent v-slot="slotProps"> 
                    {{ slotProps.name }}
                </MyComponent>
            </div>
        }
    })

最后

欢迎关注同名公众号《熊的猫》,文章会同步更新,也可快速加入前端交流群!

以上就是本文的全部内容了,由于前段时间开始写 jsx 语法,期间总是需要来回查找对应的正确写法,于是就打算整理成文章顺便分享出来.

希望本文对你有所帮助!!!

相关推荐
上官花雨11 分钟前
什么是axios?怎么使用axios封装Ajax?
前端·ajax·okhttp
米奇妙妙wuu12 分钟前
React中 setState 是同步的还是异步的?调和阶段 setState 干了什么?
前端·javascript·react.js
李刚大人14 分钟前
react-amap海量点优化
前端·react.js·前端框架
闹闹没有闹31 分钟前
socket连接封装
前端
qq_364371722 小时前
Vue 内置组件 keep-alive 中 LRU 缓存淘汰策略和实现
前端·vue.js·缓存
y先森2 小时前
CSS3中的弹性布局之侧轴的对齐方式
前端·css·css3
new出一个对象6 小时前
uniapp接入BMapGL百度地图
javascript·百度·uni-app
你挚爱的强哥7 小时前
✅✅✅【Vue.js】sd.js基于jQuery Ajax最新原生完整版for凯哥API版本
javascript·vue.js·jquery
y先森8 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy8 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html