@hyext/utils
v3.0.6
Published
A tools lib for huya miniapp business development
Downloads
143
Readme
@hyext/utils
小程序业务开发工具库
Installation
$ npm i @hyext/utils -S
Usage
import { memory, createLogger, createSDKPolyfill } from '@hyext/utils'
下述的每个函数都可以参照上方import导入。
Summary
- createLogger - 创建一个logger对象
- createSDKPolyfill - 基于小程序SDK二次封装的SDK对象
- createPromisfyFnWithCatch - Catch Promisy Func
- once - once
- throttle - 防抖
- createPolling - 轮询
- memory - 记忆函数
- promiseTimeout - 超时promise
- promiseRetry - 重试promise
- isUndef (v: any): boolean - 判断值是否为undefined或null
- isDef (v: any): boolean - 判断值不是undefined且不是null
- isTrue (v: any): boolean - v === true
- isFalse (v: any): boolean - v === false
- isObject (v: any): boolean - 判断值是否是一个对象
- isFunction (v: any): boolean - 判断值是否是一个函数
- isArray (v: any): boolean - 判断值是否是一个数组
- isPlainObject(v: any): boolean - 判断值是否是一个pure object
- isRegExp (v: any): boolean - 判断值是否是一个RegExp
- capitalize(v: string): string - 转首字母大写
- camelize(v: string): string - 转驼峰
- hyphenate(v: string): string - 转连字:xxA -> xx-a
- toNumber (val: string): number | string - 尝试将val转成number,失败则返回原字符串
- toString (val: any): string - 将任意值转成一个字符串
- second2Minute (seconds: number): string - 秒转成mm:ss
- delay (duration: number): Promise - 延迟
- getTime(date:Date): string - date实例转成hh:mm:ss
- patchNumber(v: number): string - 数字补0
- second2Minute(seconds: number): string - 秒转·mm:ss·
- limit(v: string|number): string - 收缩范围,1 - +00 -> 1 - 99+
- isWebViewEnv() - 是否在webview环境。
- scalePxOnWebView(px: number, base: number = 750) - 在 webview 环境中使用 scalePx 接口。
模块类
createLogger
- createLogger(options: CreateLoggerOptions) - 创建一个logger对象
- logger.log(msg: string, data: any) - 默认输出绿色字体的log
- logger.info(msg: string, data: any) - 默认输出蓝色字体的log
- logger.warn(msg: string, data: any) - 默认输出黄色字体的log
- logger.error(msg: string, data: any) - 默认输出红色字体的log
入参解析
type CreateLoggerOptions = {
onBefore?: (logInfo: LogInfo) => void // log之前触发,传入一个LogInfo
onAfter?: (logInfo: LogInfo) => void // log之后触发,传入一个LogInfo
prefix?: (() => string) | string // 打印日志前缀
logColor?: LogColor // 每个接口的字体颜色配置
enableLog?: boolean | ((logInfo: LogInfo) => boolean) // 是否打印原生log,传入一个LogInfo
}
type LogInfo = {
prefix: string // CreateLoggerOptions.prefix中获取
msg: string // logger接口传入的msg
data: any // logger接口传入的data
desc: string // desc = prefix + msg
}
type LogColor = {
info?: string // 颜色,可以是颜色英文或hex: 例如:red, #cccccc
warn?: string
error?: string
log?: string
}
Demo
// create
const logger = createLogger({
onAfter(logInfo) {
global.hyext.logger.log(logInfo.desc + ' ' + JSON.stringify(logInfo.data))
},
prefix () {
return `miniappName * date:${Date.now()} uid: ${uid} sessionId:${sessionId} -`
},
enableLog(logInfo) {
// 例如我们在APP只通过onAfter钩子打印sdk的log,
// 禁止打印原生log,提高性能
if (platform !== 'web') return false
return true
}
})
// call
logger.log('oh my god~')
createSDKPolyfill
- createSDKPolyfill(options: SDKPolyfillOptions) - 基于小程序SDK二次封装的SDK对象,补丁了错误的catch,可控制其接口的call,resolve, reject
参数解析
type SDKPolyfillOptions = {
paths: string[] // 接口访问路径队列,例如: hyExt.advance.sendWup -> paths => ['advance.sendWup']
SDK: SDKModel // 传入global.hyExt
onError?: (errMsg: string, apiName: string, path: string) => void // SDK调用失败时 触发
onCall?: (calledArgs: Array<any>, apiName: string, path: string) => void // SDK调用时 触发
onSuccess?: (res: any, apiName: string, path: string) => void // SDK调用成功时 触发
}
type SDKModel = {
[key: string]: any
}
Demo
// 与logger配合使用的例子,甩锅神器
const ployfillSDK = createSDKPolyfill({
SDK: global.hyExt,
paths: [
'advance.sendWup'
],
onCall(callArgs, apiName, path) {
logger.log(`SDK.${path}开始调用`, callArgs)
},
onError(errMsg, apiName, path) {
logger.log(`SDK.${path}调用失败`, errMsg)
},
onSuccess(res, apiName, path) {
logger.log(`SDK.${path}调用成功`, res)
},
onPolyfill(polyfillSDK, apiName, apiFN) {
// 补丁前触发, 可以拦截sdk进行二次处理。
polyfillSDK[apiName] = jest.fn(apiFN)
}
})
// ployfillSDK的每个接口只会resolve,因为内部已被catch错误,发生错误res是false
ployfillSDK.sendWup(options).then((res) => {
if (!res) return; // 发生错误就跳过了
// do something
})
高阶函数类
getSetModeWithQueueFn
背景: 小程序SDK原来的setMode方法, resolve后小程序马上会被显示. 但是在新版APP上, setMode resolve 并不表示马上会显示, 而是会在恰当的时候通过 hyExt.popup.onNoticeShow 通知小程序. 这个变更会给部分小程序带来影响, 比如你要10秒后关闭浮窗, 并不能在调用setMode之后开始倒计时, 而是在 onNoticeShow 回调中开始倒计时.
本函数封装了SDK中的以下方法:
- setMode
- onNoticeShow/offNoticeShow
- onNoticeHide/offNoticeHide
getSetModeWithQueueFn 每次调用都会取消监听 noticeShow/noticeHide 事件, 再重新进行监听. 调用后返回 setModeWithQueue 函数
setModeWithQueue 函数是对setMode的封装, 用于支持 notice 排队.
对比setMode, 添加两个额外的参数:
- onShow: 如果当前mode是NOTICE, 那么当收到终端的展示事件时, 会调用这个函数. 如果当前mode不是NOTICE, 则在 setMode resolve 后调用这个函数
- onNoticeHide: 当notice超时被终端隐藏, 或者调用了setMode 切换到非NOTICE模式时 , 会调用这个函数.
setModeWithQueue 兼容性:
- 可以兼容老版本APP, 在老版本APP上和原始的 setMode 方式行为一致
- 可以兼容不同的mode, 'NOTICE'/'NORMAL'/'RIGHT_BOTTOM_BTN'
Demo
const setModeWithQueue = getSetModeWithQueueFn(msg => console.log(msg))
const App = () => {
// 收到推送后才显示notice
const dataPush = useMainSelector(s => s.dataPush)
const [didShow, setShow] = useState(false)
useEffect(() => {
setModeWithQueue({ mode: dataPush ? 'NOTICE' : 'NORMAL' }, onShow: () => {console.log('show')})
}, [dataPush])
if (!dataPush) return null
return (
<View>
<Text>我迟早会显示</Text>
</View>
)
}
参数
export function getSetModeWithQueueFn(logger: (msg: string) => void): SetModeWithQueueFn;
type SetModeWithQueueFn = (params: {
mode: string,
onShow?: Callback,
onNoticeHide?: Callback,
showTime?: number, // 期望展示的时间
liveroomPopupKey?: string,
waitAfterResetNormal?: number, // 重置为NORMAL后, 等待多久才调用setMode设置其他值, 默认800ms
skipResetNormal?: boolean, // 是否跳过重置为NORMAL, 默认false
} & { [key: string]: any }) // 其他需要透传给hyExt.popup.setMode的参数
=> Promise<any>
createPromisfyFnWithCatch
- createPromisfyFnWithCatch(options: PromisfyFnWithCatchOptions) - hack一个promisfy函数的Pending、Resolve、Reject过程,返回一个新promisfy函数。
参数解析
type PromisfyFn<T, U> = (...args: Array<T>) => Promise<U>
type PromisfyFnWithCatchOptions = {
executePromiseFn: PromisfyFn<any, any>,
onPending?: (callArgs: Array<any>) => void
onResolve?: (response: any) => void
onReject?: (error: Error) => void
}
Demo
// __tests__/func.test.ts
const mockFn = createMockFn(() => Promise.resolve({b: 'bar'}))
const mockCall = { a: 'foo' }
const fn = createPromisfyFnWithCatch({
executePromiseFn: mockFn,
onPending(args: any) {
expect(args[0]).toBe(mockCall)
},
onResolve(res: any) {
expect(res).toMatchObject({b: 'bar'})
}
})
fn(mockCall).then((res) => {
expect(res).toBeTruthy()
expect(mockFn.mock.calls.length).toBe(1)
done()
})
once
- once(fn) - 返回一个cache函数,缓存fn首次调用的结果,fn只会执行一次。
// __tests__/func.test.ts
const mockFn = createMockFn(() => true)
const mockFnOnce = once(mockFn)
const result1 = mockFnOnce()
const result2 = mockFnOnce()
expect(mockFn.mock.calls.length).toBe(1)
expect(result1 === result2).toBe(true)
createPolling
- createPolling(options: PollingOptions) - 返回一个轮询函数
参数解析
type PollingOptions = {
intervalTime: number // 轮询周期。
fn: (...args: Array<any>) => boolean // 执行函数,返回一个是否继续轮询的标记,true代表继续,false代表结束。
immediately?: boolean // 默认是true, 轮询函数调用就马上执行fn;false,要等到intervalTime时间到达时执行fn。
onEnd?: NormalFn // 轮询结束时调用。
}
Demo
// __tests__/func.test.ts
let callCount = 0
const mockFn = createMockFn((res: any) => {
expect(res).toBe(1)
callCount += 1
return callCount > 1 ? false : true
})
const pollingFn = createPolling({
intervalTime: 500,
fn: mockFn,
onEnd() {
expect(callCount).toBe(2)
done()
}
})
pollingFn(1)
throttle
- throttle(delay: number, fn: NormalFn) - 返回一个防抖函数, delay代表延时触发的时间,fn代表执行函数
Demo
// __tests__/func.test.ts
const mockFn = createMockFn((res:any) => {
expect(res).toBe(1)
expect(mockFn.mock.calls.length).toBe(1)
done()
})
const throttleFn = throttle(1000, mockFn)
throttleFn(1)
throttleFn(1)
memory
- memory(fn) - 返回一个缓存每次调用的结果并输出结果的函数,fn代表纯函数,入参成员必须是 number | string, 输出可以是any。
Demo
// __tests__/func.test.ts
const mockFn = createMockFn((str: string, num: number) => {
return str + num
})
const cacheFn = memory<[string, number], string>(mockFn)
const result1 = cacheFn('alex', 1)
const result2 = cacheFn('alex', 1)
expect(result1 === result2).toBe(true)
expect(mockFn.mock.calls.length).toBe(1)
})
Promise风格函数
promiseTimeout
- promiseTimeout(ms: number, promise: Promise) - 返回一个promise,超时会reject一个超时字符串。
Demo
it('promiseTimeout pass', (done) => {
const passPromise = createDelayPromise(200);
promiseTimeout(500, passPromise).then((res) => {
expect(res).toBe(true)
done()
}).catch(done)
})
it('promiseTimeout timeout', (done) => {
const failPromise = createDelayPromise(200);
const timeout = 100
promiseTimeout(timeout, failPromise).catch((err) => {
expect(err).toMatch(`Timed out in ${timeout}ms.`)
done()
})
})
promiseRetry
- promiseTimeout(maxExeCount: number, interval: number, cb: RetryHandleCallback) - 返回一个promise,超过调用数会reject一个错误。
参数解析
type RetryResult = {
isDone: boolean
payload: any
}
type RetryHandleCallback = (currExeCount: number) => Promise<RetryResult>
Demo
it('retry resolve', (done) => {
const fn = jest.fn(async (count) => {
return {
isDone: count === 3 ? true : false,
payload: { foo: 'bar' }
}
})
const promise = promiseRetry(3, 100, fn)
promise.then((payload) => {
expect(fn).toBeCalledTimes(3)
expect(payload).toMatchObject({ foo: 'bar' })
done()
}).catch(done)
})