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

React Aria Table上下文值类型推断错误,如何正确处理?

问题描述

使用React Aria Components的<Table>组件结合TableContext实现表格行全选/取消全选功能时,访问tableContext的selectedKeys和onSelectionChange出现TypeScript类型错误:

Property 'selectedKeys' does not exist on type 'WithRef<TableProps, HTMLTableElement> | SlottedValue<WithRef<TableProps, HTMLTableElement>>'
Property 'selectedKeys' does not exist on type 'SlottedValue<WithRef<TableProps, HTMLTableElement>>'

相关代码示例:

import { useContext } from "react";
import {
  TableContext,
  Table,
  TableHeader,
  TableBody,
  Row,
  Cell,
  Column,
} from "react-aria-components";
import { Checkbox } from "./Checkbox";

type User = {
  firstName: string;
  lastName: string;
  id: number;
};

export default function App() {
  const users: User[] = [
    { firstName: "John", lastName: "Doe", id: 1 },
    { firstName: "Jane", lastName: "Doe", id: 2 },
    { firstName: "Joe", lastName: "Doe", id: 3 },
  ];
  const tableContext = useContext(TableContext);
  const selectedKeysCount = tableContext?.selectedKeys.size || 0;

  const handleSelectionChange = () => {
    if (selectedKeysCount === users.length) {
      tableContext?.onSelectionChange(new Set());
      return;
    }

    const newSelectedKeys = new Set();
    users.forEach((user) => newSelectedKeys.add(user.id));
    tableContext?.onSelectionChange(newSelectedKeys);
  };

  return (
    <>
      <Checkbox
        slot="selection"
        isIndeterminate={
          selectedKeysCount > 0 && selectedKeysCount < users.length
        }
        isSelected={selectedKeysCount > 0 || false}
        onChange={handleSelectionChange}
      >
        Select all
      </Checkbox>

      <Table aria-label="Users" selectionMode="multiple">
        <TableHeader>
          <Column isRowHeader />
          <Column>First Name</Column>
          <Column>Last Name</Column>
        </TableHeader>
        <TableBody items={users}>
          {(item) => (
            <Row>
              <Cell>
                <Checkbox slot="selection" />
              </Cell>
              <Cell>{item.firstName}</Cell>
              <Cell>{item.lastName}</Cell>
            </Row>
          )}
        </TableBody>
      </Table>
    </>
  );
}

当前临时通过@ts-expect-error忽略错误,但希望了解类型推断失败的原因及正确的类型声明方式。

类型错误原因分析
  1. 上下文类型范围过宽:TableContext的默认类型包含了用于插槽处理的SlottedValue<WithRef<TableProps, HTMLTableElement>>,但只有当表格启用选择功能(设置selectionMode="multiple"或"single")时,上下文才会包含selectedKeys和onSelectionChange属性。TypeScript无法自动推断当前上下文处于已启用选择的表格环境中。
  2. 组件层级问题:useContext(TableContext)调用在<Table>组件外部,此时上下文未被<Table>实例化注入选择相关属性,TypeScript无法确认上下文的具体类型。
正确解决方案

方案1:将全选组件移入<Table>内部并窄化类型

把全选Checkbox放到<TableHeader>的列中,同时通过类型断言明确上下文类型:

import { useContext } from "react";
import {
  TableContext,
  Table,
  TableHeader,
  TableBody,
  Row,
  Cell,
  Column,
  TableProps,
  WithRef,
} from "react-aria-components";
import { Checkbox } from "./Checkbox";

type User = {
  firstName: string;
  lastName: string;
  id: number;
};

// 定义启用多选的表格上下文类型
type TableSelectionContext = WithRef<TableProps<User["id"]>, HTMLTableElement> & {
  selectedKeys: Set<User["id"]>;
  onSelectionChange: (keys: Set<User["id"]>) => void;
};

export default function App() {
  const users: User[] = [
    { firstName: "John", lastName: "Doe", id: 1 },
    { firstName: "Jane", lastName: "Doe", id: 2 },
    { firstName: "Joe", lastName: "Doe", id: 3 },
  ];

  return (
    <Table aria-label="Users" selectionMode="multiple">
      <TableHeader>
        <Column>
          {() => {
            const tableContext = useContext(TableContext) as TableSelectionContext;
            const selectedKeysCount = tableContext.selectedKeys.size;

            const handleSelectionChange = () => {
              tableContext.onSelectionChange(
                selectedKeysCount === users.length 
                  ? new Set() 
                  : new Set(users.map(u => u.id))
              );
            };

            return (
              <Checkbox
                slot="selection"
                isIndeterminate={selectedKeysCount > 0 && selectedKeysCount < users.length}
                isSelected={selectedKeysCount === users.length}
                onChange={handleSelectionChange}
              >
                Select all
              </Checkbox>
            );
          }}
        </Column>
        <Column>First Name</Column>
        <Column>Last Name</Column>
      </TableHeader>
      <TableBody items={users}>
        {(item) => (
          <Row>
            <Cell>
              <Checkbox slot="selection" />
            </Cell>
            <Cell>{item.firstName}</Cell>
            <Cell>{item.lastName}</Cell>
          </Row>
        )}
      </TableBody>
    </Table>
  );
}

方案2:使用useTableContext钩子(React Aria v1.0+)

React Aria Components v1.0及以上版本提供了useTableContext钩子,会自动根据表格配置推断类型:

// 替换useContext为useTableContext
import { useTableContext } from "react-aria-components";

// 在Table内部组件中使用
const tableContext = useTableContext();
// 此时TypeScript会自动识别selectedKeys和onSelectionChange属性

关键说明

  • 确保上下文访问在<Table>内部:只有在<Table>的子组件中访问上下文,TypeScript才能准确推断类型,因为此时上下文已被<Table>实例化并注入选择相关属性。
  • 类型断言需谨慎:如果必须在外部访问上下文,需通过类型断言明确指定类型,但需确保外部组件始终包裹在<Table>中,避免运行时错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 12:43:27