前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >什么才是一个让人舒服的js库

什么才是一个让人舒服的js库

作者头像
吴文周
发布2022-03-09 14:45:31
4170
发布2022-03-09 14:45:31
举报
文章被收录于专栏:吴文周的专栏吴文周的专栏

简述

  • 封装一个js库其实没有想象中的那么困难,常见时间格式化,发个npm仓库,搞个cdn,引入就能正常使用。
  • 随着诉求的复杂性,往往就不是我们想象的那么简单了,就算代码上面把功能封装得很全面依然会存在真正业务上面不能满足的场景。

真实场景

  • 例如公司业务上面希望封装通用axios的请求库,同时给h5端,移动端,pc端,客户端使用。具体到各个场景下面就会出现问题,在客户端请求前后希望写入本地日志,其他端不做处理。
  • 不使用通用封装,公用的能力又不想多处重写,那可维护性,通用性上面就不能得到保障。
  • 使用封装库能力又得不到满足,两难境地!

核心问题

  • 既想使用公共的能力,又想库有扩展的能力,想一想有哪些思路可以匹配这样的场景?

实现思路

  • 装饰器模式
  • 插件设计方案

实现详细

装饰器模式

概念定义:允许向一个现有的对象添加新的功能,同时又不改变其结构。

实现一:ts语法糖最好看官方文档

代码语言:javascript
复制
//方法装饰器  当装饰器 @enumerable(false)被调用时,它会修改属性描述符的enumerable属性。
class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }

    @enumerable(false)
    greet() {
        return "Hello, " + this.greeting;
    }
}
function enumerable(value: boolean) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        descriptor.enumerable = value;
    };
}
代码语言:javascript
复制
// 访问器装饰器(@configurable)的例子,应用于Point类的成员上:
class Point {
    private _x: number;
    private _y: number;
    constructor(x: number, y: number) {
        this._x = x;
        this._y = y;
    }

    @configurable(false)
    get x() { return this._x; }

    @configurable(false)
    get y() { return this._y; }
}

function configurable(value: boolean) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        descriptor.configurable = value;
    };
}
代码语言:javascript
复制
// 属性装饰器当  @format("Hello, %s")被调用时,它添加一条这个属性的元数据,通过reflect-metadata库里的Reflect.metadata函数。 当 getFormat被调用时,它读取格式的元数据。
class Greeter {
    @format("Hello, %s")
    greeting: string;

    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        let formatString = getFormat(this, "greeting");
        return formatString.replace("%s", this.greeting);
    }
}

import "reflect-metadata";

const formatMetadataKey = Symbol("format");

function format(formatString: string) {
    return Reflect.metadata(formatMetadataKey, formatString);
}

function getFormat(target: any, propertyKey: string) {
    return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}
代码语言:javascript
复制
//参数装饰器 @required装饰器添加了元数据实体把参数标记为必需的。 @validate装饰器把greet方法包裹在一个函数里在调用原先的函数前验证函数参数。
class Greeter {
    greeting: string;

    constructor(message: string) {
        this.greeting = message;
    }

    @validate
    greet(@required name: string) {
        return "Hello " + name + ", " + this.greeting;
    }
}

import "reflect-metadata";

const requiredMetadataKey = Symbol("required");

function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
    let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
    existingRequiredParameters.push(parameterIndex);
    Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey);
}

function validate(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<Function>) {
    let method = descriptor.value;
    descriptor.value = function () {
        let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName);
        if (requiredParameters) {
            for (let parameterIndex of requiredParameters) {
                if (parameterIndex >= arguments.length || arguments[parameterIndex] === undefined) {
                    throw new Error("Missing required argument.");
                }
            }
        }

        return method.apply(this, arguments);
    }
}
  • ES5实现使用对象对扩展循环的方式,插入新的属性和方法
  • ES7中自带装饰的语法糖 使用文档

插件方案

问题回归

  • 既想要封装功能的能力,也允许各个业务使用方去很好的扩展功能
  • 实现一个公共请求库带插件的
代码语言:javascript
复制
/*
 * @Description:
 * @version: 1.0.0
 * @Author: 吴文周
 * @Date: 2021-03-31 19:38:15
 * @LastEditors: 吴文周
 * @LastEditTime: 2021-03-31 19:41:59
 */
import { SyncHook } from 'tapable'
import axios from 'axios'
/**
* 初始化hooks
* @param options 初始化参数
*/
const initHooks = (options: any) => {
  const hooks = {
    request: new SyncHook(['config', 'error']),
    response: new SyncHook(['response', 'error']),
  }
  if (Array.isArray(options.plugins)) {
    for (const plugin of options.plugins) {
      if (typeof plugin === 'function') {
        plugin.call(hooks)
      } else {
        plugin.apply(hooks)
      }
    }
  }
  return hooks
}
/**
* 封装的请求方法
* @param options 初始化参数
*/
export const request = (options: any) => {
  const hooks = initHooks(options)
  const http = axios.create()
  http.interceptors.request.use(
    (config) => {
      hooks.request.call(config, '')
      return config
    },
    (error) => {
      hooks.request.call('', error)
      return Promise.reject(error)
    },
  )
  http.interceptors.response.use(
    (response) => {
      hooks.response.call(response, '')
      return response
    },
    (error) => {
      hooks.response.call('', error)
      return Promise.reject(error)
    },
  )
  const request = (args: any) => {
    return new Promise((resolve, reject) => {
      http
        .get('http://localhost:3000/xx', {//测试代码
          // params: params ? params : "",
        })
        .then((res) => {
          try {
            const data = res.hasOwnProperty('data') ? res.data : {}
            resolve(data)
          } catch (error) {
            resolve(error)
          }
        })
        .catch((err) => {
          reject(err)
        })
    })
  }
  return request
}
代码语言:javascript
复制
class Test {
    constructor() {}
    apply(hooks) {
      hooks.request.tap('request', (config, error) => {
        if (error) {
          console.log(error)
        } else {
          console.log('Test请求正常的')
        }
      })
      hooks.response.tap('response', (response, error) => {
        if (error) {
          console.log(error)
        } else {
          console.log(response)
        }
      })
    }
  }
  class Test1 {
    constructor() {}
    apply(hooks) {
      hooks.request.tap('request', (config, error) => {
        if (error) {
          console.log(error)
        } else {
          console.log('Test1请求正常的')
        }
      })
      hooks.response.tap('response', (response, error) => {
        if (error) {
          console.log(error)
        } else {
          console.log(response)
        }
      })
    }
  }
  //插件Test,Test1
  var options = {
    plugins: [new Test(), new Test1()],
  }
  // 实例化对象
  const r = index.request(options)
  // 调用请求,插件扩展能力
  r()
    .then((data) => {
      console.log(data)
    })
    .catch((err) => {
      console.log(err)
    })

总结

  • 一个js库怎么让人使用的舒服,两个关键点无侵入面向切片,可扩展提供额外的能力
  • 装饰器模式和插件的方式都是基础库开发过程中最常见的实践
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021/03/31 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简述
  • 真实场景
  • 核心问题
  • 实现思路
  • 实现详细
    • 装饰器模式
      • 插件方案
      • 问题回归
      • 总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档