【问题标题】:React redux typescript: connect has missing type errorReact redux typescript:连接缺少类型错误
【发布时间】:2017-06-08 02:01:29
【问题描述】:

我正在尝试让一个 react-redux 应用程序与 typescript 一起使用,但我一直围绕着同样的错误。以下代码编译并产生预期结果

// State definition
interface HelloWorldState {
    clickCount: number
}
interface AppState extends HelloWorldState {}


// Props definitions
interface HelloWorldProps {
    count: number
}


// Actions
const CLICK = 'CLICK';
const click = () => {return {type:CLICK}};


// Reducers
function clickCount(state:number = 0, action:Action) {
    if (typeof state === 'undefined') {
        return 0;
    }
    switch (action.type) {
        case CLICK:
            return state + 1;
        default:
            return state;
    }
}
let rootReducer = combineReducers({
    clickCount
});


// Store
let store = createStore(rootReducer);


// Components
class HelloWorld extends React.Component<any, any> {
    render() {
        return <div onClick={this.handleClick.bind(this)}>Hello world "{this.props.count}"</div>
    }

    handleClick() {
        store.dispatch(click())
    }
}


// Container components
const mapStateToProps = (state:AppState):HelloWorldState => {
    return Immutable.fromJS({
        count: state.clickCount
    })
};
const ConnectedHelloWorld = connect(
    mapStateToProps
)(HelloWorld);

render(
    <Provider store={store}>
        <ConnectedHelloWorld/>
    </Provider>,
    container
);

太棒了!但我使用的是 TypeScript,因为我想在编译时进行类型检查。类型检查最重要的是状态和道具。所以不是class HelloWorld extends React.Component&lt;any, any&gt;,我想做class HelloWorld extends React.Component&lt;HelloWorldProps, any&gt;。但是,当我这样做时,我从对 render 的调用中得到以下编译错误

TS2324:Property 'count' is missing in type 'IntrinsicAttributes & IntrinsicClassAttributes<HelloWorld> & HelloWorldProps & { children?: React...'.

我真的不明白为什么。 count IS 出现在HelloWordProps 定义中,它是由reducer 提供的,所以我应该没问题,对吧?类似的问题表明这是一个推理问题,我应该将调用的类型声明为connect,但我似乎不知道如何

package.json

{
  "name": "reacttsx",
  "scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "ts-loader": "^1.3.3",
    "typescript": "^2.1.5",
    "webpack": "^1.14.0",
    "typings": "^2.1.0"
  },
  "dependencies": {
    "es6-promise": "^4.0.5",
    "flux": "^3.1.2",
    "immutable": "^3.8.1",
    "isomorphic-fetch": "^2.2.1",
    "jquery": "^3.1.1",
    "react": "^15.4.2",
    "react-dom": "^15.4.2",
    "react-redux": "^5.0.2",
    "redux": "^3.6.0",
    "redux-logger": "^2.7.4",
    "redux-thunk": "^2.2.0"
  }
}

typings.json

{
  "dependencies": {
    "flux": "registry:npm/flux#2.1.1+20160601175240",
    "immutable": "registry:npm/immutable#3.7.6+20160411060006",
    "react": "registry:npm/react#15.0.1+20170104200836",
    "react-dom": "registry:npm/react-dom#15.0.1+20160826174104",
    "react-redux": "registry:npm/react-redux#4.4.0+20160614222153",
    "redux-logger": "registry:dt/redux-logger#2.6.0+20160726205300",
    "redux-thunk": "registry:npm/redux-thunk#2.0.0+20160525185520"
  },
  "globalDependencies": {
    "es6-promise": "registry:dt/es6-promise#0.0.0+20160726191732",
    "isomorphic-fetch": "registry:dt/isomorphic-fetch#0.0.0+20170120045107",
    "jquery": "registry:dt/jquery#1.10.0+20170104155652",
    "redux": "registry:dt/redux#3.5.2+20160703092728",
    "redux-thunk": "registry:dt/redux-thunk#2.1.0+20160703120921"
  }
}

更新

由于它抱怨count 丢失,我尝试更新到

