vue3实践-使用tsx的一点总结

1,241 阅读8分钟

vue3实践-使用tsx的一点总结

文档创建人创建日期文档内容更新时间
adsionli2022-09-08使用tsx的一点总结2022-09-08

在找工作的7,8两个月中间,虽然一直在背八股、做题,没什么时间学习新的内容,但是还是抽空学了一下tsx的书写,以及对tsx进行了实际使用,使用下来的感受就是十分十分的好用,再配合上vue3中对副作用的超级封装,使得可以使用hooks的特性,用起来可以说是如鱼得水,书写代码变成了一种极致的享受。

但是,我还是在使用期间,遇到了太多太多的坑,有的坑,一困住就是一天,不过最后都还好走了出来,现在就把自己犯过的错误来和大家进行一下分享,避免掉入相同的陷阱中。

如何支持tsx

首先,我们需要让我们的vue3项目可以支持tsx,根据官方文档中的说明,我们需要下载一个babel的插件,通过这个插件,先让jsx被支持。

npm i -S @vue/babel-plugin-jsx

安装完成之后,我们需要在babel.config.js中配置一下,告诉babel在生成AST语法树阶段能够转变基于vue的jsx语法。

module.exports = {
  plugins: [
    "@vue/babel-plugin-jsx"
  ]
}

然后我们还需要去修改一下tsconfig.json中的一些内容

{
    "compilerOptions": {
        "jsx": "preserve",
        ...
    },
    "include": [
        "src/**/*.ts",
        "src/**/*.tsx",
        "src/**/*.vue",
        ...
    ]
}

就是在compilerOptions中配置上jsx: "preserve"以及在include中加上tsx结尾的文件。

Vue 的类型定义也提供了 TSX 语法的类型推导支持。当使用 TSX 语法时,确保在 tsconfig.json 中配置了 "jsx": "preserve",这样的 TypeScript 就能保证 Vue JSX 语法编译过程中的完整性。

摘自官方文档

tsx开发时的注意事项

这里先给出两个地址:

  1. vue中内置组件在tsx中的使用
  2. vue渲染函数案例

这里强烈建议大家按照官方给出的来,网上乱七八糟的内容太多了,很多都很乱,还是以官方为准。

这里稍微对官方说的不太多的内容进行一些补充:

事件修饰符

对于一些事件修饰符的使用,虽然官方给出了一些,但是实在是太少了,我们常用的阻止冒泡的stop,以及阻止标签默认事件发生的prevent的用法没有给出,虽然有说withModifiers可以进行使用,但还是有点抽象,这里就写一段代码帮助大家进行理解。

<div
    class={styles.renderContainer + ' renderContainer'}
    id="positionShowBack"
    onClick={withModifiers(() => {
        emit('clostProjection')
    }, ['stop', 'prevent'])}
    ref={preRenderContainer}
>
    .....
</div>

这段代码中我们可以看到我们可以在对应标签下直接在onClick中传入一个对象,其中包括了withModifiers这个函数,并且传入我们的回调执行函数进去,然后在设置第二个参数,用来指明我们需要添加的事件修饰符即可。

v-show问题

在官方中没有提到的v-show的使用问题,但是实际上还是可以进行使用的,但是这里就需要搭配上官方提到的withDirectives自定义指令啦,其实v-show依然是提供给我们使用的,不过官方没有提出,所以大家会不知道这一点,下面就是在tsx中使用的方法

import {
  vShow,
  withDirectives,
} from 'vue'
<ItemAnimation animate={animate.value} duration={playTime.value}>
    {withDirectives(
        <div
            class={styles.preRender}
            tabindex="-1"
            onClick={withModifiers(() => {}, ['stop'])}
            style={renderBackground(pageMap.value.setting, null, true)}
            >
            {renderText(pageMap.value.item.text)}
            {renderImage(pageMap.value.item.image)}
        </div>,
        [[vShow, props.display && !playPosition.value]]
    )}
</ItemAnimation>

我们可以直接从vue中导入vShow,然后在使用withDirectives,第一个参数是我们需要渲染出来的虚拟Node,第二个参数是一个数组,里面需要放入我们需要添加的自定义指令,这里我们需要传入的就是vShow这个指令,然后第二个就是传入参数值,用于触发指令。

withDirectives的参数有两个:

  1. 现有的vnode
  2. 自定义指令数组

自定义指令数组包括以下内容

function withDirectives(
  vnode: VNode,
  directives: DirectiveArguments
): VNode

// [Directive, value, argument, modifiers]
/**
* @param {Directive} Directive 自定义指令函数
* @param {any} value 自定义指令传入的参数
* @param {argument} arg 自定义指令的额外参数,其实就比如在使用v-model的时候,我们会指定v-model:name,后面的那个name
* @param {DirectiveModifiers} modifiers 自定义指令的一些标识符,比如stop/once等,需要写成{stop: true, once: true}这种形式
*/
type DirectiveArguments = Array<
  | [Directive]
  | [Directive, any]
  | [Directive, any, string]
  | [Directive, any, string, DirectiveModifiers]
>

组件使用

这里说的组件导入要分成两块,一种是从另外一个tsx文件进行导入,还有一种就是从vue文件中进行导入,这两种导入和使用方式都是相同的,不过有一点需要注意:当使用vue文件进行组件导入到tsx文件中是,vue文件中的组件不需要声明Name属性,否则会报错。但是如果是通过tsx导出组件的话,又可以申明name,不知道为啥。

