拥抱jsx,开启vue3用法的另一种选择🔥🔥
阅读原文时间:2023年08月09日阅读:1

背景

公司高级表单组件ProForm高阶组件都建立在jsx的运用配置上,项目在实践落地过程中积累了丰富的经验,也充分感受到了jsx语法的灵活便捷和可维护性强大,享受到了用其开发的乐趣,独乐乐不如众乐乐,为了帮助大家更好的运用jsx,开发提效,特此总结分享。

效果对比

以往我们开发一个列表的增加、编辑、查看详情三个操作要准备3个form表单文件,表单中ui元素共性部分我们要复制三次,例如:

// addForm.vue
<template>
<el-form :model="form" >
 <el-form-item label="活动名称">
 <el-input v-model="form.name" />
 </el-form-item>
 <el-form-item label="活动区域">
 <el-input v-model="form.region" />
 </el-form-item>
 <el-form-item label="活动形式">
 <el-input v-model="form.type" />
 </el-form-item>
 </el-form>
  <el-form-item>
      <el-button >新增</el-button>
    </el-form-item>
</template>

<script setup>
import { reactive } from 'vue'
const form = reactive({
  name: '',
  region: '',
  type: '',
})
...
</script>

// editForm.vue

<template>
<el-form :model="form" >
 <el-form-item label="活动名称">
 <el-input v-model="form.name" />
 </el-form-item>
 <el-form-item label="活动区域">
 <el-input v-model="form.region" />
 </el-form-item>
 <el-form-item label="活动形式">
 <el-input v-model="form.type" />
 </el-form-item>
  <el-form-item>
      <el-button >编辑</el-button>
    </el-form-item>
  </el-form>
</template>

<script setup>
import { reactive,inject } from 'vue'
const form = reactive({})
form=inject('detailData')
...
</script>

// detailForm.vue

<template>
<el-form :model="form" :disabled="true" >
 <el-form-item label="活动名称">
 <el-input v-model="form.name" />
 </el-form-item>
 <el-form-item label="活动区域">
 <el-input v-model="form.region" />
 </el-form-item>
 <el-form-item label="活动形式">
 <el-input v-model="form.type" />
 </el-form-item>
  <el-form-item>
      <el-button >关闭详情</el-button>
    </el-form-item>
  </el-form>
</template>

<script setup>
import { reactive,inject  } from 'vue'
const form = reactive({})
form=inject('detailData')
...
</script>

如果遇到改字段名的情况,如把活动名称的name改成activityName,对应的3个文件都得去修改,表单改动多的话还有可能存在有得文件改漏的情况。

现在使用ProForm结合jsx配置,如下

// add.vue
<template>
 <activity-form
 ref="formRef"
 mode="Add"
 />
      <el-button >新增</el-button>
</template>

<script setup>
Import  ActivityForm from './form.vue'
...
</script>

// edit.vue
<template>
<activity-form
 ref="formRef"
 mode="Edit"
 />
   <el-button >编辑</el-button>
</template>

<script setup>
Import  ActivityForm from './form.vue'
...
</script>
// detail.vue
<template>
 <activity-form
 ref="formRef"
 mode="detail"
 />
   <el-button >关闭详情</el-button>
</template>

<script setup>
Import  ActivityForm from './form.vue'
...
</script>

Form.vue

<template>
  <yun-pro-form
    ref="formRef"
    :form="form"
    :columns="columns"
    :form-props="{ labelPosition: 'top',disabled:mode==='detail' }"
  />
</template>

<script lang="jsx" setup>
import { reactive, ref, computed } from 'vue'
...
const formRef = ref()
const form = reactive({
  name: '',
  region: '',
  type: '',
})
const props = defineProps({
  mode: {
    type: String,
    default: 'Add',
  },
});
const columns = [
  {
    prop: 'name',
    label: '活动名称',
    type: 'input',
  },
  {
      prop: 'region',
      label: '活动区域',
      // jsx部分
      render: (form) => (
        <el-input clearable  v-model={form.region} />
      ),
   },
    {
      prop: 'type',
      label: '活动形式',
      // jsx部分
      render: (form) => (
        <el-input clearable  v-model={form.type} />
      ),
   },
]

...
</script>

改成上面的形式后,一处改动,3处对应生效,开发的重点也转移到form表单中对columns的配置上,columns配置则建立在对jsx的运用,当然举的这个例子只是一个简单案例,复杂的例子在项目中,下面我们正式开启jsx之旅吧

