11
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

記事投稿キャンペーン 「2024年!初アウトプットをしよう」

【React】JavaScriptからTypeScriptに移行してみた

Posted at

概要

前回、「文字の類似度」からポケモン名をサジェストするコンポーネントを実装しました。

ただ、勢いで作ってしまったため、JavaScriptベースのReactアプリで作ってしまいました。そのため、TypeScriptベースのReactに移行したいと思います。

移行作業

今回の移行方法としては、TypeScriptベースのReactで新規のアプリを新規で作成し、その上に追加開発分を引っ越しします。

1. 新しくTypeScriptベースのReactアプリを作成

下記コマンドで新しくTypeScriptベースのReactアプリを作成します。
名前は何でも良いです。

$ npx create-react-app new-app --template typescript

2. 追加のライブラリをインストール

今回のプロジェクトは、sassしか追加インストールしていないので直接コマンドをたたきました。

$ yarn add sass

3. 追加開発分ファイルの移行

旧プロジェクトから、新プロジェクトに追加開発分のファイルを持ってきます。拡張子がjsのファイルはtsxに書き換えます。

私のプロジェクトでは以下のファイルのみでした。

旧プロジェクト 新プロジェクト
App.js App.tsx
pokeNameList.js pokeNameList.tsx
App.scss App.scss

4. JavaScriptをTypeScriptに書き換え

pokeNameList.tsxは、ただの配列が定義されているだけだったのでファイル名の修正だけで問題ありませんでした。

App.tsxについては、下記の変更を行いました。

一応、ファイルも記載しておきます。

移行前のApp.js
App.js
import React, { useState, useEffect } from 'react';
import styles from './App.scss';
import { pokeNameList } from './pokeNameList';

const App = () => {
  return <SuggestForm />;
};

const SuggestForm = () => {
  const [value, setValue] = useState('');
  const suggestNum = 15 ;
  const wordList = pokeNameList;
  const [suggestions, setSuggestions] = useState([]);

  useEffect(() => {
    const suggestions = getSuggest(hiraganaToKatakana(value), wordList, suggestNum);
    setSuggestions(suggestions);
  }, [value, wordList]);

  // イベント
  const handleChange = (event) => {
    const updatedValue = event.target.value;
    setValue(updatedValue);
  };
  /**
  * クリックされたワードを検索ボックスにセット
  * @param {word} str1 文字列
  */
  const handleClickWord = (word) => {
    setValue(word); 
  };
  /**
  * レーベンシュタイン距離の計測
  * @param {string} str1 文字列1
  * @param {string} str2 文字列2
  * @return {array} レーベンシュタイン距離の計測結果
  */
  const levenshteinDistance = (str1, str2) => {
    const len1 = str1.length;
    const len2 = str2.length;
    
    const dp = [];
    
    for (let i = 0; i <= len1; i++) {
      dp[i] = [];
      for (let j = 0; j <= len2; j++) {
        if (i === 0) {
          dp[i][j] = j;
        } else if (j === 0) {
          dp[i][j] = i;
        } else {
          dp[i][j] = 0;
        }
      }
    }
    
    for (let i = 1; i <= len1; i++) {
      for (let j = 1; j <= len2; j++) {
        if (str1[i - 1] === str2[j - 1]) {
          dp[i][j] = dp[i - 1][j - 1];
        } else {
          dp[i][j] = 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
        }
      }
    }
    
    return dp[len1][len2];
  };
  /**
  * 複数の文字列をレーベンシュタイン距離で評価した結果をソートし返却する
  * @param {string} searchWord 検索文字列
  * @param {array} wordList 検索対象の一覧
  * @param {number} n 返却する文字列の数
  * @return {array} 評価順のデータ
  */
  const getSuggest = (searchWord, wordList, n) => {
    return wordList
      .sort((a, b) => levenshteinDistance(searchWord, a) - levenshteinDistance(searchWord, b))
      .slice(0, n);
  };
  /**
  * ひらがなをカタカナに変換する
  * @param {string} s ひらがな文字列
  * @return {string} カタカナ文字列
  */
  const hiraganaToKatakana = (s) => {
    return s.normalize('NFKC').replace(/[\u3041-\u3096]/g, function(match) {
      return String.fromCharCode(match.charCodeAt(0) + 0x60);
    });
  };

  return (
    <div>
      <div>
        <label name="pokemonSearch">ポケモン名 </label>
        <input id="pokemonSearch" type="search" value={value} onChange={handleChange} autoComplete="off" />
      </div>
      <div id="wordList">
        <p>もしかして...</p>
        {suggestions.map((word, index) => (
          <span
            key={index}
            className="keyword"
            onClick={() => handleClickWord(word)}
          >
            {word}
          </span>
        ))}
      </div>
    </div>
  );
};

