使用 React/Relay/Typescript 跨片段重用组件的最佳实践是什么?

问题描述

我正在使用 React、Relay(实验性)和 Typescript 制作一个网络应用程序,但在跨不同片段重用具有相似数据/道具的组件时遇到了一些问题。

假设我有以下 Relay 查询和主要应用组件:

const query = graphql`
  query AppQuery {
    currentUser {
      ...HomePage_currentUser
      ...ProfilePage_currentUser
    }
  }
`;

export default function App(): JSX.Element {
  const appData = useLazyLoadQuery<AppQuery>(query,{});
 
 return (
   <>
    <HomePage currentUser={appData.currentUser}>
    <ProfilePage currentUser={appData.currentUser}>
   </>
 );
}

以及以下页面组件:

interface HomePageProps {
  currentUser: HomePage_currentUser$key | null;
}

const currentUserFragment = graphql`
  fragment HomePage_currentUser on User {
    id
    profileImageUrl
    items {
      id
      name
    }
  }
`;


export default function HomePage(props: HomePageProps): JSX.Element {
  const currentUser = useFragment(currentUserFragment,props.currentUser);

  return (
    <>
      {/* ... */}
      <ProfileIcon user={currentUser} >
      {/* ... */}
    </>
  )
}
interface ProfilePageProps {
  currentUser: ProfilePage_currentUser$key | null;
}

const currentUserFragment = graphql`
  fragment ProfilePage_currentUser on User {
    id
    profileImageUrl
    lastLoggedInTimestamp
  }
`;


export default function ProfilePage(props: ProfilePageProps): JSX.Element {
  const currentUser = useFragment(currentUserFragment,props.currentUser);

  return (
    <>
      {/* ... */}
      <ProfileIcon user={currentUser} >
      {/* ... */}
    </>
  )
}

以及以下 ProfileIcon 组件

interface ProfileIconProps {
  currentUser: ???
}

export default function ProfileIcon(props: ProfileIconProps): JSX.Element {
  return (
    <div>
      <img src={props.currentUser.profileImageUrl} />
    </div>
  )
}

我的主要问题是关于 currentUser 组件中 ProfileIcon 道具的类型。尽管请求的数据非常相似并且显然是为了这个组件兼容,但似乎没有干净的方法可以将组件重用于 ProfilePage_currentUser 类型和 HomePage_currentUser 类型。

除此之外,还有什么推荐的方法来处理这个问题吗?

interface ProfileIconProps {
  currentUser: Omit<ProfilePage_currentUser,' $refType'>
}

解决方法

在 Relay 中手动提取数据并将其作为 prop 传递给组件不是一个好习惯。相反,您应该在 ProfileIcon 组件上有一个片段,让 Relay 负责将相同的数据向下传递到您的组件。

因此您需要在 ProfileIcon 组件中创建一个片段并查询 profileImageUrl,然后将此片段添加到 HomePage_currentUserProfilePage_currentUser 片段中,relay 将为您处理剩下的事情。

interface ProfileIconProps {
  currentUser: ProfileIcon_currentUser$key | null;
}

const currentUserFragment = graphql`
  fragment ProfileIcon_currentUser on User {
    profileImageUrl
  }
`;


export default function ProfileIcon(props: ProfileIconProps): JSX.Element {
  const currentUser = useFragment(currentUserFragment,props.currentUser);

  return (
    <div>
     <img src={props.currentUser.profileImageUrl} />
    </div>
  )
}

在 HomePage 组件中添加 ProfileIcon_currentUser 片段

const currentUserFragment = graphql`
 fragment HomePage_currentUser on User {
    id
    items {
      id
      name
    }
    ...ProfileIcon_currentUser
 }

`;

在 ProfilePage 组件中添加 ProfileIcon_currentUser 片段

 const currentUserFragment = graphql`
  fragment ProfilePage_currentUser on User {
    id
    lastLoggedInTimestamp
    ...ProfileIcon_currentUser
  }
`;

使用这种模式的一个好处是,如果你在其他地方(甚至不在同一个反应节点树层次结构中)对 User 进行了突变,那么假设你 更改 profileImageUrl 然后 ProfileIcon 将自动接收新的 profileImageUrl,而无需您传递 profileImageUrl 的新值。事实上,中继存储将使用变异有效负载上的新值进行更新,一旦存储中有新的更新,它就会将该数据传递给任何使用该数据的组件。