3

In project development, we often encounter the situation of judging whether a variable is a valid value, or performing different operations according to different types according to the type of the variable.

For example, the most common one is to determine whether a variable is a Truthy value (what is a Truthy value ):

 if (value !== null && value !== undefined) {
  // 搞事情
}

At first glance, there are only two sentences, but when this thing needs to be done 10 or 100 times, you may start to think of encapsulation:

 function isDefined(value) {
  return value !== undefined && value !== null
}

Now that we have this idea, why not do it to the end, we will directly encapsulate a method library of our own is .

The following method, which will be written using the standard TypeScript , you will see: Generics, Type Predicate is .

Some general is methods

The type predicate is can narrow the type in TypeScript to help better type inference, which is not expanded here.

Judging Truthy and Falsy :

 // 可以思考一下 value !== undefined 和 typeof value !== 'undefined' 有什么区别?
// null 呢?
function isDefined<T = unknown>(value: T | undefined | null): value is T {
  return value !== undefined && value !== null
}

function isNull(value: unknown): value is null | undefined {
  return value === undefined || value === null
}

Judge other basic types (except null and undefined ):

 function isNumber(value: unknown): value is number {
  return typeof value === 'number'
}

// 提问:NaN 是不是一个基本类型呢?
function isNaN(value: unknown): value is number {
  return Number.isNaN(value)
}

function isString(value: unknown): value is string {
  return typeof value === 'string'
}

function isBoolean(value: unknown): value is boolean {
  return typeof value === 'boolean'
}

// 严格判断 true
function isTrue(value: unknown): value is true {
  return value === true
}

// 严格判断 false
function isFalse(value: unknown): value is false {
  return value === false
}

// 别忘了 Symbol
function isSymbol(value: unknown): value is symbol {
  return typeof value === 'symbol'
}

// 还有一个基本类型,它是谁呢?

In addition to the basic types, the next step is to judge some common object types. Before this, you can think about a question:

typeof object === 'object' Can you effectively judge whether a variable is an object?

Broadly speaking, as long as this is true, the variable is indeed an object, but this is often not what we need and expect, because it does not distinguish between arrays [] and objects {} The difference includes some other objects like Date .

So we use a We all know Judging by a little way around: Object.prototype.toString , here we go directly, if you don't know the specific principle, please search by yourself.

Judgment of common objects:

 // 存一下,减少对象属性的读取
const toString = Object.prototype.toString

function is(value: unknown, type: string) {
  return toString.call(value) === `[object ${type}]`
}

// 这里可以思考对象类型的收窄,用 Record<string, any> 是否合适?
function isObject<T extends Record<string, any> = Record<string, any>>(
  value: unknown
): value is T {
  return is(value, 'Object')
}

// 数组可以使用原生的方法获得更高的效率
function isArray(value: unknown): value is any[] {
  return Array.isArray(value)
}

// 插播一个 function
function isFunction(value: unknown): value is (...any: any[]) => any {
  return typeof value === 'function'
}

// 补充上面被遗忘的 BigInt 基本类型
function isBigInt(value: unknown): value is bigint {
  return typeof value === 'bigint'
}

// 这里如果想要同时支持 PromiseLike 的类型收窄的话要怎么写呢?
function isPromise(value: unknown): value is Promise<any> {
  return (
    !!value &&
    typeof (value as any).then === 'function' &&
    typeof (value as any).catch === 'function'
  )
}

function isSet(value: unknown): value is Set<any> {
  return is(value, 'Set')
}

function isMap(value: unknown): value is Map<any, any> {
  return is(value, 'Map')
}

function isDate(value: unknown): value is Date {
  return is(value, 'Date')
}

function isRegExp(value: unknown): value is RegExp {
  return is(value, 'RegExp')
}

Note that a separate is method is encapsulated here. This method can be expanded arbitrarily. For example, when you want to judge some custom classes, you can repackage it based on this is (above The methods are all this principle):

 function isMyClass(value: unknown): value is MyClass {
  return is(value, 'MyClass')
}

Some less conventional is methods

In addition to some types of judgments, we often have things like judging whether the variable is a Empty value:

什么是Empty指的,常规一点来讲就是包括:空数组、空字符串、空Map 、空Set 、空对象{} .
 function isEmpty(value: unknown) {
  if (Array.isArray(value) || typeof value === 'string') {
    return value.length === 0
  }

  if (value instanceof Map || value instanceof Set) {
    return value.size === 0
  }

  if (isObject(value)) {
    return Object.keys(value).length === 0
  }

  return false
}

