NK-dev
@NK-dev (N K)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

TypeScriptのpropsで受け取った時の型定義がわからない

解決したいこと

親コンポーネント(TodoList.tsx)から受け取ったpropsを子コンポーネント(TodoITem.tsx)で受け取った時に、エラーが出るので型指定したいがany以外だと何なのか助言を頂きたいです。

発生している問題・エラー

プロパティ 'todo' は型 '{}' に存在しません。

または、問題・エラーが起きている画像をここにドラッグアンドドロップ

該当するソースコード

TodoList.tsx
import React, { useState } from 'react';
import { useRecoilValue } from 'recoil';
import { todoListState } from '../State/atom';
import { DATA } from '../types/DataType';
import TodoItem from './TodoItem';
import TodoItemCreator from './TodoItemCreator';
import TodoListStatus from './TodoListStatus';

const TodoList = () => {
  const todoList = useRecoilValue(todoListState);

  return (
    <div>
      <h1>初めてのRecoil</h1>
      <TodoListStatus />
      <TodoItemCreator />
      {todoList.map((todo) => (
        <TodoItem key={todo.id} todo={todo} />
      ))}
    </div>
  );
};

export default TodoList;
TodoItem.tsx
import React from 'react';

//<{todo:any}>を外すとpropsで受け取ったtodoで上記のエラーメッセージがはかれる
const TodoItem: React.FC<{ todo: any }> = ({ todo }) => {
  return (
    <div>
      <ul key={todo.id}></ul>
      <div key={todo.id}>{todo.title}</div>
    </div>
  );
};

export default TodoItem;
atom.ts
//atom(store)
import { atom } from 'recoil';
import { DATA } from '../types/DataType';

export const todoListState = atom<Array<DATA>>({
  key: 'todoListState',
  default: [
    {
      id: 0,
      title: '送信設定',
      isComplete: false,
    },
  ],
});

selector.ts
//selector
import { selector } from 'recoil';
import { todoListState } from './atom';

export const todoListStatusState = selector<number>({
  key: 'todoListStatusState',
  get: ({ get }) => {
    const todoList = get(todoListState);
    const totalNum = todoList.length;
    return totalNum;
  },
});
DataType.ts
//型を指定
export type DATA = {
  id: number;
  title: string;
  isComplete: boolean;
};

自分で試したこと

下記コンポーネントにReact.FCに受け取ったpropsにanyを定義することでエラーは解除される。しかし、anyは避けたいので具体的な型定義を施したい。

TodoItem.tsx
const TodoItem: React.FC<{ todo: any }> = ({ todo }) => {
  return (
    <div>
      <ul key={todo.id}></ul>
      <div key={todo.id}>{todo.title}</div>
    </div>
  );
};

使用技術

package.json
   "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "recoil": "^0.7.6",
    "typescript": "^4.8.4",
0

2Answer

= = = = = = = = = = = = = = =
2022/10/28に訂正
元の回答に重大な間違いがありました。
以下は修正した回答になります。
= = = = = = = = = = = = = = =

シンタックスハイライトが効くので、こちらで返信します。
質問者様のコメントから、ご自身のコードと私の回答への理解ができていない印象を受けましたので、もう少し噛み砕きます。

今回の目的は「親コンポーネント(TodoList.tsx)から受け取ったpropsを子コンポーネント(TodoItem.tsx)で受け取りたい」と「propsに具体的な型定義を施したい」です。
前者は既に実装されています。
問題は後者ですが、propsに型定義を行うために、TodoItemTodoListから受け取りたい型を把握する必要がありますが、質問者様はそれが分かっていない状況です。
なのでここは逆に、TodoListTodoItemに渡したい型を知ることで、propsに同じ型を定義することができます。

<TodoItem key={todo.id} todo={todo} />

この時点、TodoItemのpropsの型はこうです。

{
    todo: ? // <- ここが分からない
}

todoプロパティの型が分からないため、質問者様はany型で定義していました。
ここでは、TodoList視点で、何をtodoに渡したかを確認しましょう。

