TypeScriptでuseStateを完璧に使いこなす10のステップ

TypeScriptのuseState関数の使用例と詳細なガイドTypeScript
この記事は約25分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

この記事を読めば、TypeScriptでのuseStateを完璧に使いこなすことができるようになります。

ReactとTypeScriptを組み合わせた開発では、状態管理が鍵となります。

useStateはその基本となる関数ですが、正しく活用するためのノウハウやテクニックが多数存在します。

この記事で、その全てを押さえて、より効果的な開発を目指しましょう!

●useStateとは

useStateは、Reactフックの1つであり、関数コンポーネント内で状態を持たせるための非常に強力なツールです。

TypeScriptと組み合わせることで、型の安全性を持った状態管理を実現することができます。

○TypeScriptでのuseStateの基本形

TypeScriptを使用する際のuseStateの基本形は次のようになります。

const [state, setState] = useState<型名>(初期値);

このコードでは、useStateを使ってstateという名前の状態を宣言しています。

setStateは、その状態を更新するための関数として定義されています。

また、useStateのジェネリクスを使用して、その状態が持つべきデータの型を指定します。

●useStateの使い方

○サンプルコード1:基本的なuseStateの使用

TypeScriptで数字のカウントを行うシンプルな例を紹介します。

import React, { useState } from 'react';

const Counter: React.FC = () => {
  const [count, setCount] = useState<number>(0);

  return (
    <div>
      <p>現在のカウント: {count}</p>
      <button onClick={() => setCount(count + 1)}>増加</button>
      <button onClick={() => setCount(count - 1)}>減少</button>
    </div>
  );
};

export default Counter;

このコードでは、useState<number>(0)を使って数値型のcountという状態を初期値0で作成しています。

ボタンをクリックすると、setCount関数を使ってcountの値が増減します。

実行すると、現在のカウントが表示されるとともに、増加と減少のボタンが表示されます。

これらのボタンをクリックするとカウントが増減します。

○サンプルコード2:複数の状態を管理する方法

複数の状態を管理する際も、useStateを複数回呼び出すことで簡単に実現できます。

import React, { useState } from 'react';

const UserInfo: React.FC = () => {
  const [name, setName] = useState<string>('');
  const [age, setAge] = useState<number>(0);

  return (
    <div>
      <input 
        type="text" 
        value={name} 
        onChange={e => setName(e.target.value)} 
        placeholder="名前を入力" 
      />
      <input 
        type="number" 
        value={age} 
        onChange={e => setAge(Number(e.target.value))} 
        placeholder="年齢を入力" 
      />
      <p>{name}さんは{age}歳です。</p>
    </div>
  );
};

export default UserInfo;

このコードでは、名前と年齢という2つの状態をuseStateを用いて管理しています。

入力フォームから名前や年齢を入力すると、下の文章にリアルタイムで反映されます。

実行すると、入力フォームが2つ表示されるとともに、下に名前と年齢が表示されます。

これらの入力フォームに情報を入力すると、下の文章が更新されます。

これにより、useStateを複数使用することで複数の状態を簡単に管理することができます。

○サンプルコード3:オブジェクトとしての状態の使用

useStateは、オブジェクトとしても状態を管理することができます。

import React, { useState } from 'react';

type UserInfo = {
  name: string;
  age: number;
};

const UserDetail: React.FC = () => {
  const [userInfo, setUserInfo] = useState<UserInfo>({ name: '', age: 0 });

  return (
    <div>
      <input 
        type="text" 
        value={userInfo.name} 
        onChange={e => setUserInfo({ ...userInfo, name: e.target.value })

} 
        placeholder="名前を入力" 
      />
      <input 
        type="number" 
        value={userInfo.age} 
        onChange={e => setUserInfo({ ...userInfo, age: Number(e.target.value) })}
        placeholder="年齢を入力" 
      />
      <p>{userInfo.name}さんは{userInfo.age}歳です。</p>
    </div>
  );
};

export default UserDetail;

このコードでは、userInfoという名前のオブジェクト型の状態を作成しています。

