How can I abstract from the repetitive properties of children while preserving types that are not optional?

I have a component that is used several times in a row with some identical properties and some unique properties:

interface InsideComponentProps {
    repeatedThing: string;
    uniqueThing: string;
}

const InsideComponent: React.SFC<InsideComponentProps> = ({ repeatedThing, uniqueThing }) => (
    <div>{repeatedThing} - {uniqueThing}</div>
);

const Example = () => (
    <div>
        <InsideComponent repeatedThing="foo" uniqueThing="1" />
        <InsideComponent repeatedThing="foo" uniqueThing="2" />
        <InsideComponent repeatedThing="foo" uniqueThing="3" />
    </div>
);

Duplicate properties repeatedThingbother me, so I'm looking for a way to remove this redundancy. One thing I did in non TypeScript applications is to introduce a shell component that clones all the children, adding duplicate properties in the process:

interface OutsideComponentProps {
    repeatedThing: string;
}

const OutsideComponent: React.SFC<OutsideComponentProps> = ({ repeatedThing, children }) => (
    <div>
        {React.Children.map(children, (c: React.ReactElement<any>) => (
            React.cloneElement(c, { repeatedThing })
        ))}
    </div>
);

const Example = () => (
    <OutsideComponent repeatedThing="foo">
        <InsideComponent uniqueThing="1" />
        <InsideComponent uniqueThing="2" />
        <InsideComponent uniqueThing="3" />
    </OutsideComponent>
);

As a result, the JavaScript code has the behavior that I want, but the TypeScript compiler has errors, because I do not pass all the necessary properties when creating the instance InsideComponent:

ERROR in [at-loader] ./src/index.tsx:27:26
    TS2322: Type '{ uniqueThing: "1"; }' is not assignable to type 'IntrinsicAttributes & InsideComponentProps & { children?: ReactNode; }'.
  Type '{ uniqueThing: "1"; }' is not assignable to type 'InsideComponentProps'.
    Property 'repeatedThing' is missing in type '{ uniqueThing: "1"; }'.

, , , InsideComponent repeatedThing , , .

, InsideComponent , ?

React 16.2.0 TypeScript 2.6.2.

+4
2

TypeScript , React. OutsideComponent, .

- , InsideComponent. , :

interface OutsideComponentProps {
    repeatedThing: string;
    children: (outerProps: OutsideComponentProps) => React.ReactElement<any>;
}

const OutsideComponent: React.SFC<OutsideComponentProps> = (o) => o.children(o);

const Example = () => (
    <OutsideComponent repeatedThing="foo">{(o) => 
        <div>
            <InsideComponent uniqueThing="1" {...o} />
            <InsideComponent uniqueThing="2" {...o} />
            <InsideComponent uniqueThing="3" {...o} />
        </div>
    }</OutsideComponent>
);

, OutsideComponent , , ; , , OutsideComponentProps ?

, , . , .

function GenericOutsideComponent<T>(props: { children: (o: T) => React.ReactElement<any> } & Partial<T>, context?: any): React.ReactElement<any> {
    return props.children(props as any);
}

const Example = () => (
    <GenericOutsideComponent repeatedThing="foo">{(o: InsideComponentProps) =>
        <div>
            <InsideComponent uniqueThing="1" {...o} />
            <InsideComponent uniqueThing="2" {...o} />
            <InsideComponent uniqueThing="3" {...o} />
        </div>
    }</GenericOutsideComponent>
);

JavaScript-, , InsideComponent , GenericOutsideComponent Partial<T> ( repeatedThing) o is T, repeatedThing InsideComponent. InsideComponent , children (o: Partial<T>) => React.ReactElement<any>, .

- , GenericOutsideComponent, Pick:

function GenericOutsideComponent<T>(props: { children: (o: T) => React.ReactElement<any> } & T, context?: any): React.ReactElement<any> {
    return props.children(props);
}

const Example = () => (
    <GenericOutsideComponent repeatedThing="foo">{(o: Pick<InsideComponentProps, "repeatedThing">) =>
        <div>
            <InsideComponent uniqueThing="1" {...o} />
            <InsideComponent uniqueThing="2" {...o} />
            <InsideComponent uniqueThing="3" {...o} />
        </div>
    }</GenericOutsideComponent>
);
+2

TypeScript, :

// OutsideComponent.tsx
import * as React from "react";

const OutsideComponent: React.SFC<{ children?: React.ReactNode, [rest: string]: any }> = (props) => {
    const { children, ...rest } = props;

    return (
        <div>
            {React.Children.map(children, ((child, i) =>
                React.cloneElement(child as React.ReactElement<any>, { key: i, ...rest }))
            )}
        </div>
    )
};

type Sub<
    O extends string,
    D extends string
    > = {[K in O]: (Record<D, never> & Record<string, K>)[K]}[O]

export type Omit<O, D extends keyof O> = Pick<O, Sub<keyof O, D>>

export default OutsideComponent;

(Omit, )

import OutsideComponent, { Omit } from './OutsideComponent';

const Example = (): React.ReactElement<any> => {
    const PartialInsideComponent: React.SFC<Omit<InsideComponentProps, 'repeatedThing'>> = InsideComponent;

    return (
        <OutsideComponent repeatedThing="foo">
            <PartialInsideComponent uniqueThing="1" />
            <PartialInsideComponent uniqueThing="2" />
            <PartialInsideComponent uniqueThing="3" />
        </OutsideComponent>
    )
};
+1

Source: https://habr.com/ru/post/1692681/


All Articles