You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Render Prop时如何让复合组件自动传递泛型类型?

复合组件中Render Prop的泛型自动推断方案

我正在开发一个Compound Component模式的List组件,希望实现Render Prop中参数类型的自动推断,无需手动指定泛型。当前组件代码如下:

interface ListContextValue<T> {
  rows: T[];
}

const createListContext = once(<T>() =>
  createContext<ListContextValue<T> | undefined>(undefined)
);

const useListContext = <T>() => {
  const context = useContext(createListContext<T>());
  if (!context) {
    throw new Error(
      "useListContext must be used within an ListContext.Provider"
    );
  }
  return context;
};

interface ListProps<T> {
  rows: T[];
  children?: React.ReactNode;
}

export const List = <T,>({ rows, children }: ListProps<T>) => {
  const ListContext = createListContext<T>();

  return (
    <ListContext.Provider value={{ rows }}>
      {children}
    </ListContext.Provider>
  );
};

export interface ListContainerProps<T> extends Omit<SlotProps, "children"> {
  children?: (props: { row: T }) => React.ReactNode;
}

export const ListContainer = <T,>({ children }: ListContainerProps<T>) => {
  const { rows } = useListContext<T>();

  return (
    <div>
      {rows.map((row) => {
        return children ? children({ row }) : null;
      })}
    </div>
  );
};

使用组件时,Render Prop函数里的row类型无法自动推断,默认是unknown:

export default function UserList() {
  return (
    <List rows={rows}>
      <ListContainer>
        {({ row }) => (
          <div>
            {/* row is unknown type */}
            {row}
          </div>
        )}
      </ListContainer>
    </List>
  );
}

手动指定泛型虽然可行,但不够优雅:

export default function UserList() {
  return (
    <List<User[]> rows={rows}>
      <ListContainer<User>>
        {({ row }) => (
          <div>
            {row}
          </div>
        )}
      </ListContainer>
    </List>
  );
}

解决方案

核心问题在于List组件的children类型是React.ReactNode,无法携带泛型信息传递给子组件ListContainer。我们需要让List的children成为一个接收泛型参数的函数,让TypeScript能够自动推断并传递类型。

方案一:通过函数子组件透传上下文类型

修改List的children为函数类型,让TypeScript从rows的类型推断出泛型,并传递给子组件:

import React, { createContext, useContext } from 'react';
import { once } from 'your-utils-package';

interface ListContextValue<T> {
  rows: T[];
}

const createListContext = once(<T>() =>
  createContext<ListContextValue<T> | undefined>(undefined)
);

const useListContext = <T>() => {
  const context = useContext(createListContext<T>());
  if (!context) {
    throw new Error("useListContext must be used within an ListContext.Provider");
  }
  return context;
};

// 修改ListProps,children为无参函数
interface ListProps<T> {
  rows: T[];
  children: () => React.ReactNode;
}

export const List = <T,>({ rows, children }: ListProps<T>) => {
  const ListContext = createListContext<T>();

  return (
    <ListContext.Provider value={{ rows }}>
      {children()}
    </ListContext.Provider>
  );
};

// ListContainer不再手动指定泛型,从useListContext推断
export interface ListContainerProps<T> extends Omit<SlotProps, "children"> {
  children?: (props: { row: T }) => React.ReactNode;
}

export const ListContainer = <T,>({ children }: ListContainerProps<T>) => {
  const { rows } = useListContext<T>();

  return (
    <div>
      {rows.map((row, index) => (
        <React.Fragment key={index}>
          {children?.(row)}
        </React.Fragment>
      ))}
    </div>
  );
};

使用示例

此时row类型会自动推断,无需手动指定泛型:

export default function UserList() {
  const rows: User[] = [{ id: 1, name: "张三" }];
  
  return (
    <List rows={rows}>
      {() => (
        <ListContainer>
          {({ row }) => (
            <div>
              {/* row自动推断为User类型 */}
              {row.name}
            </div>
          )}
        </ListContainer>
      )}
    </List>
  );
}

方案二:通过类型标记传递泛型

另一种更直观的方式是给List的children传递一个类型标记参数,明确告知子组件泛型类型:

// 调整ListProps,让children接收类型标记参数
interface ListProps<T> {
  rows: T[];
  children: (props: { __type: T }) => React.ReactNode;
}

export const List = <T,>({ rows, children }: ListProps<T>) => {
  const ListContext = createListContext<T>();

  return (
    <ListContext.Provider value={{ rows }}>
      {/* 传递类型标记,仅用于类型推断,实际值无意义 */}
      {children({ __type: {} as T })}
    </ListContext.Provider>
  );
};

使用示例

export default function UserList() {
  const rows: User[] = [{ id: 1, name: "张三" }];
  
  return (
    <List rows={rows}>
      {({ __type }) => (
        <ListContainer>
          {/* 通过typeof __type自动推断row类型 */}
          {({ row }: { row: typeof __type }) => (
            <div>{row.name}</div>
          )}
        </ListContainer>
      )}
    </List>
  );
}

核心原理

两种方案都是通过将List的children从静态的ReactNode改为动态的泛型函数,让TypeScript能够在组件树中追踪泛型类型信息,从而实现row类型的自动推断,避免手动指定泛型的繁琐。

内容的提问来源于stack exchange,提问作者hackid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 03:27:32