Tsx寫一個通用的button組件

一年又要到年末了,vue3.0都已經出來了,咱們也不能一直還停留在過去的js中,是時候學習而且在項目中使用一下Ts了。css

  若是說jsx是基於js的話,那麼tsx就是基於typescript的html

  廢話也很少說,讓咱們開始寫一個Tsx形式的button組件,vue

  ts真的不單單隻有咱們經常熟知的數據類型,還包括接口,類,枚舉,泛型,等等等,這些都是特別重要的git

  項目是基於vue-cli 3.0 下開發的,能夠本身配置Ts,不會的話那你真的太難了github

  

 

     咱們再compenonts中新建一個button文件夾,再建一個unit文件夾,button放button組件的代碼,unit,放一些公共使用模塊vue-cli

  咱們再button文件夾下建立 ,index .tsx放的button源碼,index.less放的是樣式,css也是不可缺乏的typescript

       

 

   分析一下button須要的一些東西less

  第一個固然是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可能有些並無合併

 

原文出處:https://www.cnblogs.com/czkolve/p/11890941.html

相關文章
相關標籤/搜索