【问题标题】:Typing Generic Actions in Redux在 Redux 中键入通用操作
【发布时间】:2018-03-28 19:08:50
【问题描述】:

在我的 redux 商店中,我有许多几乎相同的状态切片:

{
  cars: { fetching: false, data: [], error: '' },
  buses: { fetching: false, data: [], error: '' },
  bicycles: { fetching: false, data: [], error: '' }
}

我想要执行以下操作:

const requestCars = {
  type: 'REQUEST::cars',
  brand: 'Toyota'
}

const errorBuses = {
  type: 'ERROR::buses',
  error: 'An error'
}

我可以生成动作而不是显式声明每个动作:

const requestAction = (vehicle: string) => ({
  type: `REQUEST::${vehicle}`
})

我的减速器看起来像:

const makeReducer = (vehicle: string) => (state, action) => {

  // I want to capture any action that has type REQUEST:vehicle

  switch(action.type) {
    case // problem is here
  }
}

所以我的根减速器是:

{
  cars: makeReducer('cars'),
  buses: makeReducer('buses'),
  bicycles: makeReducer('bicycles')
}

所以在makeReducer(vehicle) 中,我实际上需要不加选择地捕获所有REQUEST::${vehicle} 类型的操作(比如将fetching 设置为true)。

典型的例子是使用标记的联合类型,但我不能,因为action.type 没有文字类型。所以在 switch 案例中,TypeScript 不允许我访问action.brand。我也不能使用类型保护,因为我的操作中没有判别属性。有没有办法在不维护类型的字符串文字列表的情况下实现这一点?

[编辑] - 结果证明这种方法被严重误导,因为它会在为一种车辆类型调度操作时将所有车辆的 fetching 状态设置为 true。我想我对这样一个事实感到困惑:state 在被传递到减速器之前被缩小,所有 动作都通过它。但是,在极少数 (?) 情况下,我将把它留给后代,您需要在单个减速器中监听满足特定条件的所有操作。

在这种情况下,解决方案是使用类型保护,从而得到接受的答案。

【问题讨论】:

  • 为什么vehicle 是动作类型的一部分?在我看来,这应该是动作有效负载的一部分。
  • 我正在使用 redux-saga,如果我一个接一个地调度两个动作(一个用于cars,一个用于buses),具有相同的动作类型会导致不必要的取消。同时,我想要如果在前一个相同车辆类型的传奇尚未结束之前调度了一种车辆类型的操作,则取消。
  • 为什么不能使用类型保护?像这样:const makeReducer = (vehicle: string) => (state: any, action: {type: string}) => { if(isRequest(action)){ console.log(action.brand); } } function isRequest(action: {type: string}) : action is { type: string, brand: string } { return action.type.startsWith("REQUEST::"); }
  • 我试过了,但它似乎不起作用 - 动作被键入到类型保护之外的 never,这表明它匹配所有动作,但我可能忽略了一些东西。
  • @TitianCernicova-Dragomir 这似乎有效,我之前一定做错了什么。很高兴接受此评论的格式化版本。

标签: typescript redux


【解决方案1】:

由于type 不是字符串常量,因此您不能使用标记联合,因为它们的类型保护行为依赖于字符串文字类型才能起作用。

但是,您可以使用自定义类型保护来检查操作是否是请求:

const makeReducer = (vehicle: string) => (state: any, action: {type: string}) => 
{ 
    if(isRequest(action)){ 
        console.log(action.brand); 
    } 
} 
function isRequest(action: {type: string}) : action is { type: string, brand: string } 
{ 
    return action.type.startsWith("REQUEST::"); 
}

【讨论】:

  • 我最初的实现有一点缺陷——我不能随意切换任何"REQUEST:: 类型,因为它会更新所有我不想要的车辆。但是,我将把它作为对如何进行复杂类型缩小的一般问题的回答。
  • @ThomasChia 如果您发布带有更完整示例的新问题,我们可以看看 :)
  • 谢谢,但无论如何我试图做的最终都是错误的(我已经编辑了原始问题)。你的回答对这个问题是正确的,只是问题是错误的。
【解决方案2】:

为什么不让您的makeReducer 函数采用比车辆类型名称更复杂的对象?

carType = {
   name: 'car',
   requestType : requestAction('car').type
}

然后:

makeReducer(carType)

然后

const makeReducer = (vehicleType) => (state, action) => {
  switch(action.type) {
    case vehicleType.requestType:
        //do your thing
  }
}

【讨论】:

  • 我认为问题在于 TypeScript 无法推断出具体的动作对象,因为vehicleType.requestType 是动态的。因此,在 switch 案例中,当我尝试执行 action.brand 时,我收到编译器错误,因为 TS 认为该操作可能是错误类型(没有 brand 键)。
  • 我不太了解 TS,但你不能使用接口 vehicleType 并让类型实现它以便类型检查起作用吗?
  • 如果我有那个界面,它必须有type: string,因为每辆车都有不同的动作类型。这意味着我不能使用接口进行类型缩小,因为所有 类型都是strings。在 switch 的情况下,TS 仍然无法知道我有哪些具体的动作。另一种方法是字符串文字的联合,我希望避免这种情况,但也许这不符合 TS 的精神。
  • 好吧,如果 TS 在这种特定情况下对您没有帮助,最好直接无类型
猜你喜欢
  • 2016-05-30
  • 2021-10-07
  • 2018-01-22
  • 2023-03-18
  • 2019-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-27
相关资源
最近更新 更多