likes
comments
collection
share

Vue 中从 template 到 jsx 语法指南

作者站长头像
站长
· 阅读数 23

前言

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

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

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

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

Vue 中从 template 到 jsx 语法指南

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

从 template 到 jsx

插值表达式(文本插值)

template 语法

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

<span>Message: {{ msg }}</span>

jsx 语法

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

<span>Message: { msg }</span>

原始 HTML

template 语法

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

// 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 结构 配合上 文本插值 {} 即可:

// 最终变成纯文本
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 来实现:

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

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

jsx 语法

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

if-else

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

&&

{ isShow && <div>hello world!</div> }
{ !isShow && <div>hello bros!</div> }

||

const content1 = <h1>hello world!</h1>
const content2 = <h1>hello world!</h1>

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

三元表达式

const content1 = <h1>hello world!</h1>
const content2 = <h1>hello world!</h1>

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

列表渲染

tempalte 语法

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

<ul>
  <li v-for="item in list" :key="item.key">{{ item.text }}</li>
</ul>

jsx 语法

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

<ul>
  {
    list.map((item) => (
      <li key={item.key}>{ item.text }</li>
    ))
  }
</ul>

style 外部样式

template 语法

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

<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 导入
    import { defineComponent, ref } from 'vue'
    import './index.less'
    
    export default defineComponent({})
    
  • 通过 CSS Module 导入
    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(简写 @) 来实现,并且可以在模板中 直接传递参数 给目标事件,也可以配合使用 事件修饰符,支持内联事件等等。

绑定处理函数

<!-- 方法处理函数 --> 
<button v-on:click="doThis"></button>

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

使用修饰符

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

传递参数

<!-- 传参 --> 
<button @click="doThis($event, params)"></button>

内联事件

<!-- 传参 --> 
<button @click="count++"></button>

jsx 语法

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

绑定处理函数

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

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

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

使用修饰符

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

<!-- withModifiers  -->
<button onClick={withModifiers(doThis, ['prevent'])}></button>

传递参数

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

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

内联事件

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

<!-- 方式一 -->
<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 绑定的 属性名事件名
// 常见表单
<input v-model="searchText" />

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

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

jsx 语法

正常绑定

<CustomInput v-model={searchText} />

自定义名称

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

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

slot 插槽

template 语法

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

默认插槽

<button class="fancy-btn">
  <slot></slot> <!-- 插槽出口 -->
</button>

具名插槽

<button class="fancy-btn">
  <slot name="content"></slot> <!-- 插槽出口 -->
</button>

动态插槽

<div> 
    <!-- 动态插槽 --> 
    <template v-slot:[dynamicSlotName]> ... </template> 

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

作用域插槽

<!-- MyComponent 模板 --> 
<div>
    <slot :text="greetingMessage" :count="1"></slot> 
</div>

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

jsx 语法

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

  • 从 SetupContext 中获取
    defineComponent({
        setup(props, { slots }) {
            return <div>
                { slots.default && slots.default() }
                { slots.nameSlot && slots.nameSlot() }
            </div>
        }
    })
    
  • 使用 useSlot() 方法
    import { defineComponent, renderSlots } from 'vue'
    
    defineComponent({
        setup(props, context) {
            const slots = useSlots();
            
            return <div>
                { renderSlot(slots, 'default') }
            </div>
        }
    })
    
  • 使用 renderSlot() 方法
    import { defineComponent, renderSlot } from 'vue'
    
    defineComponent({
        setup(props, { slots }) {
            return <div>
                { renderSlot(slots, 'default') }
            </div>
        }
    })
    
  • 使用 Scoped Slots 作用域插槽
    <!-- 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 语法,期间总是需要来回查找对应的正确写法,于是就打算整理成文章顺便分享出来.

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

Vue 中从 template 到 jsx 语法指南