Skip to content

Commit

Permalink
feat(utils): 新增 ListWrapper 列表打包器
Browse files Browse the repository at this point in the history
  • Loading branch information
fjc0k committed Jan 14, 2021
1 parent bac4742 commit 54644fb
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 0 deletions.
48 changes: 48 additions & 0 deletions src/utils/ListWrapper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { ListWrapper, RawList, WrappedList } from './ListWrapper'

describe('ListWrapper', () => {
interface Item {
id: number
name: string
}

const rawList: RawList<Item> = [
{ id: 1, name: 'Jay' },
{ id: 2, name: 'Jay2' },
{ id: 3, name: 'Jay3' },
{ id: 4, name: 'Jay4' },
]
const wrappedList: WrappedList<Item> = new ListWrapper(rawList).wrap()
const unwrappedList: RawList<Item> = new ListWrapper(wrappedList).unwrap()

test('基础表现正常', () => {
expect(unwrappedList).toEqual(rawList)
expect(ListWrapper.unwrapIfNeeded(wrappedList)).toEqual(rawList)
expect(ListWrapper.unwrapIfNeeded([wrappedList])).toEqual([wrappedList])
expect(ListWrapper.unwrapIfNeeded(1)).toEqual(1)
expect(ListWrapper.unwrapIfNeeded([])).toEqual([])
})

test('支持递归', () => {
expect(
ListWrapper.unwrapIfNeeded({
list: wrappedList,
}),
).toEqual({ list: rawList })
expect(
ListWrapper.unwrapIfNeeded({ list: { list: wrappedList } }),
).toEqual({ list: { list: rawList } })
expect(
ListWrapper.unwrapIfNeeded({
list: { list: { list: wrappedList } },
}),
).toEqual({ list: { list: { list: wrappedList } } })
expect(
ListWrapper.unwrapIfNeeded({ list: { list: { list: wrappedList } } }, 3),
).toEqual({ list: { list: { list: rawList } } })
})

test('bug: 空数据正常', () => {
expect(new ListWrapper(new ListWrapper([]).wrap()).unwrap()).toEqual([])
})
})
113 changes: 113 additions & 0 deletions src/utils/ListWrapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { base64UrlDecode, base64UrlEncode } from './base64'
import { isPlainObject, mapValues, range, shuffle } from 'lodash-uni'

export type RawList<TItem = any> = TItem[]

export interface WrappedList<TItem = any> {
readonly _k: Array<keyof TItem>
readonly _v: Array<Array<TItem[keyof TItem]>>
readonly _s: string
}

/**
* 列表打包器。
*/
export class ListWrapper<TItem> {
constructor(private list: RawList<TItem> | WrappedList<TItem>) {}

/**
* 如果是打包后的列表数据,则解包后返回,否则直接返回。如果是对象,则递归尝试解包。
*
* @param value 数据
* @param depth 递归层级,默认:2
* @returns 返回结果数据
*/
static unwrapIfNeeded(value: any, depth = 2): any {
if (isPlainObject(value)) {
if (
(value as WrappedList)._k &&
(value as WrappedList)._v &&
(value as WrappedList)._s
) {
return new ListWrapper(value as any).unwrap()
}
if (depth > 0) {
return mapValues(value, v => ListWrapper.unwrapIfNeeded(v, depth - 1))
}
}
return value
}

private static rot13(str: string) {
return str.replace(/[a-z]/gi, char => {
return String.fromCharCode(
char.charCodeAt(0) + (char.toLowerCase() < 'n' ? 13 : -13),
)
})
}

private static encodeValueIndexes(indexes: number[]) {
return ListWrapper.rot13(
base64UrlEncode(`${new Date().getTime()}.${indexes.join('.')}`),
)
}

private static decodeValueIndexes(value: string) {
return base64UrlDecode(ListWrapper.rot13(value))
.split('.')
.slice(1)
.map(Number)
}

/**
* 打包结构化列表数据。
*
* @returns 返回打包后的结构化列表数据
*/
wrap(): WrappedList<TItem> {
const rawList: RawList<TItem> = this.list as any
const keys: Array<keyof TItem> = rawList.length
? (Object.keys(rawList[0]) as any)
: []
const valueIndexes: number[] = shuffle(range(0, keys.length))
const values = []
for (const structuredItem of rawList) {
const item = []
for (let i = 0, len = valueIndexes.length; i < len; i++) {
item[valueIndexes[i]] = structuredItem[keys[i]]
}
values.push(item)
}
return {
_k: keys,
_v: values,
_s: ListWrapper.encodeValueIndexes(valueIndexes),
}
}

/**
* 返回结果同 `wrap()`,不过类型是原列表的类型。
*/
wrapAsRawType(): RawList<TItem> {
return this.wrap() as any
}

/**
* 解包结构化列表数据。
*
* @returns 返回解包后的结构化列表数据
*/
unwrap(): RawList<TItem> {
const wrappedList: WrappedList<TItem> = this.list as any
const rawList: TItem[] = []
const valueIndexes = ListWrapper.decodeValueIndexes(wrappedList._s)
for (const values of wrappedList._v) {
const item: TItem = {} as any
for (let i = 0, len = valueIndexes.length; i < len; i++) {
item[wrappedList._k[i]] = values[valueIndexes[i]]
}
rawList.push(item)
}
return rawList
}
}
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export * from './isPossibleChineseMobilePhoneNumber'
export * from './isPromiseLike'
export * from './isUrl'
export * from './keysStrict'
export * from './ListWrapper'
export * from './loadCss'
export * from './loadResource'
export * from './md5'
Expand Down

0 comments on commit 54644fb

Please sign in to comment.