我正在使用React和Redux,并将操作类型指定为接口,以便我的减量器可以利用标记的联合类型来提高类型安全性。

我有这样的类型声明:

interface AddTodoAction {
    type: "ADD_TODO",
    text: string
};

interface DeleteTodoAction {
    type: "DELETE_TODO",
    id: number
}

type TodoAction = AddTodoAction | DeleteTodoAction

我想创建辅助函数来创建这些动作,我倾向于使用箭头函数。如果我这样写:

export const addTodo1 = (text: string) => ({
    type: "ADD_TODO",
    text
});

编译器不能提供任何帮助来确保这是一个有效的AddTodoAction,因为没有显式地指定返回类型。我可以通过这样做显式地指定返回类型:

export const addTodo2: (text: string) => AddTodoAction = (text: string) => ({
    type: "ADD_TODO",
    text
})

但这需要两次指定函数参数,因此冗长且难以阅读。

是否有一种方法,我可以明确指定返回类型时,使用箭头符号?

我想试试这个:

export const addTodo3 = (text: string) => <AddTodoAction>({
    type: "ADD_TODO",
    text
})

在这种情况下,编译器现在推断返回类型为AddTodoAction,但它不验证我返回的对象是否具有所有适当的字段。

我可以通过切换到不同的函数语法来解决这个问题:

export const addTodo4 = function(text: string): AddTodoAction {
    return {
        type: "ADD_TODO",
        text
    }
}

export function addTodo5(text: string): AddTodoAction {
    return {
        type: "ADD_TODO",
        text
    }
}

这些方法中的任何一种都会导致编译器使用正确的返回类型,并强制我已经适当地设置了所有字段,但它们也更冗长,并且它们改变了'this'在函数中的处理方式(这可能不是一个问题,我想)。

有什么最好的建议吗?


当前回答

首先,考虑你最初问题中的以下符号:

export const addTodo3 = (text: string) => <AddTodoAction>({
    type: "ADD_TODO",
    text
})

使用这种表示法,可以将返回的对象类型转换为AddTodoAction类型。然而,函数声明的返回类型仍然是未定义的(编译器将隐式地假定any为返回类型)。

请使用以下符号:

export const addTodo3 = (text: string): AddTodoAction => ({
    type: "ADD_TODO",
    text: text
})

在这种情况下,省略必要的属性将产生预期的编译器错误。例如,省略text属性将产生以下(期望的)错误:

Type '{ type: "ADD_TODO"; }' is not assignable to type 'TodoAction'.
  Type '{ type: "ADD_TODO"; }' is not assignable to type 'DeleteTodoAction'.
    Types of property 'type' are incompatible.
      Type '"ADD_TODO"' is not assignable to type '"DELETE_TODO"'.

也可以参考操场的例子。

其他回答

https://www.typescriptlang.org/docs/handbook/functions.html

输入函数

function add(x: number, y: number): number {
  return x + y;
}
 
let myAdd = function (x: number, y: number): number {
  return x + y;
};

使用返回类型编写函数类型:

let myAdd: (x: number, y: number) => number = function (
  x: number,
  y: number
): number {
  return x + y;
};

有两种方法可以通过正确的输入和最少的代码来实现这一点:

interface AddTodoAction {
    type: "ADD_TODO",
    text: string
};

// Because the this keyword works different in arrow functions these 
// 2 implementations are different in some cases:

// arrow function form/ function expression
const addTodo1 = (text: string): AddTodoAction => ({
    type: "ADD_TODO",
    text: text
})

// function declaration form
function addTodo2 (text: string): AddTodoAction {
    return ({
        type: "ADD_TODO",
        text: text
    })
}

现在TS编译器可以检查返回的类型。例如:

const todo = addTodo1('hi');

// Following gives TS compile time error
// addTodo1 returns AddTodoAction which does not have id on the type

const id = todo.id // Property 'id' does not exist on type 'AddTodoAction'.

首先,考虑你最初问题中的以下符号:

export const addTodo3 = (text: string) => <AddTodoAction>({
    type: "ADD_TODO",
    text
})

使用这种表示法,可以将返回的对象类型转换为AddTodoAction类型。然而,函数声明的返回类型仍然是未定义的(编译器将隐式地假定any为返回类型)。

请使用以下符号:

export const addTodo3 = (text: string): AddTodoAction => ({
    type: "ADD_TODO",
    text: text
})

在这种情况下,省略必要的属性将产生预期的编译器错误。例如,省略text属性将产生以下(期望的)错误:

Type '{ type: "ADD_TODO"; }' is not assignable to type 'TodoAction'.
  Type '{ type: "ADD_TODO"; }' is not assignable to type 'DeleteTodoAction'.
    Types of property 'type' are incompatible.
      Type '"ADD_TODO"' is not assignable to type '"DELETE_TODO"'.

也可以参考操场的例子。

我认为你最好的选择是为你的函数创建一个具有正确类型的接口,然后你只需要指定该类型,而不是你的接口的所有嵌套类型:

interface AddTodoAction {
    type: "ADD_TODO",
    text: string
};

interface AddTodoActionCreator {
    (text: string): AddTodoAction;
};

export const addTodo: AddTodoActionCreator = (text) => ({
    type: "ADD_TODO",
    text
});

更新:如何对类型进行此操作

export interface GeneralAction<T> {
    type: string;
    payload: T;
}

export interface GeneralActionCreator<T> {
    (payload: T): GeneralAction<T>;
}

export const SAVE_EVENT = 'SAVE_EVENT';

export const SaveEvent: GeneralActionCreator<UserEvent> = (payload) => { 
    return {type: SAVE_EVENT, payload}; 
};