JSX是什么

JSX(JavaScript 和 XML),是一个 HTML-in-JavaScript 的语法扩展,首先在 React 中被进入,它允许我们在JavaScript中编写类似HTML的代码,并将其转换为JavaScript对象。Vue3中引入了对JSX的支持,使得我们可以更加灵活地编写组件模板,不再局限于Vue2.x中的模板语法。JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。

形如:

const element = <div id="root">Hello World</div>

function getGreeting(user) {
    if (user) {
        return <h1>Hello, {formatName(user)}!</h1>;
    }
    return <h1>Hello, Stranger.</h1>;
}

JSX 和 template 的区别

--语法上有很大区别:

•JSX 本质就是 js 代码,可以使用 js 的任何能力

•template 只能嵌入简单的 js 表达式,其他需要指令,如 v-if

•插值不同,jsx 使用单括号 { },template 使用双括号 {{ }}

•JSX 已经成为 ES 规范,template 还是 Vue 自家规范

•--本质是相同的:

•都会被编译为 js 代码(render 函数)

基本用法

vite 官方提供了官方的插件来支持在 vue3 中使用 jsx

yarn add @vitejs/plugin-vue-jsx

安装完之后在 vite.config.js 文件中的 plugins 字段中添加 jsx 支持:

import vueJsx from "@vitejs/plugin-vue-jsx";
export default defineConfig({
  plugins: [
    vueJsx(),
  ]
})

// template
<template>
  <span>{{ a + b }}</span>
</template>

// jsx
 render: () => {
    return (
       <span>{ a + b }</span>
    )
}

•jsx只保留了 v-show指令,没有 v-if指令

•使用 if/else和三目表达式来实现判断

// template v-if  v-show
<template>
  <div v-if="show">是</div>
  <div  v-else>否</div>
   <div v-show="show">我是v-show</div>
</template>

// jsx
 render: () => {
    const show = ref(true);
    return (
    <div>
       <div>{show.value ? <div>是</div> : <div>否</div>}</div>
        <div v-show={!show.value}>我是v-show</div>
     </div>
    )
}
// jsx  if-else

 render: () => {
    const isShow = false
      const element = () => {
       if (isShow) {
           return <div>是</div>
       } else {
           return <div>否</div>
       }
   }
    return (
    <div>
           {
               element()
           }
        <div v-show={!show.value}>我是v-show</div>
     </div>
    )
}

•class类名绑定有两种方式,使用模板字符串或者使用数组。

•style绑定需要使用 双大括号

// template
<template>
 <div  class="static" :class="{ active: isActive }"></div>
 <div :class="[isActive ? activeClass : '', static]"></div>
 <div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
</template>

// jsx
// jsx 模板字符串
<div className={`static  ${ isActive ? 'active' : '' }`}>header</div>
//jsx 数组
<div class={ [ 'static', isActive && 'active' ] } >header</div>

// jsx 样式绑定需要使用双大括号。
 render: () => {
    const width = '100px'
    return (
      <button style={{ width, fontSize: '16px' }}></button>
    )
}



// template v-for

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

// jsx 数组 .map 函数

 render: () => {
    return <>
      <ul>
        { list.map(item => <li>{item}</li>)}
      </ul>
    </>
  }

•jsx绑定事件使用的是 单大括号 {},事件绑定不是以 @为前缀了,而是改成了 on,例如:click 事件是 onClick

•如果需要使用事件修饰符,就需要借助withModifiers方法啦,withModifiers 方法接收两个参数,第一个参数是绑定的事件,第二个参数是需要使用的事件修饰符,或者在有些修饰符不生效的情况下采用链式驼峰的形式进行设置,如@click.once变为 onClickOnce

// template 

<template>
 <button @click="clickButton('button1')"> 点击</button>
 <!-- 单击事件将停止传递 -->
  <a @click.stop="doThis"></a>
 <!-- 点击事件最多被触发一次 -->
 <a @click.once="doThis"></a>
</template>

// jsx 数组 .map 函数

 render: () => {
     const clickButton = val => {
        console.log(val)
    }
    return (
      <div>
      <button  onClick={() =>clickButton('button1')}>点击</button>
       <a onClick={withModifiers(() => doThis(), ['stop'])}></a>
       <a onClickOnce={doThis}></a>
      </div>
    )
  }



// template
<template>
<div v-bind="properties"></div>
</template>

