2016-11-18 20 views
7

Mam problem z ustaleniem, jak poprawnie wpisać Redux containers.TypScript walczy z kontenerami Redux

Rozważmy prosty prezentacyjnego składnik, który może wyglądać następująco:

interface MyProps { 
    name: string; 
    selected: boolean; 
    onSelect: (name: string) => void; 
} 
class MyComponent extends React.Component<MyProps, {}> { } 

Z perspektywy tego składnika są wymagane wszystkie rekwizyty.

Teraz chcę napisać pojemnik, który ściąga wszystkie te rekwizyty z państwa:

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); 

To działa, ale nie jest to duży problem, wpisując: funkcje mapowania (mapStateToProps i mapDispatchToProps) posiadają żadnej ochrony, że dostarczają odpowiednich danych do spełnienia MyProps. Jest to skłonne do błędów, literówek i złego refaktoryzacji.

mogłem zrobić funkcje mapowania typu MyProps powrotu:

function mapStateToProps(state: MyState): MyProps { } 

function mapDispatchToProps(dispatch: IDispatch): MyProps { } 

Jednak to nie działa, chyba że zrobię wszystko MyProp rekwizyty opcjonalne, tak, że każda funkcja mapowania może zwrócić tylko część dbają o. Nie chcę, aby rekwizyty były opcjonalne, ponieważ nie są opcjonalne w stosunku do komponentu prezentacji.

Inną opcją jest podzielona rekwizyty dla każdej funkcji mapy i połączyć je do podpór składowych:

// 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, że coraz mnie bliżej do tego, co chcę, ale jego rodzaj głośny i jego tworzenia piszę mój prezentacyjny komponentowy interfejs propowy wokół kształtu kontenera, którego nie lubię.

A teraz pojawia się drugi problem. Co zrobić, jeśli chcę umieścić pojemnik wewnątrz innego składnika:

<MyContainer /> 

Daje skompilować błędy name, selected i onSelect są brakujące ... ale to jest celowe, ponieważ pojemnik jest podłączenie do Redux i dostarczanie tych. Więc to zmusza mnie do tego, by wszystkie komponenty były opcjonalne, ale nie podoba mi się to, ponieważ nie są one opcjonalne.

Robi się gorzej, gdy MyContainer ma jedne z własnych rekwizytów, że chce być przekazywane w:

<MyContainer section="somethng" /> 

Teraz co próbuję zrobić, to mieć section wymaganym rekwizytem MyContainer ale nie rekwizyt z MyComponent i name, selected i onSelect są wymagane podpory MyComponent, ale opcjonalne lub nie rekwizyty w ogóle z MyContainer. Nie jestem w stanie wyrazić tego.

Wszelkie wskazówki na ten temat będą mile widziane!

Odpowiedz

5

Jesteś na dobrej drodze z twoim ostatnim przykładem. Trzeba również zdefiniować interfejs MyOwnProps i wpisać funkcję connect.

Tymi typowania: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/react-redux/react-redux.d.ts, można zrobić coś takiego

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); 

Pozwala także wpisać ownProps w mapStateToProps i mapDispatchToProps, na przykład

function mapStateToProps(state: MyState, ownProps: MyOwnProps): MyStateProps 

Nie trzeba zdefiniować typ przecięcia dla MyProps typ tak długo, jak można zdefiniować wysyłkę, stanu i rodzaju ownProps. W ten sposób możesz przechowywać MyProps w swoim własnym miejscu i pozwolić pojemnikom lub miejscu, w którym komponent jest używany bez pojemników, zastosować rekwizyty tak, jak powinny. Domyślam się, że to zależy od Ciebie i Twojego przypadku użycia - jeśli zdefiniujesz MyProps = MyStateProps & MyDispatchProps & MyOwnProps, jest on powiązany z jednym konkretnym kontenerem, który jest mniej elastyczny (ale mniej szczegółowy).

Jako rozwiązanie jest dość gadatliwe, ale nie sądzę, że istnieje sposób na obejście, mówiąc, że różne rodzaje wymaganych rekwizytów będą montowane w różnych miejscach, a connect będzie je łączył.

Ponadto, na ile to jest możliwe, zwykle korzystam z opcjonalnych rekwizytów, ze względu na prostotę, więc nie mam zbyt wiele doświadczenia, aby podzielić się z tym podejściem.

