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

Posted

技术标签:

【中文标题】在 typescript 中为 JSX.Element 添加更多道具【英文标题】:Add more props to JSX.Element in typescript 【发布时间】: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" />
      )
    </>
  );
;

【问题讨论】:

您可能对React Context感兴趣。 【参考方案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

【讨论】:

以上是关于在 typescript 中为 JSX.Element 添加更多道具的主要内容,如果未能解决你的问题,请参考以下文章

如何在 TypeScript 中为对象动态分配属性?

如何在 TypeScript 中为 css 模块设置汇总

在 TypeScript 中为 Firebase Cloud Function 配置 mailgun

我们如何在 TypeScript 中为非全局接口创建扩展方法?

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

你应该如何在 TypeScript 中为 Morgan 创建 Winston 记录器流