株式会社グローバルゲート公式ブログ

Javascriptフレームワーク「React」の特徴と実際の活用サンプル

※かわいいReactのロゴはさわらつきさんのKawaiiLogosをお借りしました。

こんにちは、株式会社グローバルゲートのモーリーです。
10月というのに暑い日が続きます。9月から秋のはずなのにどうして…。

さて、今回はJavascriptフレームワークであるReactを実際の使用例を交えてご紹介したいと思います。 
 
現在のWebアプリケーション開発ではプレーンなJavascriptを使用するケースは稀で、開発の効率化や管理のためにフレームワークを使用することが一般的です。 
ReactはMeta(Facebook・Instagramなどの運営会社)によって開発されたJavaScriptフレームワークで、Metaが自身で展開するサービスをはじめ多くのWebアプリケーションで採用されています。 
また、React NativeというReactと基本設計を同一とするスマートフォン向けフレームワークもあり、こちらも多くの採用例があります。

Google TrendsでReactと競合するフレームワークであるVue、Angular、Svelteについて調べてみましたが、Reactの検索数が頭一つ抜けています。
Javascriptフレームワークとしては最も高いシェアを持っていると考えていいでしょう。

Reactと人気や採用数を二分するVueについては過去記事もご覧ください。 
近年人気のJavascriptフレームワーク「Vue.js」でできるインタラクティブな制作事例

Reactを使用するメリット 

Reactが多くの開発者に使われ、規模を問わず様々なWebアプリケーションで採用される理由はどこにあるのでしょうか? 
それには次のようなメリットが挙げられます。

コンポーネントベースアーキテクチャ

Reactの特徴として外せない点が、Reactによるアプリケーションはコンポーネントの集合体である点です。 
 
コンポーネントは小規模なパーツであり、コンポーネント単位で開発を進めることで分業体制が容易となります。 
各コンポーネントを再利用することでソースコードの一元化や効率化も行えます。 

仮想DOMによる高速な画面描画

WebページはDOM(Document Object Model)という階層構造を持ったオブジェクトで構成されています。 
このDOMの再描画は多くのメモリを使用する負荷の高い作業ですが、ReactはこのDOMの仮想コピーである仮想DOMを使用し、高速で効率的な画面表示を実現しています。

JSX(Javascript XML)の採用

ReactはJSX(Javascript XML)というJavscriptとHTMLが混在したような記法によりソースコードを記述し、コンパイラによってブラウザで動作するプレーンなJavascriptに変換します。 
基本はHTMLとJavascriptですので、新たな言語を習得する学習コストも最小限で済みます。

エコシステム

Reactはオープンソース(MITライセンス)で提供されています。 
そのため、世界中の技術者がバグフィックスや機能の追加に貢献し、サードパーティー製のプラグインも豊富に公開されています。 
ユーザー数が多いために情報を得やすいこともReactを扱う上で助けとなるでしょう。

スマートフォンアプリへの展開

MetaはReactと同じ技術基盤であるReact Nativeというフレームワークもリリースしています。 
これはAndroid、iOS用アプリケーション開発のためのフレームワークであり、Reactと同じ文法で記述することができます。 
そのためWebアプリ・スマートフォンアプリの両展開を共通化されたコードで行うことが可能であり、スマートフォン全盛の現在では大きなメリットとなります。

実際に簡単なアプリケーションを作ってみる

ということで、Reactは多くの強みがあるフレームワークです。 
次は実際にReactを使って簡単なWebアプリケーションを作ってみたいと思います。

共通

いずれの場合も、まずターミナルからコマンドを入力してプロジェクトを作成します。

npx create-react-app プロジェクト名

これでプロジェクト名のフォルダが作成され、その中に必要なファイルが生成されます。

このプロジェクト名フォルダをエディタで開き、コードを書いていきます。
srcフォルダ配下のファイルがソースコードとなりますので、基本的にはsrc配下のみを修正することになります。

また、npm start とコマンドを打つことで開発サーバーが起動し、ローカルでアプリの確認ができるようになります。

create-react-app が完了した直後はReactのロゴが表示されるサンプルページとなっています。

基本的な構文

ReactのコンポーネントはHTMLを返り値とする関数になります。
その返り値内で変数や関数を持つことができるため、動的な表現を行うことができます。

//importで必要なクラスやCSSを読み込む 
import React, {useState} from 'react'; 
import 'bootstrap/dist/css/bootstrap.min.css'; 
 