组件ref获取dom

然后就是ref获取Dom对象问题,实际上和在普通的vue中使用是一样的,但是由于我们把render的内容写在了setup中,导致我们在expose的时候是拿不到ref对象然后返回给引入这个组件的父组件中的,这里有一种我自己的解决方法,就是通过emit进行返回。就如下面这段代码中所示:

setup(props, { emit }) {
    const instance = getCurrentInstance()
    ......
    const preRenderContainer: any = ref()
    ......

    provide('playPosition', playPosition)
    watch(
      () => handleObj.currentPageData,
      async (newV: any, oldV: any) => {
        ......
        emit('getRef', preRenderContainer.value)
        ......
      }
    )
    return () => (
      <Teleport to={'body'} disabled={teleportFullScreen.value}>
        <div
          class={styles.renderContainer + ' renderContainer'}
          id="positionShowBack"
          onClick={withModifiers(() => {
            emit('clostProjection')
          }, ['stop'])}
          ref={preRenderContainer}
        >
          ......
        </div>
      </Teleport>
	)
}

可以看到我们在代码里会把获取到的ref对象emit给父组件,这样就不需要再通过expose进行返回了。

这里主要就是因为jsx代码是写在setup中,才导致这个问题出现,主要是为了使用composition-api的编码风格,其实写在render中的话,我没试过...

props类型判断

还有一个问题就是props类型的问题,我看了不少issue之后,发现还是只有这种方式可行,代码如下:

interface ToolbarProps {
  position: number[]
}

const PreViewToolbar = defineComponent({
  emits: [],
  props: ['position'],
  setup(props: ToolbarProps, { emit }) {
    ......
  },
})

首先就是需要在外部先设置一个Interface或者TypeProps接收的内容的类型进行定义,然后在使用options-api的形式,定义一下Props接受的参数有哪些,最后再在setup中的props声明一下其类型。

实在是过于复杂,我试过其他方法,发现都无法有效果,只有这样来定义props,才能获取到代码提示和类型检测。

css导入

css导入很简答,可以按照普通的形式进行导入就可以了,就下面这段代码:

import './index.scss'
export default defineComponent({
    setup(){
        return () => (
            <div class="xxx"></div>
        )
    }
})

除了上面这种导入之外,我们可以通过css-module的形式进行导入,通过css-module导入的话,我们可以将其看做一个对象,然后写成下面这种形式:

import styles from './scss/toolbar.module.scss'

const PreViewToolbar = defineComponent({
  setup() {
    ......
    return () => <div class={styles.toolbar_container}>{getToolButton(instance.ctx.$el)}</div>
  },
})

我们就可以把样式全部当成一个对象然后传入进去使用就可以了,也是挺方便的,不过这种就需要配置过了,具体可以看一下这篇文章,跟着配一下即可,完全是可用的,亲测,强烈推荐,非常的好用。

emit问题

不知道为啥,我看别人写的时候,他们并没有声明emit的内容,也没有警告,但是我自己在写的时候,如果使用了emit,但是没有声明过的话,就会有控制台警告,很烦,所以自己定一下也没什么问题,这样还能有代码提示呢。

export default defineComponent({
  name: 'PreRenderContainer',
  emits: ['getRef', 'getPage', 'clostProjection'],
  setup(props, { emit }) {
    ......
  },
})

组件类型问题

这个问题没有解决掉,现在都只能使用any的类型来代替,就很麻烦,各位大佬求求指点一些,呜呜呜。

v-for循环获取ref

这个其实和之前在fragment中的操作差不多,需要先声明一个ref([]),然后在组件循环时,在ref传入一个回调执行函数就可以了,如下段代码

const pageImage = ref([])
arr.map((v: any) => {
    return (
        <div
            class={styles.preRender}
            tabindex="-1"
            ref={(el: any) => {
                if (!pageImage.value.includes(el)) {
                    pageImage.value.push(el)
                }
            }}
            style={renderBackground(v.setting, null, true)}
            >
            {renderText(v.item.text)}
            {renderImage(v.item.image)}
        </div>
    )
})

当然,在tsx中v-for的形式可以使用Arraymap函数中的返回来替代,因为tsx可以支持直接解析一个虚拟Node数组的。

上面差不多就是在开发组件时遇到的一些问题啦,就在这里记录一下,避免以后再遇到同样的问题,导致重复踩坑的惨案发生。

hooks使用

虽然说hooks的使用并不是在tsx中才独有的,在正常的vue开发中也可以使用hooks,但是我感觉tsx+hooks的开发实在是太香了,现在vue3之后,对于副作用的比较好的捕获之后,也可以开始逐渐导入hooks的感念,让代码达到松耦合、高内聚。

不过关于hooks的内容已经太多太多了,好的使用方式也特别多了,所以这里就不再继续阐述了,大家可以在自己平时的代码开发中去使用这些好的设计方式,来让自己的代码变得易度、可维护。

总结

这里会持续进行更新,记录自己在使用tsx开发过程中遇到的问题,然后慢慢的记录下来,防止自己重复踩坑,同时方便大家进行查看,写的有点混乱,希望大家多多包含😂