export default App;
移行後のApp.tsx
App.tsx
import React, { useState, useEffect } from 'react';
import './App.scss';
import { pokeNameList } from './pokeNameList';

const App = () => {
  return <SuggestForm />;
};

const SuggestForm = () => {
  const [value, setValue] = useState<string>('');
  const suggestNum: number = 15 ;
  const wordList: string[] = pokeNameList;
  const [suggestions, setSuggestions] = useState<string[]>([]);

  useEffect(() => {
    const suggestions: string[] = getSuggest(hiraganaToKatakana(value), wordList, suggestNum);
    setSuggestions(suggestions);
  }, [value, wordList, suggestNum]);

  // イベント
  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const updatedValue = event.target.value;
    setValue(updatedValue);
  };
  const handleClickWord = (word: string) => {
    setValue(word); // クリックされたワードを検索ボックスにセットする
  };
  /**
  * レーベンシュタイン距離の計測
  * @param {string} str1 文字列1
  * @param {string} str2 文字列2
  * @return {array} レーベンシュタイン距離の計測結果
  */
  const levenshteinDistance = (str1: string, str2: string) => {
    const len1 = str1.length;
    const len2 = str2.length;
    
    const dp: number[][] = [];
    
    for (let i = 0; i <= len1; i++) {
      dp[i] = [];
      for (let j = 0; j <= len2; j++) {
        if (i === 0) {
          dp[i][j] = j;
        } else if (j === 0) {
          dp[i][j] = i;
        } else {
          dp[i][j] = 0;
        }
      }
    }
    
    for (let i = 1; i <= len1; i++) {
      for (let j = 1; j <= len2; j++) {
        if (str1[i - 1] === str2[j - 1]) {
          dp[i][j] = dp[i - 1][j - 1];
        } else {
          dp[i][j] = 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
        }
      }
    }
    
    return dp[len1][len2];
  };
  /**
  * 複数の文字列をレーベンシュタイン距離で評価した結果をソートし返却する
  * @param {string} searchWord 検索文字列
  * @param {array} wordList 検索対象の一覧
  * @param {number} n 返却する文字列の数
  * @return {array} 評価順のデータ
  */
  const getSuggest = (searchWord: string, wordList: string[], n: number) => {
    return wordList
      .sort((a, b) => levenshteinDistance(searchWord, a) - levenshteinDistance(searchWord, b))
      .slice(0, n);
  };
  /**
  * ひらがなをカタカナに変換する
  * @param {string} s ひらがな文字列
  * @return {string} カタカナ文字列
  */
  const hiraganaToKatakana = (s: string) => {
    return s.normalize('NFKC').replace(/[\u3041-\u3096]/g, function(match) {
      return String.fromCharCode(match.charCodeAt(0) + 0x60);
    });
  };
  return (
    <div>
      <div>
        <label htmlFor="pokemonSearch">ポケモン名 </label>
        <input id="pokemonSearch" type="search" value={value} onChange={handleChange} autoComplete="off" />
      </div>
      <div id="wordList">
        <p>もしかして...</p>
        {suggestions.map((word, index) => (
          <span
            key={index}
            className="keyword"
            onClick={() => handleClickWord(word)} 
          >
            {word}
          </span>
        ))}
      </div>
    </div>
  );
};

export default App;

5. 動作確認

$ yarn start

Animation4.gif

問題なく動いていますね!

6. Gitへ変更内容をプッシュ

新プロジェクトのgitの紐づけが無い状態になっているので、旧プロジェクトのGitへ紐づけを行います。

  • 新プロジェクト内の.gitフォルダを削除
  • 旧プロジェクト内の.gitフォルダを新プロジェクトにコピーする
  • 変更内容をコミットする(下記コマンド参照)
    $ git add --all
    $ git commit -m "JavaScript から TypeScriptへ移行"
    $ git push origin HEAD
    

終わりに

今回は、JavaScriptベースのReactアプリをTypeScriptベースのReactアプリに移行する方法をまとめました。

アプリの開発量が多いと、こんなに簡単にはいかないと思いますが、参考程度になれば幸いです。

ここまで読んでいたきありがとうございました。

11
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?