質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Ruby on Rails 6

Ruby on Rails 6は、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

Q&A

1回答

900閲覧

React内のJavaScriptに型をつけてTypeScriptに書き換えたいです。

ryu2023

総合スコア1

Ruby on Rails 6

Ruby on Rails 6は、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

0グッド

0クリップ

投稿2022/10/09 12:15

前提

ReactとTypeScriptを用いてバックエンド(rails)からのuser情報のapiを画面に表示させたいです。javascriptでの書き方はわかったのですが、TypeScriptにする際にどんな型をつければいいのかわからないので教えていただきたいです。
名前やemailが存在するオブジェクトの入った配列であるusersがrails側から渡ってきます。

実現したいこと

ここに実現したいことを箇条書きで書いてください。

  • axios.getに書くtypeを知りたい。。
  • useReducerの引数であるstateとactionの型を知りたい。
  • map関数の内のuserの型を知りたい。

発生している問題・エラーメッセージ

エラーメッセージ

Users_index.tsx

1//ユーザー一覧を表示します。 2import React, { useEffect, useReducer } from "react"; 3import { fetchUsers } from "../apis/users"; 4import { initialState, usersReducer } from "../reducers/users"; 5 6export const Users = () => { 7 8 const [state, dispatch] = useReducer(usersReducer, initialState); 9 10 useEffect(() => { 11 dispatch({ type: 'FETCHING' }); 12 fetchUsers() 13 .then((data) => 14 dispatch({ 15 type: 'FETCHI_SUCCESS', 16 payload: { 17 users: data.users 18 } 19 }) 20 ) 21 }, []) 22 return ( 23 <> 24 <p>ユーザー一覧ページです</p> 25 { 26 state.usersList.map(user => 27 <div> 28 {user.name} 29 </div> 30 ) 31 } 32 </> 33 ) 34}; 35

users.ts

1//rails側からのapiを取得します。 2import axios from "axios"; 3import { usersIndex } from "../urls"; 4 5export const fetchUsers = () => { 6 return axios.get(usersIndex).then(res => 7 { 8 return res.data 9 }).catch((e) => console.error(e)) 10}

users.ts

1//useReducerを作成します。 2import { REQUEST_STATE } from '../constants'; 3 4export const initialState = { 5 fetchState: REQUEST_STATE.INITIAL, 6 usersList: [], 7}; 8 9export const usersReducer = (state, action) => { 10 switch (action.type) { 11 case 'FETCHING': 12 return { 13 ...state, 14 fetchState: REQUEST_STATE.LOADING, 15 }; 16 case 'FETCHI_SUCCESS': 17 return { 18 fetchState: REQUEST_STATE.OK, 19 usersList: action.payload.users, 20 }; 21 default: 22 throw new Error(); 23 } 24}

users_controller.rb

1//usersコントローラーを作成 2module Api 3 module V1 4 class UsersController < ApplicationController 5 def index 6 @users = User.all 7 8 render json: { 9 users: @users 10 }, status: :ok 11 end 12 end 13 end 14end

渡ってくるapiです。

イメージ説明

試したこと

type State = {
fetchState: string;
usersList: Array<object>;
}

type Action = {
type: string;
payload: any;
}
をuseReducerに渡したがうまくいかなかった。

補足情報(FW/ツールのバージョンなど)

ここにより詳細な情報を記載してください。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

maisumakun

2022/10/09 12:18

> うまくいかなかった。 どのような問題が生じたのですか?
ryu2023

2022/10/09 12:35

Users_index.tsx内の、dispatch({ type: 'FETCHING' });の箇所で型エラーが出ます。内容は「型 '{ type: string; }' の引数を型 'Action' のパラメーターに割り当てることはできません。 プロパティ 'payload' は型 '{ type: string; }' にありませんが、型 'Action' では必須です。」です。 また、Users_index.tsx内map関数の中のuserに対して「パラメーター 'user' の型は暗黙的に 'any' になります」というエラーが発生します。
ryu2023

