• Tsx写一个通用的button组件


    一年又要到年底了,vue3.0都已经出来了,我们也不能一直还停留在过去的js中,是时候学习并且在项目中使用一下Ts了。

      如果说jsx是基于js的话,那么tsx就是基于typescript的

      废话也不多说,让我们开始写一个Tsx形式的button组件,

      ts真的不仅仅只有我们常常熟知的数据类型,还包括接口,类,枚举,泛型,等等等,这些都是特别重要的

      项目是基于vue-cli 3.0 下开发的,可以自己配置Ts,不会的话那你真的太难了

      

         我们再compenonts中新建一个button文件夹,再建一个unit文件夹,button放button组件的代码,unit,放一些公共使用模块

      我们再button文件夹下创建 ,index .tsx放的button源码,index.less放的是样式,css也是不可缺少的

           

       分析一下button需要的一些东西

      第一个当然是props,还有一个是点击事件,所以我们第一步就定义一下这两个类型

    type ButtonProps = {
      tag: string,
      size: ButtonSize,
      type: ButtonType,
      text: String
    }
    
    type ButtonEvents = {
      onClick?(event: Event) :void
    }
    type ButtonSize = 'large' | 'normal' | 'small' | 'mini'
    type ButtonType = 'default' | 'primary' | 'info' | 'warning' | 'danger'

      因为button是很简单的组件,内部也没有一些特别的状态需要改变,所以我们用函数式组件的方式去写(之后的render会用到这个方法)

    function Button (h: CreateElement, props: ButtonProps, slots: DefaultSlots, ctx: RenderContext<ButtonProps>) {
      const { tag, size, type } = props
      let text
      console.log(slots)
      text = slots.default ? slots.default() : props.text
      function onClick (event: Event) {
        emit(ctx, 'click', event)
      }
      let classes = [size,type]
      return (
        <tag
          onClick = {onClick}
          class = {classes}
        >
          {text}
        </tag>
      )
    }

      h 是一个预留参数,这里并没有用到 ,CreateElement  这个是vue从2.5之后提供的一个类型,也是为了方便在vue项目上使用ts

      props 就是button组件的传入的属性,slots插槽,ctx,代表的是当前的组件,可以理解为当前rendercontext执行环境this

      DefaultSlots是我们自定义的一个插槽类型

    export type ScopedSlot<Props = any> = (props?: Props) => VNode[] | VNode | undefined;
    
    export type ScopedSlots = {
      [key: string]: ScopedSlot | undefined;
    }

      插槽的内容我们都是需要从ctx中读取的,默认插槽的key就是defalut,具名插槽就是具体的name

      button放发内部还有一个具体的点击事件,还有一个emit方法,从名字我们也可以看的出,他是粗发自定义事件的,我们这里当然不能使用this.emit去促发,

      所以我们需要单独这个emit方法,我们知道组件内所以的自定义事件都是保存在listeners里的,我们从ctx中拿取到所以的listeners

    
    
    
    
      import { RenderContext, VNodeData } from 'vue' // 从vue中引入一些类型

    function
    emit (context: RenderContext, eventName: string, ...args: any[]) { const listeners = context.listeners[eventName] if (listeners) { if (Array.isArray(listeners)) { listeners.forEach(listener => { listener(...args) }) } else { listeners(...args) } }

      这样我们组件内部的事件触发就完成了

      我们的button肯定是有一些默认的属性,所以,我们给button加上默认的属性

    Button.props = {
      text: String,
      tag: {
        type: String,
        default: 'button'
      },
      type: {
        type: String,
        default: 'default'
      },
      size: {
        type: String,
        default: 'normal'
      }
    }

      我们定义一个通用的functioncomponent 类型

    type FunctionComponent<Props=DefaultProps, PropsDefs = PropsDefinition<Props>> = {
      (h: CreateElement, Props:Props, slots: ScopedSlots, context: RenderContext<Props>): VNode |undefined,
      props?: PropsDefs
    }

      PropsDefinition<T>  这个是vue内部提供的,对 props的约束定义

      不管怎么样我们最终返回的肯定是一个对象,我们把这个类型也定义一下

      ComponentOptions<Vue> 这个也是vue内部提供的

     interface DrmsComponentOptions extends ComponentOptions<Vue> {
      functional?: boolean;
      install?: (Vue: VueConstructor) => void;
    }

      最终生成一个组件对象

    function transformFunctionComponent (fn:FunctionComponent): DrmsComponentOptions {
      return {
        functional: true, // 函数时组件,这个属性一定要是ture,要不render方法,第二个context永远为underfine
        props: fn.props,
        model: fn.model,
        render: (h, context): any => fn(h, context.props, unifySlots(context), context)
      }
    }

      unifySlots 是读取插槽的内容

    // 处理插槽的内容
    export function unifySlots (context: RenderContext) {
      // use data.scopedSlots in lower Vue version
      const scopedSlots = context.scopedSlots || context.data.scopedSlots || {}
      const slots = context.slots()
    
      Object.keys(slots).forEach(key => {
        if (!scopedSlots[key]) {
          scopedSlots[key] = () => slots[key]
        }
      })
    
      return scopedSlots
    }

      当然身为一个组件,我们肯定是要提供全局注入接口,并且能够按需导入

      所以我们给组件加上名称和install方法,install 是 vue.use() 方法使用的,这样我们能全部注册组件

    export function CreateComponent (name:string) {
      return function <Props = DefaultProps, Events = {}, Slots = {}> (
        sfc:DrmsComponentOptions | FunctionComponent) {
        if (typeof sfc === 'function') {
          sfc = transformFunctionComponent(sfc)
        }
        sfc.functional = true
        sfc.name = 'drms-' + name
        sfc.install = install
        return sfc 
      }
    }

      index.tsx 中的最后一步,导出这个组件

    export default CreateComponent('button')<ButtonProps, ButtonEvents>(Button)

      还少一个install的具体实现方法,加上install方法,就能全局的按需导入了

    function install (this:ComponentOptions<Vue>, Vue:VueConstructor) {
      const { name } = this
      Vue.component(name as string, this)
    }

       最终实现的效果图,事件的话也是完全ok的,这个我也是测过的

       代码参考的是vant的源码:https://github.com/youzan/vant

      该代码已经传到git:   https://github.com/czklove/DrpsUI  dev分支应该是代码全的,master可能有些并没有合并

  • 相关阅读:
    为Fiddler增加Burp-like Inspector扩展 实现类似Burpsuite爆破、一键重放、编码转换等功能
    SVN常见问题总结一
    手把手教你学SVN
    js基本语法汇总
    最全的常用正则表达式大全
    CSS padding margin border属性详解
    从零开始学习jQuery (五) 事件与事件对象
    js正则表达式语法
    浏览器内部工作原理
    原生AJAX入门讲解(含实例)
  • 原文地址:https://www.cnblogs.com/czkolve/p/11890941.html
Copyright © 2020-2023  润新知