+0

Dzięki, że to pomaga. Domyślam się, że bez TypeScript wszystko jest opcjonalne. Po prostu podoba mi się to, że rekwizyty części mogą ci powiedzieć, jakie rzeczy są oczekiwane, a jakie opcjonalne. – Aaron

+0

Pytanie: ponieważ to rozwiązanie w zasadzie oznacza, że ​​definiujesz każdą propę dwukrotnie (raz w 'MyProps' i raz w odpowiednim' MyStateProps' lub 'MyDispatchProps'), co się stanie, jeśli zmienisz nazwę lub zmienisz rekwizyt w' MyProps', na przykład "nazwa" do "id" lub "selected: boolean" na "selected: SelectionEnum"? Czy kontroler typu zdaje sobie sprawę, że 'MyOwnProps, MyDispatchProps, MyStateProps' nie spełnia już' MyProps'? – Aaron

+0

Nieco. Bawiłem się z tym od czasu napisania odpowiedzi. Rekwizyty State i Dispatch muszą się zgadzać, ale OwnProps nie muszą się zgadzać. Myślę, że może dlatego, że można używać selfProps tylko w mapStateToProps i mapDispatchToProps i nie używać ich w MyComponent? –

1

wystarczy użyć Partial<MyProps>, gdzie Partial jest wbudowany typ maszynopis zdefiniowany jako:

type Partial<T> = { 
    [P in keyof T]?: T[P]; 
} 

Zajmuje interfejs i sprawia, że ​​każdy obiekt w nim opcjonalne.

Oto przykład z prezentacyjnego/części pary Redux-Aware pisałem:

/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> { 
    ... 
} 

/pojemniki/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; 

Prezentacyjny składnik pr ops są nieobowiązkowe - dokładnie tak, jak wymagają tego prezentacje, bez żadnego wpływu na odpowiedni "inteligentny" komponent.

Tymczasem mapStateToProps i mapDispatchToProps pozwolą mi przypisać podzbiór wymaganych rekwizytów prezentacyjnych w każdej funkcji, podczas gdy oznaczanie jakichkolwiek podpowiedzi nie zdefiniowanych w interfejsie rekwizytów prezentacyjnych jest błędem.

+0

Tak, od czasu wydania TS z obsługą 'Partial' używam tego w niektórych projektach. Jest łatwy w użyciu. Nie jest to tak ścisłe, jak formalne podejście "StateProps", "DispatchProps" i "OwnProps", ponieważ nie zapewnia kompletnej funkcji odwzorowania, tyle że nie zwraca błędnie zaimplementowanej podpory. Bawiłem się przy użyciu 'Pick', aby podważyć pojedynczy interfejs rekwizytów komponentu prezentacji, ale to wciąż jest dość nudne. – Aaron

+2

Uczynienie wszystkiego opcjonalnym nie jest rozwiązaniem, ponieważ pytanie było jednoznaczne. – wegginho

+0

@wegginho OP napisał: "Jednak to nie działa, chyba że zrobię wszystkie podpórki ** MyProp ** opcjonalne, tak, że każda funkcja odwzorowania może zwrócić tylko część, na której im zależy. Nie chcę tworzyć rekwizytów ** [ MyProp] ** opcjonalnie ". Moją sugestią jest * nie *, aby opcja 'MyProp' była opcjonalna lub faktycznie wprowadziła jakąkolwiek zmianę w komponencie prezentacji lub jego interfejsie. Chodzi o owinięcie 'MyProp' w typ, który eksponuje wszystkie jego właściwości jako opcjonalne, tylko dla typowania Redux. Jest to idealne rozwiązanie IMO, ponieważ pozwala każdej funkcji Redux obsługiwać podzbiór rekwizytów, chroniąc przed nieznanymi rekwizytami. – Tagc

1
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); 

Można użyć użyć coś zwane Rodzaje skrzyżowaniu https://www.typescriptlang.org/docs/handbook/advanced-types.html#intersection-types

+0

Dzięki za ten przykład. Jest to w zasadzie kombinacja mojej pierwotnej próby (używając typów skrzyżowań) i akceptowanej odpowiedzi @ radio (używając argumentów typu "connect()"). – Aaron

Powiązane problemy