Skip to content

Commit

Permalink
feat: 使用 DataPacker 替换 ListWrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
fjc0k committed Feb 1, 2021
1 parent f3d4101 commit 220c5be
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 147 deletions.
61 changes: 61 additions & 0 deletions src/utils/DataPacker.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { DataPacker } from './DataPacker'

describe('DataPacker', () => {
const rawData = [
{ id: 1, name: 'Jay' },
{ id: 2, name: 'Jay2' },
{ id: 3, name: 'Jay3' },
{ id: 4, name: 'Jay4' },
]
const packedData = DataPacker.pack(rawData)
const unpackedData = DataPacker.unpack(packedData)

test('基础表现正常', () => {
expect(unpackedData).toEqual(rawData)
expect(DataPacker.unpackIfNeeded(packedData)).toEqual(rawData)
expect(DataPacker.unpackIfNeeded([packedData])).toEqual([packedData])
expect(DataPacker.unpackIfNeeded(1)).toEqual(1)
expect(DataPacker.unpackIfNeeded([])).toEqual([])
})

test('支持递归', () => {
expect(
DataPacker.unpackIfNeeded({
list: packedData,
}),
).toEqual({ list: rawData })
expect(DataPacker.unpackIfNeeded({ list: { list: packedData } })).toEqual({
list: { list: rawData },
})
expect(
DataPacker.unpackIfNeeded({
list: { list: { list: packedData } },
}),
).toEqual({ list: { list: { list: packedData } } })
expect(
DataPacker.unpackIfNeeded({ list: { list: { list: packedData } } }, 3),
).toEqual({ list: { list: { list: rawData } } })
})

test('bug: 空数据正常', () => {
expect(DataPacker.unpack(DataPacker.pack([]))).toEqual([])
})

test('支持对象', () => {
expect(
DataPacker.unpack(
DataPacker.pack({
x: 1,
y: {
z: 'hello',
},
}),
),
).toEqual({
x: 1,
y: {
z: 'hello',
},
})
})
})
120 changes: 120 additions & 0 deletions src/utils/DataPacker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { base64UrlDecode, base64UrlEncode } from './base64'
import { isPlainObject, mapValues, range, shuffle } from 'lodash-uni'
import { isType } from './isType'
import { rot13 } from './rot13'

export type RawObjectData = Record<any, any>
export type RawListData<
TRawObjectData extends RawObjectData = RawObjectData
> = TRawObjectData[]
export type RawData<TRawObjectData extends RawObjectData = RawObjectData> =
| TRawObjectData
| RawListData<TRawObjectData>
export type ElementOfRawData<
TRawData extends RawData
> = TRawData extends RawData<infer X> ? X : never
export type KeyOfRawData<
TRawData extends RawData
> = keyof ElementOfRawData<TRawData>
export type ValueOfRawData<
TRawData extends RawData
> = ElementOfRawData<TRawData>[KeyOfRawData<TRawData>]
export type PackedData<TRawData extends RawData> = {
readonly _k: Array<KeyOfRawData<TRawData>>
readonly _v: Array<Array<ValueOfRawData<TRawData>>>
readonly _s: string
}

/**
* 数据打包器。
*/
export class DataPacker {
private static encodeIndexes(indexes: number[]): string {
return rot13(
base64UrlEncode(
`${Math.random().toString(36).slice(2)}.${indexes.join('.')}`,
),
)
}

private static decodeIndexes(value: string): number[] {
return base64UrlDecode(rot13(value)).split('.').slice(1).map(Number)
}

/**
* 打包数据。
*/
static pack<TRawObjectData extends RawData>(
rawData: TRawObjectData,
): PackedData<TRawObjectData> {
const notArray = !Array.isArray(rawData)
const rawList: RawListData = notArray ? [rawData] : (rawData as any)
const keys: Array<KeyOfRawData<TRawObjectData>> = rawList.length
? shuffle(Object.keys(rawList[0]) as any)
: []
const indexes: number[] = shuffle(range(0, keys.length))
const values = []
for (const rawItem of rawList) {
const item = []
for (let i = 0, len = indexes.length; i < len; i++) {
item[indexes[i]] = rawItem[keys[i]]
}
values.push(item)
}
return {
_k: keys,
_v: values,
_s: DataPacker.encodeIndexes(notArray ? [-1, ...indexes] : indexes),
}
}

/**
* 返回结果同 `pack()`,不过类型是原数据的类型。
*/
static packAsRawType<TRawObjectData extends RawData>(
rawData: RawData<TRawObjectData>,
): RawData<TRawObjectData> {
return DataPacker.pack(rawData) as any
}

/**
* 解包数据。
*/
static unpack<TRawObjectData extends RawData>(
packedData: PackedData<TRawObjectData>,
): RawData<TRawObjectData> {
const rawList: Array<RawListData<TRawObjectData>> = []
const indexes = DataPacker.decodeIndexes(packedData._s)
const notArray = indexes[0] === -1
if (notArray) {
indexes.shift()
}
for (const values of packedData._v) {
const item: ElementOfRawData<TRawObjectData> = {} as any
for (let i = 0, len = indexes.length; i < len; i++) {
item[packedData._k[i]] = values[indexes[i]]
}
rawList.push(item)
}
return (notArray ? rawList[0] : rawList) as any
}

/**
* 如果是打包后的数据,则解包后返回,否则直接返回。如果是对象,则递归尝试解包。
*
* @param value 数据
* @param depth 递归层级,默认:2
* @returns 返回结果数据
*/
static unpackIfNeeded(value: any, depth = 2): any {
if (isPlainObject(value) && isType<PackedData<RawObjectData>>(value)) {
if (value._k && value._v && value._s) {
return DataPacker.unpack(value)
}
if (depth > 0) {
return mapValues(value, v => DataPacker.unpackIfNeeded(v, depth - 1))
}
}
return value
}
}
48 changes: 0 additions & 48 deletions src/utils/ListWrapper.test.ts

This file was deleted.

98 changes: 0 additions & 98 deletions src/utils/ListWrapper.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export * from './constantCase'
export * from './copyTextToClipboard'
export * from './createSubmit'
export * from './createUrlQueryString'
export * from './DataPacker'
export * from './dedent'
export * from './EventBus'
export * from './formatBytes'
Expand All @@ -42,7 +43,6 @@ export * from './isPromiseLike'
export * from './isType'
export * from './isUrl'
export * from './keysStrict'
export * from './ListWrapper'
export * from './loadCss'
export * from './loadResource'
export * from './md5'
Expand Down

0 comments on commit 220c5be

Please sign in to comment.