【问题标题】:How to pass only one store type to a component (when the store is a union type)如何仅将一种商店类型传递给组件(当商店是联合类型时)
【发布时间】:2019-03-06 23:13:58
【问题描述】:

我正在使用带有 React/Redux 的 Typescript。

网站访问者可以处于两种状态之一,LoggedInLoggedOut。我已经相应地构建了我的状态:

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

interface LoggedOut {
    isLoading: boolean,
    lastAttempFailed: boolean
}

type Store = LoggedIn | LoggedOut

来自 Haskell & Elm,感觉很自然。理论上,实现不可能访问undefinednull 数据,因为它们只能访问与组件相关的状态(即一旦用户登录,组件就无法访问LoggedOut.isLoading )。

如何将其与mapStateToProps 集成?我有一个 Provider 组件供应我的商店。我希望某些组件只接受 LoggedInLoggedOut 实例 - 而不是整个商店。

理想情况下,这将由父组件进行类型检查和传递,例如:

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"/>;
        }
    }
}

但这很hacky,通过道具传递商店感觉不习惯。 (另外:这也使得在react-router 中使用路由参数变得非常困难)。

对于这个问题有没有好的、类型安全的解决方案?干杯

【问题讨论】:

  • 您能解释一下您认为您的解决方案有什么不妥之处吗?是您将store 转换为LoggedIn 吗?如果您正确设置LoggedInLoggedOut 的类型,则不会发生这种情况。此外,您说您不想传入整个存储区——但整个存储区是 LoggedIn/LoggedOut 的整个对象,所以这是不可避免的。你能澄清一下你的意思吗?
  • 基本上,在 Elm/Haskell 中,我可以保证我的主要状态将处于某种配置中,这样就不需要很多“如果状态是 x 则 y,否则为 z”。我正在尝试在 Typescript 中实现相同的保证,以避免不断的 switch 语句。
  • TypeScript 可以保证 store 是 valid,但这并不能消除代码中的任何条件,因为您需要根据它们的不同来处理它们状态。
  • 这就是我想要解决的问题。在 Elm 中,我可以对联合类型进行模式匹配并相应地呈现不同的路由。然后将特定类型(例如LoggedInLoggedOut)传递给组件,而不是整个联合。我怎样才能在 Typescript 中巧妙地做到这一点,而不通过道具(很多人不鼓励)传递商店?也许我需要与Context 做点什么?我以前没用过。

标签: reactjs typescript redux type-safety


【解决方案1】:

以下应该正确键入检查。不幸的是,我认为react-reduxconnect 实用程序的类型定义不能正确支持在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>;

【讨论】:

    猜你喜欢
    • 2020-09-01
    • 1970-01-01
    • 2022-10-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-03
    • 1970-01-01
    • 2021-09-27
    • 1970-01-01
    相关资源
    最近更新 更多