【问题标题】:Why ts will infer to `never` when I lack index signature?当我缺少索引签名时,为什么 ts 会推断为“从不”?
【发布时间】:2022-06-10 21:01:42
【问题描述】:

当我没有在FormData中添加索引签名时:

interface FormData {
  applicationName: string;
  cluster: string;
  stackCode: number;
  GitHubToken: string;
}

enum FieldChangeType {
  TextInput,
  Toggle,
}

interface FieldAction {
  type: FieldChangeType;
  field: keyof FormData;
  payload?: FormData[keyof FormData];
}

function useFormRedux() {
  function reducer(preState: FormData, action: FieldAction) {
    const nextState: FormData = cloneDeep(preState);

    switch(action.type) {
      case FieldChangeType.TextInput:
        nextState[action.field] = action.payload!;
        // Error: Type 'string | number' is not assignable to type 'never'.
    }

    return nextState;
  }
}

当我在FormData 中添加索引签名时,错误消失了:

interface FormData {
  [index: string]: boolean | number | string | string[]
  applicationName: string;
  cluster: string;
  stackCode: number;
  GitHubToken: string;
}

这让我很困惑当我缺少索引签名时,为什么 ts 会推断为 never

【问题讨论】:

    标签: typescript


    【解决方案1】:

    首先,FormData 是一个内置类型,如下所示:

    interface FormData {
        append(name: string, value: string | Blob, fileName?: string): void;
        delete(name: string): void;
        get(name: string): FormDataEntryValue | null;
        getAll(name: string): FormDataEntryValue[];
        has(name: string): boolean;
        set(name: string, value: string | Blob, fileName?: string): void;
        forEach(callbackfn: (value: FormDataEntryValue, key: string, parent: FormData) => void, thisArg?: any): void;
    }
    
    interface FormData {
        [Symbol.iterator](): IterableIterator<[string, FormDataEntryValue]>;
        /** Returns an array of key, value pairs for every entry in the list. */
        entries(): IterableIterator<[string, FormDataEntryValue]>;
        /** Returns a list of keys in the list. */
        keys(): IterableIterator<string>;
        /** Returns a list of values in the list. */
        values(): IterableIterator<FormDataEntryValue>;
    }
    

    因此,我认为重命名您的自定义界面是个好主意。

    错误:

    Type 'string | number' is not assignable to type 'never'.
      Type 'string' is not assignable to type 'never'.(2322)
    
    

    nextState[action.field] = action.payload!;
    

    从类型的角度来看,action.payload 可能是 string | number | undefined,而action.field 可能是来自 CustomFormData 的任何键。这意味着action.fieldaction.payload 之间没有直接关联。

    更多,

    interface FieldAction {
        type: FieldChangeType;
        field: keyof CustomFormData;
        payload?: CustomFormData[keyof CustomFormData];
    }
    

    非常不安全。 考虑一下:

    const unsafeAction: FieldAction = {
        type: FieldChangeType.TextInput,
        field: 'cluster',
        payload: 42
    }
    

    这个对象的表示无效,因为payload 应该是字符串。

    为了修复它,您应该使无效状态无法表示。考虑这个例子:

    
    type FieldAction = {
        [Field in keyof CustomFormData]: {
            type: FieldChangeType,
            field: Field,
            payload?: CustomFormData[Field]
        }
    }[keyof CustomFormData]
    
    const ok: FieldAction = {
        type: FieldChangeType.TextInput,
        field: 'cluster',
        payload: 'str'
    } // ok
    
    const expected_error: FieldAction = {
        type: FieldChangeType.TextInput,
        field: 'cluster',
        payload: 42
    } // error
    

    我们创建了所有允许状态的联合。顺便说一句,TypeScript 不喜欢突变,您应该始终考虑到这一点。

    这是我的建议:

    interface CustomFormData {
        applicationName: string;
        cluster: string;
        stackCode: number;
        GitHubToken: string;
    }
    
    enum FieldChangeType {
        TextInput,
        Toggle,
    }
    
    type FieldAction = {
        [Field in keyof CustomFormData]: {
            type: FieldChangeType,
            field: Field,
            payload?: CustomFormData[Field]
        }
    }[keyof CustomFormData]
    
    
    const makePayload = (state: CustomFormData, action: FieldAction)
        : CustomFormData => ({
            ...state,
            [action.field]: action.payload
        })
    
    function useFormRedux() {
        function reducer(preState: CustomFormData, action: FieldAction) {
            const nextState: CustomFormData = null as any;
    
            switch (action.type) {
                case FieldChangeType.TextInput:
                    return makePayload(preState, action)
            }
    
            return nextState;
        }
    }
    
    

    Playground

    Here你可以找到官方解释

    您可以在此处找到有关 htis 主题的更多问题/答案: firstsecondalmost a duplicate

    Here你可以找到我的文章,里面有一些例子和链接

    主要用途说明

    type FieldAction = {
        /**
         * This syntax mean iteration
         * See docs https://www.typescriptlang.org/docs/handbook/2/mapped-types.html
         */
        [Field in keyof CustomFormData]: {
            type: FieldChangeType,
            field: Field,
            payload?: CustomFormData[Field]
        }
    }
    
    /**
     * I have removed [keyof CustomFormData] from the end for the sake of readability
     * You can instead wrap FieldAction into Values utility type
     */
    
    type Values<T> = T[keyof T]
    {
        type Test1 = Values<{ a: 1, b: 2 }> // 1 | 2
    }
    
    type Result = Values<FieldAction>
    

    Playground

    【讨论】:

    • ? 你能解释一下这段:``` type FieldAction = { [Field in keyof CustomFormData]: { type: FieldChangeType, field: Field, payload?: CustomFormData[Field] } }[keyof CustomFormData ] ```这对我来说很难理解。
    • @Pandy 进行了更新
    猜你喜欢
    • 1970-01-01
    • 2022-08-21
    • 2023-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-12
    • 2019-04-28
    • 2023-03-23
    相关资源
    最近更新 更多