20210304のReactに関する記事は10件です。

Reactのmaterial-tableで行をクリックしたときにポップアップを表示する

目的

Reactのmaterial-tableで行をクリックしたときにポップアップで詳細を表示できるようにしたので、実装方法とハマった点の覚書です

仕様

  1. テーブルの行をクリックするとポップアップを表示します
  2. ポップアップでクリックした行の詳細が表示されます
  3. ポップアップのOKをクリックすると、ポップアップが閉じます

スクリーンショット 2021-03-04 22.07.47.png

スクリーンショット 2021-03-04 22.08.02.png

環境

  • React 17.0
  • material-table 1.69.2
  • material-ui-core 4.11.3

実装

表とデータの表示

公式の Remote Data Example を使いましたので、説明は省略します

ポップアップの実装

  • ポップアップの開閉は Material UIのDialogコンポーネントの仕様 に従い props open にboolean値で指定します
  • ポップアップの開閉はstateで保持し、イベント発生ごとに切り替えます
    • テーブルの行がクリックされた時はtrue、OKボタンを押した時はfalse になるよう指定して開閉を切り替えます
  • ポップアップに表示したいデータを state rowData で保持します
  • ポップアップの状態は1つのstate status で保持し、setStateは1回で済ませます
    • setOpen, setRowData のように複数に分けてsetStateしてしまうと、その都度レンダリングが走ってしまい効率が悪いです
      const [status, setStatus] = React.useState({ open: false, rowData: {} });
      (ここにMaterialTableがある)
      <Dialog
        open={status.open}
        aria-labelledby="draggable-dialog-title"
        fullWidth
        maxWidth="lg"
      >
        <DialogTitle id="draggable-dialog-title">Detail</DialogTitle>
        <DialogContent>
          <DialogContentText>
            <img
              style={{ height: 36, borderRadius: "50%" }}
              src={status.rowData.avatar}
              alt={status.rowData.avatar}
            />
            <br />
            {status.rowData.first_name} {status.rowData.last_name}
            <br />
            Contact: {status.rowData.email}
          </DialogContentText>
        </DialogContent>
        <DialogActions>
          <Button
            onClick={() =>
              setStatus({ open: false, rowData: status.rowData })
            }
            color="primary"
          >
            OK
          </Button>
        </DialogActions>
      </Dialog>

テーブルがクリックされたイベントの実装

  • material-tableのpropsonRowClick 関数で
  • 『ポップアップの実装』の通り、ポップアップの状態はtrueにし、ポップアップの表示用にテーブルの行のデータを利用します
  • テーブルの行のデータは onRowClick の引数 rowData で取得できます
        onRowClick={(event, rowData) => {
          event.preventDefault();

          setStatus({ open: true, rowData: rowData });
        }}

おまけ:ページング情報の保持

1ページあたりの表示件数を切り替えてから行をクリックし、 OKを押してポップアップを閉じると、1ページあたりの件数が5件に戻ってしまいます。

stateで1ページあたりの件数を保持し、データを取得する関数の中で setState すると修正できます。
初期値は material-tableの pageSize のデフォルトの5にしました。

const [pageSize, setPageSize] = React.useState(5);

data={(query) =>
   new Promise((resolve) => {
      (中略)
       setPageSize(query.pageSize);  
      (中略)  
   })
}

サンプル

実装サンプルはCodeSandboxに置いておきます。
CodeSandboxのサンプルページで setPageSize(query.pageSize); をコメントアウトすると『おまけ』で説明した現象が確認できます。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フォームの設定「reactメモ」

reactを今日から初めていく、メモとして覚えた知識をここに残す。

・フォームの設定
入れた値を表示させる

react_app
 <body>
    <h1>React</h1>
    <div id="root">wait.......</div>

    <script type="text/babel">
    let dom = document.querySelector('#root');

    const p = {
     fontSize: "20px",
     padding: "10px",
     };

    const input = {
     fontSize: "16px",
     padding: "5px 10px",
    }

  let message= 'お名前をどうぞ:';
  let in_val = '';


   let doChange = (event)=>{
     in_val = event.target.value ;
     message = 'こんにちは、' + in_val + 'さん!!!';
   };

    let doAction = (event)=>{

      let el = (
        <div>
          <p style={p}>{message}</p>
            <div>
              <input type="text" id="input" style={input}
              onChange={doChange} />
              <button onClick={doAction} style={input}>
                Click
                </button>
              </div>
          </div>

      );
      ReactDOM.render(el, dom);
    };
    doAction();


    </script>

  </body>

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSXの使い方「reactメモ」

reactを今日から初めていく、メモとして覚えた知識をここに残す。

・AJXの使い方
AJXとは、「タグをそのまま値として記述する」というシンプルな機能

flgの値がtrueなら、&&の<p>タグが表示される。

