天天看点

【组件库从0到1】Vite + Vue3 + TSX开发指南

前言

不少前端小伙伴问过我,说业务代码写了2、3年了,应该如何再进一步?村长的回答中大多数会提到两个关键点:加强工程化实践能力和底层原理源码知识。工程化能力对于小伙伴们在工作中解决问题有直接影响,如果你想成为小组内的核心程序员,拿更高的职位和薪资,能够给大家搭架子,解决问题,重难点突破是非常关键的几个考察点。实践中,我们如何刻意提升自己这方面的能力,我觉得给公司搭建一套组件库就很好。在这个过程中大家需要解决不少工程问题,比如:

  • 如何构建一个敏捷的开发、发布环境
  • 如何实现组件库文档系统
  • 引入jsx/tsx
  • 组件的设计与编写
  • 代码质量:eslint、单测等
  • 风格化、国际化等

知识储备

本次我要带大家编写一个Tree组件,会用到JSX,因此我们提前做一些知识储备:

  • 配置Vite支持JSX
  • Vue3中的JSX:jsx-next语法学习

Vite中引入JSX

本次Tree组件实现要用到JSX支持,所以小伙伴们有必要先了解如何在Vite项目中引入JSX。

我们可以通过一个叫做@vitejs/plugin-vue-jsx插件实现,

首先安装它:

yarn add @vitejs/plugin-vue-jsx -D      

配置一下该插件,​

​vite.config.ts​

​:

// vite.config.js
import vueJsx from '@vitejs/plugin-vue-jsx'

export default {
  plugins: [
    vueJsx({
      // options are passed on to @vue/babel-plugin-jsx
    })
  ]
}      

测试一下,​

​components/Tree.tsx​

export default () => <div>tree</div>      

如何定义一个组件

在TSX文件中定义组件有几种常用方式:

  • 函数式组件:最简单的方式,你可以把它理解为​

    ​setup​

    ​函数,但是不同之处它可以直接返回JSX
export default (props, ctx) => <div>tree</div>      
  • ​defineComponent​

    ​​:传递​

    ​render​

    ​选项,也就是Options API,这种缺点是要访问​

    ​this​

export default defineComponent({
  render() {
    return <div>tree</div>
  }
})      
  • defineComponent:传递​

    ​setup​

    ​选项,利用Composition API,避免​

    ​this​

    ​,是推荐的形式
export default defineComponent({
  setup(props, ctx) {
    return () => <div>tree</div>
  }
})      

Vue3中JSX/TSX特殊语法

如果有过react的开发经验,可以发现除了vue中独有的几个新概念:​

​slot​

​​、​

​directive​

​​、​

​emit​

​等以外,其他都是相同的,因此这里仅关注这些特殊部分:

指令

指令使用需要我们转换思维,有几种不同情况:

  • 常用指令v-model,v-show跟以前用法类似,但也要注意后面是​

    ​{}​

    ​,不是​

    ​""​

<input type="text" v-model={this.counter} />      
  • v-if使用条件语句或三元表达式代替
<div>{ condition ?  <span>A</span> : <span>B</span> }</div>      
  • v-for使用map代替
import { defineComponent, ref } from "vue";
const App = defineComponent({
  setup(){
    const list = ref<string[]>([])
    return () => {
      list.value.map((data,index) => <p key={index}>{data}</p>)
    }
  }
});
      

插槽

JSX中想要实现Vue中的插槽写法也有很大不同,主要利用一个叫做​

​v-slots​

​的指令来实现:

export default defineComponent({
  setup() {
    return () => (
      <Child
        v-slots={{
          prefix: () => <i>prefix</i>, // 具名插槽
          suffix: (props: Record<"name", string>) => <span>{props.name}</span>, // props可作插槽作用域的作用
        }}
      >
        默认插槽内容
      </Child>
    );
  },
});      
const Child = defineComponent({
  setup(props, { slots }) {
    return () => (
      <>
        默认插槽: {slots.default && slots.default()}
        <br />
        具名插槽: {slots.prefix && slots.prefix()}
        <br />
        作用域插槽:{slots.suffix && slots.suffix({ name: "suffix" })}
      </>
    );
  },
});      

emit

defineComponent({
  emits: ["click"],
  setup(props ,{ emit }) {
    return () => (
      <button onClick={() => {emit("click")}}>点我触发emit</button>
    );
  },
});