【问题标题】:Add more props to JSX.Element in typescript在 typescript 中为 JSX.Element 添加更多道具
【发布时间】:2021-06-30 19:22:15
【问题描述】:

我正在使用带有 React 的 TypeScript,我想将一个 userId 属性传递给每个渲染的组件。 我不想将userId 添加到每个组件的 props 中,并且无法弄清楚如何让每个 React 组件都具有该 prop。

为了让这一切变得更加困难,现在它给了我一个 TS 错误JSX element type 'Component' does not have any construct or call signatures.

我将如何使这一切正常工作?


type Props = {
  children: JSX.Element;

  // I have tried many different things, but nothing worked...
  // React.ComponentType<{ user: PartialUser }>;
  
};

type PartialUser = {
  id: number;
  username: string;
  email: string;
};

const PrivateRoute = ({ children, ...rest }: Props) => {
  const { data, error, loading } = useMeQuery();

  let me;
  let user;
  if (data?.me) {
    me = data!.me!;

    user = {
      id: me.id,
      username: me.username,
      email: me.email,
    };
  }

  if (error) console.error(error);
  const Component = children;
  return (
    <>
      {!loading && user ? (
        <Route {...rest}>
          <Component user={user} />
        </Route>
      ) : (
        <Redirect to="/login" />
      )}
    </>
  );
};

【问题讨论】:

标签: reactjs typescript react-router react-router-dom


【解决方案1】:

我会这样做:

types.ts

export interface Propys{
  userId:string;
}

MyComponent.tsx

import { Propys } from "./types";

const MyComponent:React.FC<Propys> = ({userId}) => {
  return <>userId</>;
}

【讨论】:

  • 当然这是一种方法,但这正是我不想做的事情。这意味着我必须将这些道具添加到我创建并希望使用 userId 的每个组件中。
【解决方案2】:

另外一种方法是使用 Context API。看看https://reactjs.org/docs/context.html#when-to-use-context了解更多

我要做的是创建一个包含用户 ID 的自定义类型,然后使用该类型创建反应上下文。

现在下一步将使用此上下文并创建一个提供程序,然后在此上下文提供程序中呈现必要的组件。然后,您可以在嵌套在此自定义创建的上下文中的任何深层的任何子组件中使用此上下文,并且您可以获得用户 ID。

例如。

创建自定义上下文类型和反应上下文:

export type MyContextType = {
    userId: number
}

export const MyContext = React.createContext<MyContextType>(undefined!);

现在使用提供者并在你的主/根文件中传递初始值

const context: MyContextType = {
     userId: 1 (I am assuming you would get this from API response or local storage)
};
<MyContext.Provider value={context}>
    ...nested components
    <MyComponent />
</MyContext.Provider>

然后在您的任何嵌套组件中,您可以使用以下方法获取此上下文和值:

class MyComponent extends React.Component<MyComponentProps, State> {
    static contextType = MyContext;
    context!: React.ContextType<typeof MyContext>;

    render() {
       console.log(this.context.userId); // You should be able to see 1 being printed in the console.
    }
}

【讨论】:

    【解决方案3】:

    组件与元素

    将组件本身作为children prop 传递是有区别的:

    <PrivateRoute>
      {MyComponent}
    </PrivateRoute>
    

    并传递组件的实例:

    <PrivateRoute>
      <MyComponent/>
    </PrivateRoute>
    

    children: JSX.Element 类型适用于第二种情况,这就是您收到错误的原因:

    JSX 元素类型“组件”没有任何构造或调用签名。

    如果您已经通过 JSX 调用了组件,例如 &lt;MyComponent/&gt;,那么您只会得到一个不可调用的 JSX.Element


    输入组件

    你想要传递一个可调用的组件。如果这是react-router-dom,我建议使用component 属性而不是children 属性,因为它们是behave differently

    我们想说我们的component 可以采用标准的RouteComponentPropsuser 属性。

    PrivateRoute 本身应该包含所有的 RouteProps,例如 toexact 等。但我们需要一个带有修改后类型的 component 属性。

    为了使用我们的额外属性调用component,我们可以使用内联render 函数render={props =&gt; &lt;Component {...props} user={user} /&gt;}。为了调用它,我们需要用大写名称对其进行解构。我们还可以将身份验证逻辑移到更高阶的组件中并使用component={withUser(component)}


    其他问题

    我们将把这个PrivateRoute 与其他Route 组件一起包括在内。所以我们只想在我们实际在这个Route 上时才渲染Redirect。换句话说,它应该是component 的替代品,而不是Route 的替代品。否则到其他非私有路由的流量会被无意中重定向。

    loading 完成之前,您可能不希望Redirect 进入登录页面。您可以在等待完成时渲染加载微调器或什么都不渲染。

    我不确定为什么对user 有如此多的检查和断言。您应该可以通过const user = data?.me; 获得PartialUserundefined


    代码

    import React from "react";
    import { Redirect, Route, RouteComponentProps, RouteProps, Switch } from "react-router-dom";
    
    type PartialUser = {
        id: number;
        username: string;
        email: string;
    };
    
    declare function useMeQuery(): { error: any; loading: boolean; data?: { me: PartialUser } }
    
    type ComponentProps = RouteComponentProps & {
        user: PartialUser;
    }
    
    type Props = RouteProps & {
        component: React.ComponentType<ComponentProps>;
        // disallow other methods of setting the render component
        render?: never;
        children?: never;
    };
    
    const PrivateRoute = ({ component: Component, ...rest }: Props) => {
      const { data, error, loading } = useMeQuery();
    
      const user = data?.me;
    
      return (
        <Route
          {...rest}
          render={(props) => {
            if (user) {
              return <Component {...props} user={user} />;
            } else if (loading) {
              return null; // don't redirect until complete
            } else {
              return <Redirect to="/login" />;
            }
          }}
        />
      );
    };
    
    const MustHaveUser = ({user}: {user: PartialUser}) => {
        return <div>Username is {user.username}</div>
    }
    
    export const Test = () => (
        <Switch>
            <Route path="/home" render={() => <div>Home</div>} />
            <PrivateRoute path="/dashboard" component={MustHaveUser} />
        </Switch>
    )
    

    Typescript Playground Link

    【讨论】:

      猜你喜欢
      • 2019-10-18
      • 2021-10-30
      • 2020-02-01
      • 2022-01-07
      • 1970-01-01
      • 2021-03-17
      • 1970-01-01
      • 2021-01-24
      • 2021-04-01
      相关资源
      最近更新 更多