名前や年齢を入力すると、オブジェクトの中の情報が更新され、それがリアルタイムで文章に反映されます。

実行すると、入力フォームが2つ表示され、名前や年齢の情報がオブジェクトの中で一元管理されます。

このような形式は、関連する複数の情報をまとめて管理したい場合に非常に便利です。

○サンプルコード4:状態の更新関数の活用法

ReactのuseStateフックには、状態を直接更新するだけでなく、更新関数を使って状態を更新する機能も備わっています。

状態更新関数を使用することで、前の状態に基づいて新しい状態を計算することができます。

このコードでは、状態の更新関数を使って数字のカウントを行う例を表しています。

この例では、状態更新関数を使用して、前のカウントの状態に基づいて新しいカウントの状態を計算しています。

import React, { useState } from 'react';

const AdvancedCounter: React.FC = () => {
    const [count, setCount] = useState<number>(0);

    const increaseByTwo = () => {
        setCount(prevCount => prevCount + 2);
    };

    return (
        <div>
            <p>現在のカウント: {count}</p>
            <button onClick={increaseByTwo}>2つ増加</button>
        </div>
    );
};

export default AdvancedCounter;

この例のポイントは、setCount関数を呼び出す際に、前の状態を引数として取る状態更新関数を渡していることです。

prevCount => prevCount + 2の部分が状態更新関数になります。

この状態更新関数は、前のカウントの状態をprevCountとして受け取り、それに2を加えて新しい状態を返しています。

上記のコードをブラウザで実行すると、表示されるカウントは「現在のカウント:0」となります。

ここで、「2つ増加」ボタンをクリックすると、カウントが2ずつ増加して表示が更新されます。

状態更新関数を使用するメリットの一つは、前の状態を基に新しい状態を安全に計算できることです。

特に非同期処理などの中で複数回状態を更新する際に、状態更新関数を使用することで状態の不整合を防ぐことができます。

●useStateの応用例

○サンプルコード5:カスタムフックを使った例

Reactフックの魅力の一つは、カスタムフックを作成することができる点です。

カスタムフックを作成することで、状態管理のロジックを再利用可能な関数としてまとめることができます。

このコードでは、カスタムフックを使ってカウントアップとカウントダウンの機能を持ったカウンタを作成する例を表しています。

この例では、カスタムフックを使用してカウントのロジックを再利用しています。

import React, { useState } from 'react';

// カスタムフック
function useCounter(initialValue: number = 0) {
    const [count, setCount] = useState<number>(initialValue);
    const increment = () => setCount(prev => prev + 1);
    const decrement = () => setCount(prev => prev - 1);

    return { count, increment, decrement };
}

const CustomCounter: React.FC = () => {
    const { count, increment, decrement } = useCounter();

    return (
        <div>
            <p>現在のカウント: {count}</p>
            <button onClick={increment}>増加</button>
            <button onClick={decrement}>減少</button>
        </div>
    );
};

export default CustomCounter;

この例では、useCounterというカスタムフックを定義しています。

このカスタムフックは、状態としてカウントを持ち、それを増減させる関数も提供しています。

このカスタムフックを使用すると、任意のコンポーネントでカウントロジックを簡単に再利用することができます。

上記のコードをブラウザで実行すると、カウントが「現在のカウント:0」と表示され、増加と減少のボタンでカウントを操作することができます。

○サンプルコード6:非同期処理とuseStateの組み合わせ

ReactのuseStateを非同期処理と組み合わせて使用する場合、非同期の動作をスムーズに状態管理と合わせて行う方法が求められます。

ここでは、非同期処理でデータを取得してその結果を状態として管理するシンプルな例を取り上げます。

import React, { useState, useEffect } from 'react';

