Открыть список
Как стать автором
Обновить

Комментарии 6

Поделюсь своим рецептом
QueryModel. Обсервится во вьюхе
import { CustomJsonParser, CustomJsonStringify } from "@/utils/JsonHelpers";

const DefaultRowsPerPage: number = 40;

export default class QueryModel<T>
{
	limit: number|null;
	page: number;
	readonly sorts: SortParam<T>[];
	readonly filters: FilterParam<T, keyof T>[];

	private _serverItemsCount: number|null = null;

	public get Hash(): string
	{
		return this.ToUrlSearchParams().toString();
	}

	constructor(qm?: Partial<QueryModel<T>>)
	{
		this.limit = qm?.limit ?? null;
		this.page = qm?.page ?? 0;
		this.sorts = qm?.sorts ?? [];
		this.filters = qm?.filters ?? [];
	}

	public static Create<T>(cfg?: (qm: QueryModel<T>) => void): QueryModel<T>
	{
		let res = new QueryModel<T>({limit: DefaultRowsPerPage, page: 0});
		if(!!cfg) cfg(res);
		return res;
	}

	public ToFilterOnlyModel(): QueryModel<T>
	{
		return new QueryModel<T>({filters: this.filters, sorts: this.sorts});
	}

	public ToUrlSearchParams(): URLSearchParams
	{
		const usp = new URLSearchParams();

		if ( this.limit !== null && this.limit > 0 )
		{
			usp.append('$limit', this.limit.toString());
		}
		if ( this.page > 0 )
		{
			usp.append('$page', this.page.toString());
		}
		if ( this.sorts.length )
		{
			usp.append('$sortby', this.sorts.filter(x => !!x).map(x => `${x![1] ? "-" : ""}${x![0]}`).join(",") );
		}
		if ( this.filters.length )
		{
			for (let [field, expr, vals] of this.filters)
			{
				usp.append(field as string, `${expr}:${stringifyVals(vals)}`);
			}
		}

		return usp;
	}

	public static Parse<T>(queryString: string): QueryModel<T>
	{
		const usp = new URLSearchParams(queryString);

		const limit: number = parseInt(usp.get("$limit") ?? '');
		const page: number = parseInt(usp.get("$page") ?? '');

		const sorts: SortParam<T>[] = usp.getAll('$sortby')
			.map(x => x.split(',')).flat().filter(x => !!x)
			.map(x => x.indexOf('-') === 0 ? [x.slice(1) as keyof T, true] : [x as keyof T, false]);


		let filters: FilterParam<T, keyof T>[] = [];
		for( let [k, v] of usp.entries())
		{
			if(k.includes('$') || !v?.length) continue;
			const vs = v.split(/\:|\,/g);
			let values = vs.splice(1).map(y => !isNaN(Number(y)) ? +y : JSON.parse(`"${y}"`, CustomJsonParser));
			filters.push([k as keyof T, vs[0] as TFilterExpression<T[keyof T]>, values])
		}

		return new QueryModel<T>({
			limit: !isNaN(limit) && isFinite(limit) ? limit : null,
			page: !isNaN(page) && isFinite(page) ? page : 0,
			sorts: sorts,
			filters: filters
		})
	}

	public UnsetLimit(): void
	{
		this.limit = null;
	}

	public SetLimit(limit: number): void
	{
		this.limit = limit;
	}

	public SetTotalItemsCount(cnt: number): void
	{
		this._serverItemsCount = cnt;
	}

	public get MaxPage(): number
	{
		return this.limit == null ? 0 : Math.floor((this._serverItemsCount ?? 0) / this.limit);
	}

	public get Page(): number
	{
		return this.page;
	}

	public set Page(page: number)
	{
		if(page < 0 || page > this.MaxPage) return;
		this.page = page;
	}

	public SetFilter<K extends keyof T = keyof T>(field: K, filterExpr: TFilterExpression<T[K]>, values: T[K][]): void
	{
		let vals = values.filter(v => (
			v !== undefined &&
			v !== null &&
			!(typeof(v) == "string" && !v.length)
		));
		if(!vals.length) return;
		let i = this.filters.findIndex(x => x[0] == field);
		if(i > -1)
		{
			if(this.filters[i][2] === vals) return;
			this.filters.splice(i, 1, [field, filterExpr, vals])
		}
		else
		{
			this.filters.push([field, filterExpr, vals]);
		}

		this.page = 0;
	}

	public HasFilter(field: keyof T): boolean
	{
		return !!this.GetFilter(field)?.length;
	}

	public GetFilter<K extends keyof T = keyof T>(field: keyof T): FilterParam<T, K>|null
	{
		return this.filters.find(x => x[0] == field) as FilterParam<T, K> ?? null;
	}

	public GetFilterValue<K extends keyof T = keyof T>(field: keyof T): T[K][]|null
	{
		return (this.filters.find(x => x[0] == field) as FilterParam<T, K>)?.[2] ?? null;
	}

	public ResetFilter(field: keyof T): void
	{
		let i = this.filters.findIndex(x => x[0] == field);
		if(i < 0) return;
		this.filters.splice(i, 1);
	}

	public ResetAllFilters(): void
	{
		this.filters.splice(0, this.filters.length);
	}

	public SetSort(field: keyof T): void
	{
		let sortI = this.sorts.findIndex(x => x![0] == field);
		if(sortI == -1)
		{
			this.sorts.push([field, false]);
		}
		else
		{
			if(!this.sorts[sortI]![1]) this.sorts.splice(sortI, 1, [field, true]);
			else this.sorts.splice(sortI, 1);
		}
	}

	public GetSort(field: keyof T): [boolean, number] | null
	{
		let sortI = this.sorts.findIndex(x => x![0] == field);
		if(sortI == -1) return null;
		else return [this.sorts[sortI]![1], sortI];
	}

	public ResetAllSort()
	{
		this.sorts.splice(0, this.sorts.length);
	}

	public ResetAll()
	{
		this.ResetAllFilters();
		this.ResetAllSort();
		this.limit = DefaultRowsPerPage;
		this.page = 0;
	}
}

function stringifyVals(x: any[])
{
	return x.map(x => {
		let res = JSON.stringify(x, CustomJsonStringify).trim().replace(/^"?|"?$/g, '');
		if(x instanceof Date) return res.slice(0, 10);
		return  res;
	}).join(",");
}

export type SortParam<T> = [keyof T, boolean] | null;
export type FilterParam<T, K extends keyof T> = [K, TFilterExpression<T[K]>, T[K][]]

"Производительность поиска на бэке стремится к 0" ?!?
"Что если записей 1 млн"!?
Что это?? Приплыли? Это результат новомодных техник? В мегафоне… Полный привет.
Теперь я понимаю, почему техподдержка не может привязать номер к аккаунту.
Теперь-то все ясно.
Вы хоть понимаете, что для нормально спроектированного бэка поиск по миллиону кортежей, двум, пяти, десяти это семечки.
С какими вы там данными работаете? С тестовыми?

Я так понял, что имеется в виду что «держать в браузере в массиве 1 млн строк для выпадающего списке на фронте» — это плохой вариант. И производительность поиска низкая не на бэке, а в этом большом списке на стороне фронтенда, потому что там же подходящие варианты фильтруются — и это каждый раз цикл в браузере по миллиону элементов.

Именно так. Люди разучились внимательно читать.

Детский сад
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Информация

Дата основания
Местоположение
Россия
Сайт
job.megafon.ru
Численность
свыше 10 000 человек
Дата регистрации

Блог на Хабре