一年又要到年末了,vue3.0都已经出来了,咱们也不能一直还停留在过去的js中,是时候学习而且在项目中使用一下Ts了。css
若是说jsx是基于js的话,那么tsx就是基于typescript的vue
废话也很少说,让咱们开始写一个Tsx形式的button组件,git
ts真的不单单只有咱们经常熟知的数据类型,还包括接口,类,枚举,泛型,等等等,这些都是特别重要的github
项目是基于vue-cli 3.0 下开发的,能够本身配置Ts,不会的话那你真的太难了vue-cli
咱们再compenonts中新建一个button文件夹,再建一个unit文件夹,button放button组件的代码,unit,放一些公共使用模块typescript
咱们再button文件夹下建立 ,index .tsx放的button源码,index.less放的是样式,css也是不可缺乏的less
分析一下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会用到这个方法)this
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
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可能有些并无合并