TypeScript обходной путь для реквизита отдыха в реактиве
Обновлено для TypeScript 2.1
TypeScript 2.1 теперь поддерживает распространение/отдых объектов, поэтому обходных путей больше не требуется!
Оригинальный вопрос
TypeScript поддерживает атрибуты распространения JSX, которые обычно используются в React для передачи атрибутов HTML из компонента в визуализированный элемент HTML:
interface LinkProps extends React.HTMLAttributes {
textToDisplay: string;
}
class Link extends React.Component<LinkProps, {}> {
public render():JSX.Element {
return (
<a {...this.props}>{this.props.textToDisplay}</a>
);
}
}
<Link textToDisplay="Search" href="#" onclick="location.href='http://google.com'; return false;" />
Однако React ввел предупреждение , если вы передаете какой-либо неизвестный реквизит элементу HTML. Приведенный выше пример выдаст предупреждение времени выполнения React о том, что textToDisplay
является неизвестной опорой <a>
. Предлагаемое решение для случая, подобного этому, состоит в том, чтобы использовать свойства покоя объекта для извлечения ваших пользовательских реквизитов и использовать остальное для атрибутов распространения JSX:
const {textToDisplay, ...htmlProps} = this.props;
return (
<a {...htmlProps}>{textToDisplay}</a>
);
Но TypeScript пока не поддерживает этот синтаксис. Я знаю, что, надеюсь, когда-нибудь мы сможем сделать это в TypeScript. (Обновление: TS 2.1 теперь поддерживает распространение/отдых объектов! Почему ты все еще читаешь это?) А пока какие обходные пути есть? Я ищу решение, которое не ставит под угрозу безопасность типов, и нахожу его на удивление трудным. Например, я мог бы сделать это:
const customProps = ["textDoDisplay", "otherCustomProp", "etc"];
const htmlProps:HTMLAttributes = Object.assign({}, this.props);
customProps.forEach(prop => delete htmlProps[prop]);
Но для этого необходимо использовать имена строковых свойств, которые не проверяются на соответствие фактическим реквизитам и поэтому подвержены опечаткам и плохой поддержке IDE. Есть ли лучший способ сделать это?
Ответы
Ответ 1
Вероятно, вам не удастся создать новый объект с подмножеством свойств this.props
, но вы можете сделать это с безопасностью типа.
Например:
interface LinkProps {
textToDisplay: string;
}
const LinkPropsKeys: LinkProps = { textToDisplay: "" };
class Link extends React.Component<LinkProps & React.HTMLAttributes, {}> {
public render(): JSX.Element {
return (
<a { ...this.getHtmlProps() }>{ this.props.textToDisplay }</a>
);
}
private getHtmlProps(): React.HTMLAttributes {
let htmlProps = {} as React.HTMLAttributes;
for (let key in this.props) {
if (!(LinkPropsKeys as any)[key]) {
htmlProps[key] = this.props[key];
}
}
return htmlProps;
}
}
Использование объекта LinkPropsKeys
, который должен соответствовать LinkProps
, поможет вам синхронизировать ключи между интерфейсом и синхронизацией времени выполнения.
Ответ 2
React.HtmlAttributes в приведенном выше примере теперь является общим, поэтому мне нужно было перейти от React.AnchorHTMLAttributes<HTMLAnchorElement>
.
Пример:
import React from 'react';
type AClickEvent = React.MouseEvent<HTMLAnchorElement>;
interface LinkPropTypes extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
to: string;
onClick?: (x: AClickEvent) => void;
}
class Link extends React.Component<LinkPropTypes> {
public static defaultProps: LinkPropTypes = {
to: '',
onClick: null,
};
private handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
...
event.preventDefault();
history.push(this.props.to);
};
public render() {
const { to, children, ...props } = this.props;
return (
<a href={to} {...props} onClick={this.handleClick}>
{children}
</a>
);
}
}
export default Link;
Ответ 3
Я принял Nitzen Tomer, потому что это была основная идея, по которой я собирался.
В качестве более обобщенного решения это то, с чем я столкнулся:
export function rest(object: any, remove: {[key: string]: any}) {
let rest = Object.assign({}, object);
Object.keys(remove).forEach(key => delete rest[key]);
return rest;
}
Поэтому я могу использовать его следующим образом:
const {a, b, c} = props;
const htmlProps = rest(props, {a, b, c});
И как только TypeScript поддерживает объект rest/spread, я могу просто искать все применения rest()
и упростить его до const {a, b, c, ...htmlProps} = props
.
Ответ 4
Такой геттер может сработать:
class Link extends React.Component<{
textToDisplay: string;
} & React.HTMLAttributes<HTMLDivElement>> {
static propTypes = {
textToDisplay: PropTypes.string;
}
private get HtmlProps(): React.HTMLAttributes<HTMLAnchorElement> {
return Object.fromEntries(
Object.entries(this.props)
.filter(([key]) => !Object.keys(Link.propTypes).includes(key))
);
}
public render():JSX.Element {
return (
<a {...this.HtmlProps}>
{this.props.textToDisplay}
</a>
);
}
}
<Link textToDisplay="Search" href="http://google.com" />
Ответ 5
Это на самом деле проще, чем все ответы выше. Вам просто нужно следовать примеру ниже:
type Props = {
id: number,
name: string;
// All other props
[x:string]: any;
}
const MyComponent:React.FC<Props> = props => {
// Any property passed to the component will be accessible here
}
Надеюсь, это поможет.