Skip to content

Commit

Permalink
fix(perf): reduce array lookups and use a map closes #4382
Browse files Browse the repository at this point in the history
  • Loading branch information
logaretm committed Jul 20, 2023
1 parent f590fa7 commit c511385
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 53 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"@typescript-eslint/camelcase": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/explicit-module-boundary-types": "off"
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-explicit-any": "warn"
}
}
61 changes: 33 additions & 28 deletions packages/vee-validate/src/useField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,8 @@ import {
withLatest,
isEqual,
isTypedSchema,
lazyToRef,
} from './utils';
import { isCallable, isObject } from '../../shared';
import { isCallable, isObject, normalizeFormPath } from '../../shared';
import { FieldContextKey, FormContextKey, IS_ABSENT } from './symbols';
import { useFieldState } from './useFieldState';
import { refreshInspector, registerSingleFieldWithDevtools } from './devtools';
Expand Down Expand Up @@ -91,7 +90,7 @@ export type RuleExpression<TValue> =
export function useField<TValue = unknown>(
path: MaybeRefOrGetter<string>,
rules?: MaybeRef<RuleExpression<TValue>>,
opts?: Partial<FieldOptions<TValue>>
opts?: Partial<FieldOptions<TValue>>,
): FieldContext<TValue> {
if (hasCheckedAttr(opts?.type)) {
return useFieldWithChecked(path, rules, opts);
Expand All @@ -103,7 +102,7 @@ export function useField<TValue = unknown>(
function _useField<TValue = unknown>(
path: MaybeRefOrGetter<string>,
rules?: MaybeRef<RuleExpression<TValue>>,
opts?: Partial<FieldOptions<TValue>>
opts?: Partial<FieldOptions<TValue>>,
): FieldContext<TValue> {
const {
initialValue: modelValue,
Expand All @@ -122,7 +121,7 @@ function _useField<TValue = unknown>(

const injectedForm = controlled ? injectWithSelf(FormContextKey) : undefined;
const form = (controlForm as PrivateFormContext | undefined) || injectedForm;
const name = lazyToRef(path);
const name = computed(() => normalizeFormPath(toValue(path)));

const validator = computed(() => {
const schema = unref(form?.schema);
Expand Down Expand Up @@ -203,7 +202,7 @@ function _useField<TValue = unknown>(
meta.valid = result.valid;

return result;
}
},
);

const validateValidStateOnly = withLatest(
Expand All @@ -214,7 +213,7 @@ function _useField<TValue = unknown>(
meta.valid = result.valid;

return result;
}
},
);

function validate(opts?: Partial<ValidationOptions>) {
Expand Down Expand Up @@ -294,11 +293,11 @@ function _useField<TValue = unknown>(

if (value === oldValue && isEqual(value, oldValue)) {
warn(
'Detected a possible deep change on field `value` ref, for nested changes please either set the entire ref value or use `setValue` or `handleChange`.'
'Detected a possible deep change on field `value` ref, for nested changes please either set the entire ref value or use `setValue` or `handleChange`.',
);
}
},
{ deep: true }
{ deep: true },
);
}

Expand Down Expand Up @@ -340,7 +339,7 @@ function _useField<TValue = unknown>(
},
{
deep: true,
}
},
);
}

Expand Down Expand Up @@ -376,23 +375,29 @@ function _useField<TValue = unknown>(
return {};
}

return Object.keys(rulesVal).reduce((acc, rule: string) => {
const deps = extractLocators(rulesVal[rule])
.map((dep: any) => dep.__locatorRef)
.reduce((depAcc, depName) => {
const depValue = getFromPath(form.values, depName) || form.values[depName];

if (depValue !== undefined) {
depAcc[depName] = depValue;
}

return depAcc;
}, {} as Record<string, unknown>);
return Object.keys(rulesVal).reduce(
(acc, rule: string) => {
const deps = extractLocators(rulesVal[rule])
.map((dep: any) => dep.__locatorRef)
.reduce(
(depAcc, depName) => {
const depValue = getFromPath(form.values, depName) || form.values[depName];

if (depValue !== undefined) {
depAcc[depName] = depValue;
}

return depAcc;
},
{} as Record<string, unknown>,
);

Object.assign(acc, deps);
Object.assign(acc, deps);

return acc;
}, {} as Record<string, unknown>);
return acc;
},
{} as Record<string, unknown>,
);
});

