# vue3项目中jsx的应用

# 在vue3中使用jsx的两种方式

# 1. 基于单文件SFC


<template>
  <Rander/>
</template>

<script setup lang="tsx">
  import {ref, computed} from 'vue'

  const count = ref(0)
  const Rander = () => {
    return (
        <div>
          <p>{count.value}</p>
          <button onClick={() => count.value++}>点我+1</button>
        </div>
    )
  }
</script>

# 2. 基于JSX文件

  1. 新建jsx文件
  2. 借助defineComponent来定义组件
import {defineComponent, ref} from 'vue'
//方法一 组合式API写法 (推荐)
export default defineComponent({
    setup() {
        const count = ref(0)
        //jsx UI结构
        return () => (
            <div>
                <div>{count.value}</div>
                <button
                    onClick={() => {
                        count.value++
                    }}>
                    点我+1
                </button>
            </div>
        )
    }
})


//方法二
import {defineComponent, ref} from 'vue'

export default defineComponent({
    setup() {
        const count = ref(0)
        return {
            count
        }
    },
    //jsx UI结构
    render() {
        return (
            <div>
                <div>{this.count}</div>
                <button
                    onClick={() => {
                        this.count++
                    }}>
                    点我+1
                </button>
            </div>
        )
    }
})

注意:

vue内置的defineComponent函数作用为:在定义 Vue 组件时提供类型推导的辅助函数。
该方法还有一种备用签名 旨在与组合式 API 和 渲染函数或 JSX 一起使用。

# 插件安装

vue3中的JSX是使用插件@vitejs/plugin-vue-jsx 来支持JSX写法的。

pnpm i @vitejs/plugin-vue-jsx -D

# 插件配置

安装完之后在vite.config.ts进行插件使用,代码如下:

import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'

export default defineConfig({
    plugins: [
        vue(),
        vueJsx()]
})

jsconfig.json配置

{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@/*": [
        "src/*"
      ]
    }
  },
  "include": [
    "src/**/*.tsx"
  ]
}

# defineComponent 和 setup

SFC方式结构固定:template、script、style


<template>
  <div>hello word</div>
</template>

<script lang="ts" setup></script>

<style scoped></style>

TSX方式就完全是一个ts文件的写法,没有模板template和样式style

import {defineComponent} from 'vue'

export default defineComponent({
    setup() {
        return () => <div>hello word</div>
    }
})

# setup中函数的返回值有多种方式,可以直接返回html,这个适合结构简单的页面,如果返回比较多,可以使用如下方式:

import {defineComponent} from 'vue'

export default defineComponent({
    setup() {
        return () => (
            <div>
                <div>child1</div>
                <div>child2</div>
                <div>child3</div>
            </div>
        )
    }
})

# 如果是多节点,可以使用空符号包裹

 return () => (
    <>
        <div>child1</div>
        <div>child2</div>
        <div>child3</div>
    </>
)

# 在以上的方式中我们把除了布局以外的逻辑都写在//Todo部分,但是有时候我们需要做一些按条件渲染的逻辑,那么也可以在return里加处理逻辑,例如:

import {defineComponent} from 'vue'

export default defineComponent({
    setup() {
        return () => {
            if (someThing) {
                return (
                    <div>
                        <div>child1</div>
                        <div>child2</div>
                        <div>child3</div>
                    </div>
                )
            } else return (
                <div>noThing</div>
            )

        }

    }
})

注意

这种方式类似v-if,但是和v-if还是有点区别,v-if可以作用在更小的范围,而tsx这种方式只适合整个组件的条件渲染,

//sfc
<template>
  <div v-if=“soneThing”>A</div>
  <div v-else>B</div>
</template>
return () => (
    <div>
        {
            soneThing ? <div>A</div> : <div>B</div>
        }
    </div>
)

# v-bind

绑定变量,也就是简写的:冒号,修改方式就是将冒号去掉,把双引号改为大括号

//SFC
<template>
  <div :class="myClass" :style="myStyle" :type="myType"></div>
</template>
//TSX
return () => (
    <div class={myClass} style={myStyle} type={myType}></div>
)

# v-for

采用map循环的方式,返回一个数组

//SFC
<template>
  <div v-for="(item ,index) in list" :key="index">{{item}}</div>
</template>
//TSX
return () => (
    <>
        {
            list.map((item, index) => <div>{item}</div>)
        }
    </>
)

# 自定义指令

自定义指令和普通指令v-model一样

//SFC
<template>
  <div v-someThing="someThing">自定义指令</div>
</template>
//TSX
return () => (
    <div v-someThing={someThing}></div>
)

# 插槽

插槽有两种实现方式,一种是用v-slots绑定对象,一种是直接在元素中使用对象。

//SFC 子级
<template>
  <div>
    <slot>默认插槽</slot>

    <slot name="header">具名插槽</slot>

    <slot name="main" :item="item">作用域插槽</slot>
  </div>
</template>

