如何使用Typescript向样式化的组件添加属性?

时间:2019-02-21 17:27:13

标签: reactjs typescript styled-components

我有这样的组件

import React from 'react';

import styled from '../../styled-components';

const StyledInput = styled.input`
    display: block;
    padding: 5px 10px;
    width: 50%;
    border: none;
    border-radius: 10px;
    outline: none;
    background: ${(props) => props.theme.color.background};
    font-family: 'Roboto', sans-serif;
`
;

export const Input = () => {
    return <StyledInput placeholder="All notes"></StyledInput>
}

我想像这样将它们作为属性放置在“索引”组件上

import styled from 'styled-components';

import { Input } from './Input';

const NoteTags:any = styled.div`
    display: flex;
    justify-content: space-between;
    align-items: center;
    width: 20%;
    height: 5%;
    border-bottom: 2px solid ${(props) => props.theme.color.background}; `;

NoteTags.Input = Input;

export default NoteTags;

所以我可以这样使用它们

import React from 'react';

import NoteTags from '../../blocks/note-tags';

const ComponentNoteTags = () => {
    return (
        <React.Fragment>
            <NoteTags.Input></NoteTags.Input>
        </React.Fragment>
    )
}

export default ComponentNoteTags;

此问题是Input上不存在属性NoteTags,所以打字稿给我一个错误。我可以通过将类型any设置为NoteTags来解决此问题,但是我不确定这是最好的方法。

有更好的解决方法吗?

2 个答案:

答案 0 :(得分:1)

更新

我发现使用类型帮助器将额外的属性添加到组件的变量中会更好:

// type-helper.ts
export function withProperties<A, B>(component: A, properties: B): A & B {
  Object.keys(properties).forEach(key => {
    component[key] = properties[key]
  });
  return component as A & B;
}
  

注意:@Allan在注释中指出,可能需要像下面这样声明类型:

(component as any)[key] = (properties as any)[key]
     

避免打字稿抱怨Element implicitly has an 'any' type because type '{}' has no index signature

我们只是将properties中的每个属性都传递给component,并为其赋予两者的联合类型。然后,我们可以将子组件“粘合”到主组件中,如下所示:

// component/index.ts
import MainComponent from './MainComponent';
import SubComponent1 from './SubComponent1';
import SubComponent2 from './SubComponent2';
import { withProperties } from '../type-helper.ts';

export withProperties(MainComponent, { SubComponent1, SubComponent2 })

并照常使用

import MainComponent from './Component';

export default () => (
  <MainComponent>
    <SubComponent1 />
    <SubComponent2 />
  </MainComponent>
)

这种方法也适用于常规的React组件,而不仅仅是样式化的组件,因此我认为更好。

这里是codesandbox demo with both approaches I've shared,每个使用您的代码。


上一个答案

假设您使用的是最新软件包:

"styled-components": "^4.1.3",
"@types/styled-components": "^4.1.10",

您可以创建一个同时包含父组件和子组件类型的interection type

import styled, { StyledComponent } from 'styled-components'
import ChildComponent from '...'

type Component = StyledComponent<'div', any> & {
    ChildComponent: /* your component type */
}

interface IProps {
  ...
}

// create styled component per usual
const MyComponent = styled.div<IProps>`
  ...
` as Component

MyComponent.ChildComponent = ChildComponent;

这里是StyledComponent definition

export type StyledComponent<
    C extends keyof JSX.IntrinsicElements | React.ComponentType<any>,
    T extends object,       // theme interface
    O extends object = {},  // props interface
    A extends keyof any = never
> = string & StyledComponentBase<C, T, O, A>;

您可以将其用作StyledComponent<C, T, O, A>StyledComponent<C, T>

答案 1 :(得分:0)

我已经成功地通过定义样式化组件的调用签名以及要添加的其他属性来使其工作。 Typescript会抱怨_mapEventToState上不存在Item,所以我不得不使用Object Assign创建样式化的组件并同时分配属性。

List

然后您可以在JSX中像这样使用

import styled from 'styled-components'
import { PropsWithChildren, ReactElement } from 'react'

interface ListOverload {
    ({ children }: PropsWithChildren<{}>): ReactElement
    Item({ children }: PropsWithChildren<{}>): ReactElement
}

export const Item = styled.li(
    ({ theme }) => css`
        padding: ${theme.space.md};
        border-bottom: 1px solid ${theme.colors.greyLight};
    `
)

export const List: ListOverload = Object.assign(
    styled.ul`
        list-style: none;
        padding: 0;
        margin: 0;
    `,
    { Item }
)