// Adds a watcher that runs the validation whenever field dependencies change
Expand Down Expand Up @@ -492,14 +497,14 @@ function normalizeOptions<TValue>(opts: Partial<FieldOptions<TValue>> | undefine
function useFieldWithChecked<TValue = unknown>(
name: MaybeRefOrGetter<string>,
rules?: MaybeRef<RuleExpression<TValue>>,
opts?: Partial<FieldOptions<TValue>>
opts?: Partial<FieldOptions<TValue>>,
): FieldContext<TValue> {
const form = !opts?.standalone ? injectWithSelf(FormContextKey) : undefined;
const checkedValue = opts?.checkedValue;
const uncheckedValue = opts?.uncheckedValue;

function patchCheckedApi(
field: FieldContext<TValue> & { originalHandleChange?: FieldContext['handleChange'] }
field: FieldContext<TValue> & { originalHandleChange?: FieldContext['handleChange'] },
): FieldContext<TValue> {
const handleChange = field.handleChange;

Expand Down Expand Up @@ -590,7 +595,7 @@ function useVModel<TValue = unknown>({ prop, value, handleChange }: ModelOpts<TV
}

handleChange(newValue);
}
},
);
}

Expand Down
26 changes: 19 additions & 7 deletions packages/vee-validate/src/useForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
MaybeRef,
MaybeRefOrGetter,
} from 'vue';
import { PartialDeep } from 'type-fest';
import { klona as deepCopy } from 'klona/full';
import {
FieldMeta,
Expand Down Expand Up @@ -64,14 +65,12 @@ import {
normalizeErrorItem,
normalizeEventValue,
omit,
isPathsEqual,
} from './utils';
import { FormContextKey } from './symbols';
import { validateTypedSchema, validateObjectSchema } from './validate';
import { refreshInspector, registerFormWithDevTools } from './devtools';
import { isCallable, merge, normalizeFormPath } from '../../shared';
import { getConfig } from './config';
import { PartialDeep } from 'type-fest';

type FormSchema<TValues extends Record<string, unknown>> =
| FlattenAndSetPathsType<TValues, GenericValidateFunction | string | GenericObject>
Expand Down Expand Up @@ -137,6 +136,17 @@ export function useForm<

const extraErrorsBag: Ref<FormErrorBag<TValues>> = ref({});

const pathStateLookup = computed<Record<string, PathState>>(() => {
return pathStates.value.reduce(
(names, state) => {
names[normalizeFormPath(toValue(state.path))] = state;

return names;
},
{} as Record<string, PathState>,
);
});

/**
* Manually sets an error message on a specific field
*/
Expand All @@ -151,8 +161,9 @@ export function useForm<

// Move the error from the extras path if exists
if (typeof field === 'string') {
if (extraErrorsBag.value[normalizeFormPath(field) as Path<TValues>]) {
delete extraErrorsBag.value[normalizeFormPath(field) as Path<TValues>];
const normalizedPath = normalizeFormPath(field) as Path<TValues>;
if (extraErrorsBag.value[normalizedPath]) {
delete extraErrorsBag.value[normalizedPath];
}
}

Expand Down Expand Up @@ -256,7 +267,7 @@ export function useForm<
config?: Partial<PathStateConfig>,
): PathState<TValue> {
const initialValue = computed(() => getFromPath(initialValues.value, toValue(path)));
const pathStateExists = pathStates.value.find(s => isPathsEqual(s.path, toValue(path)));
const pathStateExists = pathStateLookup.value[toValue(path)];
if (pathStateExists) {
if (config?.type === 'checkbox' || config?.type === 'radio') {
pathStateExists.multiple = true;
Expand Down Expand Up @@ -396,7 +407,8 @@ export function useForm<
}

function findPathState<TPath extends Path<TValues>>(path: TPath | PathState) {
const pathState = typeof path === 'string' ? pathStates.value.find(s => isPathsEqual(s.path, path)) : path;
const normalizedPath = typeof path === 'string' ? normalizeFormPath(path) : path;
const pathState = typeof normalizedPath === 'string' ? pathStateLookup.value[normalizedPath] : normalizedPath;

return pathState as PathState<PathValue<TValues, TPath>> | undefined;
}
Expand Down Expand Up @@ -509,7 +521,7 @@ export function useForm<
handleSubmit.withControlled = makeSubmissionFactory(true);

function removePathState<TPath extends Path<TValues>>(path: TPath, id: number) {
const idx = pathStates.value.findIndex(s => isPathsEqual(s.path, path));
const idx = pathStates.value.findIndex(s => s.path === path);
const pathState = pathStates.value[idx];
if (idx === -1 || !pathState) {
return;
Expand Down
6 changes: 1 addition & 5 deletions packages/vee-validate/src/utils/assertions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Locator, TypedSchema, YupSchema } from '../types';
import { isCallable, isObject, normalizeFormPath } from '../../../shared';
import { isCallable, isObject } from '../../../shared';
import { IS_ABSENT } from '../symbols';

export const isClient = typeof window !== 'undefined';
Expand Down Expand Up @@ -186,7 +186,3 @@ export function isFile(a: unknown): a is File {

return a instanceof File;
}

export function isPathsEqual(lhs: string, rhs: string) {
return normalizeFormPath(lhs) === normalizeFormPath(rhs);
}
18 changes: 6 additions & 12 deletions packages/vee-validate/src/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ import {
getCurrentInstance,
inject,
InjectionKey,
MaybeRefOrGetter,
ref,
Ref,
unref,
toValue,
warn as vueWarning,
watch,
MaybeRef,
Expand Down Expand Up @@ -35,12 +33,12 @@ export function getFromPath<TValue = unknown>(object: NestedRecord | undefined,
export function getFromPath<TValue = unknown, TFallback = TValue>(
object: NestedRecord | undefined,
path: string,
fallback?: TFallback
fallback?: TFallback,
): TValue | TFallback;
export function getFromPath<TValue = unknown, TFallback = TValue>(
object: NestedRecord | undefined,
path: string,
fallback?: TFallback
fallback?: TFallback,
): TValue | TFallback | undefined {
if (!object) {
return fallback;
Expand Down Expand Up @@ -210,7 +208,7 @@ export function throttle<T extends (...args: any) => any>(func: T, limit: number

export function debounceAsync<TFunction extends (...args: any) => Promise<any>, TResult = ReturnType<TFunction>>(
inner: TFunction,
ms = 0
ms = 0,
): (...args: Parameters<TFunction>) => Promise<TResult> {
let timer: number | null = null;
let resolves: any[] = [];
Expand Down Expand Up @@ -249,7 +247,7 @@ export function applyModelModifiers<TValue = unknown>(value: TValue, modifiers:

export function withLatest<TFunction extends (...args: any[]) => Promise<any>, TResult = ReturnType<TFunction>>(
fn: TFunction,
onDone: (result: Awaited<TResult>, args: Parameters<TFunction>) => void
onDone: (result: Awaited<TResult>, args: Parameters<TFunction>) => void,
) {
let latestRun: Promise<TResult> | undefined;

Expand Down Expand Up @@ -282,7 +280,7 @@ export function computedDeep<TValue = unknown>({ get, set }: { get(): TValue; se
},
{
deep: true,
}
},
);

watch(
Expand All @@ -296,16 +294,12 @@ export function computedDeep<TValue = unknown>({ get, set }: { get(): TValue; se
},
{
deep: true,
}
},
);

return baseRef;
}

export function lazyToRef<T>(value: MaybeRefOrGetter<T>): Ref<T> {
return computed(() => toValue(value));
}

export function normalizeErrorItem(message: string | string[] | null | undefined) {
return Array.isArray(message) ? message : message ? [message] : [];
}
Expand Down

0 comments on commit c511385

Please sign in to comment.