J'ai du mal à comprendre comment taper correctement Les conteneurs Redux .
Considérons un composant de présentation simple qui pourrait ressembler à ceci:
interface MyProps {
name: string;
selected: boolean;
onSelect: (name: string) => void;
}
class MyComponent extends React.Component<MyProps, {}> { }
Du point de vue de ce composant, tous les accessoires sont nécessaires.
Maintenant, je veux écrire un conteneur qui tire tous ces accessoires hors d'état:
function mapStateToProps(state: MyState) {
return {
name: state.my.name,
selected: state.my.selected
};
}
function mapDispatchToProps(dispatch: IDispatch) {
return {
onSelect(name: string) {
dispatch(mySelectAction(name));
}
};
}
const MyContainer = connect(
mapStateToProps,
mapDispatchToProps
)(MyComponent);
Cela fonctionne, mais il y a un gros problème de dactylographie: les fonctions de mappage (mapStateToProps
et mapDispatchToProps
) ne disposent pas de la protection qu'elles fournissent les bonnes données pour répondre à MyProps
. Ceci est sujet aux erreurs, fautes de frappe et refactoring médiocre.
Je pourrais faire en sorte que les fonctions de mappage renvoient le type MyProps
:
function mapStateToProps(state: MyState): MyProps { }
function mapDispatchToProps(dispatch: IDispatch): MyProps { }
Cependant, cela ne fonctionne que si je rends tous les accessoires MyProp
facultatifs, afin que chaque fonction de mappage ne puisse renvoyer que la partie qui les intéresse. Je ne veux pas rendre les accessoires facultatifs, car ils ne sont pas facultatifs pour le composant de présentation.
Une autre option consiste à diviser les accessoires pour chaque fonction de la carte et à les combiner pour les accessoires composant:
// MyComponent
interface MyStateProps {
name: string;
selected: boolean;
}
interface MyDispatchProps {
onSelect: (name: string) => void;
}
type MyProps = MyStateProps & MyDispatchProps;
class MyComponent extends React.Component<MyProps, {}> { }
// MyContainer
function mapStateToProps(state: MyState): MyStateProps { }
function mapDispatchToProps(dispatch: IDispatch): MyDispatchProps { }
Ok, ça me rapproche de ce que je veux, mais c'est plutôt bruyant et ça me fait écrire mon interface de composant de présentation prop sur la forme d'un conteneur, ce que je n'aime pas.
Et maintenant, un deuxième problème se pose. Et si je veux mettre le conteneur dans un autre composant:
<MyContainer />
Cela donne des erreurs de compilation qui name
, selected
et onSelect
sont tous manquants ... mais cela est intentionnel car le conteneur se connecte à Redux et les fournit. Cela me pousse donc à rendre tous les accessoires de composants facultatifs, mais je n'aime pas cela parce qu'ils ne sont pas vraiment facultatifs.
La situation empire lorsque MyContainer
a certains de ses propres accessoires qu'il souhaite transmettre:
<MyContainer section="somethng" />
Maintenant, ce que j'essaie de faire, c'est d'avoir section
un support requis de MyContainer
mais pas un support de MyComponent
, et name
, selected
, et onSelect
sont des accessoires requis de MyComponent
mais facultatifs ou ne sont pas du tout accessoires de MyContainer
. Je ne sais vraiment pas comment exprimer cela.
Toute indication sur ce serait apprécié!
Vous êtes sur la bonne voie avec votre dernier exemple. Vous devez également définir une interface MyOwnProps
et taper la fonction connect
.
Avec ces saisies: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/react-redux/react-redux.d.ts , vous pouvez faire quelque chose comme ceci
interface MyProps {
section: string;
name: string;
selected: boolean;
onSelect: (name: string) => void;
}
interface MyStateProps {
name: string;
selected: boolean;
}
interface MyDispatchProps {
onSelect: (name: string) => void;
}
interface MyOwnProps {
section: string;
}
class MyComponent extends React.Component<MyProps, {}> { }
function mapStateToProps(state: MyState): MyStateProps { }
function mapDispatchToProps(dispatch: IDispatch): MyDispatchProps { }
const MyContainer = connect<MyStateProps, MyDispatchProps, MyOwnProps>(
mapStateToProps,
mapDispatchToProps
)(MyComponent);
Cela vous permet également de taper ownProps
dans mapStateToProps
et mapDispatchToProps
, par ex.
function mapStateToProps(state: MyState, ownProps: MyOwnProps): MyStateProps
Vous n'avez pas besoin de définir un type d'intersection pour le type MyProps tant que vous définissez les types dispatch, state et ownProps. De cette façon, vous pouvez conserver MyProps à son emplacement et laisser les conteneurs, ou les lieux où le composant est utilisé sans conteneurs, appliquer les accessoires comme il se doit. Je suppose que cela dépend de vous et de votre cas d'utilisation - si vous définissez MyProps = MyStateProps & MyDispatchProps & MyOwnProps
, il est lié à un conteneur spécifique, qui est moins flexible (mais moins détaillé).
En guise de solution, c'est assez bavard, mais je ne pense pas qu'il soit possible de dire à TypeScript que les différents accessoires requis seront assemblés à différents endroits et que connect
les liera.
Aussi, pour ce que cela vaut, je suis généralement allé avec des accessoires optionnels, par souci de simplicité, donc je n'ai pas beaucoup d'expérience à partager sur l'utilisation de cette approche.
Je viens d'utiliser Partial<MyProps>
, où Partial
est un type TypeScript intégré défini comme suit:
type Partial<T> = {
[P in keyof T]?: T[P];
}
Il prend une interface et rend chaque propriété en option.
Voici un exemple d'une paire de composants présentationnelle/prise en compte de Redux que j'ai écrite:
/components/ConnectionPane/ConnectionPane.tsx
export interface IConnectionPaneProps {
connecting: boolean;
connected: boolean;
onConnect: (hostname: string, port: number) => void;
}
interface IState {
hostname: string;
port?: number;
}
export default class ConnectionPane extends React.Component<IConnectionPaneProps, IState> {
...
}
/containers/ConnectionPane/ConnectionPane.ts
import {connect} from 'react-redux';
import {connectionSelectors as selectors} from '../../../state/ducks';
import {connect as connectToTestEcho} from '../../../state/ducks/connection';
import {ConnectionPane, IConnectionPaneProps} from '../../components';
function mapStateToProps (state): Partial<IConnectionPaneProps> {
return {
connecting: selectors.isConnecting(state),
connected: selectors.isConnected(state)
};
}
function mapDispatchToProps (dispatch): Partial<IConnectionPaneProps> {
return {
onConnect: (hostname, port) => dispatch(connectToTestEcho(hostname, port))
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ConnectionPane) as any;
Les accessoires de composant de présentation ne sont pas facultatifs - exactement comme requis pour la présentation sans aucune incidence sur le composant "intelligent" correspondant.
Pendant ce temps, mapStateToProps
et mapDispatchToProps
me permettront d’affecter un sous-ensemble des accessoires de présentation requis dans chaque fonction, tout en signalant comme erreur tous les accessoires non définis dans l’interface des accessoires de présentation.
interface MyStateProps {
name: string;
selected: boolean;
}
interface MyDispatchProps {
onSelect: (name: string) => void;
}
interface MyOwnProps {
section: string;
}
// Intersection Types
type MyProps = MyStateProps & MyDispatchProps & MyOwnProps;
class MyComponent extends React.Component<MyProps, {}> { }
function mapStateToProps(state: MyState): MyStateProps { }
function mapDispatchToProps(dispatch: IDispatch): MyDispatchProps { }
const MyContainer = connect<MyStateProps, MyDispatchProps, MyOwnProps>(
mapStateToProps,
mapDispatchToProps
)(MyComponent);
Vous pouvez utiliser quelque chose appelé Types d'intersection https://www.typescriptlang.org/docs/handbook/advanced-types.html#intersection-types