likes
comments
collection
share

【前端工程化-组件库】从0-1构建Vue3组件库(组件开发)

作者站长头像
站长
· 阅读数 18

前言:知识储备

TSX 定义组件

选择 TSX 来开发组件,可以更加灵活地使用 render 函数,动态创建组件,同时也方便使用 TS 来控制参数/变量的类型。

强大的灵活度控制力对于开发底层组件尤为重要!!!

使用 TSX 定义组件有几种常用的方法:

  1. 函数式写法
export default (props, ctx) => <div>button</div>
  1. defineComponent({render() {}})
export default defineComponent({
    render() {
        return <div>button</div>
    }
})
  1. defineComponent({setup() {}})
export default defineComponent({
    setup(props, ctx) {
        return () => <div>button</div>
    }
})

Vue3中JSX/TSX语法(API变化)

指令

内置指令

v-if

v-if 使用条件语句或三目运算符来代替:

<div>{ condition ? <span>A</span> : <span>B</span> }</div>

v-for

v-for 使用 map 来代替:

import { defineComponent, ref } from "vue";

const App = defineComponent({
    setup(){
        const list = ref<string[]>([1,2,3])
        return () => {
            list.value.map((data,index) => <p key={index}>{data}</p>)
        }
    }
});

扩展个知识点

  • 数组遍历常用 forEachmapforEach重在执行遍历,没有返回值;map可以设置返回值。
  • 在 JSX/TSX 语法中最终是要返回 html 标签或对应内容,所以JSX中使用map方法来遍历。

自定义指令

自定义指令需要配置 directives 选项:

defineComponent({
    directives: {
        // 模版中使用 v-focus
        focus: {
            mounted(el) {
                el.focus()
            }
        }
    },
    setup() {
        return () => <input v-focus>
    }
});

修饰符

借用 withModifiers 向事件处理函数添加修饰符:

import { withModifiers, defineComponent } from "vue";

const App = defineComponent({
    setup() {
        const count = ref(0);
        const inc = () => {
            count.value++;
        };
        return () => (
            <div onClick={withModifiers(inc, ["self"])}>{count.value}</div>
        );
    }
});

插槽

JSX/TSX 中使用插槽,主要使用 v-slots 指令来实现:

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

emit 子向父组件通信

JSX/TSX 中使用 emit 实现子组件向父组件传值,多了 emits 选型配置:

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

开发组件

Button组件

功能需求

【前端工程化-组件库】从0-1构建Vue3组件库(组件开发)

组件开发

我们使用 TSX 语法来开发 Button 组件,代码如下:

packages/components/button/src/button.tsx (button 组件)

import { computed, defineComponent, toRefs } from "vue"
import { buttonProps, ButtonProps } from "./button-type"
import { getComponentCls } from "@vue3-ui/utils"

export default defineComponent({
  name: "UButton",
  props: buttonProps,
  setup(props: ButtonProps, { slots }) {
    const { type, size, disabled } = toRefs(props)
    // class处理
    const prefixCls = getComponentCls("button")
    const classes = computed(() => [
      prefixCls,
      `${prefixCls}--${type.value}`,
      `${prefixCls}--${size.value}`,
      disabled.value ? "is-disabled" : ""
    ])
    return () => {
      const { tag: Component } = props
      const defaultSlot = slots.default ? slots.default() : "按钮"
      return (
        <Component disabled={disabled.value} class={classes.value}>
          {defaultSlot}
        </Component>
      )
    }
  }
})

packages/utils/index.ts

const CLASS_PREFIX = "u"

// 拼装组件className
export const getComponentCls = (componentName: string): string =>
  `${CLASS_PREFIX}-${componentName}`

packages/components/button/src/button-type.ts (提取 button 属性定义)

import { ExtractPropTypes, PropType } from "vue"
export type buttonType =
  | "default"
  | "primary"
  | "success"
  | "info"
  | "warning"
  | "danger"

export type buttonSize = "small" | "default" | "large"

export const buttonProps = {
  type: {
    type: String as PropType<buttonType>,
    default: "default"
  },
  size: {
    type: String as PropType<buttonSize>,
    default: "default"
  },
  disabled: {
    type: Boolean,
    default: false
  },
  tag: {
    type: String as PropType<keyof HTMLElementTagNameMap>,
    default: "button"
  }
}

export type ButtonProps = ExtractPropTypes<typeof buttonProps>

至此,Button 组件的逻辑代码已完成,样式编码可以考虑接入CSS预编译器:LessSassStylus等。组件库里选择接入最早出现成熟稳定的 Sass

Sass 的优势如下:

  1. Sass完全兼容所有版本的CSS。
  2. 特性丰富,Sass拥有比其他任何CSS扩展语言更多的功能和特性。
  3. 技术成熟,功能强大。
  4. 行业认可,越来越多的人使用Sass。
  5. 社区庞大,大多数科技企业和成百上千名开发者为Sass提供支持。
  6. 较多优秀框架使用Sass,比如Element、Bootstrap、Bourbon等。

插件注册

Button 组件功能开发好,需要将其注册成插件导出,实现代码如下:

packages/components/install.ts

import { App } from "vue"
import { Vue3UIOption, installComponent } from "@vue3-ui/utils"
import { components } from "./components"

const Vue3UI = {
  install(app: App, options?: Vue3UIOption) {
    components.forEach(component => {
      installComponent(app, component, options)
    })
  }
}

export default Vue3UI

packages/utils/index.ts

import type { App } from "vue"

export interface Vue3UIOption {
  componentPrefix?: string
}
// 注册插件
export const installComponent = (
  app: App,
  component: any,
  options?: Vue3UIOption
) => {
  app.component(component.name, component)
}

组件使用

项目入口引入组件库即可使用预览,具体如下:

play/main.ts

import { createApp } from "vue"
import "./style.css"
import App from "./App.vue"
import Vue3UI from "@vue3-ui/components"
import "@vue3-ui/theme-chalk/src/index.scss"

createApp(App).use(Vue3UI).mount("#app")

总结

行文至此,我们已经完整开发了 Vue3 组件库的Button组件,其余组件后面会陆续补充完善,有兴趣参与的小伙伴欢迎到 github 协同开发👏👏 ps:组件库 github 地址:github.com/GGXXMM/vue3…

本专栏文章:

  1. 【前端工程化-组件库】从0-1构建Vue3组件库(概要设计)
  2. 【前端工程化-组件库】从0-1构建Vue3组件库(组件开发)
  3. 【前端工程化-组件库】从0-1构建Vue3组件库(单元测试)
  4. 【前端工程化-组件库】从0-1构建Vue3组件库(打包发布)