Another common scenario is to judge whether a variable is the key value of an object, we can use Object.prototype.hasOwnProperty to judge:

 const hasOwnProperty = Object.prototype.hasOwnProperty

function has(value: Record<string, any>, key: string | symbol): key is keyof typeof value {
  return hasOwnProperty.call(value, key)
}

Integrate it

Ok, so far, a function library that contains basic types and some common types is is done. Finally, a complete integrated code is attached. You can make some of your own on this basis. expand( No one should need the pure js version. ):

 const toString = Object.prototype.toString
const hasOwnProperty = Object.prototype.hasOwnProperty

export function is(value: unknown, type: string) {
  return toString.call(value) === `[object ${type}]`
}

export function has(value: Record<string, any>, key: string | symbol): key is keyof typeof value {
  return hasOwnProperty.call(value, key)
}

export function isDefined<T = unknown>(value: T | undefined | null): value is T {
  return value !== undefined && value !== null
}

export function isNull(value: unknown): value is null | undefined {
  return value === undefined || value === null
}

export function isNumber(value: unknown): value is number {
  return typeof value === 'number'
}

export function isNaN(value: unknown): value is number {
  return Number.isNaN(value)
}

export function isString(value: unknown): value is string {
  return typeof value === 'string'
}

export function isBoolean(value: unknown): value is boolean {
  return typeof value === 'boolean'
}

export function isTrue(value: unknown): value is true {
  return value === true
}

export function isFalse(value: unknown): value is false {
  return value === false
}

export function isSymbol(value: unknown): value is symbol {
  return typeof value === 'symbol'
}

export function isBigInt(value: unknown): value is bigint {
  return typeof value === 'bigint'
}

export function isArray(value: unknown): value is any[] {
  return Array.isArray(value)
}

export function isObject<T extends Record<string, any> = Record<string, any>>(
  value: unknown
): value is T {
  return is(value, 'Object')
}

export function isPromise(value: unknown): value is Promise<any> {
  return (
    !!value &&
    typeof (value as any).then === 'function' &&
    typeof (value as any).catch === 'function'
  )
}

export function isFunction(value: unknown): value is (...any: any[]) => any {
  return typeof value === 'function'
}

export function isSet(value: unknown): value is Set<any> {
  return is(value, 'Set')
}

export function isMap(value: unknown): value is Map<any, any> {
  return is(value, 'Map')
}

export function isDate(value: unknown): value is Date {
  return is(value, 'Date')
}

export function isRegExp(value: unknown): value is RegExp {
  return is(value, 'RegExp')
}

export function isEmpty(value: unknown) {
  if (Array.isArray(value) || typeof value === 'string') {
    return value.length === 0
  }

  if (value instanceof Map || value instanceof Set) {
    return value.size === 0
  }

  if (isObject(value)) {
    return Object.keys(value).length === 0
  }

  return false
}

some thoughts

I recently recalled my work in the past few years and found that I have encapsulated various tool functions, but many of them are scattered throughout the project.

It is also for the purpose of sorting and reviewing, thinking of sharing some of the things I have written, so I tried to write this article, hoping to help some people.

renew:

[Encapsulation Tips] Encapsulation of List Processing Functions
[Encapsulation Tips] Encapsulation of digital processing functions

Finally, let me recommend my personal open source project Vexip UI - GitHub

A relatively complete Vue3 component library that supports comprehensive css variables, built-in dark theme, full TypeScript and combined Api, its feature is that almost every property of all components supports modifying its default value through configuration (passing an object). It should be a feature that other component libraries do not have at present~

I am currently recruiting small partners to use or participate in the maintenance and development of this project. My strength is very limited. Documentation, unit testing, server-side rendering support, peripheral plug-ins, use cases, etc., as long as you are interested, you can start from each Click to participate, very welcome~

The content source code of these issues of [Packaging Tips] is included in the @vexip-ui/utils package, GitHub , this package is also released separately, but there is no Api documentation yet, you may need to directly check the source code to eat~


未觉雨声
1.5k 声望70 粉丝

Vue3 组件库 VexipUI 作者,擅长 js 和 vue 系列技术,主攻前端(交互),稍微会一点点 Java(Spring Boot)。