render(
    <Provider store={store}>
        <ConnectedHelloWorld count={0}/>
    </Provider>,
    container
);

这解决了这个问题。所以问题是编译器不知道Provider 提供了计数。提供者使用商店。 store 应该有 clickCount 值,该值由容器组件映射到 count

我注意到我忘记了商店的初始状态。因此,即使类型已签出,状态也会为空。我把它更新为

// Store
let initialState:AppState = {clickCount: 0};
let store = createStore(rootReducer, initialState);

现在我确定 clickCount 在商店中设置正确。所以我希望mapStateToProps 函数采用AppState 并按指定返回HelloWorldProps,然后Provider 应该提供计数值。这是真的,但编译器看不到。

那么如何解决呢?

【问题讨论】:

  • 即使您只执行简单的HelloWorld 课程,您是否也会收到此错误? (也就是说,没有 redux)因为它对我有用。另外,您使用的是什么版本的反应?
  • 简单的意思是HelloWorld extends React.Component&lt;any, any&gt;?是的,这总是有效的,因为它的状态和道具的类型是any。所以那里没有错误。
  • 不,我的意思是只有class HelloWorld extends React.Component&lt;HelloWorldProps, any&gt; 没有redux 的东西。对我来说编译得很好。你使用的是什么版本的 react/typescript?
  • 我更新了问题。你是对的,问题来自 redux 或 react-redux
  • 您可以在此处找到连接示例:github.com/Black-Monolith/LightCycle/blob/master/app/containers/…。它将当前组件与我们connect() 组件的容器文件分离到存储区。我们还导出了MappedProps 类型,以便能够将其用作组件中的道具类型。

标签: reactjs typescript redux react-redux


【解决方案1】:

在我的例子中,我像这样在 connect 函数中为 mapDispatchToProps 参数传递了 null,因为我没有为此组件使用调度:

export default connect(mapStateToProps, null)(MainLayout);

将其更改为仅省略 mapDispatchToProps 参数已为我修复了它

export default connect(mapStateToProps)(MainLayout);

