【React】React基礎編(TypeScript編)

いよいよReactにTypeScriptを導入する方向に舵を切ろうかと思っているので、その導入前の学習備忘録となります。

この記事は、下記の事について記載したいと思います。

  • ReactへのTypeScript導入方法
  • TypeScriptの基本情報
  • ReactでのTypeScript記述方法

一先ず、そこそこTypeScriptが使えるようになる事を目的とします。
TypeScriptの深掘りに関しては、また別の機会で行いたいです。

はじめに

JavaScriptでは、型を意識したコーディングが難しい言語ですが、型の曖昧さは開発規模が大きくなればなるほど致命的になってきます。

TypeScriptは、そのJavascriptの弱点である「静的型付けの定義」が行えます。
導入する事で開発がスムーズにすすむ事と思います。

良い事づくめのTypeScriptですが、やはり学習コストはかかってしまいます。
この記事では、サンプルを参照しつつTypeScriptに徐々に慣れていければと思います。

ReactへのTypeScriptの導入

今までのプロジェクトを継続しても良いのですが、導入コストを減らすため、今回から新しいプロジェクトにてTypeScriptを学んでいこうと思います。
 ※今回のプロジェクト名は「tsapp」

TypeScriptを導入するには、「create-react-app」に「–template typescript」を付与します。
では、コマンドプロンプトを起動し、下記コマンドを実行しましょう。

npx create-react-app [プロジェクト名(tsapp)] --template typescript

コマンドが正常に終了したら、作成したフォルダの構成を確認します。

フォルダ構造は下記になります。
ファイルの拡張子が「.tsx」となっているのが確認できます。
TypeScriptが導入されたことが確認できます。

また、TypeScriptの設定ファイル「tsconfile.json」が新しく追加されています。

一まず動作確認をしてみましょう。
コマンドプロンプトより、プロジェクトのルートディレクトリへ移動し、下記コマンドを実行します。
 →今までの起動方法と何ら変わらないです。

npm start

起動した画面になります。
「App.tsx」と表示されていますね!

次に、TypeScriptを少し使いやすくしたいので、「tsconfig.json」を少し修正したいと思います。

{
  "compilerOptions": {
    "target": "ESNext", // es5 > ESNext
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "baseUrl": "src", // 追記
    "downlevelIteration": true // 追記
  },
  "include": [
    "src",
  ],
}

baseUrl : moduleのインポート時に起点となるディレクトリを指定します。
相対パスで指定していたjsxファイルを、「src/」の相対パスで指定可能になります。

downlevelIteration:念のため。

TypeScriptの基本

プリミティブ型

変数宣言時に初期値として値を代入する場合、「型推論」として明示的に型を記述しなくても問題ありません。

下記は、初期値が設定されているため型の記載は任意ですが、サンプルの為記載しています。

// 変数
const str: string = 'サンプルです'; // 文字列
const num: number = 80; // 数値
const bol: boolean = true; // 真偽値
const emp: null = null; // 値なし
const udf: undefined = undefined; // 未定義
const bint: bigint = 10n; // 大きな整数
const uniq: symbol = Symbol("sample");

// 関数
function Test(num: number, str:string): string {
    return `${num} ${str}`;
}

// 「?」を使用する事で、引数の省略が可能
function Test2(str?: string): string {
    return str ?? 'サンプルです';
}

特殊な型(any, unknown, void)

特殊な型として、any, unknown, voidを解説します。

any

どんな型でも代入を許す型。
コンパイラの型チェックが行われないので、anyの使用はなるべく避けた方が良いでしょう。

const any: any = 100; // 何でも代入可能
console.log(any + 1); // -> 101

// 暗黙のany
function Sample(text) {
    console.log(text)
}

unknown

どんな型でも代入可能を許す型だが、値の操作は不可。
値の再代入が不可能なため、anyよりは安全に使用できます。

const unk: unknown = 100; // 何でも代入可能。ただし値の操作は不可
console.log(unk + 1); // -> error

void

関数の戻り値として設定する特殊な型。

// void:戻り値のない関数
function noReturnValue(): void {}

// 戻り値を返さない関数
function error(): never { throw new Error() }

複合型

型を複数宣言する事で、どれかに該当すればよい宣言方法です。

// 複合型での型定義
let test: string | number;

// 記述例
test = '文字列';
test = 80;

型エイリアス(type)

型に名前を付けて、新たに定義する事が可能。
またReactでは、Props等で使用するので、必須の知識になります。

// プリミティブ型の型定義
type numString = string | number;

// 記述例
const str: numString = 'てすと';
const num: numString = 80;


// オブジェクトの型
type drinkType = {
    title: string,
    price: number,
    material: string[]
};

// 記述例
const orange: drinkType = {
    title: 'オレンジジュース',
    price: 150,
    material: ['みかん', '炭酸']
};

配列の型

配列の型は、「プリミティブ型 + []」or「Array<プリミティブ型>」として宣言します。

// 配列(プリミティブ)の型定義
const ary1: string[] = ['藤原道長', '藤原野兼家', '藤原道綱', '藤原道隆'];

// 配列(ジェネリクス)の型定義
const ary2: Array<string> = ['藤原道長', '藤原野兼家', '藤原道綱', '藤原道隆'];


// ジェネリクスの記述サンプル
let mp = new Map<string, number>(
    ['藤原道長', 25],
    ['源倫子', 27]
);
let st = new Set<number>([1,2,3,4,5]);

タプル型

配列位置の型が固定される。
 ※NGの方は、配列三番目が数値設定ですが、二番目に数値が来ているのが原因になります。