function FetchDataComponent() {
  // データの状態とデータ取得の完了を示す状態を設定
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // 非同期処理を行う関数
    async function fetchData() {
      const response = await fetch('https://api.example.com/data');
      const result = await response.json();
      setData(result);
      setLoading(false);
    }

    fetchData();
  }, []); // 空の依存配列を指定して、コンポーネントのマウント時に一度だけ実行する

  // ローディング中とデータ取得後の表示を分岐
  if (loading) {
    return <div>読み込み中...</div>;
  }

  return (
    <div>
      データ:{data}
    </div>
  );
}

export default FetchDataComponent;

このコードでは、useEffectを使って非同期処理を行っています。

fetchDataという非同期関数を定義し、その中でデータを取得しています。

取得したデータはsetDataを使って状態を更新し、setLoadingを使ってデータの読み込みが完了したことを表しています。

この例では、APIからデータを取得してそのデータを表示する簡単なコンポーネントを作成しています。

このコンポーネントを画面上に表示すると、まず「読み込み中…」というテキストが表示され、APIからのデータ取得が完了すると、「データ:取得したデータ」という形式でデータが表示されることになります。

非同期処理とuseStateの組み合わせは非常に強力です。

しかし、注意が必要な点もいくつかあります。

例えば、非同期処理が完了する前にコンポーネントがアンマウントされた場合、状態の更新によってエラーが発生する可能性があります。

このようなケースを避けるための対策として、非同期処理の完了を待つためのフラグを設定する方法などが考えられます。

また、複雑な非同期処理を行う場合やエラーハンドリングを考慮する必要がある場合は、useReducerや外部ライブラリを使用することで、より柔軟かつ安全に非同期処理と状態管理を組み合わせることができます。

○サンプルコード7:外部ライブラリとの連携例

ReactのuseStateフックは非常に強力であり、さまざまなシチュエーションで使用することができます。

それだけでなく、多くの外部ライブラリとの組み合わせにも対応しており、これによりアプリケーションの拡張性や柔軟性が向上します。

ここでは、外部ライブラリとuseStateを連携させる典型的な例を取り上げ、その活用方法を詳しく解説していきます。

具体的には、人気のライブラリである「lodash」を使用し、useStateとの連携を表すコードを紹介します。

この例では、lodashの関数を使って複雑な配列やオブジェクトの操作を行い、その結果をuseStateで管理する方法を紹介しています。

import React, { useState } from 'react';
import _ from 'lodash';

