百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

vue3+tsx开发语法详解

ztj100 2024-11-26 11:15 46 浏览 0 评论

很多组件库都使用了TSX的方式开发,主要因为其灵活性比较高,TSXSFC开发的优缺点就不介绍了,这里主要说一下将SFC项目改造为TSX的过程。

安装JSX库

pnpm install @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() //插件使用
  ],
});

然后就可以愉快的使用TSX来开发Vue组件了,下面主要说一下SFCTSX的部分区别。

基本语法对照 SFC

defineComponent 和 setup

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

<template>
  <div>Hello World</div>
</template>

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

<style scope>
</style>

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

import { defineComponent } from 'vue';

export default defineComponent({
  setup() {
    // Todo
    return () => <div>Hello World</div>
  }
})

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

import { defineComponent } from 'vue';

export default defineComponent({
  setup() {
    // Todo
    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() {
    // Todo
    return () => {
      if (something) {
        return (
          <div>
            <div>Child1</div>
            <div>Child2</div>
            <div>Child3</div>
          </div>
        )
      } else {
        return (
          <div>noChild</div>
        )
      }
    }
  }
})

这种方式类似v-if,但是和v-if还是有点区别,v-if可以作用在更小的范围,而这种方式只适合整个组件的条件渲染,这个可能不好理解,在下面v-if的使用中我们会看到区别。

v-if

使用条件判断语句来实现v-if的功能,一般是三目运算符

// SFC
<template>
  <div>
    <span v-if="condition ">A</span>
    <span v-else>B</span>
  </div>
</template>

// TSX
return () => (
  <div>
    {
      condition ?  <span>A</span> : <span>B</span>
    }
  </div>
)

在这里你可以看到v-if的使用和我们上面的条件返回不一样,区别就是整体渲染没有大的变化,只是其中部分地方要按条件显示。

v-bind

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

// SFC
<template>
  <div :class="c_class" :style="c_style" :custom="custom">
</template>

// TSX
return () => (
  <div class={c_class} style={c_style} custom={custom}>
)

v-for

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

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

// TSX
return () => (
  <>
    {
      list.map((item, index) => <div>{item}</div>
    }
  </>
)

自定义指令

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

// SFC
<template>
  <div v-custom="command">自定义指令</div>
</template>

// TSX
return () => (
  <div v-custom={command}>自定义指令</div>
)

插槽

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

// SFC child
<template>
  <div>
    <slot>默认插槽: default</slot>
    <br />
    <slot name="header">具名插槽:header</slot>
    <br />
    <slot name="main" :var1="111" class="item" :var2="222">作用域插槽:main</slot>
  </div>
</template>

// SFC parent
<template>
  <div>
    <child>
      我就是默认的
      <template #main="row"> 我是主要的{{ row.var1 }} </template>
      <template #header>我是头</template>
    </child>
  </div>
</template>

// TSX child
import { defineComponent } from 'vue';

export default defineComponent({
  setup(props, { slots }) {
    return () => (
      <div>
        默认插槽: {slots.default && slots.default()}
        <br />
        具名插槽: {slots.header && slots.header()}
        <br />
        作用域插槽:{slots.main && slots.main({ name: '我是作用域插槽的传值' })}
      </div>
    );
  }
});

// TSX parent 第一种方式
return () => (
  <Child v-slots={{
      default: () => '默认的内容是',
      header: () => '我是有名称的',
      main: (props: Record<'name', string>) => '我才是主要的' + props.name
    }}>
  </Child>
)

// 第二种方式
return () => (
  <Child v-slots={{
      default: () => '默认的内容是',
      header: () => '我是有名称的',
      main: (props: Record<'name', string>) => '我才是主要的' + props.name
    }}>
  </Child>
)

props

父组件向子组件传值

// SFC
defineProps<{
  name: string,
  childs: string[]
}>()

// TSX
export default defineComponent({
  props: {
    name: String,
    childs: {
      type: Array as PropsType<string[]>,
      default: []
  },
  setup(props, { slots }) {
    return () => <div>{props.name}</div>
  }
})

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

emit

子组件向父组件传值

// SFC
const emits = defineEmits<{
  (e: 'changeName', name: string): void;
}>();

emits('changeName', '张三')

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

事件监听

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

// SFC
<template>
  <div @click="handleClick">无参数</div>
  <div @click="(event) => handleClick1(event)">鼠标事件参数</div>
  <div @click="handleClick2('abc')">自定义参数</div>
</template>

// TSX
return () => (
  <>
    <div onClick={handleClick}>无参数</div>
    <div onClick={(event) => handleClick1(event)}>鼠标事件参数</div>
    <div onClick={() => handleClick2('abc')}>自定义参数</div>
  </>
);

// 函数定义相同
const handleClick = () => {
  console.log('click');
};

const handleClick1 = (e: MouseEvent) => {
  console.log(e.offsetX);
};

const handleClick2 = (name: string) => {
  console.log(name);
};

自定义事件只需要在事件名前面加上on即可,参数传递与上面一致

// SFC
<div @custom="handleCustom()"></div>

// TSX
<div onCustom={handleCustom}></div>

TSX中处理事件不能使用事件修饰符,因此需要在事件函数中自行处理,例如冒泡、阻止默认行为等。

属性/事件继承

对于这个我也不知道怎么描述,当我们给一个组件传递属性和事件时,一般子组件在props中接收属性值,emits中接收事件,但是我们也可以传一些额外的属性和事件,即不在propsemits中的属性和事件,虽然这是不推荐的做法,但是有时候当我们封装第三方库的时候,这种用法就非常的方便。具体看如下代码:

// parent name和click都不在子组件的明确接收中
<Child name="张三" @click="handleClick" />


// SFC child
<div v-bind="$attrs">继承属性/事件</div>

// TSX child
<div {...attrs}>继承事件</div>

SFC中,在template中我们可以通过$attrs获取到额外的属性和方法,script中可以通过getCurrentInstance方法获取组件对象,然后通过.attrs拿到属性和方法。

TSX中,直接通过attrs获取属性和方法,通过{...attrs}把属性和方法传递给子元素。

其他命令

v-showv-modelSFC中使用一样,这里不做示例

组件引用

通过ref获取组件dom信息

// SFC
<ChildComponent ref="com" />

const com = ref(null)

// TSX
const com = ref(null)

return () => <ChildComponent ref={com} />

对外暴露属性和方法

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

// SFC
defineExpose<{
  name: Ref<string>;
  handleClick: () => void;
}>({
  name,
  handleClick
});


// TSX
setup(props, { expose }) {
  expose({name, handleClick})
}

样式修改

样式的改造一度是我切换TSX的最大痛点,因为在SFC中最麻烦的是修改第三方库的样式,一般要用到:deep,而且有时候还不一定成功,非常麻烦,改为TSX后我一直不知道怎么解决这种问题,后来搞定以后再回过头来看,发现是vue写久了养成了固定思维。我们在vue文件中写的样式都包含在scoped下面,如果不加scoped就可能会造成全局样式污染。那为什么会造成全局样式污染?又为什么加了scoped就不会呢?实际上我们只要知道CSS基础,明白CSS中的样式优先级即可。vue生成的项目最终还是会回归到htmlcssjs来,因此我们从这里来理解就方便多了。

  • 为什么会造成全局样式污染?

这个不是vue的专利,而是css本身的优先级问题,就是如果我们定义了相同的css类,并以相同的方式来使用它,那么根据先后加载顺序,就会导致后加载的覆盖掉先加载的样式,造成先加载的样式无效,这就是所谓的样式污染。

  • 为什么加了scoped就不会造成样式污染呢?

我们看一个简单的例子:

<div class="item">样式示例</div>

<style lang="less" scoped>
.item {
  background-color: pink;
}
</style>

看一下htmlcss源码

可以看到,vue组件在渲染的时候,会给元素增加一个属性data-v-xxxx,然后在生成样式的时候也会在样式上加上[data-v-xxxxx],这是css属性选择器的用法,这样根据css选择器的优先级,这个属性就具有唯一性。

但是在TSX中没有了scoped怎么办?很简单,回归原始的css即可。在原始css中需要我们自己来保证css选择的唯一性,具体做法就是给组件内使用的css类都加上唯一前缀,例如组件名称为Child,那么所有的css类都加上child-xxx,因为我们肯定要保证组件名称的唯一性,所以这样下来对应的样式也就是唯一的。这就要求我们给所有需要修改样式的元素都加上类或者自定义属性,以便于我们可以通过唯一的css选择器选中它。

示例如下:

创建一个css文件:child.css

.child-item {
  background-color: pink;
}

tsx文件中引用

// TSX child.tsx
import './child.css'

return () => <div class="child-item"></div>

除了上面这种保证样式名称唯一的方式以外,vue其实一直为我们提供了另外一种方式-css module,具体来讲就是把css作为模块引入到js中,然后会生成一个唯一的名称,在以前用webpack的时候还需要装额外的包,现在vite已经帮我们集成了,只需要在vite.config.ts中加一下配置即可。

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

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

示例如下:

创建一个css文件:child.module.css

.child-item {
  background-color: pink;
}

tsx文件中引用

// TSX child.tsx
import styles from './child.module.css'

return () => <div class={styles.childItem}></div>

打开浏览器看一下源码

可以看到元素上绑定的css和全局的css都出现了变化,这种方式我们就不需要去关注编写的css是否是唯一的,vite会帮我们自行处理,只是在使用的时候有一些区别。

除了常规的css使用,我们还有动态class的使用。

// SFC
<div class="box" :class="{active: count === 1}"></div>

// TSX
<div class={['box', count===1 ? 'active' : '']}></div>

我们把需要的class处理成一个数组给它即可。

除了动态class还有动态style的使用。

// SFC
<div :style="{ height: height + 'px' }"></div>

// TSX
<div style={{ height: height.value + 'px' }}></div>

总结

目前对于大部分场景的使用都写到了,如果后期有其他的语法糖在进行补充,示例项目:vue3-tsx-todo,请在gitee上进行搜索

相关推荐

Python 操作excel的坑__真实的行和列

大佬给的建议__如何快速处理excelopenpyxl库操作excel的时候,单个表的数据量大一些处理速度还能接受,如果涉及多个表甚至多个excel文件的时候速度会很慢,还是建议用pandas来处理,...

Python os.path模块使用指南:轻松处理文件路径

前言在Python编程中,文件和目录的操作是非常重要的一部分。为了方便用户进行文件和目录的操作,Python标准库提供了os模块。其中,os.path子模块提供了一些处理文件路径的函数和方法。本文主要...

Python常用内置模块介绍——文件与系统操作详解

Python提供了多个强大的内置模块用于文件和系统操作,下面我将详细介绍最常用的几个模块及其核心功能。1.os模块-操作系统交互...

Python Flask 建站框架实操教程(flask框架网页)

下面我将带您从零开始构建一个完整的Flask网站,包含用户认证、数据库操作和前端模板等核心功能。##第一部分:基础项目搭建###1.创建项目环境```bash...

为你的python程序上锁:软件序列号生成器

序列号很多同学可能开发了非常多的程序了,并且进行了...

PO设计模式全攻略,在 UI 自动化中的实践总结(以企业微信为例)

一、什么是PO设计模式?PO(PageObject)设计模式将某个页面的所有元素对象定位和对元素对象的操作封装成一个Page类,即一个py文件,并以页面为单位来写测试用例,实现页面对象和测试用例的...

这种小工具居然也能在某鱼卖钱?我用Python一天能写...

前两天在某鱼闲逛,本来想找个二手机械键盘,结果刷着刷着突然看到有人在卖——Word批量转PDF小工具...

python打包成exe,程序有图标,但是任务栏和窗口都没有显示图标

代码中指定图标信息#设置应用ID,确保任务栏图标正确显示ifsys.platform=="win32":importctypesapp_id=...

使用Python构建电影推荐系统(用python做推荐系统)

在日常数据挖掘工作中,除了会涉及到使用Python处理分类或预测任务,有时候还会涉及推荐系统相关任务。...

python爬取并分析淘宝商品信息(python爬取淘宝商品数据)

python爬取并分析淘宝商品信息背景介绍一、模拟登陆二、爬取商品信息1.定义相关参数2.分析并定义正则3.数据爬取三、简单数据分析1.导入库2.中文显示3.读取数据4.分析价格分布5.分析销售...

OpenCV入门学习基础教程(从小白变大神)

Opencv是用于快速处理图像处理、计算机视觉问题的工具,支持多种语言进行开发如c++、python、java等,下面这篇文章主要给大家介绍了关于openCV入门学习基础教程的相关资料,需要的朋友可以...

python图像处理-一行代码实现灰度图抠图

抠图是ps的最基本技能,利用python可以实现用一行代码实现灰度图抠图。基础算法是...

从头开始学python:如何用Matplotlib绘图表

Matplotlib是一个用于绘制图表的库。如果你有用过python处理数据,那Matplotlib可以更直观的帮你把数据展示出来。直接上代码看例子:importmatplotlib.pyplot...

Python爬取爱奇艺腾讯视频 250,000 条数据分析为什么李诞不值得了

在《Python爬取爱奇艺52432条数据分析谁才是《奇葩说》的焦点人物?》这篇文章中,我们从爱奇艺爬取了5万多条评论数据,并对一些关键数据进行了分析,由此总结出了一些明面上看不到的数据,并...

Python Matplotlib 库使用基本指南

简介Matplotlib是一个广泛使用的Python数据可视化库,它可以创建各种类型的图表、图形和可视化效果。无论是简单的折线图还是复杂的热力图,Matplotlib提供了丰富的功能来满足我们...

取消回复欢迎 发表评论: