Как передать компоненту только один тип магазина (если это тип объединения) - PullRequest
0 голосов
/ 07 марта 2019

Я использую Typescript с React / Redux.

Посетитель сайта может находиться в одном из двух состояний: LoggedIn или LoggedOut.Я структурировал свое состояние соответственно:

interface LoggedIn {
    token: string,
    user: Data.User
}

interface LoggedOut {
    isLoading: boolean,
    lastAttempFailed: boolean
}

type Store = LoggedIn | LoggedOut

Исходя из Haskell & Elm, это кажется естественным.Теоретически, реализациям было бы невозможно получить доступ к данным undefined или null, поскольку они могли бы получить доступ только к состоянию, относящемуся к компонентам (т. Е. После входа пользователя компонент не может получить доступ к LoggedOut.isLoading).

Как я могу интегрировать это с mapStateToProps?У меня есть Provider компонент, поставляющий мой магазин.Я хочу, чтобы определенные компоненты принимали только экземпляр LoggedIn или LoggedOut, а не весь магазин.

В идеале это проверяется типом и передается родительским компонентом, например:

class PrivateRouteComponent extends React.Component<OwnProps & ConnectedState, any> {
    render() {
        const { store, component, ...props } = this.props;
        const Component = component;
        switch (store.type) {
            case "LOGGED_IN":
                return <Route render={() => <Component store={store as Store.LoggedIn} {...props}/>}/>;
            case "LOGGED_OUT":
                return <Redirect to="/login"/>;
        }
    }
}

Но это глупо, и кажется, что пропускать магазин через реквизит не имеет смысла.(Помимо: это также очень затрудняет использование параметров маршрута в react-router).

Есть ли хорошее, безопасное для типов решение этой проблемы?Приветствия

1 Ответ

0 голосов
/ 07 марта 2019

Следующее должно правильно проверить тип. К сожалению, я считаю, что определения типов утилиты react-redux connect не поддерживают правильное отображение типов объединения в mapStateToProps.

.

Это все еще имеет условный рендеринг, но я не вижу, чем это отличается от использования сопоставления с образцом в Elm.

/** represents the store when logged out */
interface LoggedOutStore {
  type: 'LOGGED_OUT';
  login: LoggedOut;
}

/** represents the store when logged in */
interface LoggedInStore {
  type: 'LOGGED_IN';
  login: LoggedIn;
}

type LogState = LoggedInStore | LoggedOutStore; // the part of the store with login state
type AppState = LogState & OtherStateFields; // the actual redux store

// dummy components w/ typing
const LoggedInComponent = ({ login }: { login: LoggedIn }) => <></>;
const LoggedOutComponent = ({ logout }: { logout: LoggedOut }) => <></>;

function LogStateComponent(props: LogState) {
  switch (props.type) {
    case 'LOGGED_IN':
      return <LoggedInComponent login={props.login} />;
    case 'LOGGED_OUT':
      return <LoggedOutComponent logout={props.login} />;
  }
}
// Pick<LogState, never> === {}
type ConnectedLogStateComponentType = ConnectedComponentClass<
  typeof LogStateComponent,
  Pick<LogState, never>>;

// this should work in theory; but unfortunately, it needs casting
const ConnectedLogStateComponent = connect((s: AppState): object => s)
  (LogStateComponent) as any as ConnectedLogStateComponentType;

const initState: AppState = {
  login: { isLoading: false, lastAttemptFailed: false },
  type: 'LOGGED_OUT',
};

export const App = () => <Provider store={createStore((s = initState) => s)}>
  <ConnectedLogStateComponent />
</Provider>;
...