55828


再現コードは最小限にシンプルにしたので、意味自体はありません。

[code]

    interface TextProps { data: { text: string }; }
    interface ObjProps { data: object; }
    
    const Para: React.FC<TextProps> = ({ data }) => <p>{data.text}</p>;
    const Json: React.FC<ObjProps> = ({ data }) => <pre>{JSON.stringify(data)}</pre>;
    
    const make = <T extends { data: {} }>(Elem: React.FC<T>, data: T['data']) => <Elem data={data} />;
    
    export const res1 = make<ParaProps>(Para, { text: 'hoge' });
    export const res2 = make<ObjProps>(Json, { foo: 'bar' });
    
[/code]

上のコードはざっと通したい内容です。似たようなPropsを持つ（上の例では`data`）、複数のコンポーネントがあり、それを作成するmakeという関数があるという状態です。

このmakeのタグの部分で「型 '{ data: T["data"]; }' を型 'T'
に割り当てることはできません。ts(2322)」というエラーが出てくるのですが、解消できるような型をanyを使わずに書くことができません。

通るような書き方、またはそもそもこのようなことができないのかを教えてください。

補足ですが、説明のために `make` とした部分は

[code]

    const opt = (Elem) => ({ data, ...props }) => data ? <Elem data={data} {...props} /> : null;
    export const OptPara = opt(Para);
    export const OptJson = opt(Json);
    
[/code]

という感じのことがやりたいのですが、不必要に型が複雑になり（しかも通っていない）ので、上のように書いた感じです。こちらの方が答えやすいとかあれば、こちらに答えていただいても構いません。


CC BY-SA 4.0
2019-06-15T18:09:40.420
55826
2019-06-15T21:29:18.783
29556
question
reactjs typescript
TypeScriptのgenericsを使ったReactコードのコンパイルが通らなくて困っています


とりあえず質問で提示されているコードのコンパイルが通るものを考えてみます。

TS2322が吐かれる理由はエラーメッセージにも表示されている通り、「`T`は`data`以外のプロパティを持ち得る（が、`data`しか指定されていない）」ため。  
ということはPropsのキーとして使えるものを制限できるようにすればいいのでは？というのがまず案1。

試行錯誤の結果こんなものが生まれました。

[code]

    import * as React from 'react'
    
    interface BaseProps { data: {} }
    interface ParaProps { data: { text: string } }
    interface ObjProps { data: object; }
    
    const Para: React.FC<ParaProps> = ({ data }) => <p>{data.text}</p>;
    const Json: React.FC<ObjProps> = ({ data }) => <pre>{JSON.stringify(data)}</pre>;
    
    const make = <T extends BaseProps>(Elem: React.FC<{ [K in keyof BaseProps]: T[K] }>, data: T['data']) => <Elem data={data} />;
    
    export const res1 = make<ParaProps>(Para, { text: 'hoge' });
    export const res2 = make<ObjProps>(Json, { foo: 'bar' });
    
[/code]

うん。なんとも香ばしい…

でもコードがおかしいときに正しくコケるのか自信がない上に(一応試したので大丈夫そうですが)、`make<ParaProps>`とかわざわざ書く必要がない気がしてきました。

Propsのキーが固定されているのであればその部分だけGenerics使うべきなのでは！？  
ということで以下のようなものでも要件が満たせそう。

[code]

    import * as React from 'react'
    
    type SomeProps<T> = { data: T }
    type ParaProps = SomeProps<{ text: string }>
    type ObjProps = SomeProps<object>
    
    const Para: React.FC<ParaProps> = ({ data }) => <p>{data.text}</p>;
    const Json: React.FC<ObjProps> = ({ data }) => <pre>{JSON.stringify(data)}</pre>;
    
    const make = <T extends {}>(Elem: React.FC<SomeProps<T>>, data: T) => <Elem data={data} />;
    
    export const res1 = make(Para, { text: 'hoge' });
    export const res2 = make(Json, { foo: 'bar' });
    
[/code]

そこそこスッキリした気がします。(もうちょっとどうにかなりそう)

* * *

ところでもしpropsが`data`以外も保つ場合持たない場合が存在するのであればObject spredするだけなんですけどね…

[code]

    import * as React from 'react'
    
    interface TextProps { data: { text: string }; }
    interface ObjProps { data: object; }
    
    const Para: React.FC<TextProps> = ({ data }) => <p>{data.text}</p>;
    const Json: React.FC<ObjProps> = ({ data }) => <pre>{JSON.stringify(data)}</pre>;
    
    const make = <T extends { data: {} }>(Elem: React.FC<T>, props: T) => <Elem {...props} />;
    
    export const res1 = make(Para, { data: { text: 'hoge' } });
    export const res2 = make(Json, { data: { foo: 'bar' } });
    
[/code]

* * *

(深夜テンションで書いたコードなのでいろいろと怪しいところがあるかもしれません。ご了承ください。)


CC BY-SA 4.0
2019-06-15T21:29:18.783
55828
2019-06-15T21:29:18.783
2376
55826
answer