//関数定義 
//この関数は最終的に出力されるHTMLを返す必要があります。 
function App() { 
//変数を定義したり 
  const [year, setYear] = useState('2024'); 
  ... 
 
  //関数を定義したり 
  const convertToEra = (e) => { 
  ... 
  }; 
 
 
 
  //返り値としてHTMLを返す 
  //内部に変数や関数を持たせることもできる 
  return ( 
    <div className="container">... 
    </div>); 

 
export default App; 

この基本を踏まえ、いくつかアプリケーションを作ってみましょう。

 BMI計算機

身長と体重を入力したらBMIを出力してくれるアプリケーションを作ってみましょう。

BMIは 体重 / 身長*身長 で計算できます。

オータニサンが肥満と判定されてしまうのでBMIを健康の指針に使うのはよくないと言われています

App.jsに以下のコードを記述します。
別途見栄えのためにReact-Bootstrapをインストールしました。

import React, {useState} from 'react'; 
 
import 'bootstrap/dist/css/bootstrap.min.css'; 
import './App.css'; 
 
 
 
function App() { 
  const [height, setHeight] = useState(''); 
  const [weight, setWeight] = useState(''); 
  const [bmi, setBmi] = useState(null); 
  const [message, setMessage] = useState(''); 
 
  // BMIを計算する関数 
  const calculateBMI = (e) => { 
    e.preventDefault(); 
 
    // 身長と体重が入力されていない場合は計算しない 
    if (!height || !weight) { 
      setMessage('身長と体重を入力してください'); 
      return; 
    } 
 
    // BMIの計算 
    const heightInMeters = height / 100; // 身長をcmからmに変換 
    const bmiValue = (weight / (heightInMeters * heightInMeters)).toFixed(2); 
 
    setBmi(bmiValue); 
 
    // BMIの範囲に応じてメッセージを設定 
    if (bmiValue < 18.5) { 
      setMessage('低体重です'); 
    } else if (bmiValue >= 18.5 && bmiValue < 24.9) { 
      setMessage('標準体重です'); 
    } else { 
      setMessage('過体重です'); 
    } 
  }; 
 
 
  return ( 
    <div className="container"> 
      <div className="row py-3"> 
        <div className="col-12 text-center"> 
          <h1>BMI計算機</h1> 
        </div> 
      </div> 
      <div className="row align-items-center"> 
        <div className="col-auto"> 
          身長: 
        </div> 
        <div className="col"> 
          <input className="form-control" 
            type="number" 
            value={height} 
            onChange={(e) => setHeight(e.target.value)} 
            placeholder="身長を入力" 
 
          /> 
        </div> 
        <div className="col-auto"> 
          cm 
        </div> 
        <div className="col-auto"> 
          体重: 
        </div> 
        <div className="col"> 
          <input className="form-control" 
            type="number" 
            value={weight} 
            onChange={(e) => setWeight(e.target.value)} 
            placeholder="体重を入力" /> 
        </div> 
        <div className="col-auto"> 
          kg 
        </div> 
      </div> 
 
      <div className="row justify-content-center mt-4"> 
        <div className="col-auto"> 
          <button className="btn btn-primary" 
            onClick={calculateBMI} 
          > 
            BMIを計算する 
          </button> 
        </div> 
      </div> 
 
      <div className="row mt-4"> 
        <div className="col text-center"> 
          <div> 
            あなたのBMIは<span className="strong fs-3">{bmi}</span> 
          </div> 
          <div className="fs-4 mt-4"> 
            {message} 
          </div> 
        </div> 
      </div> 
    </div> 
  ); 

 
export default App; 

西暦→和暦変換機

西暦を入力したら和暦(明治・大正・昭和・平成・令和)を計算してくれるアプリを作ってみましょう。
和暦は
 
明治・・・1867年から1911年まで
大正・・・1912年から1925年まで
昭和・・・1926年から1988年まで
平成・・・1989年から2019年まで
令和・・・2020年から


で計算できます。

import React, {useState} from 'react'; 
 
import 'bootstrap/dist/css/bootstrap.min.css'; 
import './App.css'; 
 
function App() { 
  const [year, setYear] = useState('2024'); 
  const [era, setEra] = useState(''); 
  const [error, setError] = useState(''); 
 
  // 和暦に変換する関数 
  const convertToEra = (e) => { 
    e.preventDefault(); 
    setError(''); 
 
    const yearNumber = parseInt(year); 
 
    // 年が正しい範囲か確認 
    if (isNaN(yearNumber) || yearNumber < 1868) { 
      setError('1868年以降の西暦を入力してください'); 
      setEra(''); 
      return; 
    } 
 
    if (yearNumber >= 2019) { 
      setEra(`令和 ${yearNumber - 2018}年`); 
    } else if (yearNumber >= 1989) { 
      setEra(`平成 ${yearNumber - 1988}年`); 
    } else if (yearNumber >= 1926) { 
      setEra(`昭和 ${yearNumber - 1925}年`); 
    } else if (yearNumber >= 1912) { 
      setEra(`大正 ${yearNumber - 1911}年`); 
    } else if (yearNumber >= 1868) { 
      setEra(`明治 ${yearNumber - 1867}年`); 
    } else { 
      setError('不明なエラーが発生しました'); 
    } 
  }; 
 
 
 
  return ( 
    <div className="container"> 
      <div className="row py-4"> 
        <div className="col text-center"> 
          <h1>西暦を和暦に変換</h1> 
        </div> 
      </div> 
 
      <div className="row text-center"> 
        <div className="col-12 py-3"> 
          西暦を入力してください。 
        </div> 
        <div className="col-12 py-3"> 
          <div className="row justify-content-center align-items-center"> 
            <div className="col-auto"> 
 
              <input type="number" min="1867" style={{ 
                width: '100px' 
              }} className="form-control" value={year} 
                onChange={(e) => setYear(e.target.value)} 
              /> 
            </div> 
            <div className="col-auto"> 
              年 
            </div> 
          </div> 
        </div> 
        <div className="col-12 py-3"> 
          <button className="btn btn-primary" onClick={convertToEra}> 
            計算する 
          </button> 
        </div> 
        <div className="col-12"> 
          <div>和暦は</div> 
          <div class="py-3 fs-2"> 
            {era || error} 
          </div> 
          <div> 
            です 
          </div> 
        </div> 
      </div> 
    </div>); 

 
export default App; 

メルカリ手数料計算機

もうちょっと実用的に、メルカリの手数料を計算してくれる計算機を作ってみます。

メルカリの手数料は 販売価格*10% で計算できます。
以前は送料にも手数料率がかかっていたような気がしますが、現在は売上金だけ?

import React, {useState} from 'react'; 
 
import 'bootstrap/dist/css/bootstrap.min.css'; 
import './App.css'; 
 
 
 
function App() { 
  const [price, setPrice] = useState('300'); 
  const [send, setSend] = useState(''); 
  const [rate, setRate] = useState('10'); 
  const [cost, setCost] = useState(''); 
  const [profit, setProfit] = useState(''); 
 
  const checkCost = (e) => { 
    e.preventDefault(); 
    const priceNumber = parseInt(price) 
    const sendNumber = parseInt(send) 
    const rateNumber = parseInt(rate) 
 
    const calculatedCost = parseInt(priceNumber * (rateNumber / 100)) 
    setCost(calculatedCost) 
 
    setProfit((priceNumber - calculatedCost - sendNumber)) 
 
 
 
 
 
  } 
 
  return ( 
    <div className="container"> 
      <div className="row py-4"> 
        <div className="col text-center"> 
          <h1 class="fw-bold fs-2">メルカリ手数料計算機</h1> 
        </div> 
      </div> 
 
      <div className="row align-items-center justify-content-center py-2"> 
 
        <div className="col-2"> 
          販売価格 
 
        </div> 
        <div className="col-auto"> 
          <input type="number" min="300" className="form-control" 
            style={{ 
              width: '200px' 
            }} 
            value={price} 
            onChange={(e) => setPrice(e.target.value)} 
          /> 
        </div> 
        <div className="col-auto">円</div> 
      </div> 
 
      <div className="row align-items-center  justify-content-center py-2"> 
 
        <div className="col-2"> 
          送料 
        </div> 
        <div className="col-auto"> 
          <input type="number" min="0" className="form-control" 
            style={{ 
              width: '200px' 
            }} 
            value={send} 
            onChange={(e) => setSend(e.target.value)} 
          /> 
        </div>        <div className="col-auto">円</div> 
 
      </div> 
 
      <div className="row align-items-center  justify-content-center py-2"> 
 
        <div className="col-2"> 
          手数料率 
        </div> 
        <div className="col-auto"> 
          <input type="number" min="0" className="form-control" 
            style={{ 
              width: '200px' 
            }} 
            value={rate} 
            onChange={(e) => setRate(e.target.value)} 
          /> 
        </div> 
        <div className="col-auto">%</div> 
      </div> 
 
      <div className="row justify-content-center py-2"> 
        <div className="col-auto"> 
          <button className="btn btn-primary" 
            onClick={checkCost} 
          > 
            計算する 
          </button> 
        </div> 
      </div> 
 
      <div className="row pt-4"> 
        <div className="col-12 text-center"> 
          手数料は<span className="fs-2 fw-bold px-1">{cost}</span>円、利益は<span className="fs-2 fw-bold px-1">{profit}</span>円です。 
        </div> 
 
      </div> 
 
    </div> 
  ); 

 
export default App; 

アナログ時計

Reactの関数は最終的にHTMLを返します。
つまりCSSやJavascriptの値を返せば動的な表示をさせることもできます。

ということでcanvasを返り値とし、setIntervalで動くアナログ時計を作ってみましょう。

import React, {useEffect, useRef} from 'react'; 
 
import 'bootstrap/dist/css/bootstrap.min.css'; 
import './App.css'; 
 
function App() { 
  const canvasRef = useRef(null); 
 
  useEffect(() => { 
    const canvas = canvasRef.current; 
    const ctx = canvas.getContext('2d'); 
    const radius = Math.min(canvas.width, canvas.height) / 2; 
    // 初回だけtranslateを行う 
    ctx.save(); 
    ctx.translate(radius, radius); // キャンバスの中心を (0, 0) に設定 
    const clockRadius = radius * 0.9; 
 
    const drawClock = () => { 
      ctx.clearRect(-radius, -radius, canvas.width, canvas.height); // 既存の描画をクリア 
      drawFace(ctx, clockRadius); 
      drawNumbers(ctx, clockRadius); 
      drawTime(ctx, clockRadius); 
    }; 
 
    const drawFace = (ctx, radius) => { 
      ctx.beginPath(); 
      ctx.arc(0, 0, radius, 0, 2 * Math.PI); 
      ctx.fillStyle = 'white'; 
      ctx.fill(); 
 
      ctx.strokeStyle = '#333'; 
      ctx.lineWidth = radius * 0.05; 
      ctx.stroke(); 
 
      ctx.beginPath(); 
      ctx.arc(0, 0, radius * 0.1, 0, 2 * Math.PI); 
      ctx.fillStyle = '#333'; 
      ctx.fill(); 
    }; 
 
    const drawNumbers = (ctx, radius) => { 
      ctx.font = `${radius * 0.15}px arial`; 
      ctx.textBaseline = 'middle'; 
      ctx.textAlign = 'center'; 
 
      for (let num = 1; num <= 12; num++) { 
        const ang = ((num * Math.PI) / 6) - Math.PI / 2; // -90度(真上から開始) 
        const x = radius * 0.85 * Math.cos(ang); 
        const y = radius * 0.85 * Math.sin(ang); 
        ctx.fillText(num.toString(), x, y); 
      } 
    }; 
 
    const drawTime = (ctx, radius) => { 
      const now = new Date(); 
      let hour = now.getHours(); 
      let minute = now.getMinutes(); 
      let second = now.getSeconds(); 
 
      // Hour hand 
      hour = hour % 12; 
      hour = 
        (hour * Math.PI) / 6 + 
        (minute * Math.PI) / (6 * 60) + 
        (second * Math.PI) / (360 * 60); 
      drawHand(ctx, hour, radius * 0.5, radius * 0.07); 
 
      // Minute hand 
      minute = (minute * Math.PI) / 30 + (second * Math.PI) / (30 * 60); 
      drawHand(ctx, minute, radius * 0.8, radius * 0.07); 
 
      // Second hand 
      second = (second * Math.PI) / 30; 
      drawHand(ctx, second, radius * 0.9, radius * 0.02, 'red'); 
    }; 
 
    const drawHand = (ctx, pos, length, width, color = '#333') => { 
      ctx.beginPath(); 
      ctx.lineWidth = width; 
      ctx.lineCap = 'round'; 
      ctx.strokeStyle = color; 
      ctx.moveTo(0, 0); 
      ctx.rotate(pos); 
      ctx.lineTo(0, -length); 
      ctx.stroke(); 
      ctx.rotate(-pos); 
    }; 
 
    const interval = setInterval(drawClock, 1000); // 1秒ごとに時計を描画 
    drawClock(); // 初回描画 
 
    return () => { 
      clearInterval(interval); // コンポーネントのアンマウント時にクリア 
      ctx.restore(); // translate状態を元に戻す 
    }; 
  }, []); 
 
  return ( 
    <div className="container"> 
      <div className="row py-4"> 
        <div className="col text-center"> 
          <h1 className="fs-2 fw-bold">アナログ時計</h1> 
        </div> 
      </div> 
      <div className="row justify-content-center"> 
        <div className="col-auto"> 
          <canvas 
            ref={canvasRef} 
            width="300" 
            height="300" // 確実に正方形にするために幅と高さを指定 
          /> 
        </div> 
      </div> 
    </div> 
  ); 

 
export default App;

RSSリーダー

fetchによる外部データの取り込みも試してみます。
RSSのURLを入力したらその内容を取得し、スタイリングして表示させるRSSリーダーを作ってみましょう。

別ドメインのデータを取得するためにrss2jsonというサービスを利用しました。
このサービスはRSSをJSONに変換し、CORSを許可して出力してくれるものです。

import React, {useState} from 'react'; 
import 'bootstrap/dist/css/bootstrap.min.css'; 
import './App.css'; 
 
 
 
function App() { 
  const [rssUrl, setRssUrl] = useState(''); 
  const [feedItems, setFeedItems] = useState([]); 
  const [error, setError] = useState(null); 
 
  const fetchRSSFeed = async (e) => { 
    e.preventDefault(); 
 
    // RSS to JSON APIを使用してRSSフィードを取得 
    const apiUrl = `https://api.rss2json.com/v1/api.json?rss_url=${encodeURIComponent(rssUrl)}`; 
 
    try { 
      const response = await fetch(apiUrl); 
      const data = await response.json(); 
 
      if (data.status === 'ok') { 
        setFeedItems(data.items); // RSSフィードのアイテムを保存 
        setError(null); 
      } else { 
        setError('RSSフィードを取得できませんでした。'); 
        setFeedItems([]); 
      } 
    } catch (err) { 
      setError('エラーが発生しました。'); 
      setFeedItems([]); 
    } 
  }; 
  const truncateText = (str, num) => { 
    return str.length > num ? str.slice(0, num) + '...' : str; 
  }; 
 
  return ( 
    <div className="container"> 
 
      <div className="row py-4"> 
        <div className="col text-center"> 
          <h1 className="fs-2 fw-bold">RSSリーダー</h1> 
        </div> 
      </div> 
 
      {/* RSSフィードURL入力フォーム */} 
      <div className="row"> 
        <div className="col-12"> 
          <label htmlFor="rssUrl" className="form-label">RSSフィードのURLを入力してください:</label> 
          <input 
            type="url" 
            className="form-control" 
            id="rssUrl" 
            value={rssUrl} 
            onChange={(e) => setRssUrl(e.target.value)} 
            placeholder="https://example.com/rss" 
            required 
          /> 
        </div> 
      </div> 
      <div className="row  py-4"> 
 
        <div className="col-auto"> 
          <button className="btn btn-primary" onClick={fetchRSSFeed}>RSSを取得</button> 
 
        </div> 
      </div> 
 
      {/* エラーメッセージ */} 
      {error && <p className="text-danger">{error}</p>} 
 
      {/* RSSフィードのアイテム表示 */} 
      <div className="row"> 
        <div className="col-12"> 
 
          {feedItems.length > 0 && ( 
            <ul className="list-group bg-white"> 
              {feedItems.map((item) => ( 
                <li key={item.guid} className="list-group-item bg-white"> 
                  <div className="mb-2"> 
                    <a href={item.link} target="_blank" rel="noopener noreferrer" class="fw-bold"> 
                      {item.title} 
                    </a> 
                  </div> 
                  <div className="mb-2"> 
                    {truncateText(item.description, 40)} 
                  </div> 
                  <div>{item.pubDate}</div> 
                </li> 
              ))} 
            </ul> 
          )} 
        </div> 
      </div> 
 
    </div> 
  ); 

 
export default App; 

余談

りあくとって書くとラノベっぽいなと思ったら同人誌がありました 
(中身は真面目な技術解説書です)

まとめ

ということで、今回の記事ではReactの紹介と実際にReactを使用したサンプルアプリケーションを作ってみました。 
 
私自身も実際にReactを扱ってみて、JSXの構文は独特ではあるもののHTMLとJavascriptの知識で十分扱えそうだという印象でした。 
 今後も注目していきたいと思います。

【関連記事】

ご相談・お問い合わせ

当社サービスについてのお問い合わせは下記までご連絡下さい。

お電話でのお問い合わせ

06-6121-7581 / 03-6415-8161