TodoListtodoListStateからtodoListを取り出し、todoListの中身を一つずつTodoItemtodoプロパティに渡します。
todoListStateから取り出すtodoListの型はArray<DATA>です。DATA型の配列です。
DATA型の配列から一つずつtodoプロパティに渡しているので、todoプロパティが受け取っているのは自然的にDATA型になります。
つまり、TodoItemのpropsの型はこうなります。

{
    todo: DATA;
}
const TodoItem: React.FC<{ todo: DATA }> = ({todo}) => {

話が変わりまして、分割代入の話になります。

分割代入を使わない場合

TodoItem.tsx
const TodoItem: React.FC<{ todo: DATA }> = (props) => {
  return (
    <div>
      <ul key={props.todo.id}></ul>
      <div key={props.todo.id}>{props.todo.title}</div>
    </div>
  );
};

分割代入を使う場合

TodoItem.tsx
const TodoItem: React.FC<{ todo: DATA }> = ({todo}) => {
  return (
    <div>
      <ul key={todo.id}></ul>
      <div key={todo.id}>{todo.title}</div>
    </div>
  );
};

分割代入を使わない場合、props{ todo: DATA }のままなので、todoを使いたい時はprops.todoで呼び出す必要があります。
分割代入を使った場合、受け取った時点であらかじめにpropsのtodoプロパティをばらして、変数todoに入れて、todoで使えます。

フレームワークやライブラリを使うのもいいですが、JavaScriptやTypeScriptの構文をもう少し分かるとできることの幅が広がるかと…
質問者様は調べる力と質問する力がありますので、
一度立ち止まって、現状のコードへの理解を深めたらより進みやすいかなと思いました。

2Like

Comments

  1. @NK-dev

    Questioner

    お忙しい中ありがとうございます。
    課題と先の進め方を明確にしていただき、この先どのように理解すればいいのか落とし込めました。
    また、上記のわかりやすい説明+自身のコード+サイトを参考にすることでpropsでの型のあり方を以前より理解することができました。改めてありがとうございます。もう少し、JSやTSの構文理解とコードを読み解くことを練習したいと思います。
  2. もう一つの質問を拝見しました。
    私の回答のコードに誤りがあるため、お手数をおかけしました。
    後ほど上記の回答内容を訂正いたします。
    申し訳ございません。
  3. 訂正いたしました。
    私の確認不足により、質問者様を正解から遠ざけるようなことをしてしまい、心苦しく感じております。
    本当に申し訳ございませんでした。

2022/10/28
この回答は間違っています。訂正した回答は別コメントに記述しました。
そちらをご確認ください。

受け取る側にも型を教える必要があります。
ストア側の定義を見ると、受け取りたいデータはDATA型ですかね?

TodoItem.tsx
import React from 'react';
+ import { DATA } from '../types/DataType';

//<{todo:any}>を外すとpropsで受け取ったtodoで上記のエラーメッセージがはかれる
- const TodoItem: React.FC<{ todo: any }> = ({ todo }) => {
+ const TodoItem: React.FC<DATA> = (todo) => {
  return (
    <div>
      <ul key={todo.id}></ul>
      <div key={todo.id}>{todo.title}</div>
    </div>
  );
};

export default TodoItem;

React.FC<DATA>

<>の中に型を指定する。

(todo)

受け取ったものをそのままtodoに入れる。

合わせて、DATA型を受け取ってtodoという名称の変数に入れるという意味です。
{}をつけると分割代入になって意味が変わります。

1Like

Comments

  1. @NK-dev

    Questioner

    aotriiさん、お忙しい中コメントありがとうございます。
    なるほど、、まだ完全な理解になってないのですが、今回ですとTodoItemという関数コンポーネントにTODOという型をつけて、親から渡ってきたpropsはオブジェクトになっているから、それを(todo)でそれぞれ受け取っているという認識で合っていますでしょうか。

    また、{todo}だと分割代入になってしまい意味が異なるのはどのように違うのでしょうか。恐れ多いのですが、教えていただけたら幸いです。
    参考記事:https://dezanari.com/react-component-props-object/

Your answer might help someone💌