Búsqueda de sitios web

reactjs - ¿Cómo agregar propiedades a un componente estilizado con Typescript?


Tengo componentes como este

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>
}

Quiero colocarlos como propiedad en el componente "index" de esta manera

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;

Entonces puedo usarlos así

import React from 'react';

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

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

export default ComponentNoteTags;

El problema con esto es que la propiedad Input no existe en NoteTags, por lo que TypeScript me da un error. Puedo solucionarlo asignando el tipo any a NoteTags, pero no estoy seguro de que sea la mejor manera.

¿Hay una mejor manera de arreglarlo?

Respuesta #1

Actualizar

Encontré mucho mejor usar un ayudante de tipo para añadir propiedades adicionales en la variable de un componente:

// 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;
}

Nota: @Allan señala en el comentario que podría ser necesaria una afirmación de tipo así:

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

para evitar que TypeScript se queje de Element implícitamente tiene un tipo 'any' porque el tipo '{}' no tiene una firma de índice.

Solo estamos pasando cada propiedad en properties a component y le damos un tipo combinado de ambos. Luego podemos 'pegar' subcomponentes en un componente principal de la siguiente manera:

// 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 })

y úsalo como de costumbre

import MainComponent from './Component';

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

Este enfoque funciona también con componentes normales de React y no solo con los componentes styled, así que creo que es mejor.

Aquí hay una demostración en CodeSandbox con ambos enfoques que he compartido, uno de cada uno use su código.

Respuesta anterior

Suponiendo que estés usando los paquetes más recientes:

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

Puedes crear un tipo de intersección que incluya tanto el tipo de los componentes padre como hijo:

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;

Aquí está la definición de StyledComponent:

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>;

Puedes usarlo como StyledComponent<C, T, O, A> o StyledComponent<C, T>

Respuesta #2

He logrado que funcione definiendo las firmas de llamada del componente estilizado y las propiedades adicionales que quieres agregar. TypeScript se quejará de que Item no existe en List, así que tuve que usar Object Assign para crear el componente estilizado y asignar la propiedad simultáneamente.

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 }
)

Entonces puedes usarlo así en tu JSX:

<List>
  <List.Item>Todo 1</List.Item>
  <List.Item>Todo 2</List.Item>
  <List.Item>Todo 3</List.Item>
</List>

Artículos relacionados