首页 文章

React redux typescript:connect缺少类型错误

提问于
浏览
3

我试图让react-redux应用程序与打字稿一起使用,但我仍然围绕同样的错误盘旋 . 以下代码编译并生成预期结果

// 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<any, any> ,而是想做 class HelloWorld extends React.Component<HelloWorldProps, any> . 但是,当我这样做时,我从调用 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"
  }
}

UPDATE

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

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

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

我注意到我忘记了商店的初始状态 . 所以即使类型已经签出,状态也是空的 . 我把它更新为

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

现在我确定 clickCount 已在商店中正确设置 . 所以我希望 mapStateToProps 函数取 AppState 并按指定返回 HelloWorldProps ,然后 Provider 应该提供计数值 . 这是事实,但编译器没有看到它 .

那么如何解决这个问题呢?

2 回答

  • 2

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

    interface HelloWorldState {
      clickCount: number
    }
    

    你要从 mapStateToProps 返回的道具是

    { 
      count: state.clickCount
    }
    

    但是您将返回类型覆盖为 HelloWorldState ,因此返回类型不包含 countclickCount .


    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的作者

  • 3

    这是我如何在Typescript Redux App中进行的(根据您的代码调整但未经过测试)

    edited with comment below

    • 键入 connect ,其中包含连接组件的道具( ConnectedHelloWorldProps
    const ConnectedHelloWorld:React.ComponentClass<ConnectedHelloWorldProps>  = 
        connect<any,any,HelloWorldProps>(mapStateToProps)(HelloWorld)
    
    interface ConnectedHelloWorldProps { }
    
    interface HelloWorldProps extends ConnectedHelloWorldProps {
        count: number
        ....
    }
    
    • 使用 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 包含需要在提供者级别传递的内容 . 在 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>;
     }
    

相关问题