简体   繁体   English

在 typescript 中为 JSX.Element 添加更多道具

[英]Add more props to JSX.Element in typescript

I'm using TypeScript with React and I want to pass a userId prop to every component rendered.我将 TypeScript 与 React 一起使用,并且我想将 userId 道具传递给渲染的每个组件。 I don't want to add userId to props for every single component and can't figure out how to make every React component have the prop.我不想将userId添加到每个组件的道具中,并且无法弄清楚如何使每个 React 组件都具有道具。

To make it all even harder, right now it's giving me a TS error JSX element type 'Component' does not have any construct or call signatures.为了让这一切变得更加困难,现在它给了我一个 TS 错误JSX element type 'Component' does not have any construct or call signatures.

How would I make this all work?我将如何使这一切正常工作?


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

Well the other way you can do is using Context API.那么您可以做的另一种方法是使用上下文 API。 Have a look at https://reactjs.org/docs/context.html#when-to-use-context for more understanding查看https://reactjs.org/docs/context.html#when-to-use-context以获得更多理解

What I would do is I would create a custom type that would contain the user id and then use this type to create react context.我要做的是创建一个包含用户 ID 的自定义类型,然后使用该类型创建反应上下文。

Now next step would be use this context and create a provider and then render necessary components inside this context provider.现在下一步将使用此上下文并创建一个提供程序,然后在此上下文提供程序中呈现必要的组件。 You can then consume this context in any of the child component that is at any deep level nested in this custom created context and you can get the user id.然后,您可以在嵌套在此自定义创建的上下文中的任何深层的任何子组件中使用此上下文,并且您可以获得用户 ID。

For eg.例如。

Create custom context type and react context:创建自定义上下文类型和反应上下文:

export type MyContextType = {
    userId: number
}

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

Now use provider and pass the initial value in your main/root file现在使用提供程序并在您的主/根文件中传递初始值

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>

and then in any of your nested component you can get this context and the value using:然后在您的任何嵌套组件中,您可以使用以下方法获取此上下文和值:

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

Component vs. Element组件与元素

There is a difference between passing the component itself as the children prop:将组件本身作为children属性传递是有区别的:

<PrivateRoute>
  {MyComponent}
</PrivateRoute>

and passing an instance of the component:并传递组件的实例:

<PrivateRoute>
  <MyComponent/>
</PrivateRoute>

The type children: JSX.Element applies to the second case and that is why you get the error: children: JSX.Element类型适用于第二种情况,这就是您收到错误的原因:

JSX element type 'Component' does not have any construct or call signatures. JSX 元素类型“组件”没有任何构造或调用签名。

When you have already called the component through JSX like <MyComponent/> then you just get a JSX.Element which is not callable.当您已经像<MyComponent/>这样通过 JSX 调用了组件时,您只会得到一个不可调用的JSX.Element


Typing the Component键入组件

You want to be passing a callable component.你想传递一个可调用的组件。 If this is react-router-dom I would recommend using the component prop instead of the children prop since they behave differently .如果这是react-router-dom我建议使用component道具而不是children道具,因为它们的行为不同

We want to say that our component can take the standard RouteComponentProps and also a user prop.我们想说我们的component可以采用标准的RouteComponentPropsuser道具。

The PrivateRoute itself should take all of the RouteProps like to , exact , etc. But we will require a component prop with our modified type. PrivateRoute本身应该采用所有的RouteProps ,例如toexact等。但是我们需要一个带有我们修改过的类型的component prop。

In order to call the component with our extra prop, we could use an inline render function render={props => <Component {...props} user={user} />} .为了使用额外的 prop 调用component ,我们可以使用内联render function render={props => <Component {...props} user={user} />} We need to destructure it with an uppercase name in order to call it.为了调用它,我们需要用大写名称对其进行解构。 We could also move the authentication logic into a higher-order component and use component={withUser(component)} .我们还可以将身份验证逻辑移动到更高阶的组件中并使用component={withUser(component)}


Other Issues其他问题

We are going to be including this PrivateRoute alongside other Route components.我们将把这个PrivateRoute与其他Route组件一起包括在内。 So we want to render the Redirect only when we are actually on this Route .所以我们只想在我们实际在这个Route上时才渲染Redirect In other words, it should be a replacement of the component rather than a replacement of the Route .换句话说,它应该是对component的替换,而不是对Route的替换。 Otherwise traffic to other non-private routes will get inadvertently redirected.否则到其他非私有路由的流量会被无意中重定向。

You probably don't want to Redirect to the login page until after loading has finished.loading完成之前,您可能不想Redirect到登录页面。 You can render a loading spinner or nothing while waiting for completion.您可以在等待完成时渲染加载微调器或什么都不渲染。

I'm not sure why there is so much checking and assertion on user .我不确定为什么对user有这么多的检查和断言。 You should be able to do const user = data?.me;你应该能够做到const user = data?.me; and get either a PartialUser or undefined .并获取PartialUserundefined


Code代码

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 Typescript 游乐场链接

I'd do it like this:我会这样做:

types.ts

export interface Propys{
  userId:string;
}

MyComponent.tsx

import { Propys } from "./types";

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 JSX.Element 作为接口 TS 中的道具 - JSX.Element as props in interface TS 反应 typescript 句柄 JSX.Element 或字符串 - React typescript handle JSX.Element or string 声明后向 JSX.Element 添加属性 - Add attribuites to JSX.Element after declaration "类型 &#39;({ 文章 }: Props) =&gt; JSX.Element&#39; 不可分配给类型 &#39;NextPage&lt;{}, {}&gt;&#39;" - Type '({ articles }: Props) => JSX.Element' is not assignable to type 'NextPage<{}, {}>' 类型 &#39;(props: RouteProps) =&gt; JSX.Element&#39; 不可分配给类型 &#39;PropsWithChildren<RouteProps> &#39; - Type '(props: RouteProps) => JSX.Element' is not assignable to type 'PropsWithChildren<RouteProps>' 反应 typescript 得到错误为 `Type '() =&gt; JSX.Element | 未定义'` - React typescript getting error as `Type '() => JSX.Element | undefined'` 在Typescript中,有什么方法可以对传入的JSX.Element进行类型检查? - In Typescript, is there any way to typecheck passed-in JSX.Element's? 将 useState 与 JSX.Element 反应 - react useState with JSX.Element '(props: ITableProps) =&gt; JSX.Element' 类型的参数不能分配给类型的参数...... - React HOC - Argument of type '(props: ITableProps) => JSX.Element' is not assignable to parameter of type … - React HOC 类型 &#39;JSX.Element&#39; 不可分配给类型 &#39;Element&#39; - Type 'JSX.Element' is not assignable to type 'Element'
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM