从 Element Plus 源码,回顾 ts/js 语法(上)

1,223 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情

最近在封装公司的组件库,寻思从 Element Plus 源码找找灵感。刚开始嘛,由浅入深,先看一下 Card.vue

<template>
  <div :class="[ns.b(), ns.is(`${shadow}-shadow`)]">
    <div v-if="$slots.header || header" :class="ns.e('header')">
      <slot name="header">{{ header }}</slot>
    </div>
    <div :class="ns.e('body')" :style="bodyStyle">
      <slot />
    </div>
  </div>
</template>
<script lang="ts" setup>
import { useNamespace } from '@element-plus/hooks'
import { cardProps } from './card'
...
</script>

一共 22 行,一个 header,一个 body 共两个 slot,果然很简单啊。

等一下,他的 Props 是 import 进来的,F12 跳转过去看下 Card 都有哪些参数

export const cardProps = buildProps({
  header: {
    type: String,
    default: '',
  },
  bodyStyle: {
    type: definePropType<StyleValue>([String, Object, Array]),
    default: '',
  },
  shadow: {
    type: String,
    values: ['always', 'hover', 'never'],
    default: 'always',
  },
} as const)

噢,参数也不多,header,bodyStyle,shadow 三个参数。 不过 buildProps 是什么函数?它对我们常用的 props 做了什么不为人知的事儿?

当我跳转到 buildProps 函数的时候,原谅才疏学浅的我,爆粗口了。 这**是什么玩意儿

BuildProps

export const buildProps = <
  Props extends Record<
    string,
    | { [epPropKey]: true }
    | NativePropType
    | EpPropInput<any, any, any, any, any>
  >
>(
  props: Props
): {
  [K in keyof Props]: IfEpProp<
    Props[K],
    Props[K],
    IfNativePropType<Props[K], Props[K], EpPropConvert<Props[K]>>
  >
} =>
  fromPairs(
    Object.entries(props).map(([key, option]) => [
      key,
      buildProp(option as any, key),
    ])
  ) as any

不忙,点根yan冷静下。 咱一起来抽丝剥茧,回顾下语法知识

源码拆解

先来TS泛型的应用

  1. 普通函数:
function drinkMilk(args) {
  console.log(args); 
}
  1. 加上泛型:在参数前加 < T >
function drinkMilk<T>(args:T) {
  console.log(args); 
}
  1. 带返回值:在参数后面加 : T
function drinkMilk<T>(args:T)T { return args }
  1. 使用箭头函数 =>
const getMilk = <T>(args: T) : T => { return args; }
  1. 给泛型 T 加上约束条件
interface WithLength { 
  length: number; 
}
const getMilk = <T extends WithLength>(args: T) : T =>{
  return args.length;
}

综上所述,BuildProps 就不再神秘了,分模块标注后如下图:

image.png

带约束的泛型

知识点:Record<key, value>

Recrod 支持两个参数,源码实际就一行 [P in K]: T; 取 K 中的每一个属性,该属性的值是 T 类型。

为了便于理解,举个简单例子:

type person = 'man' | 'woman'
interface Info {
    name: string,
    age: number
}
type personInfo = Record<person, Info>

就好像,Record 可以把超能力赋予每一个人。瞬间就让 man 和 woman 拥有了 name 和 age 属性。

再来看 Element Plus 源码中的部分,我做了简单的标注

type epValue = 
    | { [epPropKey]: true }
    | NativePropType
    | EpPropInput<any, any, any, any, any>
    
Props extends Record<string, epValue>

首先是对泛型 Props extends,表示 Props 应符合后面 Record 的约束,即:

Props 需要是 string 类型的 key,epValue 那三种类型的 value

  • { ['__epPropKey'] : true }
  • NativePropType : 原生 prop 类型
  • EpPropInput : Element Plus 定义的 Prop 输入类型
type EpPropInput = {
    type?: StringConstructor | undefined;
    required?: true | undefined;
    values?: readonly "a"[] | undefined;
    validator?: ((val: any) => boolean) | ((val: any) => val is never) | undefined;
    default?: undefined;
  }

最终结论,通过对泛型 Props 的约束,结合参数 props: Props,表示出:该函数的入参,必须满足 props 的 key 是 string 类型,value 是上述的 epValue 的三种联合类型之一。

后续

上述内容中,如果泛型的部分不是很好理解,可以 跳转到官网 再温习一下。

buildProps 函数的返回值和函数内容,将在下篇进行简析 从 Element Plus 源码,回顾 ts/js 语法(下)