Как я могу сохранить безопасность типов при использовании компонента React, подключенного к редуктору? - PullRequest
0 голосов
/ 25 октября 2018

Я играл с TypeScript в проекте React / Redux / Redux-Thunk, и я продолжаю сталкиваться с этой проблемой, когда после connect компонента не представляется возможным разумно использовать его без его приведения.поскольку процесс соединения, по-видимому, не способен передать системе типов, что некоторые или все требования к свойствам были выполнены с помощью операции соединения.Например, рассмотрим эти компоненты / типы / и т.д .:

import * as React from 'react';
import {connect} from "react-redux";
import {Action, bindActionCreators, Dispatch} from "redux";
import {ThunkDispatch} from "redux-thunk";

// Our store model
interface Model {
    name: string,
}

// Types for our component's props
interface FooDataProps {
    name: string // Single, required, string property
}

interface FooDispatchProps {
    onClick: React.MouseEventHandler<HTMLButtonElement>, // Single, required, event handler.
}

interface FooProps extends FooDataProps, FooDispatchProps { // Union the two types
}

// Make our first component...
function TrivialComponent(props: FooProps) {
    return (<button onClick={props.onClick}>{props.name}</button>);
}

// Now make a Redux "container" that wires it to the store...
const mapStateToProps = (state: Model): FooDataProps => { return { name: state.name }; };
const mapDispatchToProps = (dispatch: Dispatch): FooDispatchProps => {
    return bindActionCreators({onClick: doStuff}, dispatch);
};

// Wire it up with all the glory of the heavily-genericized `connect`
const ConnectedTrivialComponent = connect<FooDataProps, FooDispatchProps, FooProps, Model>(mapStateToProps, mapDispatchToProps)(TrivialComponent);

// Then let's try to consume it
function ConsumingComponent1() {
    // At this point, I shouldn't need to provide any props to the ConnectedTrivialComponent -- they're 
    // all being provided by the `connect` hookup, but if I try to use the tag like I'm doing here, I 
    // get this error: 
    //
    // Error:(53, 10) TS2322: Type '{}' is not assignable to type 'Readonly<Pick<FooProps, never> & FooProps>'.
    // Property 'name' is missing in type '{}'.
    //
    return (<ConnectedTrivialComponent/>)
}

// If I do something like this:
const ConnectedTrivialComponent2 = ConnectedTrivialComponent as any as React.ComponentClass<{}, {}>;

// Then let's try to consume it
function ConsumingComponent2() {
    // I can do this no problem.
    return (<ConnectedTrivialComponent2/>)
}

// Handler...
const doStuff = (e: React.MouseEvent<HTMLButtonElement>) => (dispatch: ThunkDispatch<Model, void, Action>, getStore: () => Model) => {
    // Do stuff
};

ОК, поэтому, размышляя об этой проблеме, я выдвинул несколько идей:

Идея № 1) Сделайте все реквизиты необязательными. Многие компоненты, которые я видел у сторонних разработчиков, имеют все необязательные, но по моему опыту, если бы все было необязательно, это приводит к большому количеству шаблонных нулевых проверок повсюду и усложняет код.читать.

Идея # 2) Привести к React.ComponentClass<P,S> и создать дополнительные типы для любых свойств , а не , заполненных операцией connect.Приведение работает, очевидно, но теперь у вас есть три набора вещей, которые нужно синхронизировать друг с другом (оригинальные типы Props, списки mapStateToProps и mapDispatchToProps и типы «оставшихся Props»). Этот подход чувствуетподробный, подверженный ошибкам, а также стирающий другую потенциально полезную информацию о типах.

Есть ли лучший способ управления connect ed компонентами с точки зрения типов?

Ответы [ 2 ]

0 голосов
/ 03 ноября 2018

После еще нескольких копаний, я думаю, я понял это.В форме, показанной в вопросе (и обратите внимание, что существует 12 возможных шаблонов вызовов / типов connect, вызванных в его файле определения типа - это только один), есть четыре параметра типа для connect.Похоже, они представляют:

  1. TStateProps - тип, содержащий свойства, которые вы заполняете параметром mapStateToProps до connect.(Это также случайный тип возврата функции mapStateToProps)
  2. TDispatchProps - тип, содержащий свойства, которые вы заполняете параметром mapDispatchToProps до connect.(Это также случайный тип возврата функции mapDispatchToProps)
  3. TOwnProps - тип, содержащий свойства, оставшиеся для установки для new компонента, сгенерированногоconnect вызов (вот где я был в замешательстве.) TOwnProps также тип параметра ownProps для mapStateToProps и mapDispatchToProps.
  4. State - тип корневого каталога модели магазина Redux.

Нет.3, вызывая TOwnProps, немного сбивает с толку, поскольку подразумевает связь с параметром ownProps с функциями mapStateToProps и mapDispatchToProps.У меня никогда не было случая использовать ownProps на практике, поэтому я не сразу понял, что на самом деле есть нечто большее, чем это.Проделав еще несколько копаний, я обнаружил, что connect возвращает:

InferableComponentEnhancerWithProps<TStateProps & TDispatchProps, TOwnProps>

и определение этого:

export interface InferableComponentEnhancerWithProps<TInjectedProps, TNeedsProps> {
    <C extends ComponentType<Matching<TInjectedProps, GetProps<C>>>>(
        component: C
    ): ConnectedComponentClass<C, Omit<GetProps<C>, keyof Shared<TInjectedProps, GetProps<C>>> & TNeedsProps>
}

Видя TStateProps & TDispatchProps, именуемые TInjectedProps и TOwnProps, именуемый TNeedsProps, помогло немного сфокусироваться.TStateProps & TDispatchProps - это свойства, «вставленные» в обернутый компонент с помощью connect, а TOwnProps - это свойства, которые оболочка все еще «нуждается» от потребителей подключенного компонента.

Другое понимание, которое я имел, состояло в том, что то, как у меня это было в вопросе (т. Е. connect<FooDataProps, FooDispatchProps, FooProps, Model>(mapStateToProps, mapDispatchToProps)), не имеет смысла, потому что если бы параметр третьего типа (семантически) должен был представлять «все реквизиты, состояние илидиспетчеризация "система типов могла бы легко достичь этого, если бы & ing FooDataProps и FooDispatchProps, что она получила в качестве параметра # 1 и # 2.Не было бы никакой новой информации, передаваемой с использованием этого третьего параметра типа, как я его использовал.

Ответ Мэтта Маккатчена , хотя и полезен, фокусируется только на роли TOwnProps в отношениипараметр ownProps для функций mapStateToProps и mapDispatchToProps.Он правильно заметил, что я не использую параметр ownProps в этих функциях, и предложил передать {}.Этот ответ кажется правильным для контекста, который он описывает, но он пренебрегает другой ролью TOwnProps как определяющим фактором того, какие реквизиты могут быть приняты компонентом connect ed высшего порядка.

Суммарный вывод о том, что TOwnProps выполняет здесь двойную функцию, не только как тип для параметра ownProps для функций карты, но и также как тип, фиксирующийсвойства, оставшиеся для установки потребителями упакованного / подключенного компонента.

0 голосов
/ 26 октября 2018

Насколько я понимаю, аргумент третьего типа для connect (именуемый TOwnProps в объявлениях) должен быть типом любого реквизита, используемого вашими mapStateToProps и mapDispatchToProps функциями.Поскольку ваши функции mapStateToProps и mapDispatchToProps не используют никаких реквизитов, вы должны установить аргумент этого типа на {}, а не на FooProps, и тогда ошибка исчезнет.(Удаление аргументов явного типа и использование логического вывода даст вам тот же конечный результат.)

...