react_app
 <body>
    <h1>React</h1>
    <div id="root">wait.......</div>

    <script type="text/babel">
    let dom = document.querySelector('#root');

    const p = {
     fontSize: "20px",
     padding: "10px",
     };

    const input = {
     fontSize: "16px",
     padding: "5px 10px",
    }

  let message= 'お名前をどうぞ:';
  let in_val = '';


   let doChange = (event)=>{
     in_val = event.target.value ;
     message = 'こんにちは、' + in_val + 'さん!!!';
   };

    let doAction = (event)=>{

      let el = (
        <div>
          <p style={p}>{message}</p>
            <div>
              <input type="text" id="input" style={input}
              onChange={doChange} />
              <button onClick={doAction} style={input}>
                Click
                </button>
              </div>
          </div>

      );
      ReactDOM.render(el, dom);
    };
    doAction();


    </script>

  </body>

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Todoアプリ改修とuseState

Todoアプリ改修

前回行った改修の続きとして、今回は完了ボタンを押し、完了エリアへ移動するボタンの実装です。

まずは完了エリアとなる以下のようにコンポーネントを作りました。
この部分は他のコンポーネント見ながら作ったので、すぐに出来ました。

import React from "react";

import "../styles/style.scss";

interface TodoCompleted {
  items: { id: string; text: string }[];
  onBackTodo: (id: string, text: string) => void;
}

