【问题标题】:Element implicitly has an 'any' type because expression of type 'string' can't be used元素隐式具有“任何”类型,因为不能使用“字符串”类型的表达式
【发布时间】:2021-12-31 16:15:17
【问题描述】:

我在 thread 上关注 Guilherme 提供的 TypeScript 示例(粘贴在下面)。
但我收到一个错误:Element 隐式具有“任何”类型,因为表达式类型为“字符串 |”。号码 |符号'不能用于索引类型'IState'。 在 'IState'.ts(7053) 类型上没有找到带有 'string' 类型参数的索引签名
关注this我尝试添加的文章:

    const result: IState = { ...state };
    result[action.type as keyof IState] = action.value;
    return result;

但这并不能解决问题。这样的问题有很多回答,但我错过了一些东西。

import React, { FC, Reducer, useReducer } from "react";

interface IState {
    email: string;
    password: string;
    passwordConfirmation: string;
    username: string;
}

interface IAction {
    type: string;
    value?: string;
}

const initialState: IState = {
    email: "",
    password: "",
    passwordConfirmation: "",
    username: "",
};

const reducer = (state: IState, action: IAction) => {
    if (action.type === "reset") {
        return initialState;
    }

    const result: IState = { ...state };
    result[action.type] = action.value;
    return result;
};

export const Signup: FC = props => {
    const [state, dispatch] = useReducer<Reducer<IState, IAction>, IState>(reducer, initialState, () => initialState);
    const { username, email, password, passwordConfirmation } = state;

    const handleSubmit = (e: React.FormEvent) => {
        e.preventDefault();

        /* fetch api */

        /* clear state */
        dispatch({ type: "reset" });
    };

    const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const { name, value } = e.target;
        dispatch({ type: name, value });
    };

    return (
        <form onSubmit={handleSubmit}>
            <div>
                <label>
                    Username:
                    <input value={username} name="username" onChange={onChange} />
                </label>
            </div>
            <div>
                <label>
                    Email:
                    <input value={email} name="email" onChange={onChange} />
                </label>
            </div>
            <div>
                <label>
                    Password:
                    <input
                        value={password}
                        name="password"
                        type="password"
                        onChange={onChange}
                    />
                </label>
            </div>
            <div>
                <label>
                    Confirm Password:
                    <input
                        value={passwordConfirmation}
                        name="passwordConfirmation"
                        type="password"
                        onChange={onChange}
                    />
                </label>
            </div>
            <button>Submit</button>
        </form>
    );
};

【问题讨论】:

    标签: typescript


    【解决方案1】:

    可以通过将IAction 的定义更改为:

    interface IAction {
        type: keyof IState;
        value?: string;
    }
    

    它使用keyof type operator保证type属性只能是IState的键之一。

    不过,您还会遇到另外两个错误(其中一个可能会立即显现出来):

    main.ts:21:9 - error TS2367: This condition will always return 'false' since the types 'keyof IState' and '"reset"' have no overlap.
    
    21     if (action.type === "reset") {
               ~~~~~~~~~~~~~~~~~~~~~~~
    
    main.ts:26:5 - error TS2322: Type 'string | undefined' is not assignable to type 'string'.
      Type 'undefined' is not assignable to type 'string'.
    
    26     result[action.type] = action.value;
           ~~~~~~~~~~~~~~~~~~~
    
    
    Found 2 errors.
    

    所以,看第一个错误,很明显,为了使IActiontype 属性可以接受,它还必须包含reset,这可以通过更改@987654336 的定义来完成@致:

    interface IAction {
        type: keyof IState | "reset";
        value?: string;
    }
    

    但是,这并不能解决第二个错误,这与value 属性键入为string | undefined(由于使用?)有关,但是@987654341 上的各种属性@ 都输入为string(不允许分配undefined)。

    在我看来,有三个选项:

    • 使IAction.value 成为非可选的string
    • IState的所有属性设为可选strings
    • IAction 更改为可区分的union(根据this answer

    前两个都非常简单,但是对于允许或要求做什么用途具有深远的影响,所以让我们看看第三个。将IAction 的定义更改为:

    type IAction = {
        type: keyof IState;
        value: string;
    } | {
        type: "reset";
    };
    

    我们定义了一种类型,它是两种类型的联合,并且可以通过使用narrowing 来区分这种类型的实例。因此,在减速器中:

    const reducer = (state: IState, action: IAction) => {
    
        // `action` can be either:
        //   { type: keyof IState; value: string; }
        // or:
        //   { type: "reset"; }
    
        if (action.type === "reset") {
    
            // `action` can only be:
            //   { type: "reset" }
            // as the `type` property equals "reset"
    
            return initialState;
        }
    
        // `action` can only be:
        //   { type: keyof IState; value: string; }
        // as the `type` property _doesn't_ equal "reset"
    
        const result: IState = { ...state };
        result[action.type] = action.value;
        return result;
    };
    

    通过这种方式,您保留了类型的类型安全性,而不必降低其他类型的类型安全性(例如,使IState 属性可选)或为某些字段设置不必要的值(例如,使IAction.value非可选并强制 "reset" 案例也有 value)。

    处理任意值

    在您的代码中,有一个地方使用任意值创建了 IAction

    const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const { name, value } = e.target;
        dispatch({ type: name, value });
    };
    

    正如您在评论中指出的那样,这将产生类似于以下内容的错误:

    main.ts:44:15 - error TS2322: Type 'string' is not assignable to type '"reset" | keyof IState'.
    
    44     dispatch({type: name, value });
                     ~~~~
    
      main.ts:11:5
        11     type: keyof IState;
               ~~~~
        The expected type comes from property 'type' which is declared here on type 'IAction'
    
    
    Found 1 error.
    

    这是因为HTMLInputElement.name 被键入string,它可以分配超出允许的受限集的值 (keyof IState)。正如您所发现的,一种选择是将dispatch 的调用更改为:

    dispatch({type: name as keyof IState, value });
    

    使用type assertion 通知类型检查器name 是兼容类型。您可以更严格一点,并使用type predicate,这是一种定义一些运行时逻辑的方法,如果满足,则表明该类型是兼容的:

    const isAction = (action: { type: string, value?: string }): action is IAction => {
        return action.type in ["email", "password", "passwordConfirmation", "username"];
    }
    

    用于onChange,类似:

    const onChange = (e: ChangeEvent<HTMLInputElement>) => {
        const { name, value } = e.target;
        const action = { type: name, value };
    
        if (isAction(action)) {
            dispatch(action);
        }
    };
    

    这样做的一个缺点是需要手动列出IState的所有键;如果将来接口发生更改(添加或删除属性),则还需要更新运行时类型谓词,否则它将不再有效。

    【讨论】:

    • 谢谢你一步一步的解释。它是如何工作的变得更加清晰。不幸的是,我收到一个错误:Type 'string' is notassignable to type 'keyof IState | "reset"'.ts(2322) test.ts(15, 5):预期的类型来自属性 'type',它在类型 'IAction' 上声明。但是 IState 的所有内容都被声明为字符串。为什么抱怨?
    • 通过在 dispatch({ type: name as keyof IState, value }); 中添加 keyof IState 来解决。谢谢!
    • @Weilire 听起来你已经掌握了它,但我会在dispatch 的呼叫站点添加一个关于限制它的部分。
    猜你喜欢
    • 2021-10-27
    • 2021-04-20
    • 1970-01-01
    • 2021-03-17
    • 2021-09-22
    相关资源
    最近更新 更多