//jsx
 render: () => {
    return (
      <div {...properties}></div>
    )
  }




// 一般用法
<input v-model="value" /> //  template
<input v-model={value} /> // jsx

// 指定绑定值写法
<input v-model:modelValue="value" /> //  template
<input v-model={[value,'modelValue']} /> // jsx

// 修饰符写法
<input v-model:modelValue.trim="value" /> //  template
<input v-model={[value,'modelValue',['trim']]} /> // jsx

定义插槽

jsx/tsx中是没有 slot 标签的,定义插槽需要使用{}或者使用renderSlot函数

setup 函数默认接收两个参数 1. props 2. ctx 上下文 其中包含 slots、attrs、emit 等

// template
<template>
  <div>
   <slot></slot>
   <slot name="title"></slot>
  </div>
</template>

// jsx
import { renderSlot } from "vue"
export default defineComponent({
    // 从ctx中解构出来 slots
    setup(props, { slots }) {
        return () => (
            <div>
                { renderSlot(slots, 'default') } // 等价于  { slots.default?.() }
                { slots.title?.() }
            </div>
        )
    }
})

使用插槽

通过 v-slots 来使用插槽

// template
<template>
   <yun-table
  >
    <template #action="{ row }">
      <el-button type="action" @click="handleDel(row)">
        删除
      </el-button>
    </template>
  </yun-table>
</template>

// jsx
render: (form) => {
        const slots = {
          action: ({ row }) => <el-button type="action" onClick={() => handleDel(row)}>
          删除
        </el-button>,
        };
        return (
             <yun-table
              v-slots={slots}
            >
            </yun-table>

        );
      },



import { defineComponent, onMounted, ref } from 'vue';

export default defineComponent({
  // props: ['xx'],
  setup(props,{ emit }) {
    onMounted(() => {
      // ...
    })
    return () => (
      <div></div>
    )
  }
})

在 .vue 文件中使用 jsx组件

// 父

<template>
  <div class="home">
    <JSXDemo />
  </div>
</template>

<script setup lang="jsx">
import JSXDemo from '@/components/JSXDemo.vue'

</script>

// JSXDemo.vue

<script>
import { ref } from 'vue'
export default {
  setup () {
    const countRef = ref(200)

    const render = () => {
      return <p>DEMO1--{countRef.value}</p>
    }
    return render
  }
}
</script>

在.jsx文件格式中父子组件属性传递

// 父组件

import { defineComponent, ref } from 'vue'
import JSXChild from './JSXChild.jsx'

export default defineComponent(() => { // 传入 setup 函数
  const countRef = ref(360)

  const render = () => {
    return <>
      <p>数量--{countRef.value}</p>
      <JSXChild a={countRef.value + 100}></JSXChild>  // vue3的template会自动解析ref的.value,在jsx中ref的.value是不会被自动解析的
    </>
  }
  return render
})

// 子组件 JSXChild.jsx

import { defineComponent } from 'vue'

export default defineComponent({ // 传入组件配置
  props: ['a'],
  setup (props) {
    const render = () => {
      return <>
        <p>child {props.a}</p>
      </>
    }
    return render
  }
})

经验总结

我们该怎么选择 JSX 和 template ?

template优势:template 的语法是固定的,有 v-if、v-for 等等语法。按照这种固定格式的语法书写的代码, Vue3 在编译层面就可以很方便地去做静态标记的优化,减少Diff过程。比如静态提升,类型标记,树结构打平等来提高虚拟 DOM 运行时性能。这也是 Vue 3 的虚拟 DOM 能够比 Vue 2 快的一个重要原因。

JSX优势:template 因为语法限制原因,不能够像 JSX 那样可以支持更动态的需求。每一个 .vue 文件结尾的文件都是一个组件,而且只能 export default 出一个组件,JSX 则不同 ,是可以在一个文件内返回多个组件的,比如我们写一个页面的时候其实可能会需要把一些小的节点片段拆分到小组件里面进行复用,这些小组件在jsx里面,写个简单的函数组件就能搞定,例如:

那如何选择呢?

在实现业务需求的时候,优先使用 template,尽可能地利用 Vue 本身的性能优化,如列表、弹窗和抽屉。而对于动态性要求较高的组件可以使用 JSX 来实现,比如动态表单,封装动态递归组件。而对于公司项目来说,大多数业务需求都是表单类的,那就赶紧用上jsx吧,用久了你就会发现,哎,真香。