Я хочу создать собственную таблицу. Опция в ячейке и нижнем колонтитуле функции createColumnHelper должна возвращать правильный ключ/тип объекта вместо любого.
Например, у меня есть вложенный объект в моих ДАННЫХ. Или, если это не объект, он также должен возвращать правильный тип.
Прикрепил скриншоты того, как я вижу в IDE
Помощник.ts
import { ReactElement } from "react";
export type Column<T> = {
header: string | (() => ReactElement);
accessor: (row: T) => any;
cell?:(prop: any) => ReactElement | string;
footer?: (prop: any) => ReactElement | string;
};
export const createColumnHelper = <T>() => {
return {
accessor: (
accessor: keyof T | ((row: T) => any),
column: {
header: string | (() => ReactElement);
cell?: (prop: any) => ReactElement | string;
footer?: (prop: any) => ReactElement | string;
}
) => {
return {
header: column.header,
accessor: (row: T) => (typeof accessor === "function" ? accessor(row) : row[accessor]),
cell: column.cell,
footer: column.footer,
} as Column<T>;
}
};
};
ТаблицаКомпоненты.tsx
import React from "react";
import { createColumnHelper } from "./columnHelper";
type Person = {
firstName: string
lastName: string
age: number
visits: number
status: string
progress: {
ok?: number;
no?: string
}
}
const DATA: Person[] = [
{
firstName: 'tanner',
lastName: 'linsley',
age: 24,
visits: 100,
status: 'In Relationship',
progress: {
ok: 50
},
},
{
firstName: 'tandy',
lastName: 'miller',
age: 40,
visits: 40,
status: 'Single',
progress: {
no: 'bad'
},
},
]
const columnHelper = createColumnHelper<Person>();
const columns = [
columnHelper.accessor(
'firstName', {
header: 'First Name',
cell: val => val,
}),
columnHelper.accessor(
'progress', {
header: 'Progress',
cell: prop => prop.ok,
}),
columnHelper.accessor(row => row, {
header: () => <span>Age</span>,
cell: prop => <span>{prop.age}</span>,
}),
];
const TableComponent: React.FC = () => {
return (
<table>
<thead>
<tr>
{columns.map((column, index) => (
<th key = {index}>
{typeof column.header === "function" ? column.header() : column.header}
</th>
))}
</tr>
</thead>
<tbody>
{DATA.map((row, rowIndex) => (
<tr key = {rowIndex}>
{columns.map((column, colIndex) => (
<td key = {colIndex}>
{column.cell ? column.cell(column.accessor(row)) : column.accessor(row)}
</td>
))}
</tr>
))}
</tbody>
<tfoot>
<tr>
{columns.map((column, index) => (
<td key = {index}>
{column.footer ? column.footer({ column }) : null}
</td>
))}
</tr>
</tfoot>
</table>
);
};
export default TableComponent;
У вас там много any
, и если вы начнете давать ему правильные типы, то TS расстроится из-за чего-то. Вероятно, вам нужно Column<T, V>
, где V
— тип вывода accessor
и ввода cell
/footer
. Разделяя разницу между фактической безопасностью типов и всеми any
, я полагаю, вы можете написать эту ссылку на игровую площадку, но это просто переместит any
в следующую часть вашего кода. Соответствует ли это вашим потребностям? Если да, то я напишу ответ; если нет, то что мне не хватает?
@jcalz, да, похоже, это то, что мне нужно. Тип вывода средства доступа и ввода ячейки/нижнего колонтитула. Спасибо
Поскольку вас волнует тип ввода cell
/footer
и тип вывода accessor
, вам следует рассмотреть возможность добавления общего параметра типа 🔁 в Column
для этого типа:
type Column<T, V> = {
header: string | (() => ReactElement);
accessor: (row: T) => V;
cell?: (prop: V) => ReactElement | string;
footer?: (prop: V) => ReactElement | string;
};
Тогда значение, возвращаемое createColumnHelper<T>()
, должно иметь метод accessor
, который либо принимает значение типа K extends keyof T
и выдает Column<T, T[K]>
, либо принимает значение типа (row: T) => V
для некоторых V
и выдает Column<T, V>
. Это выглядит так:
const createColumnHelper = <T,>() => {
// call signatures
function accessor<K extends keyof T>(
accessor: K, column: Omit<Column<T, T[K]>, "accessor">
): Column<T, T[K]>;
function accessor<V>(
accessor: (row: T) => V, column: Omit<Column<T, V>, "accessor">
): Column<T, V>;
// implementation
function accessor(
accessor: keyof T | ((row: T) => any), column: Omit<Column<T, any>, "accessor">
): Column<T, any> {
return {
header: column.header,
accessor: (row: T) => (typeof accessor === "function" ? accessor(row) : row[accessor]),
cell: column.cell,
footer: column.footer,
};
}
return { accessor };
};
Обратите внимание, что я использую Пропустить, чтобы легко описать Column
, у которого отсутствует свойство accessor
. Это не обязательно, но позволяет сэкономить время на написании {header: ⋯, cell?: ⋯, footer?: ⋯}
. Это устраняет проблему, с которой вы столкнулись:
const columns = [
columnHelper.accessor(
'firstName', {
header: 'First Name',
cell: val => val,
}),
columnHelper.accessor(
'progress', {
header: 'Progress',
cell: prop => String(prop.ok),
}),
columnHelper.accessor(row => row, {
header: () => <span>Age</span>,
cell: prop => <span>{prop.age}</span>,
}),
];
(и обратите внимание, что мне пришлось исправить ошибку, поскольку prop.ok
— это не string
или ReactElement
.)
Это отвечает на заданный вопрос, но если вы это сделаете, у вас возникнут проблемы с column.cell ? column.cell(column.accessor(row)) : column.accessor(row)
и тому подобным. Единственная причина, по которой раньше это работало, заключается в том, что column.cell()
принимает any
. Теперь это не так. И TS не может проверить это как действительное из-за нескольких высказываний column
типа объединения. Это ms/TS#30581 более или менее, и исправления для него выходят за рамки этого вопроса.
Самый простой вариант — потребовать, чтобы columnHelper.accessor(a, o)
входные данные выглядели как Omit<Column<T, T[K]>, "accessor">
или Omit<Column<T, V>, "accessor">
, но возвращали Column<T, any>
:
const createColumnHelper = <T,>() => {
// call signatures
function accessor<K extends keyof T>(
accessor: K, column: Omit<Column<T, T[K]>, "accessor">
): Column<T, any>;
function accessor<V>(
accessor: (row: T) => V, column: Omit<Column<T, V>, "accessor">
): Column<T, any>;
// implementation
function accessor(
accessor: keyof T | ((row: T) => any), column: Omit<Column<T, any>, "accessor">
): Column<T, any> {
return {
header: column.header,
accessor: (row: T) => (typeof accessor === "function" ? accessor(row) : row[accessor]),
cell: column.cell,
footer: column.footer,
};
}
return { accessor };
};
Это облегчит сопоставление, но проблема any
в выводе все еще сохраняется:
columns[0].cell?.(1) // no error
Вероятно, это должно привести к ошибке «number
не может быть присвоено {ok?: number, no?: string}
». Но это не так. Ну ладно, вы не спрашивали об этом, и исправление этого потребовало бы еще большего рефакторинга.
Детская площадка, ссылка на код
Спасибо! Можно ли везде убрать какие-то типы? если в Person будут все строки? { ok?: string, no?: string }
Проблема any
не имеет ничего общего со строками. Это связано с column.cell(column.accessor(row))
и ms/TS#30581. Чтобы справиться с этим, потребуется целый ряд рефакторингов, но здесь это выходит за рамки.
Но вы используете
type Column::cell: (prop: any) => •••
в объявлении. Вы хотите напечатать его по-другому, поэтому придайте ему тот тип, который вам нужен. Если я не дошел до вашей точки зрения, я расширю ее.