样式化组件,定义为 defaultProps 的必需道具显示为缺失
Styled Components, required props defined as defaultProps show as missing
定义为 defaultProps
的必需道具在使用组件时显示为缺失。
// Button.ts
interface ButtonProps {
size: 'small' | 'medium' | 'large';
inverted: boolean;
raised: boolean;
}
const Button = styled('button')<ButtonProps>`...`
Button.defaultProps = {
size: 'medium',
inverted: false,
raised: false,
}
export default Button
// Hello.tsx
import Button from './Button'
const Hello = () => <Button>Hello</Button>
按钮以红色突出显示并显示此错误消息:
Type '{}' is missing the following properties from type
'Pick<Pick<Pick<DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement>, "form" | "style" | "title" | "key" | "autoFocus" |
"disabled" | "formAction" | ... 255 more ... | "onTransitionEndCapture"> &
{ ...; } & ButtonProps, "form" | ... 329 more ... | "raised"> &
Partial<...>, "form" | ... 329 ...': size, inverted, and 1 more.
版本:
"@types/styled-components": "^4.1.9",
"@types/react": "^16.8.5",
"@types/react-dom": "^16.8.2",
"typescript": "^3.3.3333",
"styled-components": "^4.1.3"
"react": "^16.8.3",
"react-dom": "^16.8.3",
编辑 虽然我个人对这种行为没有意见,但它与普通 React 组件的行为不一致 (typescript supports defaultProps
)。
我环顾四周,似乎在 @types/styled-components
, but then removed due to it introducing another bug 中修复了这个问题。
类型维护者的注释:
However, TS doesn't set type of newly set defaultProps (aka expando), so don't expect it would allow to skip required props.
In order to modify the type, in the tests I added a simple helper function that sets defaultProps and returns modified type. This is meant to be an example only.
这是example:
// example of a simple helper that sets defaultProps and update the type
type WithDefaultProps<C, D> = C & { defaultProps: D };
function withDefaultProps<C, D>(component: C, defaultProps: D): WithDefaultProps<C, D> {
(component as WithDefaultProps<C, D>).defaultProps = defaultProps;
return component as WithDefaultProps<C, D>;
}
这个助手确实解决了这个问题(并且与 类似,所以如果您接受这个作为正确的解决方法,您应该考虑将他的答案标记为正确)。
/* setup */
interface Props {
requiredProp: number;
}
const defaultProps: Props = {
requiredProp: 1,
};
const Component = styled.div<Props>``;
const ComponentWithDefault = withDefaultProps(Component, defaultProps);
export { Component, ComponentWithDefault };
/* usage */
import { Component, ComponentWithDefault } from './component.tsx';
const a = <Component /> // Property requiredProp is missing...
const b = <ComponentWithDefault /> // no error
要求:
"@types/styled-components": "^4.1.12",
"typescript": "^3.2.4",
defaultProps
with styled-components 在过去确实有效——如果你有兴趣保持这种行为,我认为恢复到旧版本的 @types/styled-components 可能会有所帮助;我认为是 4.1.8,但我不确定。
原回答
也许这不是您正在寻找的答案,但我发现这种行为是可取的。
如果您已经在 defaultProps
中定义了某些属性,那不会使这些属性成为可选的吗?
我会将默认道具设为可选:
interface ButtonProps {
size?: 'small' | 'medium' | 'large';
inverted?: boolean;
raised?: boolean;
}
或者保持原样,但包含在 Partial<>
中,因为您已经为所有这些提供了默认值
const Button = styled('button')<Partial<ButtonProps>>`...`
我通常做的另一件事是先在变量中定义defaultProps
,这样我就可以正确定义它们的接口:
interface Props { ... }
const defaultProps: Props = { ... }
const Component = styled.div<Props>`...`
Component.defaultProps = defaultProps
问题是分配 defaultProps
不会改变它的类型,它将保持默认的 Partial<StyledComponentProps<"button", any, ButtonProps, never>>
。您可以显式键入 const
但键入很多类型绝非有趣。一种更简单的方法是使用另一个 HOC(一切都可以用另一个 HOC 解决),它改变 defaultProps
的类型以包含您在组件上实际设置的默认道具:
// Button.ts
interface ButtonProps {
size: 'small' | 'medium' | 'large';
inverted: boolean;
raised: boolean;
}
function withDefault<T extends { defaultProps?: Partial<TDefaults> }, TDefaults>(o: T, defaultProps: TDefaults): T & { defaultProps: TDefaults } {
o.defaultProps = defaultProps;
return o as any;
}
const Button = withDefault(styled('button') <ButtonProps>`...`, {
size: 'medium',
inverted: false,
raised: false,
})
export default Button
const Hello = () => <Button>Hello</Button> // OK now
应用默认道具的一种更稳健的方法是使用功能组合。
function withDefaults<T extends React.ComponentType<any>, U extends Partial<React.ComponentProps<T> & JSX.IntrinsicAttributes>>(Component: T, defaults: U): React.FC<Omit<React.ComponentProps<T>, keyof U> & Partial<U>> {
return props => React.createElement(Component, { ...defaults, ...props });
}
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
用法:
const ButtonWithDefaults = withDefaults(Button, {
size: 'medium',
inverted: false,
raised: false,
});
() => (
<ButtonWithDefaults>
Hello
</ButtonWithDefaults>
);
它实现了一切:
- ✅ 没有任何变化
- ✅ 类型已检查
- ✅ 你得到正确的代码完成
- ✅ 没有
as any
断言
一个更简单更好的解决方案是在解构时在渲染中(或你使用它们的地方)分配默认值,我经常这样做,这是一个例子:
// Button.ts
interface ButtonProps {
size?: 'small' | 'medium' | 'large'; // making optional via '?' sign
inverted?: boolean;
raised?: boolean;
}
render() {
const { size = 'medium', inverted = false, raised = false } = this.props;
// Use these props, if none are passed by user, these defaults will be used
// otherwise user versions will be used
}
您可以只创建一个常规组件,然后将该组件的引用传递给 styled 并像使用标签一样设置样式
const PlainButton = props: ButtonProps => (/* your jsx here */);
const Button = styled(PlainButton)`
color: #A5FFA5;
`;
定义为 defaultProps
的必需道具在使用组件时显示为缺失。
// Button.ts
interface ButtonProps {
size: 'small' | 'medium' | 'large';
inverted: boolean;
raised: boolean;
}
const Button = styled('button')<ButtonProps>`...`
Button.defaultProps = {
size: 'medium',
inverted: false,
raised: false,
}
export default Button
// Hello.tsx
import Button from './Button'
const Hello = () => <Button>Hello</Button>
按钮以红色突出显示并显示此错误消息:
Type '{}' is missing the following properties from type
'Pick<Pick<Pick<DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement>, "form" | "style" | "title" | "key" | "autoFocus" |
"disabled" | "formAction" | ... 255 more ... | "onTransitionEndCapture"> &
{ ...; } & ButtonProps, "form" | ... 329 more ... | "raised"> &
Partial<...>, "form" | ... 329 ...': size, inverted, and 1 more.
版本:
"@types/styled-components": "^4.1.9",
"@types/react": "^16.8.5",
"@types/react-dom": "^16.8.2",
"typescript": "^3.3.3333",
"styled-components": "^4.1.3"
"react": "^16.8.3",
"react-dom": "^16.8.3",
编辑 虽然我个人对这种行为没有意见,但它与普通 React 组件的行为不一致 (typescript supports defaultProps
)。
我环顾四周,似乎在 @types/styled-components
, but then removed due to it introducing another bug 中修复了这个问题。
类型维护者的注释:
However, TS doesn't set type of newly set defaultProps (aka expando), so don't expect it would allow to skip required props. In order to modify the type, in the tests I added a simple helper function that sets defaultProps and returns modified type. This is meant to be an example only.
这是example:
// example of a simple helper that sets defaultProps and update the type
type WithDefaultProps<C, D> = C & { defaultProps: D };
function withDefaultProps<C, D>(component: C, defaultProps: D): WithDefaultProps<C, D> {
(component as WithDefaultProps<C, D>).defaultProps = defaultProps;
return component as WithDefaultProps<C, D>;
}
这个助手确实解决了这个问题(并且与
/* setup */
interface Props {
requiredProp: number;
}
const defaultProps: Props = {
requiredProp: 1,
};
const Component = styled.div<Props>``;
const ComponentWithDefault = withDefaultProps(Component, defaultProps);
export { Component, ComponentWithDefault };
/* usage */
import { Component, ComponentWithDefault } from './component.tsx';
const a = <Component /> // Property requiredProp is missing...
const b = <ComponentWithDefault /> // no error
要求:
"@types/styled-components": "^4.1.12",
"typescript": "^3.2.4",
defaultProps
with styled-components 在过去确实有效——如果你有兴趣保持这种行为,我认为恢复到旧版本的 @types/styled-components 可能会有所帮助;我认为是 4.1.8,但我不确定。
原回答
也许这不是您正在寻找的答案,但我发现这种行为是可取的。
如果您已经在 defaultProps
中定义了某些属性,那不会使这些属性成为可选的吗?
我会将默认道具设为可选:
interface ButtonProps {
size?: 'small' | 'medium' | 'large';
inverted?: boolean;
raised?: boolean;
}
或者保持原样,但包含在 Partial<>
中,因为您已经为所有这些提供了默认值
const Button = styled('button')<Partial<ButtonProps>>`...`
我通常做的另一件事是先在变量中定义defaultProps
,这样我就可以正确定义它们的接口:
interface Props { ... }
const defaultProps: Props = { ... }
const Component = styled.div<Props>`...`
Component.defaultProps = defaultProps
问题是分配 defaultProps
不会改变它的类型,它将保持默认的 Partial<StyledComponentProps<"button", any, ButtonProps, never>>
。您可以显式键入 const
但键入很多类型绝非有趣。一种更简单的方法是使用另一个 HOC(一切都可以用另一个 HOC 解决),它改变 defaultProps
的类型以包含您在组件上实际设置的默认道具:
// Button.ts
interface ButtonProps {
size: 'small' | 'medium' | 'large';
inverted: boolean;
raised: boolean;
}
function withDefault<T extends { defaultProps?: Partial<TDefaults> }, TDefaults>(o: T, defaultProps: TDefaults): T & { defaultProps: TDefaults } {
o.defaultProps = defaultProps;
return o as any;
}
const Button = withDefault(styled('button') <ButtonProps>`...`, {
size: 'medium',
inverted: false,
raised: false,
})
export default Button
const Hello = () => <Button>Hello</Button> // OK now
应用默认道具的一种更稳健的方法是使用功能组合。
function withDefaults<T extends React.ComponentType<any>, U extends Partial<React.ComponentProps<T> & JSX.IntrinsicAttributes>>(Component: T, defaults: U): React.FC<Omit<React.ComponentProps<T>, keyof U> & Partial<U>> {
return props => React.createElement(Component, { ...defaults, ...props });
}
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
用法:
const ButtonWithDefaults = withDefaults(Button, {
size: 'medium',
inverted: false,
raised: false,
});
() => (
<ButtonWithDefaults>
Hello
</ButtonWithDefaults>
);
它实现了一切:
- ✅ 没有任何变化
- ✅ 类型已检查
- ✅ 你得到正确的代码完成
- ✅ 没有
as any
断言
一个更简单更好的解决方案是在解构时在渲染中(或你使用它们的地方)分配默认值,我经常这样做,这是一个例子:
// Button.ts
interface ButtonProps {
size?: 'small' | 'medium' | 'large'; // making optional via '?' sign
inverted?: boolean;
raised?: boolean;
}
render() {
const { size = 'medium', inverted = false, raised = false } = this.props;
// Use these props, if none are passed by user, these defaults will be used
// otherwise user versions will be used
}
您可以只创建一个常规组件,然后将该组件的引用传递给 styled 并像使用标签一样设置样式
const PlainButton = props: ButtonProps => (/* your jsx here */);
const Button = styled(PlainButton)`
color: #A5FFA5;
`;