From c4a863dd60e9825d5c7a35696c98554b262188c8 Mon Sep 17 00:00:00 2001 From: nighca Date: Sun, 22 Aug 2021 21:26:17 +0800 Subject: [PATCH] rawValue for FieldState --- src/bind.spec.ts | 4 +- src/fieldState.spec.ts | 70 +++++++++++++++--------- src/fieldState.ts | 118 ++++++++++++++++++++++++++++------------- src/formState.spec.ts | 14 ++--- src/formState.ts | 68 +++++++++++++++++++----- src/types.ts | 18 ++++--- src/utils.spec.ts | 71 ++++++++++++++++++++++++- src/utils.ts | 29 +++++----- tsconfig.json | 1 + 9 files changed, 285 insertions(+), 108 deletions(-) diff --git a/src/bind.spec.ts b/src/bind.spec.ts index 2ba50b9..72222bd 100644 --- a/src/bind.spec.ts +++ b/src/bind.spec.ts @@ -11,7 +11,7 @@ describe('bindInput', () => { const value = '123' binds.onChange(value) - expect(field._value).toBe(value) + expect(field._rawValue).toBe(value) const newBinds = bindInput(field) expect(newBinds.value).toBe(value) @@ -25,7 +25,7 @@ describe('bindInput', () => { expect(typeof binds.onChange).toBe('function') binds.onChange(123) - expect(field._value).toBe('123') + expect(field._rawValue).toBe('123') const newBinds = bindInput(field) expect(newBinds.value).toBe('123') diff --git a/src/fieldState.spec.ts b/src/fieldState.spec.ts index 85637ae..a7a1fcb 100644 --- a/src/fieldState.spec.ts +++ b/src/fieldState.spec.ts @@ -15,7 +15,7 @@ async function delayValue(value: T, millisecond: number = defaultDelay) { } function createFieldState(initialValue: T) { - return new FieldState(initialValue, defaultDelay) + return new FieldState(initialValue, { delay: defaultDelay }) } describe('FieldState', () => { @@ -23,9 +23,8 @@ describe('FieldState', () => { const initialValue = '123' const state = new FieldState(initialValue) - expect(state._value).toBe(initialValue) + expect(state._rawValue).toBe(initialValue) expect(state.value).toBe(initialValue) - expect(state.$).toBe(initialValue) expect(state.dirty).toBe(false) state.dispose() @@ -39,42 +38,33 @@ describe('FieldState', () => { const value = '123' state.onChange(value) - expect(state._value).toBe(value) + expect(state._rawValue).toBe(value) expect(state.value).toBe(initialValue) - expect(state.$).toBe(initialValue) await delay() - expect(state._value).toBe(value) + expect(state._rawValue).toBe(value) expect(state.value).toBe(value) - expect(state.$).toBe(value) expect(state.dirty).toBe(true) const newValue = '789' state.onChange('456') state.onChange(newValue) - expect(state._value).toBe(newValue) + expect(state._rawValue).toBe(newValue) expect(state.value).toBe(value) - expect(state.$).toBe(value) await delay() - expect(state._value).toBe(newValue) + expect(state._rawValue).toBe(newValue) expect(state.value).toBe(newValue) - expect(state.$).toBe(newValue) expect(state.dirty).toBe(true) const invalidValue = '123456' state.onChange(invalidValue) - expect(state._value).toBe(invalidValue) + expect(state._rawValue).toBe(invalidValue) expect(state.value).toBe(newValue) - expect(state.$).toBe(newValue) await delay() - expect(state._value).toBe(invalidValue) + expect(state._rawValue).toBe(invalidValue) expect(state.value).toBe(invalidValue) - expect(state.$).toBe(newValue) - - await delay() - expect(state.$).toBe(newValue) state.dispose() }) @@ -85,7 +75,7 @@ describe('FieldState', () => { const value = '123' state.set(value) - expect(state._value).toBe(value) + expect(state._rawValue).toBe(value) expect(state.value).toBe(value) expect(state.dirty).toBe(true) @@ -100,7 +90,7 @@ describe('FieldState', () => { state.validate() await delay() - expect(state._value).toBe(value) + expect(state._rawValue).toBe(value) expect(state.value).toBe(value) expect(state.dirty).toBe(true) @@ -115,19 +105,51 @@ describe('FieldState', () => { await delay() state.reset() - expect(state._value).toBe(initialValue) + expect(state._rawValue).toBe(initialValue) expect(state.value).toBe(initialValue) expect(state.dirty).toBe(false) state.onChange('456') state.reset() - expect(state._value).toBe(initialValue) + expect(state._rawValue).toBe(initialValue) expect(state.value).toBe(initialValue) expect(state.dirty).toBe(false) state.dispose() }) + + it('should work well with fromRaw & toRaw', async () => { + const state = new FieldState(12345678901, { + delay: defaultDelay, + rawValue: { + parse: (rawValue: string) => parseInt(rawValue, 10), + get: (value: number) => value.toString() + } + }).validators( + v => v <= 0 && 'positive required' + ).rawValidators( + v => v === '' && 'required' + ) + + expect(state.value).toBe(12345678901) + expect(state._rawValue).toBe('12345678901') + + state.set(12345678902) + expect(state.value).toBe(12345678902) + expect(state._rawValue).toBe('12345678902') + + state.onChange('') + await delay() + expect(state.value).toBeNaN() + expect(Number.isNaN(state.value)).toBeTruthy() + expect(state.error).toBe('required') + + state.set(0) + await state.validate() + expect(state._rawValue).toBe('0') + expect(state.error).toBe('positive required') + }) }) describe('FieldState validation', () => { @@ -212,7 +234,7 @@ describe('FieldState validation', () => { await delay() state.reset() - expect(state._value).toBe(initialValue) + expect(state._rawValue).toBe(initialValue) expect(state.value).toBe(initialValue) expect(state.dirty).toBe(false) expect(state.validating).toBe(false) @@ -388,7 +410,7 @@ describe('FieldState validation', () => { }) it('should work well with delay', async () => { - const state = new FieldState('0', 1000) + const state = new FieldState('0', { delay: 1000 }) state.onChange('1') expect(state.value).toBe('0') diff --git a/src/fieldState.ts b/src/fieldState.ts index d46e56b..a82743f 100644 --- a/src/fieldState.ts +++ b/src/fieldState.ts @@ -1,12 +1,31 @@ import { observable, computed, action, reaction, autorun, when, makeObservable } from 'mobx' -import { Validatable, Validator, Validated, ValidationResponse, ValidateStatus, Error, ValidateResult } from './types' -import { applyValidators, debounce, isPromiseLike } from './utils' +import { Validatable, Validator, Validated, ValidationResponse, ValidateStatus, Error, ValidateResult, RawValueOptions } from './types' +import { debounce, isPromiseLike, responsesAnd } from './utils' import Disposable from './disposable' +export interface FieldStateInitOptionsWithoutRaw { + delay?: number +} + +export interface FieldStateInitOptionsWithRaw extends FieldStateInitOptionsWithoutRaw { + delay?: number + rawValue: RawValueOptions +} + +export type FieldStateInitOptions = ( + (() => T extends TValue ? 1 : 2) extends (() => T extends TRawValue ? 1 : 2) + ? (FieldStateInitOptionsWithoutRaw | FieldStateInitOptionsWithRaw) + : FieldStateInitOptionsWithRaw +) + +function echo(v: T) { + return v +} + /** * The state for a field. */ -export default class FieldState extends Disposable implements Validatable { +export default class FieldState extends Disposable implements Validatable { /** * If activated (with auto validation). @@ -25,22 +44,24 @@ export default class FieldState extends Disposable implements Validatabl return this._dirtyWith(this.initialValue) } + private parseRawValue: (rawValue: TRawValue) => TValue + private getRawValue: (value: TValue) => TRawValue + /** * Value that reacts to `onChange` immediately. * You should only use it to bind with UI input componnet. */ - @observable.ref _value!: TValue + @observable.ref _rawValue!: TRawValue + + @observable.ref private rawValue!: TRawValue /** * Value that can be consumed by your code. * It's synced from `_value` with debounce of 200ms. */ - @observable.ref value!: TValue - - /** - * Value that has bean validated with no error, AKA "safe". - */ - @observable.ref $!: TValue + @computed get value(): TValue { + return this.parseRawValue(this.rawValue) + } /** * The validate status. @@ -89,37 +110,51 @@ export default class FieldState extends Disposable implements Validatabl } /** - * List of validator functions. + * List of validator function for value. */ @observable.shallow private _validators: Validator[] = [] /** - * Add validator function. + * Add validator functions. */ @action validators(...validators: Validator[]) { this._validators.push(...validators) return this } + /** + * List of validator function for raw value. + */ + @observable.shallow private _rawValidators: Validator[] = [] + + /** + * Add raw validator functions. + */ + @action rawValidators(...rawValidators: Validator[]) { + this._rawValidators.push(...rawValidators) + return this + } + /** * Set `_value` on change event. */ - @action onChange(value: TValue) { - this._value = value + @action onChange(value: TRawValue) { + this._rawValue = value } /** * Set `value` (& `_value`) synchronously. */ @action set(value: TValue) { - this.value = this._value = value + this.rawValue = this._rawValue = this.getRawValue(value) } /** * Reset to specific status. */ @action resetWith(initialValue: TValue) { - this.$ = this.value = this._value = this.initialValue = initialValue + this.initialValue = initialValue + this.rawValue = this._rawValue = this.getRawValue(initialValue) this._activated = false this._validateStatus = ValidateStatus.NotValidated this._error = undefined @@ -136,15 +171,15 @@ export default class FieldState extends Disposable implements Validatabl /** * Current validation info. */ - @observable.ref private validation?: Validated + @observable.ref private validation?: Validated /** * Do validation. */ private _validate() { - const value = this.value - // 如果 value 已经过期,则不处理 - if (value !== this._value) { + const { rawValue, value, _rawValidators: rawValidators, _validators: validators } = this + // 如果 rawValue 已经过期,则不处理 + if (rawValue !== this._rawValue) { return } @@ -152,10 +187,19 @@ export default class FieldState extends Disposable implements Validatabl this._validateStatus = ValidateStatus.Validating })() - const response = applyValidators(value, this._validators) + function* validate() { + for (const rawValidator of rawValidators) { + yield rawValidator(rawValue) + } + for (const validator of validators) { + yield validator(value) + } + } + + const response = responsesAnd(validate()) action('set-validation-when-_validate', () => { - this.validation = { value, response } + this.validation = { rawValue, value, response } })() } @@ -168,7 +212,7 @@ export default class FieldState extends Disposable implements Validatabl action('activate-and-sync-_value-when-validate', () => { this._activated = true // 若有用户交互产生的变更(因 debounce)尚未同步,同步之,确保本次 validate 结果是相对稳定的 - this.value = this._value + this.rawValue = this._rawValue })() // 若 `validation` 未发生变更,意味着未发生新的校验行为 @@ -225,7 +269,7 @@ export default class FieldState extends Disposable implements Validatabl if ( validation !== this.validation // 如果 validation 已过期,则不生效 - || validation.value !== this._value // 如果 value 已过期,则不生效 + || validation.rawValue !== this._rawValue // 如果 value 已过期,则不生效 ) { return } @@ -240,23 +284,32 @@ export default class FieldState extends Disposable implements Validatabl })() } - constructor(private initialValue: TValue, delay = 200) { + constructor(private initialValue: TValue, options?: FieldStateInitOptions) { super() - makeObservable(this) + const delay = options?.delay ?? 200 + + if (options && 'rawValue' in options) { + const optionsWithRaw = options as FieldStateInitOptionsWithRaw + this.parseRawValue = optionsWithRaw.rawValue.parse + this.getRawValue = optionsWithRaw.rawValue.get + } else { + this.parseRawValue = this.getRawValue = echo as any + } + this.reset() // debounced reaction to `_value` change this.addDisposer(reaction( - () => this._value, + () => this._rawValue, // use debounce instead of reactionOptions.delay // cause the later do throttle in fact, not debounce // see https://github.com/mobxjs/mobx/issues/1956 debounce(() => { - if (this.value !== this._value) { + if (this.rawValue !== this._rawValue) { action('sync-value-when-_value-changed', () => { - this.value = this._value + this.rawValue = this._rawValue this._validateStatus = ValidateStatus.NotValidated this._activated = true })() @@ -265,13 +318,6 @@ export default class FieldState extends Disposable implements Validatabl { name: 'reaction-when-_value-change' } )) - // auto sync when validate ok: this.value -> this.$ - this.addDisposer(reaction( - () => this.validated && !this.hasError, - validateOk => validateOk && (this.$ = this.value), - { name: 'sync-$-when-validatedOk' } - )) - // auto validate: this.value -> this.validation this.addDisposer(autorun( () => !this.validationDisabled && this._activated && this._validate(), diff --git a/src/formState.spec.ts b/src/formState.spec.ts index 183c870..2939854 100644 --- a/src/formState.spec.ts +++ b/src/formState.spec.ts @@ -16,7 +16,7 @@ async function delayValue(value: T, millisecond: number = defaultDelay) { } function createFieldState(initialValue: T) { - return new FieldState(initialValue, defaultDelay) + return new FieldState(initialValue, { delay: defaultDelay }) } describe('FormState (mode: object)', () => { @@ -701,7 +701,7 @@ describe('FormState (mode: array)', () => { expect(state.$).toHaveLength(value1.length) state.$.forEach((field, i) => { expect(field.value).toBe(value1[i]) - expect(field._value).toBe(value1[i]) + expect(field._rawValue).toBe(value1[i]) }) expect(state.dirty).toBe(true) expect(state.hasError).toBe(false) @@ -714,7 +714,7 @@ describe('FormState (mode: array)', () => { expect(state.$).toHaveLength(value2.length) state.$.forEach((field, i) => { expect(field.value).toBe(value2[i]) - expect(field._value).toBe(value2[i]) + expect(field._rawValue).toBe(value2[i]) }) expect(state.dirty).toBe(true) expect(state.hasError).toBe(false) @@ -728,7 +728,7 @@ describe('FormState (mode: array)', () => { expect(state.$).toHaveLength(value3.length) state.$.forEach((field, i) => { expect(field.value).toBe(value3[i]) - expect(field._value).toBe(value3[i]) + expect(field._rawValue).toBe(value3[i]) }) expect(state.dirty).toBe(true) expect(state.hasError).toBe(false) @@ -761,7 +761,7 @@ describe('FormState (mode: array)', () => { expect(state.$).toHaveLength(value1.length) state.$.forEach((field, i) => { expect(field.value).toBe(value1[i]) - expect(field._value).toBe(value1[i]) + expect(field._rawValue).toBe(value1[i]) }) expect(state.dirty).toBe(true) expect(state.hasError).toBe(true) @@ -775,7 +775,7 @@ describe('FormState (mode: array)', () => { expect(state.$).toHaveLength(value2.length) state.$.forEach((field, i) => { expect(field.value).toBe(value2[i]) - expect(field._value).toBe(value2[i]) + expect(field._rawValue).toBe(value2[i]) }) expect(state.dirty).toBe(true) expect(state.hasError).toBe(true) @@ -790,7 +790,7 @@ describe('FormState (mode: array)', () => { expect(state.$).toHaveLength(value3.length) state.$.forEach((field, i) => { expect(field.value).toBe(value3[i]) - expect(field._value).toBe(value3[i]) + expect(field._rawValue).toBe(value3[i]) }) expect(state.dirty).toBe(true) expect(field2Dispose).toBeCalled() diff --git a/src/formState.ts b/src/formState.ts index c4fcbac..148b20c 100644 --- a/src/formState.ts +++ b/src/formState.ts @@ -1,9 +1,19 @@ import { observable, computed, isObservable, action, autorun, when, reaction, makeObservable } from 'mobx' -import { Validatable, ValidationResponse, Validator, Validated, ValidateStatus, Error, ValidateResult, ValueOfObjectFields } from './types' -import { applyValidators, isPromiseLike } from './utils' +import { Validatable, ValidationResponse, Validator, Validated, ValidateStatus, Error, ValidateResult, ValueOfObjectFields, RawValueOptions } from './types' +import { responsesAnd, isPromiseLike } from './utils' import Disposable from './disposable' -export abstract class AbstractFormState extends Disposable implements Validatable { +export interface FormStateInitOptionsWithRaw { + rawValue: RawValueOptions +} + +export type FormStateInitOptions = ( + (() => T extends TValue ? 1 : 2) extends (() => T extends TRawValue ? 1 : 2) + ? (FormStateInitOptionsWithRaw | void) + : FormStateInitOptionsWithRaw +) + +export abstract class AbstractFormState extends Disposable implements Validatable { /** * If activated (with auto validate). @@ -26,16 +36,23 @@ export abstract class AbstractFormState extends Disposable implements */ declare abstract dirty: boolean + declare protected abstract parseRawValue: (rawValue: TRawValue) => TValue + declare protected abstract getRawValue: (value: TValue) => TRawValue + /** * List of fields. */ declare protected abstract fieldList: Validatable[] + @observable.ref private rawValue!: TRawValue + /** * Value that can be consumed by your code. * It's a composition of fields' value. */ - declare abstract value: TValue + @computed get value(): TValue { + return this.parseRawValue(this.rawValue) + } /** * Set form value synchronously. @@ -45,7 +62,7 @@ export abstract class AbstractFormState extends Disposable implements /** * Set form value on change event. */ - abstract onChange(value: TValue): void + abstract onChange(value: TRawValue): void /** * The validate status. @@ -144,6 +161,19 @@ export abstract class AbstractFormState extends Disposable implements return this } + /** + * List of validator function for raw value. + */ + @observable.shallow private _rawValidators: Validator[] = [] + + /** + * Add raw validator functions. + */ + @action rawValidators(...rawValidators: Validator[]) { + this._rawValidators.push(...rawValidators) + return this + } + protected abstract declare initialValue: TValue /** @@ -174,22 +204,31 @@ export abstract class AbstractFormState extends Disposable implements /** * Current validation info. */ - @observable.ref private validation?: Validated + @observable.ref private validation?: Validated /** * Do validation. */ private _validate() { - const value = this.value + const { rawValue, value, _rawValidators: rawValidators, _validators: validators } = this action('set-validateStatus-when-_validate', () => { this._validateStatus = ValidateStatus.Validating })() - const response = applyValidators(value, this._validators) + function* validate() { + for (const validator of rawValidators) { + yield validator(rawValue) + } + for (const validator of validators) { + yield validator(value) + } + } + + const response = responsesAnd(validate()) action('set-validation-when-_validate', () => { - this.validation = { value, response } + this.validation = { value, rawValue, response } })() } @@ -306,16 +345,17 @@ export type FieldsObject = { [key: string]: Validatable } * The state for a form (composition of fields). */ export class FormState< - TFields extends FieldsObject + TFields extends FieldsObject, + TValue = ValueOfObjectFields > extends AbstractFormState< - TFields, ValueOfObjectFields + TFields, TValue, ValueOfObjectFields > { @observable.ref readonly $: Readonly - protected initialValue: ValueOfObjectFields + protected initialValue: TValue - _dirtyWith(initialValue: ValueOfObjectFields) { + _dirtyWith(initialValue: TValue) { return Object.keys(this.$).some( key => this.$[key]._dirtyWith(initialValue[key]) ) @@ -365,7 +405,7 @@ export class FormState< }) } - constructor(initialFields: TFields) { + constructor(initialFields: TFields, options: FormStateInitOptions) { super() this.$ = initialFields diff --git a/src/types.ts b/src/types.ts index 94c43ff..654d91b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -13,9 +13,10 @@ export type ValidatorResponse = ValidationResponse | Promise -export type Validated = { +export type Validated = { value: TValue // value for the response - response: ValidatorResponse // response for the value + rawValue: TRawValue // raw value for the response + response: ValidatorResponse // response of validation } /** @@ -33,8 +34,7 @@ export type ValidateResultWithValue = { hasError: false, value: T } export type ValidateResult = ValidateResultWithError | ValidateResultWithValue /** Validatable object (which can be used as a field for `FormState`). */ -export interface Validatable { - readonly $: T +export interface Validatable { value: TValue hasError: boolean error: Error @@ -47,7 +47,7 @@ export interface Validatable { validate(): Promise> set: (value: TValue) => void - onChange: (value: TValue) => void + onChange: (value: TRawValue) => void reset: () => void resetWith: (initialValue: TValue) => void _dirtyWith: (initialValue: TValue) => void @@ -58,9 +58,6 @@ export interface Validatable { // disableAutoValidation: () => void } -/** @deprecated Composible validatable object (which can be used as a field for `FormState`). */ -export interface ComposibleValidatable extends Validatable {} - /** Function to do dispose. */ export interface Disposer { (): void @@ -94,3 +91,8 @@ export enum ValidateStatus { /** current validation finished */ Validated // 校验完成 } + +export interface RawValueOptions { + parse: (rawValue: TRawValue) => TValue + get: (value: TValue) => TRawValue +} diff --git a/src/utils.spec.ts b/src/utils.spec.ts index 55217ce..941b8f6 100644 --- a/src/utils.spec.ts +++ b/src/utils.spec.ts @@ -1,5 +1,6 @@ import { observable } from 'mobx' -import { asyncResponsesAnd, isEmpty, isArrayLike } from './utils' +import { ValidationResponse, ValidatorResponse } from './types' +import { asyncResponsesAnd, responsesAnd, isEmpty, isArrayLike } from './utils' const defaultDelay = 10 @@ -90,3 +91,71 @@ describe('isArrayLike', () => { expect(isArrayLike({ length: 2.3 })).toBe(false) }) }) + +describe('responsesAnd', () => { + + it('should work well', async () => { + expect(isEmpty(responsesAnd(['', null, undefined, false]) as ValidationResponse)).toBeTruthy() + expect(responsesAnd((['foo', 'bar']))).toBe('foo') + expect(responsesAnd(([null, 'foo', 'bar']))).toBe('foo') + expect(responsesAnd((['bar', undefined, 'foo']))).toBe('bar') + expect(responsesAnd((['bar', 'foo', false]))).toBe('bar') + }) + + it('should work well with async responses', async () => { + expect(isEmpty(await responsesAnd([ + delay(''), + delay(null), + delay(undefined), + delay(false) + ]))).toBeTruthy() + expect(await responsesAnd([ + delay(null), + delay('foo'), + delay('bar') + ])).toBe('foo') + expect(await responsesAnd([ + delay('foo', 20), + delay(null, 10), + delay('bar', 30) + ])).toBe('foo') + expect(await responsesAnd([ + delay('foo', 30), + delay(null, 20), + delay('bar', 10) + ])).toBe('bar') + expect(responsesAnd([ + delay('bar'), + 'foo', + delay(null) + ])).toBe('foo') + expect(await responsesAnd([ + '', + delay('foo'), + delay('bar') + ])).toBe('foo') + }) + + it('should avoid unnecessary calls', () => { + const validators = [ + () => null, + () => 'foo', + () => 'bar', + () => delay('baz') + ].map( + (fn: () => ValidatorResponse) => jest.fn(fn) + ) + + function* validate() { + for (const validator of validators) { + yield validator() + } + } + + expect(responsesAnd(validate())).toBe('foo') + expect(validators[0]).toBeCalledTimes(1) + expect(validators[1]).toBeCalledTimes(1) + expect(validators[2]).not.toBeCalled() + expect(validators[3]).not.toBeCalled() + }) +}) diff --git a/src/utils.ts b/src/utils.ts index a114feb..c5ab2a6 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -10,9 +10,8 @@ export function isEmpty(response: ValidationResponse): boolean { } export function asyncResponsesAnd(asyncResponses: Array>): ValidatorResponse { - if (asyncResponses.length === 0) { - return null - } + if (asyncResponses.length === 0) return null + if (asyncResponses.length === 1) return asyncResponses[0] return new Promise(resolve => { // 任一不通过,则不通过 asyncResponses.forEach(asyncResponse => asyncResponse.then(Response => { @@ -29,25 +28,14 @@ export function asyncResponsesAnd(asyncResponses: Array(value: TValue, validators: Validator[]) { - if (validators.length === 0) { - return null - } - - if (validators.length === 1) { - return validators[0](value) - } - +export function responsesAnd(responses: Iterable): ValidatorResponse { const asyncResponses: Array> = [] - for (const validator of validators) { - const response = validator(value) - + for (const response of responses) { if (isPromiseLike(response)) { asyncResponses.push(response) continue } - // 任一不通过,则不通过 if (!isEmpty(response)) { return response @@ -57,6 +45,15 @@ export function applyValidators(value: TValue, validators: Validator(value: TValue, validators: Validator[]): ValidatorResponse { +// function* validate() { +// for (const validator of validators) { +// yield validator(value) +// } +// } +// return responsesAnd(validate()) +// } + export function debounce(fn: () => void, delay: number) { let timeout: any = null return () => { diff --git a/tsconfig.json b/tsconfig.json index 1e739f8..3205bb9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,7 @@ "strict": true, "experimentalDecorators": true, "useDefineForClassFields": true, + "downlevelIteration": true, "outDir": "./lib", "lib": ["es2015"] },