【讨论】:

    【解决方案2】:

    这是我在 Typescript Redux 应用程序中的操作方式(已根据您的代码进行了调整,但未经测试)

    已编辑并在下方发表评论

    1. 键入 connect 并带有连接组件的道具 (ConnectedHelloWorldProps)

      const ConnectedHelloWorld:React.ComponentClass<ConnectedHelloWorldProps>  = 
          connect<any,any,HelloWorldProps>(mapStateToProps)(HelloWorld)
      
      interface ConnectedHelloWorldProps { }
      
      interface HelloWorldProps extends ConnectedHelloWorldProps {
          count: number
          ....
      }
      
    2. Provider 中使用连接的组件及其ConnectedHelloWorldProps 属性

      <Provider store={store}>
          <ConnectedHelloWorld/>
      </Provider>
      

    注意:这适用于这些类型

    "@types/react": "^0.14.52",
    "@types/react-dom": "^0.14.19",
    "@types/react-redux": "^4.4.35",
    "@types/redux-thunk": "^2.1.32",
    

    ConnectedHellowWorldProps 在这里并不真正需要,因为它是一个空接口,但在现实世界的场景中,它可能包含一些道具。

    基本原则是这样的:ConnectedHelloWorldProps 包含需要在 Provider 级别传递的内容。在mapStateToProps 和/或mapDispatchToProps 中,使用所需的任何内容丰富实际组件HelloWorldProps

    Redux Typescript 类型是一个野兽,但上面显示的内容应该足够了。

    export declare function connect<TStateProps, TDispatchProps, TOwnProps>(
    mapStateToProps: FuncOrSelf<MapStateToProps<TStateProps, TOwnProps>>,
     mapDispatchToProps?: FuncOrSelf<MapDispatchToPropsFunction<TDispatchProps, TOwnProps> | MapDispatchToPropsObject>): ComponentDecorator<TStateProps & TDispatchProps, TOwnProps>;
    
    
     interface ComponentDecorator<TOriginalProps, TOwnProps> {
        (component: ComponentClass<TOriginalProps> | StatelessComponent<TOriginalProps>): ComponentClass<TOwnProps>;
     }
    

    【讨论】:

    • 这会让你在没有道具的情况下定义&lt;ConnectedHelloWorld /&gt; 组件吗?它们到底应该由提供者提供吗?当我执行&lt;ConnectedHelloWorld count={null} /&gt; 时,编译器接受它,当我运行它时,我看到提供程序从初始状态正确地提供了 0
    • 是的。不要在ConnectedHelloWorldProps 中定义count 属性并将其分配给0mapStateToProp() 中的state.clickCount
    • 您的联系电话给了我error TS2346: Supplied parameters do not match any signature of call target.
    • 我们可能没有完全相同的类型。我已经用我使用的类型编辑了答案
    • 如果你使用最新版本的 TypeScript,你可以使用 NPM 通过@types 安装类型:blogs.msdn.microsoft.com/typescript/2016/06/15/…
    【解决方案3】:

    你的问题是HelloWorldState是这样定义的

    interface HelloWorldState {
      clickCount: number
    }
    

    而你想从mapStateToProps返回的道具是

    { 
      count: state.clickCount
    }
    

    但是您将返回类型覆盖为HelloWorldState,因此返回类型不包含count,而是包含clickCount


    fromJS 破坏类型安全

    ImmutableJS.fromJS 在 TypeScript 类型推断中效果不佳:

    const state = Immutable.fromJS({
      count: 0
    })
    

    这里state 的类型为any,因此在将其分配为HelloWorldState 类型的返回值时不会出现任何错误。


    mapStateToProps 应该返回一个简单的对象

    mapStateToProps 可以返回一个简单的对象,因为您不会直接从组件中编辑此状态:

    const mapStateToProps = (state: AppState): HelloWorldState => {
      return {
        count: state.clickCount
      }
    }
    

    这里会出现你使用 ImmutableJS 时没有的错误,告诉你不能将{ count: number } 赋值给{ clickCount: number }

    所以只需删除返回类型,类型推断就会完成这项工作,或者添加正确的类型。

    const mapStateToProps = (state: AppState) => {
      return {
        count: state.clickCount
      }
    }
    

    与 Monolite 的静态类型树结构共享

    我还建议您使用Monolite,这是一组用 TypeScript 编写的简单函数,旨在与 Redux 状态一起使用。

    它还允许您使用简单的 JavaScript 对象定义您的状态,并通过简单的函数对状态进行更新。

    import { set } from 'monolite'
    
    const state = {
      clickCount: 0
    }
    
    const newState = set(state, _ => _.clickCount)(value => value + 1)
    

    P.S.我是 Monolite 的作者

    【讨论】:

      【解决方案4】:

      我在使用 connect() 编写组件类时遇到了类似的问题。在我的情况下,道具中有一个循环依赖

      const mapDispatchToProps = (dispatch: Dispatch) => {/* return something here */}
      const mapStateToProps = (state: IState, ownProps: AllProps) => {/* return something here */}
      type AllProps = {
      someExtraProp: string;
      } & ReturnType<typeof mapDispatchToProps> & ReturnType<typeof mapStateToProps>;
      

      正如你所见,这导致了 AllProps 和 mapStateToProps 之间的循环类型依赖。我分离出 OwnProps 代码,现在看起来像这样:

      const mapDispatchToProps = (dispatch: Dispatch) => {/* return something here */}
      const mapStateToProps = (state: IState, ownProps: OwnProps) => {/* return something here */}
      const OwnProps = {
      someExtraProp: string;
      };
      type AllProps = OwnProps & ReturnType<typeof mapDispatchToProps> & ReturnType<typeof mapStateToProps>;
      

      现在,connect 调用不会引发任何类型错误。希望这可以帮助有类似问题的人。

      【讨论】:

      • 看准了!谢谢!
      猜你喜欢
      • 2018-03-19
      • 1970-01-01
      • 1970-01-01
      • 2019-01-09
      • 2016-10-09
      • 1970-01-01
      • 2015-12-02
      • 2021-07-28
      • 2021-02-28
      相关资源
      最近更新 更多