//SFC 父级
<template>
  <child>
    <div>默认插槽</div>

    <template #hearder>
      <div>具名插槽</div>
    </template>

    <template #main="{item}">
      <div>{{item}}作用域插槽</div>
    </template>
  </child>
</template>
//TSX 子级
import {defineComponent} from 'vue'

export default defineComponent({
    setup(props, {slots}) {
        return () => (
            <div>
                <div>默认插槽:{slots.default && slots.default()}</div>

                <div>具名插槽:{slots.hearder && slots.hearder()}</div>

                <div>作用域插槽:{slots.main && slots.main({name: '作用域插槽传值'})}</div>
            </div>
        )
    }
})

// TSX 父级
//第一种方式
return () => (
    <child v-slots={{
        default: () => <div>默认插槽</div>,

        header: () => <div>具名插槽</div>,

        main: (props: Record<'name', string>) => <div>{props.name}作用域插槽</div>
    }}>
    </child>
)

# props

父组件向子组件传值

//SFC
<script lang="ts" setup>
  import {defineProps} from "vue"

  const props = defineProps<{ name: string, age: number }>({
    name: '',
    age: 18
  })
</script>
//TSX
import {defineComponent} from 'vue'

export default defineComponent({
    props: {
        name: {
            type: String,
            default: ''
        },
        age: {
            type: Number,
            default: 18
        },
    },
    setup(props) {
        return () => (
            <div>{props.name}</div>
        )
    }
})

注意

需要注意的是,prop传递过来的值如果没有默认值,需要判断是否为空,可以使用计算属性或者条件渲染处理。

# emit

子组件向父组件传值


<script lang="ts" setup>
  import {defineEmits} from 'vue'

  const emits = defineEmits<{ (value: nameChange, name: String): void }>()
  emits('nameChange', '张三')
</script>
//TSX
import {defineComponent} from 'vue'

export default defineComponent({
    emits: ['changeName'],
    setup(props, {emit}) {
        emit('changeValue', '张三')
        return () => (
            <div>{props.name}</div>
        )
    }
})

# 事件监听

事件监听就是v-on或者@,在TSX中事件以on开头,即使我们的自定义事件没有on,也要在监听的时候加上
一般都采用的是小驼峰的方式

//SFC
<template>
  <div @click="handleClick">无参数</div>

  <div @click="(event)=>handleClick1(event)">有参数</div>

  <div @click="handleClick2('张三')">自定义参数</div>
</template>
//TSX
return () => (
    <>
        <div onClick={handleClick}>无参数</div>

        <div OnClick="(event)=>handleClick1(event)">有参数</div>

        <div OnClick="handleClick2('张三')">自定义参数</div>
    </>
)
//函数定义相同
const handleClick = () => {
    console.log('点击')
}

const handleClick1 = (event) => {
    console.log('有参数', event)
}

const handleClick2 = (name: string) => {
    console.log('自定义参数', name)
}

注意

自定义事件只需要在事件名前面加上on即可,参数传递与上面一致
在TSX中处理事件不能使用事件修饰符,因此需要在事件函数中自行处理,例如冒泡、阻止默认行为等。

# attrs 透传

所有没有在props和emit中声明的属性都会被attrs保存

//SFC 父级
<template>
  <child name="张三" :age="18"/>
</template>

//SFC 子级
<template>
  <div v-bind="$attrs">透传</div>
</template>
//TSX 子级 父级同上
import {defineComponent} from 'vue'

export default defineComponent({
    setup(props, {attrs}) {
        return () => (
            <div {...attrs}>{props.name}</div>
        )
    }
})

# 组件引用

通过ref获取组件dom信息

//SFC
<template>
  <child ref="dom"/>
</template>
<script lang="ts" setup>
  import {ref, onMounted} from 'vue'

  const dom = ref(null)
  onMounted(() => {
    console.log('组件实例', dom.value)
  })
</script>
const dom = ref(null)
return () => (
    <child ref={dom}/>
)

# 对外暴露属性和方法

在父组件中直接调用子组件的属性和方法

//SFC
defineExpose<{ handleClick: () => void }>({handleClick})
setup(props, {expose})
{
    expose({handleClick})
}

# tsx中的css问题

在vue文件中可以在<style/>标签中写入scoped防止造成样式污染
在tsx中我们通常都是使用css文件外部引入的方式定制组件样式 而tsx中并没有<style/>标签让我们写入scoped
在vite项目中vue为我们提供了css module的方式引入css,引入后会生成一个唯一的名称 vite.config.ts配置

 css: {
    modules:{
        localsConvention:'camelCase'
    }
}

这里规定css类名的命名规则为小驼峰,即child-item类在js中会变成childItem变量。但是要实现css module的功能,对css文件命名由要求,必须在后缀名前面是module,例如xxx.module.css、xxx.module.less、xxx.module.scss。
示例如下:

//child.module.css
.child-item {
  color: red;
}

在tsx文件中引用

import('./child.module.css')
return () => <div class={childItem}></div>
上次更新: 12/12/2023, 7:09:33 PM