2022/10/09 12:38

一つのエラーは解決しました。type Action内のpayloadの後に?をつけて解決しました。 しかし、 ```type Action = { type: string; payload?: any; }``` のpayloadの型はとりあえずanyにしましたが、本来なら何にすべきかがわかりません。
guest

回答1

0

TS化の一例を挙げます。あくまで一例なので参考に留めていただき、実際のコードを書くときは適宜修正してご自身好みの書き方や設計にフィットさせてください。

まず、ユーザーの型を定義します。

models.ts

typescript

1export interface User { 2 id: number 3 name: string 4 email: string 5}

上記は質問にあるAPIレスポンスに含まれる1個のユーザーのプロパティを端折ったものです。実際はレスポンスに含まれる全プロパティに合わせて interface User を記述します。この User はAPI、リデューサー、Reactコンポーネントのいずれからもimportされ得るものです。

  • 補足:バックエンドがRailsのようなので上記のファイル名をとりあえず models.tsにしておくことについて違和感はそれほど無いと思いますが、人によっては一言モノ申したくなるかもしれません。

reducers/users.ts

constants が質問になかったので、REQUEST_STATE は以下のようなオブジェクトを想定しました。

typescript

1const REQUEST_STATE = { 2 INITIAL: 0, 3 LOADING: 1, 4 OK: 2, 5 NG: 3, 6}

この前提で、reducers/users.ts の一例としては以下のようなものになるかと思います。なお以下ではユーザー一覧取得が失敗したときのアクションタイプFETCH_FAILUREを追加しています。

typescript

1import { REQUEST_STATE } from "../constants"; 2import { User } from "../models"; 3 4interface State { 5 fetchState: number 6 usersList: User[] 7 error: Error | null 8} 9 10interface Action { 11 type: string 12 payload?: { 13 users: User[] 14 } 15 error?: Error 16} 17 18export const initialState: State = { 19 fetchState: REQUEST_STATE.INITIAL, 20 usersList: [], 21 error: null, 22}; 23 24export const usersReducer = (state: State = initialState, action: Action): State => { 25 switch (action.type) { 26 case "FETCHING": 27 return { 28 ...state, 29 fetchState: REQUEST_STATE.LOADING, 30 error: null, 31 }; 32 case "FETCH_SUCCESS": 33 return { 34 ...state, 35 fetchState: REQUEST_STATE.OK, 36 usersList: action.payload?.users || [], 37 }; 38 case "FETCH_FAILURE": 39 return { 40 ...state, 41 fetchState: REQUEST_STATE.NG, 42 error: action.error || null 43 }; 44 default: 45 return state; 46 } 47} 48

apis/users.ts

axios.get の呼び出しシグニチャに対するジェネリクスは以下のようにするとよいでしょう。

typescript

1import axios from "axios"; 2import { usersIndex } from "../urls"; 3import { User } from "../models"; 4 5interface GetUsersResponse { 6 users: User[]; 7} 8 9export const fetchUsers = async () => { 10 try { 11 const res = await axios.get<GetUsersResponse>(usersIndex); 12 return res.data; 13 } catch (e) { 14 console.error(e); 15 throw e; 16 } 17} 18

components/Users_index.tsx

