118
Help us understand the problem. What are the problem?

はじめに

この記事は
Qiita Advent Calendar 2021
タイムリープTypeScript 〜TypeScript始めたてのあの頃に知っておきたかったこと〜

18日目の記事です。

参加理由は、私がここ1年近くTypeScriptを使っての開発を行なってきました。
ちょうどアウトプットできそうなアドベンドカレンダーがあったことと@suinさんからコメントいただきたいなという思いで、参加しました。

  • そもそもなんで使っているかを振り返って考える
  • 今後も意識したい

今記事は上記の目的で書いてみました。

また現在はReact+TypeScriptでの技術スタックでの開発が多いので
若干意見が偏る可能性もありますがそこはご了承願います。

気づきから意識したいと思ったこと

1.anyとasを使わない

as(型アサーション)

asはTypeScriptで推論される型を上書きしています。
なので、asを使用するとTypeScript仕様から自分仕様に変わります。自分仕様という言葉怖いですよね。
なるべくTypeScript推論の型を上書きをしない方がいいとう考えに変わり、使うことが減りました。
使っている例でぱっと思いつくのはObject.keysでの処理の際にasを使用します。

const lib = {
  react: { name: 'リアクト' },
  vue: { name: 'ビュー' },
  svelte: { name: 'スヴェルテ' },
};

type LibKeyType = keyof typeof lib // libのkeyだけの型生成

(Object.keys(lib) as LibKeyType[]).map((key) => {
  const data = lib[key];
  console.log(data);
});

any

TypeScriptのany型は、どんな型でも代入を許す型です。
参照:https://typescriptbook.jp/reference/values-types-variables/any

TypeScriptで型定義していくと、ん?なんでここの処理注意されるんだ?わからん anyだ!
とやってしまっちゃってたのですが、anyにすると結局実行してみないとエラーに気づけない時があるんですよね。
そこで開発スピードを早くしているようにみえた魔法が解けました。

そこからは、anyを使わず開発を行い実行前にエラーなどを知ることができ気持ちよく開発できています。

2.とにかく型を付与する

TypeScriptさわりたての頃は、どこに型を付与するのが正しいのか、どこはいらないのかわからずなんとなく書いていました。
なので、なんとなく書いている状態が続いていて理解がしっかり理解ができていませんでした。

そこで、とりあえず型が付与できる箇所にどんどん型を付与を行い、
TypeScriptの参考書やネットにあるドキュメントなどを読みながら引き算を行うことで
不要な使い方を無くし型安全に開発が行えるようになったことやTypeScriptの知識を増やせたと思います。

このやり方は、僕的にはよかった内容にもなるので
ぜひ色々な記事を見て自分にあった学習の仕方を見つけてもらえますと幸いです

3.ジェネリクスを使う

あらゆる型で同じコードを使おうとすると、型の安全性が犠牲になります。逆に、型の安全性を重視しようとすると、同じようなコードを量産する必要が出てコードの共通化が達成しづらくなります。こうした問題を解決するために導入された言語機能がジェネリクスです。ジェネリクスを用いると、型の安全性とコードの共通化を両立することができます。 
参照:https://typescriptbook.jp/reference/generics

上にも書いてあるのですが、このジェネリクスを使うことで量産される型の共通化や
処理を共通化することができかつ型安全(正しく使えば)できるので今の開発でとても助かってます。

こちらの例はfetchのresponseに対して型をジェネリクスを使って共通化させています。

response.d.ts
export type fetchResponseType<T> = {
  status: number
  data: T
}

Reactを使う時にも、カスタムフックを作る時に共通化させる時にも役立っています。
以下の記事で作成について説明してます

useArrayOperation.ts
import { useState } from "react";
import { deepCopy } from "../../../../utils/deepCopy";

export const useArrayOperation = <T>( data: T[] ) => {
  const [array, setArray] = useState(data)

  // 要素追加
  const arrayAdd = (data: T) => {
    setArray([...array, data]);
  }
  // 要素削除
  const arrayRemove = (index: number) => {
    const result = deepCopy(array);
    result.splice(index, 1);
    setArray(result)
  }
  // 要素更新
  const arrayUpdate = (index: number, data: T) => {
    const result = deepCopy(array);
    result.splice(index, 1, data);
    setArray(result)
  }

  return [
  array, 
  setArray, 
  { arrayAdd, arrayRemove, arrayUpdate }
] as const
}

React + TypeScriptで気づいたこと

実際にReactとTypeScriptで開発する機会が多いので、
開発する上でここで型付与することで、より開発のスピードがあがった!
もしくは、将来を考えた上でこうしておいたほうが良さそうと思ったことを書きます。

1.Redux-Toolkitに型定義をする

もしかしたらRedux-ToolkitとTypeScriptを使用している人は、当たり前に使っているかと思われますが、
僕は使っていなかったので書きます。
ほとんどはRedux-Toolkitのドキュメントに記載されています。

Store型定義

store.ts
// それぞれのSliceを呼び出して結合する
const store = configureStore({
  reducer: {
    // 識別する名前: importしてきたReducer名
    user: userReducer,
    chat: chatReducer,
  }
})

export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch;
export default store

export type RootState = ReturnType<typeof store.getState>

.getStateで結合させたSliceの中で定義しているstateの型を取り出し、型総合体ができてますね。

slice側でstate型総合体を定義してあげることができます。
useSelectorで特定のstateを取り出す際に、型推論が効いた状態で取り出すことができます。

userSlice.ts
export const selectUser = (state: RootState) => state.user.data

Redux PayloadActionを使ってより型安全にReduxを使う

ReduxのSliceでfetchを行い、帰ってきたデータをstateに保持させるケースをよく記述すると思います。
もしくはactionで状態変更などを行うことは多々あると思います。

そのactionを実行する際に,第二引数のactionへの型定義にPayloadActionを定義すると、呼び出し元、実行箇所で適切な処理を行うことができる。

T: 呼び出し元で、渡されるデータ。今回の例だとid: numberというオブジェクト

slice.ts(action実行箇所)
addBotIdState: (state, action: PayloadAction<{id: number}>) => {
  const data = action.payload
  state.detailUserId = data.id
},

User.tsx
const dispatch = useDispatch()
// idの値がnumberであると適切に処理が通る。
const addBotId = () => {
  dispatch(addUserIdState({ id: props.data.id })) 
}

// number以外だと警告がでる。
const addUserId = () => {
  dispatch(addUserIdState({ id: '1' })) // 型 'string' を型 'number' に割り当てることはできません。
} 

PayloadActionで型定義を行っていない場合、anyな値を受け取るという型判定されるので、
どんな値でも通りますし、実行後にしかエラーにならないということになります。助かります。

2.Propsでemotionのスタイルを受け取る際の型定義

下記にメモとして残しています。
styleのpropsを受け取る際に、型定義をする際に共通化させておいた方がいいというお話しです。

3.もっと早く出会いたかった記事

Pickや、Omitに挑戦してみようと思ったきっかけになった記事になります。

終わり

まだまだTypeScriptビギナーですが、書くのは楽しいので書き続けていき
TypeScript少しだけ理解している人になりたいです。

参考

最後にお世話になったサイト/記事/本の紹介です :medal:

TypeScript Deep Dive
サバイバルTypeScript
TypeScriptロードマップ
Redux Toolkit TypeScript Quick Start
プログラミングTypeScript――スケールするJavaScriptアプリケーション開発

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
118
Help us understand the problem. What are the problem?