const CompleteTodo: React.FC<TodoCompleted> = (props) => {
  const { items, onBackTodo } = props;

  return (
    <div className="complate">
      <p className="complate-head">完了Todo</p>
      <ul>
        {items.map((todo) => (
          <li key={todo.id}>
            <span>{todo.text}</span>
            <button className="todoBack" onClick={onBackTodo.bind(null, todo.id, todo.text)}>
              戻す
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default CompleteTodo;

次にどのように実装したら、完了エリアにTodoが行ってくれるか。。。
まずはuseStateが必要だと思い、

const App: React.FC = () => {
  const [todos, setTodos] = useState<Todo[]>([]);
  const [completeTodo, setCompleteTodo] = useState<Todo[]>([]);⇨追記したもの

上記のcompleteTodoの状態が、完了ボタンが押されたタイミングで変わっていればいいので、
次のように書いてます。
簡単にお伝えするとsetTodos関数で未完了エリアにあるTodoを削除して、
setCompleteTodoで削除されたTodoを完了エリアへ

const todoCompletedHandler = (todoId: string, text: string) => {
    setTodos((prevTodo) => prevTodo.filter((todo) => todo.id !== todoId));

    setCompleteTodo((prevTodo) => [
      ...prevTodo,
      { id: Math.random().toString(), text: text },
    ]);
  };

しっかり以下もやってます。

<TodoList
    items={todos}
    onDeleteTodo={todoDeleteHandler}
    onCompleteTodo={todoCompletedHandler}
/>
<CompleteTodo items={completeTodo} onBackTodo={todoBackHandler} />

学んだこと

ここで学んだものは、useStateです。
例えば以下のようなuseState。

const [todos, setTodos] = useState<Todo[]>([]);

今の認識はtodosは箱でsetTodosはその箱(todos)の状態を変更する関数。
useStateの<>の部分はtodosをどういう型なのか表している。
Todo[]はインターフェースで以下のように定義済み。

export interface  Todo {
  id: string;
  text: string;
}

ソースコード

以下のソースコード公開中です。
どうぞ見てやってください!

今回は以上です。
ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Netlifyを使って自分のプロフィールサイトを簡単に公開する

はじめに

何かプロフィールサイトを作って公開したいと思ったときに、方法はいくつかあると思います。
今回は、Netlifyを使って簡単に自分のプロフィールサイトを公開する方法について書いていきたいと思います。

環境

  • ArchLinux

Netlifyとは

Netlifyは、最新のWebプロジェクトを自動化するためのオールインワンプラットフォームです。ホスティングインフラストラクチャ、継続的インテグレーション、およびデプロイメントパイプラインを単一のワークフローに置き換えます。プロジェクトの成長に合わせて、サーバーレス機能、ユーザー認証、フォーム処理などの動的機能を統合します。

ドキュメントより抜粋

料金

個人的なプロジェクト、趣味のサイトなどは無料。使える機能としては

  • Gitからの自動ビルド
  • グローバルエッジネットワークにデプロイする
  • プッシュごとのサイトプレビュー
  • 任意のバージョンへの即時ロールバック
  • 静的アセットと動的サーバーレス機能をデプロイする

Netlify

手順

1. プロフィールサイトの作成

まずはプロフィールサイトを作らなければ始まりません。今回はReactのCreate React Appを使います。

yarn create react-app mypage
yarn start

qiita1.png
これがブラウザで表示されればok

※ 今回はあくまで公開する方法の紹介なので、サイト自体の作りは簡単に済ませちゃいます。

構造はこんな感じ
qiita2.png

App.js
import './App.css';
import profileimg from "./profileimg.png";

function App() {
    return (
        <div className="body">
            <div className="header">
                <h1>MyPage</h1>
            </div>

            <div className="main">
                <img src={profileimg} alt="profileimg" />
                <div className="text">
                    <h3>About</h3>
                    <p>Hello My Profile Page!<br></br>
                    Name:Taro<br></br>
                    Birthday:2021/03/04
                    </p>
                    <h3>Study</h3>
                    <p>JavaScript / HTML / CSS</p>
                </div>
            </div>

            <div className="footer">
                <p>Copyright ©</p>
            </div>
        </div>
    );
}

export default App;

App.css
.body {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}

.header {
    padding: 10px;
    background-color: #dcdcdc;
}

.header h1 {
    margin-left: 40px;
}

img {
    border: 2px solid black;
    border-radius: 50%;
}

.main {
    flex: 1;
    display: flex;
    justify-content: center;
    align-items: center;
}

.main h3 {
    margin-bottom: 0px;
}

.text {
    margin: auto 40px auto 40px;
    display: flex;
    flex-direction: column;
}

.footer {
    padding: 10px;
    text-align: center;
    background-color: #dcdcdc;
}

こんな感じの画面が表示されていたらokです
qiita3.png

そしてビルドする

yarn build

新しくbuildというディレクトリができているはずです。

GitHubにリポジトリを作ってプッシュ

分かる人はとばしてもらってもいいですが、一応手順を書いておきます。
アカウント登録をしていない人は登録してもらって、している方はリポジトリを作ります。
この時、PublicにするかPrivateにするかはどちらでも大丈夫です。
今回はPublicで作成します。
qiita4.png

作成したときに手順はページに出てきますが

git init
git add .
git commit -m "first commit"
git branch -M main
git remote add origin {url}
git push -u origin main

今回はこれでプッシュ
この時注意しないといけないのは、今回yarnを使用しているためyarn.lockがあるディレクトリ内で行ってください。理由は後ほど説明します。

Netlifyで公開する

最後の公開の手順です。Netlifyにアカウント登録をしていない人はまずアカウント登録をしてください。
登録が終わったら画面にあるNew site from Gitをクリック
qiita5.png
すると下の画面が表示されるので、
qiita6.png
GitHubをクリック。
別ウィンドウが開かれると思います。
qiita7.png
この画面が出たら、自分のアカウントを選択。
qiita8.png
スクロールするとリポジトリを選択する部分があります。All repositoriesでもいいのですが、今回は作成したリポジトリだけにしたいのでOnly select repositoriesで作成したリポジトリを選択します。

そしてNetlifyの画面へ戻ります。画面が変わらないようであればページ更新してもらって、上記と同じ様にGitHubをクリックしてもらえれば先程選択したリポジトリが表示されていると思います。
そのリポジトリをクリックすると
qiita9.png
この画面が出てくればokです。
もしBuild CommandとPublish directoryが空白であれば画像と同じ様に入力してもらえれば大丈夫です。

ここで思い出してほしいのが、リポジトリにプッシュする時のこの文。

この時注意しないといけないのは、今回yarnを使用しているためyarn.lockがあるディレクトリ内で行ってください。理由は後ほど説明します。

これがここで関わってきます。もしyarn.lockがないディレクトリでプッシュしてしまっていると、Deploy siteをしたときにエラーがでて失敗します。

Yarnをローカルで使用してJavaScriptの依存関係をpackage.jsonファイルにインストールする場合、Yarnはyarn.lockインストールされたモジュール名とバージョンを記録するファイルを作成します。このファイルをリポジトリ内のサイトのベースディレクトリにコミットすると、Yarnがインストールyarnされ、コマンドを実行してyarn.lockファイルで指定された依存関係をインストールします。この動作は、NETLIFY_USE_YARN以下で説明する環境変数でオーバーライドできます。

ドキュメントより抜粋
自分も最初、command not foundといった文でエラーが出て失敗したのですが、yarn.lockがないとyarnがインストールされず、エラーになるみたいですね。

では以上を踏まえた上でDeploy siteをクリックします。
するとデプロイが始まるので終わるまで待ちます。
成功するとPreview deployに変わりますのでクリックすると、先ほど作成したページが表示されます。

URLの変更

初期のURLでは分かりづらいので、自分が指定したものに変えます。
(そのままでもいい人は飛ばしてください)
ホームに戻り、追加されているSitesに追加されたリンクをクリック。
Site settingsをクリックしてChange site nameをクリック。
すると入力欄が出てくるので、好きな名前を入力しましょう。
※ すでに使用されているものは設定不可

まとめ

以上で簡単ではありますが自分の作成したサイトをNetlifyを使って公開する方法でした。
今回作成したページ
https://optimistic-almeida-cd1568.netlify.app/

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flutter 1 → Flutter 2 で変わったことと今後【概要】

Flutter 2 の Release

3/4にFlutterが2.0になりました。
今回のバージョンアップで興味深い内容がありました。
Flutter:heart_eyes:な人でも、別のjs系フレームワークが好きな人でも、サクッと読めるように要約しました!

On The Web

今回の目玉といって差し支えないのがWebサポートです。

ベータ版から安定版へ

今までは1つのソートコードでiOS用とAndroid用のみでした。そこに、今までベータ版だったWeb用が安定板として追加されました。

今までFlutterでモバイル開発していた人たちは、今すぐに同じ機能のWebサービスを開始できるということです!

サボり精神が満ち溢れていますね!最高です!

レンダリングエンジン

今まではHTMLベースのレンダリングエンジンを使用していました。
WebAssemblyで構築されたCanvasKitのレンダリングエンジンが追加され、パフォーマンス最適化の選択肢が増えました!

その他

Web用として、下記のような機能が追加されました。

  • テキストの自動入力(入力履歴の利用)
  • アドレスバーのURLとルーティングの制御
  • PWAマニフェストなど

デスクトップアプリ

デスクトップアプリ用に使えるようになるのは少し先で、今年の後半にベータ版が出る予定です。

既に重要な機能が追加済みなようです。

  • スクロールバー
  • キーボードショートカット
  • デスクトップ用のコンテンツ密度
  • スクリーンリーダーサポート

組み込み機器

なんと、あの世界で最も売れている自動車メーカーであるTOYOTAと協力しながらFlutterを改善していくようです!車載のソフトウェアとして利用されていくということですね。

ここから、実際の利用環境でFlutterの機能が磨かれていく未来が容易に想像できますね!期待感アップです!

広告

Google Mobile Ads for Flutterのベータ版も同時にリリースされました。

とても簡単にAdMobの広告を付けられる未来が見えます。広告収益化も出来そうですね。

Null Safety

ヌルセーフティーを大切にしていて、完全サポートしているようです。他の言語でもモダンな考え方として取り入れる動きが高まっており、これが無いと「レガシー」とまで言われてしまうようです。

C++などで開発したプログラムでよく起こりがちな「ヌルポ」と呼ばれるバグは、このヌルセーフティーによって早期発見が見込めます。

元記事

もっと正確な情報をご覧になりたい方は下記の公式記事をご覧ください。
What’s New in Flutter 2 | Medium
Announcing Flutter 2 | Google Developer

まとめ

もし「要約してくれてありがとう」と感じたら、LGTMしていただけたら嬉しいです。
次の記事も頑張ります!

Excelsior!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flutter 2 で変わったことと今後

Flutter 2 の Release

3/4にFlutterが2.0になりました。
今回のバージョンアップで興味深い内容がありました。
Flutter:heart_eyes:な人でも、別のフレームワークが好きな人でも、サクッと読めるように要約しました!

On The Web

今回の目玉といって差し支えないのがWebサポートです。

ベータ版から安定版へ

今までは1つのソートコードでiOS用とAndroid用のみでした。そこに、今までベータ版だったWeb用が安定板として追加されました。

今までFlutterでモバイル開発していた人たちは、今すぐに同じ機能のWebサービスを開始できるということです!

サボり精神が満ち溢れていますね!最高です!

レンダリングエンジン

今まではHTMLベースのレンダリングエンジンを使用していましたが、WebAssemblyで構築されたCanvasKitのレンダリングエンジンが追加され、パフォーマンス最適化の選択肢が増えました!

その他

Web用として、テキストの自動入力、アドレスバーのURLとルーティングの制御、およびPWAマニフェストなどが追加されています。

デスクトップアプリ

デスクトップアプリ用に使えるようになるのは少し先で、今年の後半にベータ版が出る予定です。

既に重要な機能が下記機能が追加済みなようです。

  • スクロールバー
  • キーボードショートカット
  • デスクトップ用のコンテンツ密度
  • スクリーンリーダーサポート

組み込み機器

なんと、あの世界で最も売れている自動車メーカーであるTOYOTAと協力しながらFlutterを改善していくようです!車載のソフトウェアとして利用されていくということですね。

ここから、実際の利用環境でFlutterの機能が磨かれていく未来が用意に想像できますね!期待感アップです!

広告

Google Mobile Ads for Flutterのベータ版も同時にリリースされました。

とても簡単にAdMobの広告を付けられる未来が見えます。広告収益化も出来そうですね。

Null Safety

ヌルセーフティーを大切にしていて、完全サポートしているようです。他の言語でもモダンな考え方として取り入れる動きが高まっており、これが無いと「レガシー」とまで言われてしまうようです。

C++などで開発したプログラムでよく起こりがちな「ヌルポ」と呼ばれるバグは、このヌルセーフティーによって早期発見が見込めます。

元記事

もっと正確な情報をご覧になりたい方は下記の公式記事をご覧ください。
What’s New in Flutter 2 | Medium
Announcing Flutter 2 | Google Developer

Excelsior!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flutter 2 で変わったことと今後【概要】

Flutter 2 の Release

3/4にFlutterが2.0になりました。
今回のバージョンアップで興味深い内容がありました。
Flutter:heart_eyes:な人でも、別のjs系フレームワークが好きな人でも、サクッと読めるように要約しました!

On The Web

今回の目玉といって差し支えないのがWebサポートです。

ベータ版から安定版へ

今までは1つのソートコードでiOS用とAndroid用のみでした。そこに、今までベータ版だったWeb用が安定板として追加されました。

今までFlutterでモバイル開発していた人たちは、今すぐに同じ機能のWebサービスを開始できるということです!

サボり精神が満ち溢れていますね!最高です!

レンダリングエンジン

今まではHTMLベースのレンダリングエンジンを使用していました。
WebAssemblyで構築されたCanvasKitのレンダリングエンジンが追加され、パフォーマンス最適化の選択肢が増えました!

その他

Web用として、下記のような機能が追加されました。

  • テキストの自動入力(入力履歴の利用)
  • アドレスバーのURLとルーティングの制御
  • PWAマニフェストなど

デスクトップアプリ

デスクトップアプリ用に使えるようになるのは少し先で、今年の後半にベータ版が出る予定です。

既に重要な機能が下記機能が追加済みなようです。

  • スクロールバー
  • キーボードショートカット
  • デスクトップ用のコンテンツ密度
  • スクリーンリーダーサポート

組み込み機器

なんと、あの世界で最も売れている自動車メーカーであるTOYOTAと協力しながらFlutterを改善していくようです!車載のソフトウェアとして利用されていくということですね。

ここから、実際の利用環境でFlutterの機能が磨かれていく未来が用意に想像できますね!期待感アップです!

広告

Google Mobile Ads for Flutterのベータ版も同時にリリースされました。

とても簡単にAdMobの広告を付けられる未来が見えます。広告収益化も出来そうですね。

Null Safety

ヌルセーフティーを大切にしていて、完全サポートしているようです。他の言語でもモダンな考え方として取り入れる動きが高まっており、これが無いと「レガシー」とまで言われてしまうようです。

C++などで開発したプログラムでよく起こりがちな「ヌルポ」と呼ばれるバグは、このヌルセーフティーによって早期発見が見込めます。

元記事

もっと正確な情報をご覧になりたい方は下記の公式記事をご覧ください。
What’s New in Flutter 2 | Medium
Announcing Flutter 2 | Google Developer

まとめ

もし「要約してくれてありがとう」と感じたら、LGTMしていただけたら嬉しいです。
次の記事も頑張ります!

Excelsior!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React/Vue】なぜコンポーネントベースでWebアプリを作るのか

React/Vue などを使ってフロントエンドを開発する際、基本はコンポーネントベースで設計・開発すると思います。
なぜコンポーネントベースなのかどういうメリット・目的があるのか、今一度確認する機会があったので記事にします。
新入社員に「ここってどうしてコンポーネントに分けるんですか?」と聞かれた時に答えられるようにしておきましょう。

コンポーネントベース以外経験が無い方へ
Web アプリの学習を始めて間も無い方の中には、そもそもコンポーネントベース以外経験がない方も居るかもしれません。
そんな方は極端な例ですが、Web アプリの画面がもし1画面1コンポーネントで作られていた場合小さなコンポーネントを積み上げて作られていた場合を想像し、以下の記事では後者のメリットが書かれていると思って読んでみてください。

コンポーネントが持つべき 5 つの特徴

はじめにコンポーネントのあるべき姿ついて確認しておきます。

  1. 単一責任の原則
    コンポーネントが責任を持つ問題は 1 つにするべきです。
    ソースがシンプルになることで可読性が向上します。
    また、ソース変更時の影響範囲が明確になることで保守性が向上します。

  2. カプセル化されている
    コンポーネントを使いたいとき、インターフェースだけ知っていれば内部の実装を気にしなくて良いです。

  3. 置換可能である
    インターフェースさえ同じであれば違うコンポーネントに差し替えることができます。

  4. 再利用可能である
    再利用可能なコンポーネントは、そのコンポーネントが担っている責務に対して過不足なく機能を提供していると言えます。
    「過不足なく」というのは意外と難しく、特にありがちなのが機能を過剰につけてしまうことです。
    ある画面の文脈では必要な機能でも、別の画面では邪魔になることもあります。
    コンポーネントの責務を常に意識し、再利用性を損なうことがないようにすることが大切です

  5. 組み合わせて別の大きなコンポーネントを作成可能である
    再利用性が十分に高いことの裏付けです。
    一つ一つのコンポーネントは小さな問題しか解決できませんが、大きな問題を小さな問題に分割し、適切なコンポーネントに振り分ける役割を持つコンポーネントを作れば、それ自体が大きな問題を解決するコンポーネントになります。

コンポーネントベースのメリット

先に紹介した 5 つの特徴を持つコンポーネントで Web アプリを構成すると以下のようなメリットがあります。

1.複雑な UI も確実に組み立てられる

コンポーネントベース最大のメリットは複雑な UI を確実・堅牢に組み立てられることです。

堅牢な UI 開発を実現する

多くの機能を提供するアプリでは複雑な UI 設計が求められます。
例として Facebook の Web アプリを見てみると、ヘッダー・サイドメニュー・投稿記事リスト・広告など沢山の UI が並んでいます。
これらの UI がもし互いに依存しているような作りになっていた場合、意図せずバグを産んでしまうことがあります。
(ある UI の機能を変更したら別の UI が動かなくなった、ある UI のレイアウトを変更したら別の UI のレイアウトが崩れてしまった、など)
コンポーネント化された UI が個別で不具合なく動作することがテストされていれば、それを正しく使ったアプリの品質も保証できます

コンポーネント単位でテストできる

アプリの品質を担保するための最も重要なポイントです
UI はアプリに組み込まれるとアプリ全体の状態に左右されてしまうことが多いので、単体でテストすることが難しいです。
コンポーネント化して独立した UI であればアプリの状態に依存せず単体でテスト可能です
適切に分割された小さな実装であれば必要なテストケースも少なく、テスト項目も作りやすいです。

不具合のリスクポイントを減らすことができる

一般的に書くコードの量が減ればバグの量も減ります
再利用可能なコンポーネントを作成することで全体のコード記述量を減らすことができます。

メンテナンスがしやすくなる

UI に変更を加えた際に影響を受ける範囲がコンポーネント内に留まるので、メンテナンスがしやすいです
見た目だけを責務とするコンポーネントであれば、修正作業はエンジニアではなくデザイナーが直接行うこともできます。

解決する問題を小さくすることで不具合発生リスクを減らす

複雑なコードより簡単なコードの方が不具合発生リスクは低いです
一つ一つのコンポーネントが単純な問題を解決するために作られているのであれば、内容は簡単になり実装の難易度も低くなります。

2.開発作業を効率化する

コンポーネントベースは品質を担保するとともに開発速度向上にも繋がります。

再利用で実装量を減らす

再利用しやすいコンポーネントを作ることは開発速度向上に直結します

並行開発で待ち時間を減らす

画面単位の開発ではなくコンポーネント単位の開発だと、1画面に必要な UI を複数人で並行開発することも可能です
各コンポーネントは独立しているため、他のタスクに依存せず開発可能です。

仕様変更による手戻り作業を効率化する

画面単位で UI 開発していた場合、画面仕様に変更があれば必ず手戻りが発生します。
小さなコンポーネントを積み上げて大きな部品を開発する方式だと、仕様変更によって影響を受けるコードは比較的少なくなります
また大きな部品ほど後から作られるので、仕様変更のタイミングでまだ画面自体の実装に着手していない可能性もあり、その場合手戻りは無くなることもあります。

新規参入開発メンバーを最短で戦力化する

コンポーネント内で使用する技術スタックだけ知っていれば開発可能なので、サービス背景についての知識は概要だけで良い場合もあります。

複数のテスト・アプローチでテスト工数を下げる

コンポーネントがアプリに依存しない環境で単体実行できるので、さまざまなアプローチでのテストが可能です。
画面に依存している UI ではテストしづらいケースを時間をかけて再現し、何とかテストするということもしばしばありますが、独立したコンポーネントだと単独のテストなら非常に簡単に行うことができます。

複数アプリケーションの開発を容易にする

UI コンポーネントが部品としてアプリから分離できる形になっていれば、別のアプリでコードを再利用することが可能です
仕組み次第では複数アプリから同じコードを参照して、全体のメンテナンスコストを下げることもできます
ただしコードを変更したときの影響範囲がかなり大きくなるので、より堅牢な運用が必要となります。

3.ユーザーメリット

ここまではアプリを開発するエンジニアが受けるメリットの紹介でしたが、アプリを使うユーザーにとってもメリットがあります。

多機能アプリのユーザービリティ向上

アプリが多機能になるとたくさんの UI を画面に表示するため複雑になります。
ある UI コンポーネントが別の画面で使われていたとしても、ユーザーはすでにその UI コンポーネントの使い方を知っているため、すぐにその機能を使い始めることができます

まとめ

コンポーネントが持つべき 5 つの特徴

  1. 単一責任の原則
  2. カプセル化されている
  3. 置換可能である
  4. 再利用可能である
  5. 組み合わせて別の大きなコンポーネントを作成可能である


コンポーネントベースのメリット

1.複雑な UI も確実に組み立てられる

  • 堅牢な UI 開発を実現する
  • コンポーネント単位でテストできる
  • 不具合のリスクポイントを減らすことができる
  • メンテナンスがしやすくなる
  • 解決する問題を小さくすることで不具合発生リスクを減らす


2.開発作業を効率化する

  • 再利用で実装量を減らす
  • 並行開発で待ち時間を減らす
  • 仕様変更による手戻り作業を効率化する
  • 新規参入開発メンバーを最短で戦力化する
  • 複数のテスト・アプローチでテスト工数を下げる
  • 複数アプリケーションの開発を容易にする


3.ユーザーメリット

  • 多機能アプリのユーザービリティ向上


コンポーネント設計をする際にはこれらを意識することでより良い設計ができるかもしれません。
最後に注意点ですが、現実問題としてWeb アプリは必ずしもこの記事で紹介したようなコンポーネントだけで作られるわけではありません
例えばAtomicDesignで言うところの Pages は、実際のコンテンツに影響されるためカプセル化はできず、さらに再利用もできません
React/Vue で書くソース全てが上記の目的を持って書かれるわけではないことは頭に入れておきましょう。

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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GitHub Actionsを使ってGithub PagesにStorybookのドキュメントをWebpack5を使って公開したメモ

概要

前回はOpenAPIを公開したので、今回はStoryBookを公開してみる。
Next.jsをWebpack5で使用しているため、StoryBookも同様にWebpack5で利用したかった。
いろいろハマったので備忘録。

できたもの。

ソース

ハマリ1 StoryBookでWebpack5が使えない。

2021/03/03時点でbetaの6.2.0から対応の模様。

npmインストールについて

nodeをDocker上で利用している。Dockerfileは以下のような感じ。
Dockerfile
FROM node:14.16.0

WORKDIR /app/internals/component-catalog
RUN npm install -g npm@7.6.0

COPY package.json /app/internals/component-catalog/package.json
COPY package-lock.json /app/internals/component-catalog/package-lock.json
RUN npm ci

# Story Book
RUN npm i -D @storybook/cli@next
RUN npx --force sb init --type react --builder webpack5
RUN npm i --no-optional -D babel-preset-react-app
RUN npm i --no-optional -D @storybook/addon-knobs@next
RUN npm i --no-optional -D @storybook/addon-storysource@next
RUN npm i --no-optional -D @storybook/addon-viewport@next
RUN npm i --no-optional -D @storybook/addon-backgrounds@next
RUN npm i --no-optional -D @storybook/builder-webpack5@next
RUN npm i --no-optional -D @storybook/addon-console

# storybookのwebpackでローダーを使えるようにする
RUN npm i --no-optional -D css-loader style-loader
RUN npm i --no-optional -D precss autoprefixer postcss

RUN npm i --no-optional -D sass-loader
RUN npm i --no-optional -D fibers
RUN npm i --no-optional -D postcss-loader@5

RUN npm i --no-optional -D html-webpack-plugin@5 --force

WORKDIR /app
RUN npm i --no-optional -D bootstrap@next

jsonはnext.jsのプロジェクトのものをそのまま。

package.json
{
  "name": "app",
  "version": "0.0.1",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "next start",
    "build": "next build",
    "dev": "next dev",
    "lint": "eslint --ext .ts,.tsx --ignore-path .gitignore .",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "hibohiboo <hibohiboo66+github@gmail.com>",
  "license": "MIT",
  "dependencies": {
    "date-fns": "^2.18.0",
    "date-fns-tz": "^1.1.3",
    "firebase": "^8.2.9",
    "isomorphic-unfetch": "^3.1.0",
    "next": "^10.0.7",
    "react": "^17.0.1",
    "react-dom": "^17.0.1"
  },
  "devDependencies": {
    "@types/node": "^14.14.31",
    "@types/react": "^17.0.2",
    "@types/react-dom": "^17.0.1",
    "@typescript-eslint/eslint-plugin": "^4.16.1",
    "@typescript-eslint/parser": "^4.16.1",
    "bootstrap": "^5.0.0-beta2",
    "classnames": "^2.2.6",
    "encoding": "^0.1.13",
    "eslint": "^7.21.0",
    "eslint-config-prettier": "^8.1.0",
    "eslint-plugin-prettier": "^3.3.1",
    "eslint-plugin-react": "^7.22.0",
    "eslint-plugin-react-hooks": "^4.2.0",
    "next-pwa": "^4.0.0-beta.0",
    "postcss-flexbugs-fixes": "^5.0.2",
    "postcss-preset-env": "^6.7.0",
    "prettier": "^2.2.1",
    "sanitize.css": "^12.0.1",
    "sass": "^1.32.8",
    "sharp": "^0.26.3",
    "styled-components": "^5.2.1",
    "styled-media-query": "^2.1.2",
    "stylelint": "^13.11.0",
    "stylelint-config-prettier": "^8.0.2",
    "stylelint-config-recess-order": "^2.3.0",
    "stylelint-config-recommended-scss": "^4.2.0",
    "stylelint-config-standard": "^20.0.0",
    "stylelint-declaration-block-no-ignored-properties": "^2.3.0",
    "stylelint-declaration-use-variable": "^1.7.2",
    "stylelint-no-unsupported-browser-features": "^4.1.4",
    "stylelint-prettier": "^1.2.0",
    "stylelint-scss": "^3.19.0",
    "typescript": "^4.2.2",
    "webpack": "^5.24.2"
  }
}


webpack5をインストールするだけではダメ。設定に、builder:'webpack5'と明示してやる必要がある。

.storybook/main.js
const { resolve } = require('path')

module.exports = {
  core: {
    builder: 'webpack5',
  },
  stories: ['../stories/**/*.stories.tsx'],
}

ハマリ2 React is not defined

これはNext.jsでReactはimportしなくても使えるのが原因だった。
1行追加してやることで解決。

StorybookのFAQにあったのでハマる人は多い模様。

+ import React from 'react'
import styles from '../styles/Count.module.scss'

const Count: React.FC<{ num: string }> = ({ num }) => {
  return <span className={styles.kotaFrame}>{num}</span>
}
export default Count

ハマリ3 @import '~bootstrap/scss/functions';が解決できない

sass-loaderでは~でnode_modulesを示すのだが、
今回storybookをsrcとは別の深い階層にインストールしていたのが問題だったもよう。

- internal
  - component-catalog
    - .storybook
    - node_modules <- ここにbootstrapを入れても`で解釈されない
      - bootstrap
- node_modules <- ここのnode_modulesが~で解釈される
  - bootstrap
- src
  - styles
    - mybootstrap.scss <- ここで~bootstrapをインポートしている

なので、それに合わせて.github/workflows/gh-pages.ymlも更新

name: github pages

on:
  push:
    branches:
      - docs  # Set a branch name to trigger deployment

jobs:
  deploy:
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v2

      - name: Setup Node
        uses: actions/setup-node@v2
        with:
          node-version: '14'

      - name: StoryBook Setup
        run: npm ci
        working-directory: ./internals/component-catalog


+      - name: StoryBook Setup for ~bootstrap.scss
+        run: npm i bootstrap@next

      - name: StoryBook Make
        run: npx build-storybook -c ./.storybook -o ./component-catalog --no-dll
        working-directory: ./internals/component-catalog

      - name: StoryBook Move files
        run: |
          mkdir -p ./docs/publish/component-catalog
          mv ./internals/component-catalog/component-catalog ./docs/publish/

      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./docs

ハマリ4 Error from chokidar

これは、公開ではなく手元で動かそうとしたときに発生したエラー。
エラーは出るが、手元でStoryBookのページは問題なく見ることができた。謎。

以下のように、chokidarがnode_modules配下のファイルを監視しようとして、監視対象がlinuxの設定上限を超えるため発生するエラーの模様。

Error from chokidar (/app/internals/component-catalog/node_modules/@storybook/components/node_modules/core-js/modules): Error: ENOSPC: System limit for number of file watchers reached, watch '/app/internals/component-catalog/node_modules/@storybook/components/node_modules/core-js/modules/esnext.iterator.reduce.js'

addonsを指定するとエラーが発生する挙動であった。

module.exports = {
  core: {
    builder: 'webpack5',
  },
  stories: ['../stories/**/*.stories.tsx'],
+  addons: ['@storybook/addon-knobs/register',],
}

なお、見るフォルダを指定するオプションがあるaddonだと、このエラーは起きなかった。

const { resolve } = require('path')

module.exports = {
  core: {
    builder: 'webpack5',
  },
  // ディレクトリと拡張子の変更
  stories: ['../stories/**/*.stories.tsx'],
  addons: [
+    {
+      name: '@storybook/addon-storysource',
+      options: {
+        rule: {
+          test: [/\.stories\.tsx?$/],
+          include: [resolve(__dirname, '../stories')], // You can specify directories
+        },
+      },
    },
  ],
}

node_modulesの監視をignoreできるオプションがないか探したけれど見つからず。

https://yatta47.hateblo.jp/entry/2020/11/21/213106 を参考に、sudo sysctl fs.inotify.max_user_watches=24288を行って設定上限をあげたところ発生はなくなった。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む