IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Vue 中从 template 到 jsx 语法指南

    熊的猫发表于 2023-10-23 11:17:39
    love 0

    前言

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

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

    • 熟悉的类 HTML 结构

      • 模板语法可以像 HTML 一样进行布局和设计,上手快、学习成本比低
    • 更简洁的写法

      • 例如,可以在模板中使用各种 修饰符 来达到简化编写代码的过程
    • 结构与逻辑分离

      • 元素结构和逻辑并没有杂糅在一起,因此结构上更简洁明了
    • 提供更好的性能

      • Vue3 中对模板语法在 编译阶段 进行的各种优化,使得其性能更好

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

    087D2CA4.jpg

    而对于习惯使用 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 语法,期间总是需要来回查找对应的正确写法,于是就打算整理成文章顺便分享出来.

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

    08809D12.gif



沪ICP备19023445号-2号
友情链接