function App() {
  // 複雑なオブジェクトの初期状態を設定
  const [data, setData] = useState([
    { id: 1, name: '田中', age: 25 },
    { id: 2, name: '鈴木', age: 30 },
    { id: 3, name: '佐藤', age: 20 },
  ]);

  // lodashを使用して、年齢が25歳以上のデータのみをフィルタリング
  const filterData = () => {
    const filtered = _.filter(data, (o) => o.age >= 25);
    setData(filtered);
  };

  return (
    <div>
      <button onClick={filterData}>25歳以上のデータのみ表示</button>
      <ul>
        {data.map((item) => (
          <li key={item.id}>{item.name} ({item.age}歳)</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

このコードでは、lodashのfilter関数を使用して、年齢が25歳以上のデータのみを表示する機能を実装しています。

初めに3つのデータが表示されるが、ボタンをクリックすると25歳以上のデータのみが表示されるようになります。

また、このように外部ライブラリの関数とuseStateを組み合わせることで、コードの読みやすさやメンテナンス性も向上します。

特に大規模なアプリケーションや複雑なデータ操作を行う場合、外部ライブラリの活用は非常に効果的です。

この方法を活用すれば、既存のライブラリやフレームワークの機能を最大限に活用しながら、アプリケーションの状態管理を行うことができます。

外部ライブラリの選び方や連携方法については、アプリケーションの要件や目的に応じて適切に選択することが大切です。

続いて、このコードを実行すると、初めに「田中」「鈴木」「佐藤」の3名が表示されます。

しかし、「25歳以上のデータのみ表示」ボタンをクリックすると、年齢が25歳未満の「佐藤」がリストから除外され、「田中」と「鈴木」のみが表示されるようになります。

○サンプルコード8:useEffectとの組み合わせ

ReactのuseStateとuseEffectは、非常に強力な組み合わせとして知られています。

useStateで状態を管理し、useEffectを用いて状態が変更されたときのサイドエフェクトを取り扱うことができます。

このコードでは、useStateを使ってカウンタの値を管理し、useEffectを使用してカウンタの値が更新された際に何らかの処理を行うシンプルな例を表しています。

この例では、カウンタの値が変更される度に、タイトルにその値を表示する動きを見せています。

import React, { useState, useEffect } from 'react';

function CounterWithEffect() {
    const [count, setCount] = useState(0);

    useEffect(() => {
        document.title = `カウント:${count}`;
        return () => {
            document.title = "Reactアプリ";
        };
    }, [count]);

    return (
        <div>
            <button onClick={() => setCount(count + 1)}>カウントアップ</button>
            <p>現在のカウント: {count}</p>
        </div>
    );
}

export default CounterWithEffect;

このコードには、カウントアップボタンがあり、ボタンをクリックするとカウントが増加します。

また、useEffect内で、カウントの値に応じてドキュメントのタイトルが変わる処理が記述されています。

具体的には、カウントが増加するたびに、例えば「カウント:3」というようにブラウザのタブのタイトルが変更されます。

この例をブラウザで実行すると、ボタンをクリックするたびにカウントが増加し、その都度タイトルが変更されることがわかります。

さらに、カスタマイズとして、カウントが10を超えた場合にアラートを表示させる方法も考えられます。

useEffect(() => {
    if (count > 10) {
        alert('カウントが10を超えました!');
    }
}, [count]);

このカスタマイズを行うと、カウントが10を超えた瞬間にアラートが表示される仕組みとなります。

このように、useEffectは状態の変更に応じて様々なサイドエフェクトを実装するのに非常に役立ちます。

○サンプルコード9:動的なリストの状態管理

Reactの中心的なフックであるuseStateは、動的なリストの状態管理にも非常に便利です。

一般的に、ユーザーからの入力やAPIからのデータフェッチなど、アプリケーションの中で動的に変化するリストデータを扱う場面が多く見られます。

ここでは、useStateを用いて動的なリストの状態をどのように管理するかを詳しく見ていきましょう。

import React, { useState } from 'react';

function DynamicList() {
  // 初期状態として空の配列を設定
  const [items, setItems] = useState([]);

  const addItem = () => {
    // 新しいアイテムをリストに追加する関数
    const newItem = `アイテム ${items.length + 1}`;
    setItems(prevItems => [...prevItems, newItem]);
  };

  const removeItem = (index) => {
    // 指定されたインデックスのアイテムを削除する関数
    setItems(prevItems => prevItems.filter((item, i) => i !== index));
  };

  return (
    <div>
      <button onClick={addItem}>アイテム追加</button>
      <ul>
        {items.map((item, index) => (
          <li key={index}>
            {item}
            <button onClick={() => removeItem(index)}>削除</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

このコードでは、空の配列を初期状態として持つitemsという状態をuseStateを用いて作成しています。

addItem関数をクリックすると、items配列に新しいアイテムが追加されます。

また、各アイテムの隣に配置された削除ボタンをクリックすると、そのアイテムがリストから削除されます。

実行すると、ユーザーは「アイテム追加」ボタンをクリックしてリストに新しいアイテムを追加できます。

そして、各アイテムの右側に表示される「削除」ボタンをクリックすると、そのアイテムがリストから消えます。

また、動的なリストを扱う際には、配列のインデックスをキーとして使用するのは避けるべきです。

なぜなら、リストが再並べられたり、アイテムが追加・削除された場合、不意の動作が起こる可能性があるからです。

安全な側面から、アイテムごとにユニークなIDを持たせることを推奨します。

動的なリストの状態管理は、カスタムフックを作成することでさらにシンプルになります。

例えば、次のようなuseListカスタムフックを考えてみましょう。

import { useState } from 'react';

function useList(initialItems = []) {
  const [items, setItems] = useState(initialItems);

  const addItem = (itemContent) => {
    setItems(prevItems => [...prevItems, itemContent]);
  };

  const removeItem = (index) => {
    setItems(prevItems => prevItems.filter((item, i) => i !== index));
  };

  return [items, addItem, removeItem];
}

このカスタムフックを使用すると、先程のDynamicListコンポーネントは更にシンプルに書き換えることができます。

カスタムフックの導入は、コードの再利用や保守性の向上に非常に役立ちます。

○サンプルコード10:useStateを用いたフォームのバリデーション

Reactの開発において、フォームの入力値のバリデーションは非常に一般的なタスクです。

useStateを使用して、簡単にこのバリデーションを実装することができます。

今回の例では、シンプルなメールアドレスのバリデーションを行います。

このコードでは、useStateを使ってフォームの入力値とエラーメッセージの状態を管理しています。

この例では、入力されたメールアドレスが有効かどうかをチェックして、無効な場合にエラーメッセージを表示します。

import React, { useState } from 'react';

function EmailForm() {
  // 入力値の状態
  const [email, setEmail] = useState('');
  // エラーメッセージの状態
  const [error, setError] = useState('');

  // メールアドレスのバリデーション関数
  const validateEmail = (input) => {
    const re = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/;
    return re.test(String(input).toLowerCase());
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    if (validateEmail(email)) {
      setError(''); // エラーメッセージをクリア
      // その他の処理
    } else {
      setError('無効なメールアドレスです。'); // エラーメッセージを設定
    }
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          placeholder="メールアドレス"
        />
        {error && <p style={{ color: 'red' }}>{error}</p>}
        <button type="submit">送信</button>
      </form>
    </div>
  );
}

export default EmailForm;

このコードを実行すると、フォームが表示され、ユーザーがメールアドレスを入力することができます。

送信ボタンをクリックすると、入力されたメールアドレスが正規表現に基づいてチェックされ、無効な場合は赤色のエラーメッセージが表示されます。

正しいメールアドレスを入力すると、エラーメッセージは表示されません。

●注意点と対処法

useStateを使ったバリデーションの際、注意すべき点は主に次の2つです。

  1. 正規表現の使用には注意が必要です。
    特定のケースにしか対応しない正規表現を使用すると、期待通りの結果が得られない場合があります。
    使用する正規表現は、十分にテストすることをおすすめします。
  2. エラーメッセージはユーザーフレンドリーであることが望ましいです。
    具体的なエラーの原因をユーザーに伝えることで、ユーザーエクスペリエンスが向上します。

例えば、上記のコードではメールアドレスの形式のみをチェックしていますが、さらに詳細なチェックや複数のバリデーションルールを追加する場合、エラーメッセージもそれに応じて適切に変更する必要があります。

●カスタマイズ方法

useStateを使ったフォームのバリデーションは、さまざまなカスタマイズが可能です。

例として、パスワードの強度をチェックするバリデーションを追加する方法を紹介します。

まず、新しい状態passwordpasswordErrorを追加します。

そして、validatePassword関数を定義して、パスワードの強度をチェックします。

const [password, setPassword] = useState('');
const [passwordError, setPasswordError] = useState('');

const validatePassword = (input) => {
  // ここにパスワードの強度をチェックするロジックを書く
  if (input.length < 8) {
    return false; // 8文字未満の場合は無効
  }
  // 他の条件を追加する場合はこちらに
  return true;
};

const handlePasswordChange = (e) => {
  setPassword(e.target.value);
  if (!validatePassword(e.target.value)) {
    setPasswordError('パスワードは8文字以上で入力してください。');
  } else {
    setPasswordError('');
  }
};

このようにして、useStateを用いたフォームのバリデーションをさまざまなシチュエーションに応じてカスタマイズすることができます。

まとめ

useStateを使用したフォームのバリデーションは、シンプルかつ効果的な方法であることがわかりました。

注意点やカスタマイズ方法も理解することで、より実践的なアプリケーションの開発が進められるでしょう。

この技術をうまく活用して、ユーザーフレンドリーなアプリケーションの開発を目指しましょう。