使用 React 和 TypeScript 时如何正确输入 @connect?
How does one type @connect correctly when using React and TypeScript?
我为此苦苦挣扎了很长时间。今天坐下来致力于摆脱任何 any
的剩余,但没有成功。
import * as React from 'react';
import * as Redux from 'redux';
import { connect } from 'react-redux';
import { ReduxState } from './types';
import { syncItem, displayAlert } from './actionCreators';
import { SyncItemAction, DisplayAlertAction } from './actions';
// Props coming from Redux using `connect` and `mapStateToProps`
type AppData = {
isSelected: boolean;
isInEditMode: boolean;
};
// Action creators from `./actionCreators` all wrapped in `dispatch` using `connect` and `mapDispatchToProps`
type AppActions = {
syncItem: (id: number) => SyncItemAction;
displayAlert: (text: string) => DisplayAlertAction;
};
// Actual JSX attributes that will be required by the type system.
type AppProps = {
id: number;
name: string;
} & Partial<AppData> & Partial<AppActions>; // Making data and actions partial so that using <App /> in JSX doesn't yell.
// The component's inner state.
type AppState = Partial<{
temp: string;
}>;
@connect<AppData, AppActions, AppProps>(mapStateToProps, mapDispatchToProps)(App) // Or mapDispatchToPropsAlt
export default class App extends React.Component<AppProps, AppState> {
constructor(props: AppProps) {
super(props);
}
render() {
return (
<div>
<h1>Hello, {this.props.name}! (#{this.props.id})</h1>
{/* In the below, syncItem should take the new name, a detail… Also ID could be provided in `mapStateToProps` by using `ownProps`! */}
Rename: <input value={this.state.temp} onChange={event => this.setState({ temp: event.target.value })} />
<button onClick={_ => this.props.syncItem(this.props.id)}>Sync</button>
</div>
);
}
}
function mapStateToProps(state: ReduxState, ownProps?: AppProps): AppData {
return {
isSelected: ownProps.id === state.selectedId,
isInEditMode: state.isInEditMode
};
}
function mapDispatchToProps(dispatch: Redux.Dispatch<ReduxState>, ownProps?: AppProps): AppActions {
return {
syncItem: (id: number) => dispatch(syncItem(id)),
displayAlert: (text: string) => dispatch(displayAlert(text, ownProps.name))
};
}
function mapDispatchToPropsAlt(dispatch: Redux.Dispatch<ReduxState>, ownProps?: AppProps): AppActions {
return {
syncItem,
// Making this `null` because `displayAlert` above changes the signature by hiding the other parametr and taking it from `ownProps` - uncommon!
displayAlert: null
};
}
function Test() {
// Only `id` and `name` is correctly required.
return <App id={0} name={'test'} />;
}
在上面的代码中,我得到以下内容:
index.tsx(31,78): error TS2345: Argument of type 'typeof App' is not assignable to parameter of type 'ComponentClass<AppData & AppActions> | StatelessComponent<AppData & AppActions>'.
Type 'typeof App' is not assignable to type 'StatelessComponent<AppData & AppActions>'.
Type 'typeof App' provides no match for the signature '(props: AppData & AppActions & { children?: ReactNode; }, context?: any): ReactElement<any>'
我已经使用 node_modules/@types
中的类型定义来得出我上面的内容,同样我已经检查了 ComponentClass<T>
的样子。它建议(在我看来)组件的 state
必须是 {} | void
,我不明白为什么会这样,如果我将上面的代码更改为 <AppProps, void>
或者 <AppProps, {}>
它不会以任何方式改变第一个错误。
我该怎么办?
syncItem
就是 function syncItem(id: number): SyncItemAction
而 SyncItemAction
就是 interface SyncItemAction extends Redux.Action { id: number; }
,与 displayAlert
.
类似
编辑:找到一个 ,但这个也没有回答如何输入 state
。
这个答案不使用注释(使用接口而不是类型)。基本思想是将 Connected 组件及其 props 与底层组件完全分离(类型明智)。
创建一个 class ConnectedAppProps
,这是您要向 Provider 公开的连接组件的属性
interface ConnectedAppProps {
id: number;
name: string;
}
从该界面扩展 AppProps
interface AppProps extends ConnectedAppProps, AppData, AppActions {
}
创建连接组件
const ConnectedApp: React.ComponentClass<ConnectedAppProps> =
connect<AppData,AppActions,AppProps>( mapStateToProps, mapDispatchToProps )( App )
使用 Provider
下的连通分量及其 ConnectedAppProps
<Provider store={store}>
<ConnectedApp id={0} name={'test'} />
</Provider>
像今天一样使用 mapStateToProp
和 mapDispatchToProps
"enrich" 来自 ConnectedAppProps
的组件 AppProps
我为此苦苦挣扎了很长时间。今天坐下来致力于摆脱任何 any
的剩余,但没有成功。
import * as React from 'react';
import * as Redux from 'redux';
import { connect } from 'react-redux';
import { ReduxState } from './types';
import { syncItem, displayAlert } from './actionCreators';
import { SyncItemAction, DisplayAlertAction } from './actions';
// Props coming from Redux using `connect` and `mapStateToProps`
type AppData = {
isSelected: boolean;
isInEditMode: boolean;
};
// Action creators from `./actionCreators` all wrapped in `dispatch` using `connect` and `mapDispatchToProps`
type AppActions = {
syncItem: (id: number) => SyncItemAction;
displayAlert: (text: string) => DisplayAlertAction;
};
// Actual JSX attributes that will be required by the type system.
type AppProps = {
id: number;
name: string;
} & Partial<AppData> & Partial<AppActions>; // Making data and actions partial so that using <App /> in JSX doesn't yell.
// The component's inner state.
type AppState = Partial<{
temp: string;
}>;
@connect<AppData, AppActions, AppProps>(mapStateToProps, mapDispatchToProps)(App) // Or mapDispatchToPropsAlt
export default class App extends React.Component<AppProps, AppState> {
constructor(props: AppProps) {
super(props);
}
render() {
return (
<div>
<h1>Hello, {this.props.name}! (#{this.props.id})</h1>
{/* In the below, syncItem should take the new name, a detail… Also ID could be provided in `mapStateToProps` by using `ownProps`! */}
Rename: <input value={this.state.temp} onChange={event => this.setState({ temp: event.target.value })} />
<button onClick={_ => this.props.syncItem(this.props.id)}>Sync</button>
</div>
);
}
}
function mapStateToProps(state: ReduxState, ownProps?: AppProps): AppData {
return {
isSelected: ownProps.id === state.selectedId,
isInEditMode: state.isInEditMode
};
}
function mapDispatchToProps(dispatch: Redux.Dispatch<ReduxState>, ownProps?: AppProps): AppActions {
return {
syncItem: (id: number) => dispatch(syncItem(id)),
displayAlert: (text: string) => dispatch(displayAlert(text, ownProps.name))
};
}
function mapDispatchToPropsAlt(dispatch: Redux.Dispatch<ReduxState>, ownProps?: AppProps): AppActions {
return {
syncItem,
// Making this `null` because `displayAlert` above changes the signature by hiding the other parametr and taking it from `ownProps` - uncommon!
displayAlert: null
};
}
function Test() {
// Only `id` and `name` is correctly required.
return <App id={0} name={'test'} />;
}
在上面的代码中,我得到以下内容:
index.tsx(31,78): error TS2345: Argument of type 'typeof App' is not assignable to parameter of type 'ComponentClass<AppData & AppActions> | StatelessComponent<AppData & AppActions>'.
Type 'typeof App' is not assignable to type 'StatelessComponent<AppData & AppActions>'.
Type 'typeof App' provides no match for the signature '(props: AppData & AppActions & { children?: ReactNode; }, context?: any): ReactElement<any>'
我已经使用 node_modules/@types
中的类型定义来得出我上面的内容,同样我已经检查了 ComponentClass<T>
的样子。它建议(在我看来)组件的 state
必须是 {} | void
,我不明白为什么会这样,如果我将上面的代码更改为 <AppProps, void>
或者 <AppProps, {}>
它不会以任何方式改变第一个错误。
我该怎么办?
syncItem
就是 function syncItem(id: number): SyncItemAction
而 SyncItemAction
就是 interface SyncItemAction extends Redux.Action { id: number; }
,与 displayAlert
.
编辑:找到一个 state
。
这个答案不使用注释(使用接口而不是类型)。基本思想是将 Connected 组件及其 props 与底层组件完全分离(类型明智)。
创建一个 class ConnectedAppProps
,这是您要向 Provider 公开的连接组件的属性
interface ConnectedAppProps {
id: number;
name: string;
}
从该界面扩展 AppProps
interface AppProps extends ConnectedAppProps, AppData, AppActions {
}
创建连接组件
const ConnectedApp: React.ComponentClass<ConnectedAppProps> =
connect<AppData,AppActions,AppProps>( mapStateToProps, mapDispatchToProps )( App )
使用 Provider
下的连通分量及其 ConnectedAppProps
<Provider store={store}>
<ConnectedApp id={0} name={'test'} />
</Provider>
像今天一样使用 mapStateToProp
和 mapDispatchToProps
"enrich" 来自 ConnectedAppProps
AppProps