样式化组件,定义为 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;
`;