Vue3 中使用 JSX

209 阅读2分钟

JSX 是什么

JSX 是 JavaScript 语法扩展,可以让你在 JavaScript 文件中书写类似 HTML 的标签。在Vue中可以更灵活的组装组件。

如何在 Vue 项目中使用

  1. 项目中安装 @vitejs/plugin-vue-jsx
  2. vite.config.ts 中配置
import vueJsx from '@vitejs/plugin-vue-jsx'

// ...
plugins: [
  vue(),
  vueJsx(),
]
// ...

单文件组件使用 tsx/jsx 文件后缀。 ShowTime.tsx

import { defineComponent } from 'vue'

export default defineComponent({
  name: 'ShowTime',
  props: {
    local: {
      type: String,
      default: 'en',
    },
  },
  setup(props) {
    return () => <div>{props.local} Hello, Vue!</div>
  },
})

函数式组件

const showUser = props => {
    return (
      <div>
        <i style={{ backgroundImage: `url(${face.value})` }}></i>
        <span>{props.name}</span>
      </div>
    )
  }

组件样式隔离

JSX 没有 scoped 隔离样式需要是用 CSS Modules (vite 脚手架已支持)来隔离组件之间的 class 名。 比如使用 scss 需要将文件后缀改成.module.scss styles.module.scss

.user-wrap {
  width: 160px;
  height: 28px;
  display: flex;
  // ...
  .face {
    display: inline-block;
    width: 24px;
    // ...
  }
}

在项目中使用:

import { computed } from 'vue'
import styles from './styles.module.scss'

export function useUser() {
  const face = computed(
    () =>
      '//i2.hdslb.com/bfs/baselabs/79ba68b71fb31194833852a95799a81c3e16443b.jpg@100w_100h_1c_1s_!web-avatar-nav.avif'
  )

  const showUser = props => {
    return (
      <div class={styles['user-wrap']}>
        <i class={styles.face} style={{ backgroundImage: `url(${face.value})` }}></i>
        <span>{props.name}</span>
      </div>
    )
  }

  return {
    showUser,
  }
}

props 和 emit 使用

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

export default defineComponent({
  props: {
    local: {
      type: String,
      default: 'en',
    },
  },
  setup(props, ctx) {
    return (
      <div>
        <span>{props.local}</span>
      </div>
    )
  },
})
  const showUser = (props, ctx) => {
    const onClickAvatar = () => {
      ctx.emit('handleAvatar')
    }
    return (
      <div class={styles['user-wrap']}>
        {/* ... */}
        <span>{props.name}</span>
      </div>
    )
  }

常用指令

  • v-text、v-html、v-show 没变化
  • v-if 可以使用三元或者函数组件返回
  // ...
  setup(props, ctx) {
    const isShow = ref(true)
    return () => (
      <div>
        {isShow.value ? <p>1</p> : <p>2</p>}
      </div>
    )
  }
  • v-for 使用原生 js 遍历
    const data = [
      {
        id: 1,
        name: '武汉',
      },
      {
        id: 2,
        name: '东高',
      },
    ]
    return () => (
      <div>
        {data.map(item => (
          <div key={item.id}>{item.name}</div>
        ))}
      </div>
    )
  • v-model 语法支持 写法上有区别
// Parent.tsx
const ageValue = ref('0')
// 指定绑定值和修饰符
<CustomInput v-model={[ageValue.value, 'modelValue', ['trim']]} /> {ageValue.value}

// CustomInput.tsx
export default defineComponent({
  name: 'CustomInput',
  props: {
    modelValue: {
      type: String,
      default: '',
    },
  },
  emits: ['update:modelValue'],
  setup(props, ctx) {
    const value = computed({
      get() {
        return props.modelValue
      },
      set(value) {
        ctx.emit('update:modelValue', value)
      },
    })
    return () => <input v-model={value.value} />
  },
})

事件

事件不使用前缀 @, 使用 on 事件修饰符需要使用 withModifiers 方法,withModifiers 方法接收两个参数,第一个参数是绑定的事件,第二个参数是需要使用的事件修饰符

<div onClick={() => clickBox('box wrap')}>
  <span>box wrap</span>
  <div onClick={withModifiers(() => clickBox('box2'), ['stop'])}>box2</div>
</div>

插槽

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

// 子组件定义插槽
import { defineComponent, renderSlot } from 'vue'
export default defineComponent({
  setup(props, { slots }) {
    return () => (
      <div>
        {renderSlot(slots, 'default')}
        {slots.title?.()}
      </div>
    )
  },
})
// 父组件使用插槽
    const slots = {
      default: () => <div>default slot</div>,
      title: () => <div>title slot</div>,
    }
    return () => (
      <div>
        {/* slot */}
        <VSlot v-slots={slots} />
      </div>
    )

作用域插槽

// 子组件定义插槽
export default defineComponent({
  setup(props, { slots }) {
    return () => (
      <div>
        {renderSlot(slots, 'default', { uname: '召唤师', message: 'Hello' })}
        {slots.title?.({ uname: '召唤师', message: 'Hello' })}
      </div>
    )
  },
})
// 父组件使用插槽
    const slots = {
      default: slotProps => (
        <div>
          default slot {slotProps.uname} {slotProps.message}
        </div>
      ),
      title: slotProps => (
        <>
          <span>title slot {slotProps.uname}</span>
          <span>{slotProps.message}</span>
        </>
      ),
    }
    return () => (
      <div>
        {/* slot */}
        <VSlot v-slots={slots} />
      </div>
    )