// タプル型としての型定義
let person: [string, string, number]; // 文字列,文字列,数値

// 記述例(OKパターンとNGパターン)
person = ['藤原道長', '右大臣', 30]; // -> OK
person = ['藤原伊周', 22]; // -> NG

連想配列

連想配列のキーの型と、要素の型を指定します。
キーの値が固定出来ない場合は、オブジェクト型よりこちらを使用します。

// 連想配列での型定義
let brother: { [index: string]: string };

// 記述例
brother = {
    '長男': '藤原道隆',
    '次男': '藤原道兼',
    '三男': '藤原道長'
};

オブジェクト型

「?」を使うと、要素を省略する事が可能です。

// オブジェクトの型定義
let person: {
    name: string,
    wives?: number
};

// 記述例
person = {
    name: '藤原道長',
    wives: 2
}
person = {
    name: '紫式部' // wivesの省略可
}

ReactにTypeScriptを導入する

Props

Propsは、型エイリアス(type)で定義します。

// Propsの型定義は型エイリアス(type)で定義
type SampleProps = {
    name: string,
    num: number
};

// Propsでの記述方法
export default function Sample({ name, num }: SampleProps) {
    return (
        <div>{name} {num}</div>
    );
}

children

要素「children」はReactの要素になります。
そのため、汎用的な型であるReactNodeを設定としておけば問題ありません。

仮に厳密に行いたい場合は、「ReactElement, ReactText」等を設定します。

import { ReactNode } from "react";

type SampleProps = {
    name: string,
    num: number
};
export default function Sample({ name, num }: SampleProps) {
    return (
        <Parent>
            <div>{name} {num}</div>
        </Parent>
    );
}

// childrenの型定義
type ParentProps = {
    children: ReactNode
};
function Parent({children}: ParentProps) {
    return(
        <div>
            <div>タイトル</div>
            {children}
        </div>
    )
}

コンポーネント

FC型をインポートしてジェネリクスで型定義をします。

import { FC } from "react";

type SampleProps = {
    title: string,
    num: number
};
const Sample: FC<SampleProps> = ({title, num}) => {
    return <div>{title}</div>;
}
export default Sample;

State

Stateは、ジェネリクスで型を付与します。

const [ count, setCount ] = useState<number>(num);

createContext, useContext

createContext()の初期値に「as」で型を設定します。
useContext()では、「ContextType」を省略して型推論で指定しても問題ありません。

import { createContext, useContext } from "react";

// コンテキストの型定義
type ContextType = {
    title: string,
    int: number
}
const Context = createContext({} as ContextType);

export default function Sample() {
    const title: string = 'コンテキスト';
    const int: number = 120;
    return (
        <Context.Provider value={{ title, int }}>
            <Child />
        </Context.Provider>
    );
}

function Child() {
    const { title, int }: ContextType = useContext(Context);
    return(
        <div>
            <div>タイトル</div>
            <div>{title} {int}</div>
        </div>
    )
}

コンテキストの初期値が設定できる場合、ジェネリクスで設定する事も可能です。

import { createContext, useContext } from "react";

// コンテキストの型定義
type ContextType = {
    title: string,
    int: number
}
const Context = createContext<ContextType>({
    title: '',
    int: 0
});

export default function Sample() {
    const title: string = 'コンテキスト';
    const int: number = 120;
    return (
        <Context.Provider value={{ title, int }}>
            <Child />
        </Context.Provider>
    );
}

function Child() {
    const { title, int } = useContext<ContextType>(Context);
    return(
        <div>
            <div>タイトル</div>
            <div>{title} {int}</div>
        </div>
    )
}

useRef

useRefは、「HTML要素を参照」する目的で利用するのが一般的ですが、「データの一時保存先」として利用するケースもあります。

そのため、型の設定はおおまかに2通りあります。

HTML要素のrefに渡す場合

HTMLタグの各インターフェースを型とします。
インターフェースは、「HTML[タグ名]Element」です。
初期値は必ず「null」を与えて下さい。

import { useRef } from "react";

const Sample = () => {
    const ipt = useRef<HTMLInputElement>(null);
    return (
        <input ref={ipt} type='text' value='' />
    );
}
export default Sample;

インスタンス変数として利用する場合

今まで通りの型設定で問題ありません。

const ref = useRef(100); // 型推論
const ref = useRef<number>(); // ジェネリクス
const ref = useRef<string | numm>(null); // ジェネリクス(nullを含める)

useReducer

useReducerは、「stateの型」、「actionの型」、「更新関数」、「初期値」を設定します。
 ※更新関数、初期値は直接記述も可

import { useReducer } from "react";

// stateの型
type tyState = {
    title: string,
    price: number
};
// Actionの型
type tyAction = {
    type: string,
    payload: {
      price: number
    }
}
// Reducerの型
type tyReducer = (state: tyState, action: tyAction) => tyState;
// Reducerの初期値
const mtReducer = (state: tyState, action:tyAction): tyState => {
    switch(action.type) {
        case 'update':
            return {...state, price: action.price};
        default:
            return state;
    }
}
// 初期値
const fArg: tyState = {title:'', price:0};

const Sample = () => {
    const [ state, dispatch ] = useReducer<tyReducer>(mtReducer, fArg);

    return (
        <div>{state.title} {state.price}</div>
    );
}
export default Sample;

おわりに

Reactで使う頻度が高いTypeScriptのサンプルになります。

TypeScriptはもっと奥が深いです、ここで、あくまでReactで使用する点に着目しているので、TypeScriptの習得が必要であれば別途習得する必要があります。

Reactに導入するのであれば、ここら辺から始めてみるのが良いかと思います。