上記のようにしておくと Users_index.tsx は以下のように書いて、Typescriptのエラーは発生せずに、

  • .then(data => のところの dataGetUsersResponse に推論され、
  • state.usersList.map(user =>userUser に推論される

ものと思います。

tsx

1import React, { useEffect, useReducer } from "react"; 2import { fetchUsers } from "../apis/users"; 3import { initialState, usersReducer } from "../reducers/users"; 4 5export const Users = () => { 6 7 const [state, dispatch] = useReducer(usersReducer, initialState); 8 9 useEffect(() => { 10 dispatch({ type: "FETCHING" }); 11 fetchUsers() 12 .then(data => 13 dispatch({ 14 type: "FETCH_SUCCESS", 15 payload: { 16 users: data.users 17 } 18 }) 19 ).catch(e => 20 dispatch({ 21 type: "FETCH_ERROR", 22 error: e 23 }) 24 ) 25 }, []) 26 return ( 27 <> 28 <p>ユーザー一覧ページです</p> 29 { 30 state.usersList.map(user => 31 <div key={user.id}> 32 {user.name} 33 </div> 34 ) 35 } 36 </> 37 ) 38}; 39

追記

上記の回答コードに対する修正を追記しておきます。

  • fetchState の取り得る値の型エイリアス RequestState を追加
  • Action の typeプロパティが取り得る値の型エイリアス ActionType を追加
  • Usersコンポーネントで fetchState の値ごとに表示内容を振り分け

src/constants.ts

以下に差し替え

typescript

1const REQUEST_STATE = ['INITIAL', 'LOADING', 'OK', 'NG' ] as const; 2 3type RequestState = typeof REQUEST_STATE[number]; 4 5export type { RequestState }; 6 7

src/reducers/users.ts

diff

1-import { REQUEST_STATE } from "../constants"; 2+import { RequestState } from "../constants"; 3 import { User } from "../models"; 4 5 interface State { 6- fetchState: number 7+ fetchState: RequestState 8 usersList: User[] 9 error: Error | null 10 } 11 12+const actionTypes = ["FETCHING", "FETCH_SUCCESS", "FETCH_FAILURE"] as const; 13+type ActionType = typeof actionTypes[number]; 14+ 15 interface Action { 16- type: string 17+ type: ActionType 18 payload?: { 19 users: User[] 20 }

diff

1 export const initialState: State = { 2- fetchState: REQUEST_STATE.INITIAL, 3+ fetchState: "INITIAL", 4 usersList: [], 5 error: null, 6 };

diff

1 case "FETCHING": 2 return { 3 ...state, 4- fetchState: REQUEST_STATE.LOADING, 5+ fetchState: "LOADING", 6 error: null, 7 }; 8 case "FETCH_SUCCESS": 9 return { 10 ...state, 11- fetchState: REQUEST_STATE.OK, 12+ fetchState: "OK", 13 usersList: action.payload?.users || [], 14 }; 15 case "FETCH_FAILURE": 16 return { 17 ...state, 18- fetchState: REQUEST_STATE.NG, 19- error: action.error || null 20+ fetchState: "NG", 21+ error: action.error || new Error("unknown error") 22 }; 23 default: 24 return state;

src/components/Users_index.tsx

diff

1 export const Users = () => { 2 3- const [state, dispatch] = useReducer(usersReducer, initialState); 4+ const [{ fetchState, usersList, error }, dispatch] = useReducer(usersReducer, initialState); 5 6 useEffect(() => { 7 dispatch({ type: "FETCHING" }); 8

diff

1 users: data.users 2 } 3 }) 4- ).catch(e => 5- dispatch({ 6- type: "FETCH_ERROR", 7- error: e 8- }) 9- ) 10- }, []) 11+ ) 12+ .catch(e => 13+ dispatch({ 14+ type: "FETCH_FAILURE", 15+ error: e 16+ }) 17+ ) 18+ }, []); 19+ 20+ if (fetchState === "LOADING") { 21+ return <div>データ取得中...</div>; 22+ } 23+ 24 return ( 25 <> 26 <p>ユーザー一覧ページです</p> 27- { 28- state.usersList.map(user => 29- <div key={user.id}> 30- {user.name} 31- </div> 32- ) 33+ {error 34+ ? <div>エラーが発生しました。原因: {error.message} </div> 35+ : usersList.map(({ id, name }) => <div key={id}>{name}</div>) 36 } 37 </> 38 )

投稿2022/10/10 12:07

編集2022/10/10 19:04
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問