20191205のReactに関する記事は19件です。

技術的負債を作らないためのReact Hooks入門

React Advent Calendar 2019 - Qiita の 12/4 です。12/5も終わろうとしていますが、僕の中ではまだ12/4です…。ほんとすみません…。

さて、技術的負債は、常に注意深く設計しない限り、必ず積み重なっていくものです。ウェブフロントエンドを含めたGUI開発では、テストがかかれないことも多いため、技術的負債を作らないために、React Hooks入門記事を書いてみました。

もっと詳しく!とかここが分からない!とかがあればぜひコメントなりいただければ、追記できる気力がある限り追記していきたいと思います。

サンプルコードは https://github.com/erukiti/react-hooks-without-debt に置いています。

React Hooks の基本

React Hooksの考え方や個々のAPI仕様に関しては、公式ドキュメント を読むとわかりやすいと思います。

また、筆者が書いた技術同人誌 Effective React Hooks もあります。

数年前に書いたReact+Redux入門のコンポーネントはES2015+のclass機能を使って書かれていましたが、React Hooksでは関数だけでコンポーネントを書きます。

関数を使う利点は、公式ドキュメントに書いてありますが、シンプルなコードにできることです。

  • 関数にすることでクラス型コンポーネントにまつわる複雑さから開放される
  • ただし、ただの関数ではステートなどを持つことができない(以前はHoCなどのテクニックを使っていた)
  • カスタムフックによりコンポーネント関数をシンプルに保つための関数分割が可能になる

といったところがポイントです。

この記事では、個々のAPI useStateuseEffect については踏み込まないので、それは公式ドキュメントを読んでおくといいと思います。

セットアップ

create-react-appというツールを使えば簡単です。

# yarn
yarn create react-app <project名> --template typescript

# npm
npx create-react-app <project名> --template typescript

カスタムフック

技術的負債を作らないためのReact Hooksの観点で重要なポイントは、カスタムフックと、カスタムフックのテストにあります。React Hooksでは、公式が用意している Hooks 関数だけではなく、自前の Hooks 関数を作成でき、これをカスタムフックと呼びます。

実は、React Hooksを使っても、単に公式の Hooks 関数を使うだけだと、密結合の呪いはクラス型コンポーネントの頃と同じ位には降り掛かってしまいます。それを解決するための方法がカスタムフックなのです。

カスタムフックは useHoge のような、useで始まる関数であり、コンポーネント関数と同じように、Hooks 関数を呼び出すことができます。

custom-hook.ts
import { useState, useCallback } from 'react'

export const useTextInput = (
  init: string = '',
): [string, (e: any) => void] => {
  const [value, setValue] = useState(init)
  const handleChange = useCallback(
    (e: any) => {
      setValue(e.target.value)
    },
    [setValue],
  )
  return [value, handleChange]
}

useTextInput はテキスト入力をするカスタムフックです。やっていることはとても単純で、初期値を元にuseStatevaluesetValueを作成し、handleChangeという関数をuseCallbackで作成し、valuehandleChange のみを返すものです。

App.tsx
import React from 'react'
import { useTextInput } from './custom-hook'

const App: React.FC = () => {
  const [name, handleChangeName] = useTextInput()
  const [favorite, handleChangeFavorite] = useTextInput()
  return (
    <div>
      名前: <input value={name} onChange={handleChangeName} />
      <br />
      好きなもの: <input value={favorite} onChange={handleChangeFavorite} />
    </div>
  )
}

export default App

使い方は<input value={value} onChange={handleChange} />のようにinputタグに渡すだけです。

さて、このカスタムフックはどうすればテストできるでしょうか?

$ yarn add -D @testing-library/react-hooks react-test-renderer
custom-hook.test.ts
import { renderHook, act } from '@testing-library/react-hooks'

import { useTextInput } from './custom-hook'

test('useTextInput', () => {
  const { result } = renderHook(() => useTextInput('hoge'))
  const [value, handleChange] = result.current
  expect(value).toBe('hoge')
  act(() => {
    handleChange({ target: { value: 'fuga' } })
  })
  expect(result.current[0]).toBe('fuga')
})

@testing-library/react-hooksに含まれるrenderHookと、actを使います。

renderHook(() => useTextInput()) のようにカスタムフックを呼び出す関数を引数として渡します。このコードではわかりやすいようにテキストの初期値として hoge を渡しています。renderHookの戻り値の .result.current にはカスタムフック関数の戻り値がそのまま入っているため、さらに value を取り出して、hoge であることを確認します。

あとは、handleChange を直接呼び出すことでカスタムフック関数の、文字入力の処理を実行しています。testing-library/reactreact-hooks では、何かしらコンポーネントに変化をもたらすときには act のコールバックの中で行います。これによりReactのレンダリングを内部的に行っています。カスタムフックを含めたフック関数はすべてReactコンポーネントありきの仕組みなため、このような処理が必要です。

act が完了すると、result.current[0] は、新しい value に置き換わっているため、fuga という値に置き換わっています。

  • renderHook を使うとカスタムフックのテストが可能
  • act コールバック内でReactコンポーネントに変化をもたらす処理を行う

ポイントはこの2点です。

これにより、少なくともカスタムフックのユニットテストは可能になります。今回書き換えたApp.tsxでは、useTextInputの呼び出しと、inputタグの組み立てくらいしか行っていないため、これ以上のテストは、できるとすればE2Eテストか画像回帰テストくらいです。

技術的負債との戦い方

技術的負債が生み出される背景は色々あります。組織論、人員不足などは大きな問題ですが、そういったものはいったんさておき、技術的側面だけで見ると、密結合や低凝集性という設計上の問題、テストが無いなどが主たる原因です。

  • 密結合・低凝集性などといった設計上の誤り
  • テストがない

密結合との戦いについては、SOLID原則に関して筆者が書いた別のブログを御覧ください。

凝集性については今回は省略します。

テスト

技術的負債と戦うためにはテストが必須です。ウェブフロントエンドでも同様です。

密結合をするとテストがしづらくなります。フルスタックフレームワークは密結合になりやすい問題があり、ユニットテストがしづらいケースがとても多いです。クリーンアーキテクチャなどでは、こういった問題に対しては、なるべくフレームワークの決定を遅らせる、依存しすぎないということで、なるべく疎結合を保つべきだとしています。

  • ロジックに対してはユニットテストを書く
    • ロジック(特にビジネスロジック)でユニットテストを書きづらいのならばそれは設計に不備がある(多くの場合は、フレームワークなどに密結合してしまっている)

カスタムフックは基本的にはユニットテストしやすいものです。

ウェブフロントエンドや他GUIにおいては、コンポーネントを Humble Object Pattern というデザインパターンで、Presentation と View を分離しましょう。

  • テストしやすいもの Presentation はユニットテストを書く
  • テストしづらいもの View は、E2Eテストか画像回帰テストなどを書く。もしくは自動テストを諦める

先程の、カスタムフックへの分割と、カスタムフックのユニットテストは、まさにHumble Object Patternです。

まとめ

結局の所、この記事ではあまり複雑なことを主張するわけではなく、React Hooksではカスタムフックを使うことで Humble Object Pattern をやりやすく、技術的負債を貯めないための第一歩にふさわしいということが、主張したいことでした。

  • React Hooksにはカスタムフックという仕組みがある
  • カスタムフックはコンポーネントをテストしやすいものとしづらいもので分離するのに向いている
    • Humble Object Pattern によりテストしやすいPresentationとしづらいViewに分離する
  • Presentationはユニットテストをする
  • Viewは、E2Eテストか画像回帰テストか、あるいは諦めるなどで対処する

サンプルコードは https://github.com/erukiti/react-hooks-without-debt に置いています。

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

htmlをReactコンポーネントにぶちこんでやるぜ〜〜!!

はじめに

いなたつアドカレの五日目の記事です。

今回はReactで生のhtmlを扱うための方法とその時の注意について少し、、、

ざっくりいうと

  • dangerouslySetInnerHTMLを使おう
  • XSS に気をつけよう
  • markdownから変換して表示も可能だよ

dangerouslySetInnerHTMLを使う

<span dangerouslySetInnerHTML={{ __html: html}} />

dangerouslySetInnerHTML属性に__html(あんだーばーあんだーばーへいちてぃーえむえる)に展開したいhtmlを設定する オブジェクト を渡すことで指定したhtmlをタグ内部に展開することができます。

dangerouslyやねん

XSSの危険がつきまとってくるんですよね。

XSSってのはクロスサイトスクリプティングといって、テキストボックスにhtmlを埋め込み、予期せぬ動作をさせようとすることです。

これの何がdangerouslyかというと
- テキストボックスにスクリプトを入力し、javascriptを実行させることで、クッキーなどを盗みなりすましができる
- 同様に、アプリケーションの機能を悪用されるかもしれない
クッキーでセッションIDなんかを保持しているとなりすましの温床になりかねないですね。。。。
dangerouslyって名前についてるのはこういうところからなので、まあ、あまり使わない方がいいですね。

じゃあどーやって対策すんの

** サニタイズ **

markdownがうんぬんかんぬん

markdownを生のhtmlに変換してここにぶちこんでやるぜーしたらmarkdownエディタのプレビューっぽいことできるって話
次回reactとmarkedのお話を書きます。。。。

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

サーバーレス将棋 AI - WebSocket 編

Web ブラウザからクラウド上の将棋 AI を呼ぶアプリケーションを作りました。(画像クリックで開きます)

Serverless_Shogi_DEMO.png

将棋 AI が 伝説の ▲52 銀 を発見する瞬間を、ブラウザで体験できます!

背景

三年前、サーバーレス将棋 AI ☖ という記事を書きましたが、あれからサーバーレスを取り巻く技術的な環境は大きく変わりました。

AWS Lambda の実行時間・メモリ上限は増え、API Gateway が WebSocket に対応 しました。また AWS Amplify の登場で、バックエンド (Lambda と種々のデータソース) とフロントエンドを一元的に、開発から本番デプロイまでほとんど全てのフェーズで具体的なインフラを意識せずに実現できるようになりました。

現在のサーバーレス技術で、あらためて将棋 AI を利用してどんなアプリケーションが作れるのか、実証のためにこのデモを作りました。

アーキテクチャ

sshogi.png

ブラウザアプリケーション、API Gateway、AWS Lambda の 3 つのコンポーネントで構成されています。
AWS Lambda 上で将棋 AI を走らせます。2019 年現在最も強い AI のひとつであるやねうら王 (探索部) と illqha4 (評価関数) をデプロイしています。
API Gateway はブラウザと Lambda の間に立つ WebSocket サーバーとして振る舞います。クライアント (ブラウザ) からのリクエストに応じて Lambda を起動し、Lambda からのメッセージをクライアントに WebSocket で非同期送信します。

実装

全てのソースコードは こちらのリポジトリ にまとまっています。

Serverless Framework (AWS Lambda + API Gateway)

本当は Amplify でやりたかったんですが、2019/12 現在 API Gateway Websocket は 非対応 の模様。
今回は Serverless Framework で実装しました。AWS Lambda と API Gateway の実装・定義とデプロイを cli で完結することができます。大きくこのような流れです。

  1. 将棋 AI を Lambda 用にビルド
  2. Lambda 関数の実装と API Gateway (Websocket イベント) の定義
  3. デプロイ

ビルドについては こちらの記事 (やねうら王を AWS Lambda で動かす) をご覧ください。
Lambda 関数では、API Gateway のリクエストを受けて将棋 AI プロセスを起動し、AI の標準出力を改行で分割し Websocket に送信しています。

React & Amplify

フロントは React を使っています。UI コンポーネントは chakra を使いました。 Box を並べるだけでそれっぽくなります。デフォルトカラーテーマが良い感じです。

あと SFEN (将棋 AI のメッセージフォーマット) の parse が地味に大変で、いつも頑張って自前実装しているんですが、今回は PEG.js という Parser Generator を使ってみました。構文定義ファイルから parser を生成してくれるもので、コーナーケースに気をつけながら集中して自前実装するより 100 倍ラクでした。

Amplify は静的コンテンツのホスティング機能のみ利用しています。create-react-app で開発開始して、amplify publish でいきなり SSL のデプロイ済み URL が降ってくるのは便利です。

注意点

API Gateway と Lambda の組み合わせで WebSocket を利用する場合にいくつか注意点があります。

1. 双方向通信ではない

WebSocket を利用すればブラウザアプリケーションと Lambda プロセスが双方向通信できるような気がしていたのですが、API Gateway + Lambda の組み合わせを使う限りこれは難しいようです。
具体的には、Lambda からブラウザに対しては非同期にメッセージを push できる一方で、ブラウザから Lambda に対しては "Lambda 起動リクエスト" しか送ることができません。そのため、将棋 AI のように単一プロセスがインメモリで状態を持ち対話的に処理を行う CLI アプリケーションについては、工夫が必要です。最初のリクエストで全ての情報を渡して、Lambda から逐次レスポンスを受け取る形になります。

2. WebSocket が到達順序を保証しない

保証してくれません。かなりズレます。将棋 AI のように最新の結果のみに興味がある場合、何らかの対応を行う必要があります。
今回はメッセージに Sequence ID を振り、クライアントサイドで受信メッセージの再ソートを行いました。

まとめと今後の展望

サーバーレス関連技術すごいですね。今回作ったアプリケーションについても、一連のバックエンド機能を実装するのにひと昔前だったらどれくらい手間がかかっただろうと考えると、気が遠くなります。
また、WebSocket ✕ 将棋 AI の相性の良さを感じました。従来の Web API だと 30 秒後に結果のみがポンと返ってきます。WebSocket にすることでリアルタイムに思考過程を表示でき、ユーザーが受け取れる情報量が格段に増えました。
今後は Windows ユーザーの恵まれた将棋検討環境 (将棋所など) を Mac やスマホで実現するウェブアプリケーションを作りたいです。ずっと言ってますが。

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

Reactの式展開について

Reactでコンポーネントを作るに当たって、変数展開は欠かせないものですが、微妙に気になることがいくつかあります。

消えるもの、消えないもの

よく条件で表示するものを変えるときに、{condition || <SomeComponent />}{condition && <SomeComponent />}のように書くこともあるかと思いますが、条件を満たさない場合、左側がそのまま描画対象となります。

そして、truefalsenullundefined、空文字列ならもちろん何も描画されないのですが、truthyなものは基本的に描画対象となりますし、falsyなものでも0は「0」として描画されてしまいます。

? :を使って条件判定の後で描画するものを明確にする、あるいは展開箇所で不等号や!!!などを直接書いてtruefalseしか入らないようにする、といった注意は払ったほうがいいかもしれません。

テキストノードが入らない

ふつうに以下のようなHTMLを書くと、<button>テキストの間には改行があるので、スペースが入ります。

<p>
  <button type="button">ボタン</button>
  テキスト
</p>

一方で、JSXの場合は改行の絡むスペースはすべて無視される</button> テキストのように、同一行に書いたときは無視されません)ので、<button>テキストがくっついてしまいます。

もちろん、「CSSで隙間を開ける」という解決策もあるのですが、スペースとしてのテキストノードを書いたほうがいい場面もあります。

このような場合、式展開の形で書けばスペースが消えることはありません。

<p>
  <button type="button">ボタン</button>
  { ' ' }
  テキスト
</p>
<p>
  <button type="button">ボタン</button>
  { ' テキスト' }
</p>

スペースだけ独立して書いてもいいですし、スペースに続く文言まで文字列として含むのもありです。

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

React Web App #2 とりあえず動かしてみる

概要

React のチュートリアルをもとにとりあえず動くものを作ってみました。

アプリ

縦横に部屋が広がる様子を作るのが目的ですが、
いったん一次元の配列で部屋を作ってみます。
移動もただのボタンで行い、createも色をつけるだけです。

https://codepen.io/shti-f/pen/Jjodroz?editors=0010

Architecture

Atomic Architectureを採用したので、
Atoms, Organisms, Templates, Pagesと分かれるはずなのですが、
現状でTemplatesはないです。

Atomsとして、ボタンと場所表示用の要素。
Organismsとして、Roomを作っています。(今回のアプリではTemplatesにあたる可能性もあります)
PageとしてPageを作り、主ロジックは現在全てPageが持っています。

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

Firebase×Reactでプログラミングお題サイトをつくった話

Firebase×Reactでプログラミングお題サイトをつくった話

この記事は、ひとり開発 Advent Calendar 2019の13日目の記事になります。

プログラミングお題サイトの紹介

個人開発が趣味の人ならあるあるだと思いますが、なんとなく開発したいけど何を作ろうかな~でも特に思いつかないな~:thinking:なんて迷ったりすることありますよね

そんな迷える個人開発者やプログラミング初心者向けのプログラミングお題サイトFirebase×Reactで作ってみました!
:point_right::point_right::point_right:プログラミングお題サイト:point_left::point_left::point_left:
プログラミングお題サイト.PNG

ソースコードはこちら
https://github.com/yakipudding/programming-odai

特徴①自分に合ったお題を探せる!

プログラミングお題サイトでは、人気のお題や特定のタグのお題を探すことができます
人気のお題.PNG

お題ページ.PNG

気に入ったお題に対して「いいね」をしたり、「つくってみたれぽ」を投稿することができます!

特徴②お題を投稿できる!

プログラミングお題は自分で投稿することもできます!
お題投稿.PNG

Markdown記法でブログ記事のように書けるので、体裁を気にせず気楽に投稿することができます!
チュートリアルでやった良い題材を投稿したり、自分のアイデアを共有してみんな作ってみて!ということもできます。

特徴③つくってみたを投稿できる!

お題に対して、C○○kpadの「つくれぽ」ならぬ「つくってみたれぽ」を投稿することができます

つくってみた投稿.png

お題をつくったブログ記事やGitHubレポジトリを投稿することで記事を見てもらうきっかきになったり、初心者プログラマが参考にしたりすることができます!

作ってみた系や個人開発系の記事はQiitaだろうがブログだろうがスルーされやすい:sob:全然いいねつかない:sob::sob::sob:(気がする)ので、つくってみたれぽを公開して他の人に見てもらう機会を増やそう!というコンセプトもあったりします笑

プログラミングお題サイトを作ってみて

我ながらなかなか良いアイデアのWebアプリが作れたな~と、わりと気に入っています、、、が
なかなか苦労した点も多くありました

いいね機能、意外と奥が深い件

SNSっぽくいいねつけられるようにしよう!と思ったのですが、いざ実装しようとすると意外と大変でした。

「いいね」機能を実装するためには、「誰が何にいいねした」「どの記事に何いいねついているか」「自分がなんの記事にいいねしたか」などの情報を取得できるように、いいね情報をDBに登録する必要があります

  • お題に「いいね」をしたとき
    • いいねテーブルにユーザーID・お題IDのレコードを挿入
  • お題の「いいね」を削除したとき
    • いいねテーブルのユーザーID・お題IDのレコードを削除(または削除フラグ)
  • お題一覧をみたとき
    • お題ごとにログインユーザーが「いいねしているかどうか」をいいねテーブルとJOINして取得

たかだか「いいね」機能ですが、必要なDB処理はそこそこあるな、、という感じに。。。
さらにDBはFirebaseのRealtime Database※を使っていた関係でJOINができないため、手動JOIN(お題IDでループで回して取得)する必要があったりして…:innocent::innocent::innocent:
RDBの偉大さを知ったのでした。。。

DB設計に関してはお題に対していいねユーザーを紐付けるなど様々なやり方がありますが、いいねしたお題一覧も見たい(未実装)な~と考えたりしたのもあり、別テーブルとして保持することにしました

※FirebaseのDBはRealtime DatabaseとFirestoreがあり、機能としては後者のほうが良いです(クエリ機能が充実しているなど)
Firestoreを使わなかった理由は、課金単位が1日単位であり、かつそこそこシビアな上限だからです。こういうアドベントカレンダーで公開するようなサービスだと公開日にアクセスが集中してしまうと(しなかったら悲しい)上限に達してしまうため、ちょっと向いていないかもな…と避けました。ちなみにFirestoreもJOINはできません。。。

タグ機能、なかなか実装がつらかった件

お題に対してタグ(複数可)をつけて検索できるといいよね!とかる~く思ったのですが、、意外とタグ機能は要件がエグかったです。。。

  • タグの情報をどうやって保存するのか問題
    • お題に対して複数登録できるタグの情報を保存しておく必要がある
      • タグ項目を1つ用意しておき表示時に分割する?
      • お題に対して登録したタグのテーブル(1-N関係)を用意する?
    • タグで検索できるようにする必要がある
      • このタグが付与してあるお題一覧を取得する、というクエリが必要
        • タグのもたせ方によって検索がエグくなってしまう

という問題があったため、DB設計に関して工夫が必要でした。
ちなみにRealtime DatabaseはLike検索ができません:innocent::innocent::innocent:

結局、

  • お題テーブルにタグ項目(1項目でスペース区切り保持)をもたせる
  • お題投稿時、お題タグテーブルに対してタグID・お題IDのレコードを登録
  • タグ検索時、お題タグテーブルからタグIDで抽出して検索

という形で無理やりなんとかしました。。
RDBではないので正規化はしません:point_up:

世の中のWebサービスすごいってなった

個人開発レベルやいいね機能とタグ機能をさくっと実装しようとしてもこんなに大変なのに、ユーザー数多いWebサービスって本当にすごいな…と思いました。
こういった「世の中のWebサービスでよくある機能」を個人開発で実装してみるのも、実装の難しさを肌で感じたりできるので楽しいですよ!

RDBすごいってなった

タダより高いものはない精神でNoSQLのFirebeseのRealtime Databaseを使っていましたが、やはりJOINができなかったりLike検索ができない関係で、リッチな実装は限界があるなぁと感じました。
DBを無料で使えるという強力すぎるアドバンテージですが、実際の運用を考えるとかなり設計を工夫しないと難しいんだろうな~と思います。。

普段はRDB(SQL Server)を使っているのですが、RDBが便利すぎて設計はあぐらをかいていたな~というか、楽をしていたんだな~と身にしみました。
RDBであってもデータの参照のされ方を意識して、JOINやWHEREのキーを意識したりインデックスを考えた設計にしないといけないな…と改めて思いました:thinking:

締め

というわけでプログラミングお題サイトの紹介でした!
いろいろ苦労もありますが、趣味の一つとして個人開発はやっぱり楽しいですね。今後もいろいろ作っていこうと思います。

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

reactive-react-reduxの紹介

はじめに

本記事では、ReactでReduxを使うためのライブラリを紹介します。このライブラリは、先日紹介したreact-trackedのRedux版とも言えます。

reactive-react-reduxとは

リポジトリはこちらです。

https://github.com/dai-shi/reactive-react-redux

Reduxが公式に提供しているReact向けライブラリは、react-reduxと呼ばれます。reactive-react-reduxは非公式ライブラリで、公式ライブラリの代わりに使えるものです。ただし、Hooks APIしか提供しません。

公式のReact ReduxのHooks APIとの差分は2点あります。

  1. useTrackedStateというhookを提供する
  2. stateを直接contextに入れる方式を採用する(storeではなく)

今回は前者について説明します。

そもそも課題はなにか?

実はこのライブラリを作ったのは、connectへの不満からでした。Reduxはもっとシンプルに使えるものであるはずと思い、mapStateToPropsを排除したAPIを提供したかったというのが動機です。

現在では、useSelectorというhookが公式ライブラリから提供されているので、その差分で説明したいと思います。

例として、storeのstateが次のような形をしているものを考えましょう。

const state = {
  user: {
    lastName: 'React',
    firstName: 'Hooks',
    age: 1,
  },
  color: 'white',
};

このstateを使って、コンポーネントでfirstNameとlastNameを表示してみましょう。useSelectorを使った典型的な書き方は次のようになります。

const Component = () => {
  const firstName = useSelector(state => state.user.firstName);
  const lastName = useSelector(state => state.user.lastName);
  return (
    <div>
      <div>First Name: {firstName}</div>
      <div>Last Name: {lastName}</div>
    </div>
  );
};

connectに慣れている方は、useSelectorが複数あることに慣れないかもしれませんが、これがオススメされる書き方です。

仮にここで、

const { firstName, lastName } = useSelector(state => ({
  firstName: state.user.firstName,
  lastName: state.user.lastName,
}));

のように書いてしまうと、動作はしますが、望まない形になります。望まない形とは、state.colorだけに変更があった場合でも、このuseSelectorはコンポーネントを再renderすることを指します。これが起こるのは、useSelectorに指定している関数が毎回新しいオブジェクトを生成するためです。

これを回避する(オススメはuseSelectorを分けることですが、それができない場合に)方法は、公式ライブラリでは2つあります。

  1. equalityFnを第二引数に指定する
  2. memoized selectorを作って使う

この辺りが、React Reduxが難しく感じる理由の一つだと感じています。

そもそも、selectorというのは大きいstateオブジェクトから必要なものを選択してくるというAPIとしてはとても分かりやすいものです。そこに、パフォーマンス向上(ここでは無駄なrenderを抑制すること)のために、selectorにreference equality(=参照透過性)の概念を持ち込むことが難しさの原因かと思います。

useTrackedStateを使うとどうなるか?

useTrackedStateを使うと上記のコードは、次のようにかけます。

const state = useTrackedState();
const { firstName, lastState } = state.user;

もしくは、selectorを別で定義したとして、次のようにも書けます。

const selectUserNames = state => ({
  firstName: state.user.firstName,
  lastName: state.user.lastName,
});

const Component = () => {
  const { firstName, lastName } = selectUserNames(useTrackedState());
  return (
    <div>
      <div>First Name: {firstName}</div>
      <div>Last Name: {lastName}</div>
    </div>
  );
};

このように特にreferential equalityを気にせずにselectorを書いたとしても、useTrackedStateにより、無駄なrenderを抑制することができます。

なぜそんなことができるのか?

ReduxメンテナーのMarkのブログ記事では、「Magic?」として紹介されています。reference equalityを意識してselectorを書いている人には、むしろこの挙動が予測不能なものに感じるのかもしれません。

useTrackedStateが動作する仕組みは、Proxyによるものです。

Proxyを使うと、オブジェクトへの操作を追跡(Track)できます。つまり、storeの大きなstateの中でどのオブジェクトプロパティにアクセスしたかを知ることができます。これを利用して、アクセスがあったプロパティに変更があった場合のみコンポーネントを再renderするようにuseTrackedStateが制御しています。

Proxyって遅いんじゃないの?

比較対象を何にするかですが、まず、人が正しくreferential equalityを考慮したselectorを書けるとは限らないということは伝えたいと思います。その場合は、機械がTrackする方が正確になります。もちろん無駄がなく、トータルで速いです。

仮に、完璧なselectorを人が書けたとして、Proxyにはオーバーヘッドがあるのは事実です。簡単な例でベンチマークをした結果を載せます。

image.png

ここで比較すべきは、reactive-react-reduxのuseTrackedStateと同じくuseSelectorのカラムです。このベンチマークではほとんど差がないことが分かるでしょう。他のベンチマーク評価もしましたが、極端な例では差が出るものの、実用に耐えうる範囲とみています。

Proxyを利用している他のプロジェクト

React系では、immerMobXでProxyが使われています。また、Vue.jsでも使われているとのことです。

Redux Toolkitではimmerを採用していますし、今後Proxyを使ったライブラリは増えてくるかもしれません。

おわりに

reactive-react-reduxのuseTrackedStateについて紹介しました。興味を持ってくださった方は、ぜひ触ってみてください。ちなみに、react-trackedにも同じhookが用意されていますので、非Redux派の方はそちらをどうぞ。

今回は踏み込みませんでしたが、useTrackedStateにも限界がないわけではありません。例えば、次のようなuseSelectorの例については、useTrackedStateで同じ挙動を再現することはできません。

const isYoung = useSelector(state => state.user.age < 10);

最後に、Redux系は、最近、Redux Toolkitをはじめ、ドキュメントの充実化など様々な改善が行われています。これから学び始める人には、良い環境になってきていると思います。

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

【React】Propsについて

UserコンポーネントにpropsTarpを渡す

/src/App.js
import React, {Component} from 'react';

const App = () => {
  return( <div>
          <User name={"mee"} />
         </div>
  )
}
const User =(props) => {
  return <div>hi i am {props.name}</div>
}
export default App;
$ yarn run start

image.png

リストレンダリング

次のソースコードは

/src/App.js
import React, {Component} from 'react';

const App = () => {

  return( <div>
          <User name={"mee"} age={10}/>
          <User name={"mememe"} age={4}/>
         </div>
  )
}
const User =(props) => {
  return <div>hi i am {props.name}, and {props.age}さいです</div>
}
export default App;

mapを利用して記述することができる。

/src/App.js
import React, {Component} from 'react';

const App = () => {
  const  profiles = [
    {name: "taro", age:10},
    {name: "hanako", age: 5}
  ]
  return(
     <div>
          {
            profiles.map((profile) => {
              return <User name={profile.anme} age={profile.age}/>
            })
          }
      </div>
  )
}
const User =(props) => {
  return <div>hi i am {props.name}, and {props.age}さいです</div>
}
export default App;

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

? React Suite 4.0 release

The new semester is another new starting point. React Suite is welcoming the release of version 4.0. Since the design of the V4 version in March 2019, after more than six months of development and testing, discussions and disputes, I have finally completed all the plans.

In this harvest season, we have prepared a series of updates for everyone, are you ready?

1, migrate from Flow to TypeScript

First of all, thanks to Flow for supporting the entire V3 release, the component library can easily have static type checking. With the wider use of TypeScript and the problems that Flow has exposed, we have abandoned Flow in this release and refactored all the code with TypeScript. Make the code more readable and maintainable.

2, accessibility improvement

To support the new browser features, we abandoned IE9 in the previous version of V3. But we still hope that the Web application developed with React Suite will be used by as many people as possible and better. We try to reach more people in terms of accessibility.

2.1 Color contrast improvement

There are many people with low vision in the world, and the display used by these users is often uneven. The contrast between text and background becomes the most basic functional problem for users. As a thoughtful UI component library, how can you not take care of the user's eyes?

According to the requirements of the 《Web Content Accessibility Guidelines (WCAG) 》, the color of the text, the thickness of the font, we have improved the contrast, and adjusted the algorithm of the swatch, the purpose is to make your product more accessible.

2.2 Support dark mode

In the electronics around us, night mode is supported from the operating system to browsers, editors, and various readers. It is a high-contrast, or reverse-color display mode. If your users need to use your product for a long time, having a night mode can effectively alleviate eye strain and make it easier to read.

The Dark Mode theme has been added to the default theme, and provides a full customizable option. When developing, you only need to import the corresponding style file:

import 'rsuite/lib/styles/themes/dark/index.less';

More on topic related settings can be referenced: custom theme

3、Added some components

The component is the smallest unit provided by React Suite. As Web applications become richer and more diverse, we will continue to offer a richer set of components.

3.1 Support for List

The List component is used very much on the mobile side, but in the mid- and back-end products, it has always been a component that is not well standardized. The required representations in different business scenarios will be different, so that we implement it in this version. List In addition to customizing the content of each item, we provide drag and drop sorting by default.

3.2 Support for Placeholder

In the front-end industry, everyone knows the word "skeleton screen". Its function is similar to that of Loader. It is a state displayed to the user before the application is not loaded, telling the user that the current data is being loaded. The advantage of the "skeleton screen" is to give the user a general structure of the page before the data has not been loaded, and enhance the sensory experience.

Placeholder is such a component that provides a rough structure of data. The general structure of the content area can be drawn by lines, rectangles, and circular outlines.

3.3 Support for Calendar

Calendar is a simple calendar panel that displays data for the calendar. Two usage scenarios are provided. One is to display a large calendar panel full of containers by default, which can display data for one month. The other is to provide a small, compact, small calendar panel that we often encounter in some systems' sidebars for data filtering.

3.4 Support Avatar and Badge
  • Support for Avatar components for displaying an avatar or trademark.

  • Support for Badge components for buttons, numbers next to icons, or status markers.



4、Breaking changes

We hope that each update will be most compatible with the historical version. But there are still some breaking changes, such as the use of new React features, or improvements to previously unreasonable designs.

4.1 Less than React 16.6 version is not supported

Some of React's new features are used in this release. For example, the new context API, which started to support the Class component static contextType property in React 16.6.0 #13728, uses this feature. So to use React Suite 4.0, you must upgrade react and react-dom to >=16.6.

4.2 Less compatibility change

In this release, support for the Dark theme has been made, and the introduction address of the Less file has been adjusted.

3.x version
less
import 'rsuite/styles/less/index.less';

4.x version
```less
import 'rsuite/lib/styles/themes/default/index.less'

// or
// import 'rsuite/lib/styles/index.less';
```

The version of Less needs to be upgraded to the >=3.0 version.

4.3 TreePicker and CheckTreePicker discard the expandAll attribute

The TreePicker component and the CheckTreePicker component deprecated the expandAll property and added the expandItemValues property to expand the specified node.

4.4 Adjusted the values of Dropdown, Whisper, and all Picker components placement properties

The placement attribute is the position that the configuration selector displays after it is opened. To make the parameters more readable, the values are adjusted as follows:

type Placement4 = 'top' | 'bottom' | 'right' | 'left';
type Placement8 =
| 'bottomStart'
| 'bottomEnd'
| 'topStart'
| 'topEnd'
| 'leftStart'
| 'rightStart'
| 'leftEnd'
| 'rightEnd';
type PlacementAuto =
| 'auto'
| 'autoVerticalStart'
| 'autoVerticalEnd'
| 'autoHorizontalStart'
| 'autoHorizontalEnd';

Compatible with 3.x version

5、Bugfix and improvement

5.1 All Picker components support size

We have a very complete Picker series of components in the data entry component, which is often used in some data filtering columns, in addition to being used in forms. Considering that the Input and Button components have a size attribute that can be resized, the size attribute is also added to all Pickers to accommodate more scenarios.

5.2 Overflow protection for Whisper and Picker components

All Picker components and Whisper components are pop-up floating layers at a specified location, but sometimes because the size of the floating layer exceeds the extent of the container, some floating layers are not blocked. You can set a preventOverfow property at this time. The relative position of the floating layer display is adjusted according to the free space of the container, and the floating layer is displayed on the page as much as possible.

5.3 FormControl component read-only and plain text

FormControl adds 2 props support:

  • readOnly makes the form component read-only and cannot be edited.
  • plaintext lets the form components be displayed in plain text.

When these two properties are set on the Form component, all the form components in the form are globally set. In many cases, we need to add a data detail page to the completed form. At this time, we need to add a new module and display the data. To improve code reusability, you can turn a form into a data detail panel by setting a plaintext property on the Form component.

5.4 DatePicker and DateRangePicker support display weeks
<DatePicker showWeekNumbers />
<DateRangePicker showWeekNumbers /> 

If you need to view the number of weeks in your business in your business, you can set the showWeekNumbers attribute on the calendar, and the number of weeks in the current line will be displayed on the left side of the calendar.

5.5 Form combination Schema supports asynchronous check

Asynchronous verification is a basic requirement, and in this release Schema starts supporting Promise. Here are a few of the improvements to the form:

  • Set the checkAsync attribute on <FormControl> that requires asynchronous validation.
  • The validation rules for asynchronous validation add an object with a return value of Promise via the ʻaddRulemethod ofschema`.
  • The check can be triggered manually by calling checkAsync and checkForFieldAsync of <Form>.

Model

In the example we need to asynchronously verify that an email address already exists on the server. When adding a rule to Modal, we return a Promise object via the addRule method.

function asyncCheckEmail(email) {
  return new Promise(resolve => {
     // Asynchronous processing logic 
     // resolve(true);
  });
}

const model = SchemaModel({
  email: StringType()
    .isEmail('Please input the correct email address')
    .addRule((value, data) => {
      return asyncCheckEmail(value);
    }, 'Email address already exists')
});

Form
Set the declared model on Form and set a checkAsync property for the component that needs to be verified asynchronously.

const formRef = React.createRef();

function render(){
  return (
    <Form model={model} ref={formRef}>
     <FormControl checkAsync name="email"/>
    </Form>
  )
}

Form provides the check() method by default, and the checkAsync() method is called if it is an asynchronous check.

formRef.current.checkAsync().then(result => {
    console.log(result);
});
5.6 Alert and Notification support close method

Both Alert and Notification support the close and closeAll methods, closing the last message and closing all messages, respectively. In some business situations, you need to turn off the warning message on the page after performing an operation. You can do the following:

Alert.close();
Alert.closeAll();

Notification.close();
Notification.closeAll();
5.7 FlexboxGrid supports responsive

The Col component in the Grid layout is configurable for responsive layouts, but it doesn't have some features for Flex layouts. To make the two layouts fused, we can make FlexboxGrid.Item and Col Combined, combined with FlexboxGrid and with Flex layout features, and responsive configuration-related properties.

<FlexboxGrid.Item componentClass={Col} md={6}>
  content
</FlexboxGrid.Item>
5.8 All Picker new open and close methods

In some cases, you need to open or close a Picker by performing an action. For example: a cascading operation, you want to quickly select after closing a Picker, the default is to put a next Picker. We provide a open and close method on Picker:

const pickerRef = React.createRef();

function render() {
  return <SelectPicker ref={pickerRef} />;
}

// open
pickerRef.current.open();

// close
pickerRef.current.close();
5.9 Other fixes
  • Fixed a Uploader upload file larger than 1GB display issue.
  • Fixed compatibility issue with Input on IE browser display.
  • Fixed an issue where InputPicker on the keyboard Delete key would clear the input worth.
  • Fixed an issue where Dropdown set the toggleComponentClass={Button} background style error.
  • Fixed an issue where styles were missing when introduced on demand.
  • Fixed an issue where DatePicker disabled days were inconsistent with disabled months.
  • Fixed an issue where the scrollbar position was not updated after the Table data was updated.
  • Fixed the Table property expandedRowKeys update value is not controlled.
  • Fixed a callback parameter for the Table property onRowClick missing event.
  • Fixed support for focus events by the Form component.
  • Modified the default separator for Breadcrumb.
  • Fixed an issue where the position of the handle was not updated after the Slider changed from hidden to display state.

6、At last

I hope that our growth will bring a better experience to more developers.If you like React Suite, you can show your support by either

This project exists thanks to all the people who contribute.

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

GatsbyでFont Awesomeを使いたい

はじめまして、
のじみんといいます

Gatsbyほんと便利ですよね

今回はGatsbyでFontAwesomeを使用する方法について
インストールからフォントアイコンを表示するところまでを説明します

実行環境とバージョン一覧

  • Gatsby.js v2.18.4
  • react-fontawesome v0.1.7 (Font Awesome 5)

ちなみに、Gatsbyは以下のスターターを使用してビルドしました↓

$ gatsby new my-blog https://github.com/gatsbyjs/gatsby-starter-blog

また、この記事では上記スターターでビルドしたGatsbyプロジェクトのbio.js
記述していく程で話を進めていきます

インストール

この記事ではFontAwesomeを使用するために以下の3つをインストールします

  • @fortawesome/react-fontawesome
  • @fortawesome/fontawesome-svg-core
  • @fortawesome/free-solid-svg-icons
npmの場合
$ npm i --save @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons @fortawesome/react-fontawesome

もしくは

yarnの場合
$ yarn add @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons @fortawesome/react-fontawesome

使用方法

インポート

以下のコードをbio.jsに追記する

bio.js
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { library } from '@fortawesome/fontawesome-svg-core'

フォントアイコンの表示

使用するフォントアイコンによって記述方法が若干異なるので分けて説明する

Twitterなどの有名企業のフォントアイコンを使用する場合

企業系のフォントアイコンを使用する場合は以下を上記に加えてインポートする

bio.js
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { library } from '@fortawesome/fontawesome-svg-core'
import { fab } from '@fortawesome/free-brands-svg-icons' // 追加

使用するフォントアイコンを追加する

bio.js
library.add(fab)

フォントアイコンを使用したい箇所に以下のコードを追加する

bio.js
<FontAwesomeIcon icon={['fab', 'twitter']} />

それ以外のフォントアイコンを使用する場合

それ以外のフォントアイコンを使用する場合、インポート時に
使用するフォントアイコン名を記述する必要がある

bio.js
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faPen } from '@fortawesome/free-solid-svg-icons' //追加

使用するフォントアイコンを追加する

bio.js
library.add(faPen)

フォントアイコンを使用したい箇所に以下のコードを追加する

bio.js
<FontAwesomeIcon icon={faPen} />

実際に使ってみる

bio.js
import { 割愛 } from '割愛'

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { library } from '@fortawesome/fontawesome-svg-core'
import { fab } from '@fortawesome/free-brands-svg-icons'

const Bio = () => {
  const data = useStaticQuery(graphql`割愛`)

  const { author, social } = data.site.siteMetadata

  library.add(fab) // FontAwesomeのライブラリ読み込み

  return (
    <div>
      <Image/>
      <p>
        <strong>{author}</strong>
        <a href={`https://twitter.com/${social.twitter}`}>
          <FontAwesomeIcon icon={['fab', 'twitter']} /> {/* ツイッターアイコン */}
        </a>
        <span>うぇぶとかかいはつ日記</span>
      </p>
    </div>
  )

結果:noziming_bio

おまけ

無料のフォントアイコン一覧:Font Awesome

Gatsbyでフォントアイコンを使用する場合はフォントアイコン名を
キャメルケースで記述すれば使用できるはず(はず)

例)
battery-full (ケバブケース)

batteryFull (キャメルケース)

ほんでは

参考

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

Githubを入稿システムとして使ってる

この記事はRecruit Engineers Advent Calendar 2019の7日目の記事です。

リクルートライフスタイルでHOT PEPPER Beauty cosme(以下HPBC)の開発を担当している@roronyaです。GatsbyJSを使ってGithubを「運営からのお知らせ」の入稿システムにするための設定やアプリの実装方針について紹介します。

HPBCには「運営からのお知らせ」という新機能やキャンペーンをユーザーに知らせる画面があります。

お知らせ画面.png

「運営からのお知らせ」に表示する文面は、その特性上ディレクターやマーケターが用意する場合が多いのですが、HPBCでは、「運営からのお知らせ」用の入稿システムを作らずに、Githubを入稿システムとして使ってもらっています。

最近のGithubは、Web上でファイルの追加や編集ができるたり、更にその変更をブランチを切ってPRを作るところまで簡単にできたりするので、Gitの知識が無くても使えるツールになってきています。Githubに原稿が来てしまえば、あとはCIでアプリで表示するためのAPIを叩いたりデプロイをしたりといった作業を任せることができます。エンジニアが何か作業をする必要がないので効率的です。

他にも以下のようなメリットがあります。

  • Markdown Previewのついたエディタがデフォルトで備わっている
  • 意識させずにバージョン管理を強制できる
  • PRで文面のレビューがやりやすい

この記事ではGithubの機能を活かして入稿システムとして使うためのGatsbyJSの設定やAPIやアプリの実装方針について紹介します。また、サンプルコードは以下のリポジトリに置いておきます。
roronya/gatsby-notification

入稿からアプリでの表示の流れ

以下のような工程でMarkdownをもとにお知らせページを生成しアプリで見れるようにしています。

  1. GithubにMarkdownをpushする
  2. pushにフックしてGatsbyJSで静的ページをbuild
  3. FirebaseHostingにデプロイ
  4. デプロイしたURLをAPIに登録する
  5. APIでURL一覧を返しアプリでWebViewで表示する

アプリでのお知らせの表示方法

アプリからお知らせを表示する方法は2通り考えられます。

  1. APIから文面をもらってアプリでレイアウトする
  2. Webページで作ってWebViewで表示する

1番のAPIから文面をもらう方法だと作れるお知らせ画面の自由度が減りそうだったので、今回はWebページで作ることにしました。

Markdownからページを生成する

静的ページジェネレータにはGatsbyJSを選びました。

GithubはMarkdownでファイルを編集するとプレビュー機能が使えるので、お知らせの文面もMarkdownで入稿できると、出来上がりが想像できて良さそうでした。GatsbyJSならMarkdownから静的ページを生成できます。

GatsbyJSの公式のドキュメント1を参考にMarkdownからページを生成できるようにしていきます。
大まかな流れは以下のようになります。

  1. GatsbyJSの導入
  2. Markdownを読み込むためのプラグインの設定
  3. ページ生成時の処理

初期化

適当なstarterを使って初期化します。今回はチュートリアル2と同様にgatsby-starter-hello-worldを使
いました。

console
$ gatsby new gatsby-tutorial https://github.com/gatsbyjs/gatsby-starter-hello-world

Markdownを読み込むためのプラグインの設定

GatsbyJSでMarkdownを読み込めるようにするためにプラグインの設定をします。必要なパッケージをインストールしてgatsby-config.jsに以下のように設定します。

console
$ npm install --save gatsby-source-filesystem gatsby-transformer-remark
gatsby-config.js
module.exports = {
  plugins: [
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `src`,
        path: `${__dirname}/src/`,
      },
    },
    `gatsby-transformer-remark`,
  ],
}

gatsby-source-filesystem がファイルを読み込むためのプラグインです。pathに指定したディレクトリからファイルを読み込みます。

gatsby-transformer-remark は読み込んだファイルがMarkdownであるかを認識しHTMLに変換するプラグインです。加えて、このプラグインは、Markdownにfrontmatter形式で書いたメタデータを認識することも出来ます。メタデータは、URLとして表示するときのpathの設定をするために使います。

Markdownをデータソースにしてページを書き出す

Markdownは gatsby-transformer-remark プラグインでHTMLに変換されますが、それはGraphQL上に存在しているのみで、具体的なページとしてはまだ書き出されていません。gatsby-node.js はビルド時の処理を書けます。ここでMarkdownから作られたHTMLを取得し、後述するHTMLを埋め込むテンプレートを使ってページとして書き出します。

gatsby-node.js
const path = require(`path`)

exports.createPages = async ({ actions, graphql, reporter }) => {
  const { createPage } = actions

  const template = path.resolve(`src/templates/notificationTemplate.js`)

  const result = await graphql(`
    {
      allMarkdownRemark {
        edges {
          node {
            frontmatter {
              path
            }
          }
        }
      }
    }
  `)

  // Handle errors
  if (result.errors) {
    reporter.panicOnBuild(`Error while running GraphQL query.`)
    return
  }

  result.data.allMarkdownRemark.edges.forEach(({ node }) => {
    createPage({
      path: node.frontmatter.path,
      component: template,
      context: {}, // additional data can be passed via context
    })
  })
}

以下はHTMLを埋め込むテンプレートです。ここでHTMLにスタイルも当ててしまいます。HTMLを埋め込むdivにstyled-componentで適当に装飾してます。

/src/templates/notificationTemplate.js
import React from "react"
import { graphql } from "gatsby"
import styled from 'styled-components'

const Container = styled.div`
  body {
    margin: 0;
  }
  main {
    font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", YuGothic, "ヒラギノ角ゴ ProN W3", Hiragino Kaku Gothic ProN, Arial, "メイリオ", Meiryo, sans-serif;
    box-sizing: border-box;
    margin: 16px;
  }
  p {
    color: rgba(51,51,51,1);
    font-size: 13px;
  }
  h1 {
      padding-left: 4px;
      color: rgba(85,85,85,1);
      border-left: 4px solid rgba(85,85,85,1);
      font-size: 13px;
  }
`

export default function Template({
  data, // this prop will be injected by the GraphQL query below.
}) {
  const { markdownRemark } = data // data.markdownRemark holds your post data
  const { html } = markdownRemark
  return <Container dangerouslySetInnerHTML={{ __html: html }} />
}

export const pageQuery = graphql`
  query($path: String!) {
    markdownRemark(frontmatter: { path: { eq: $path } }) {
      html
    }
  }
`

APIへの登録

前述の通りアプリではWebViewでお知らせページを表示するので、APIにはURLを登録します。

URLの作り方

各MarkdownにFrontmatterでURLになるときのパスをメタデータとして記しておき、デプロイ時にデプロイ先のドメインと結合してURLにします。

APIに登録する

以下のようなデプロイスクリプトを書きます。全てのMarkdownを走査してメタデータを読んでAPIに必要なパラメータを作っています。HPBCではその他にも以下のようなメタデータをMarkdownに書いてAPIへ登録しています。

  • title
  • published_at
    • 何時にお知らせを配信するか
  • enabled
    • APIからこのお知らせを返すか否か
    • もし何か問題があったときにすぐに表示を変更できるようにしています
bin/deploy.js
const fs = require("fs").promises
const fm = require("front-matter")
const axios = require("axios")

const API_URL = process.env.API_URL
const DEPLOY_BASE_URL = process.env.DEPLOY_BASE_URL
const DIR = "./src/pages"

const main = async () => {
  const files = await fs.readdir(DIR)
  const paths = files
    .filter(file => /.*\.md$/.test(file))
    .map(file => `${DIR}/${file}`)
  const contents = await Promise.all(
    paths.map(async path => {
      const data = await fs.readFile(path, "utf8")
      return fm(data)
    })
  )
  const params = contents.map(content => ({
    title: content.attributes.title,
    published_at: content.attributes.date,
    url: `${DEPLOY_BASE_URL}/${content.attributes.path}`,  // デプロイされたときのURLを作る
    enabled: content.attributes.enabled,
  }))
  const response = await axios.post(API_URL, { contents: params })
}

main()
  .then(console.log("done!"))
  .catch(err => {
    console.log(err)
    process.exit(1)
  })

デプロイ

デプロイ先はFirebaseにしました。gatsby buildの結果をそのままdeployし、完了したら上記のスクリプトを叩いてAPIへ登録します。この一連の処理をpackage.jsonのscriptsに登録し、pushやmerge,tagのタイミングでdev,stg,prdの環境にそれぞれ適用しています。

package.json
{
  ...
  "scripts": {
    ...
    "deploy": "gatsby build && firebase deploy && node ./bin/deploy.js"
  }
}

たまにはリッチなコンテンツも作りたい

文章だけのお知らせ以外に、クリエイティブで訴えるお知らせを打ちたいということもあります。GatsbyJSはReactで自由度の高いコンテンツを作れるのでこういった要件にも答えることができます。

以下はユーザーに投稿するときの画像をどうやって撮影するかというコンテンツです。こういったコンテンツはMarkdownでは表現しづらいのでReactで作っています。

投稿ガイド.png

入稿方法は丁寧な説明書を作った

Githubは英語だし、変なボタンを触るとコードが壊れちゃうのでは!という不安があるらしかった。図つきの詳しいドキュメントを社内wikiに置いて参照してもらっています。

入稿方法ドキュメント.png

終わりに

GithubをGatsbyJSで入稿システムとして使うための設定の紹介をしました。入稿システムをいちから作るよりも早く作ることができました。CIが使えたり、GatsbyJSで作れるコンテンツの自由度が高いこともあり、クリエイティブで訴えるような施策にも対応できるので便利に使っています。

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

React で eject せずに Scoped SASS (.scss) を使う

概要

  • scoped sass (ファイル内限定で適用されるスタイル) を使いたいでござる
  • でもnpm run ejectはしたくないでござる
  • cra-sass を導入するとかんたんにできるでござる

参考文献

実行環境

  • create-react-app で作った react project
    • 既存プロジェクトなのでversionわからん すまん
  • TypeScript

サンプルコード (変更前)

node-sass を入れてふつーにscssを使うとこうなる。

Sample.tsx

Sample.tsx
import * as React from "react";
import "./Sample.scss";

export const Sample: React.FC = () => {
  return (
    <div className="outer">
      OUTER
      <div className="inner">INNER</div>
      <ul>
        {["red", "blue", "green"].map((each, index) => (
          <li className={each} key={index}>
            {each.toUpperCase()}
          </li>
        ))}
      </ul>
    </div>
  );
};

export default Sample;

Sample.scss

Sample.scss
.outer {
  &,
  * {
    display: flex;
    flex-direction: column;
    padding: 8px;
    border-left: 1px solid gray;
  }

  font-size: 1.2rem;
  .inner {
    font-weight: bold;
  }
  ul {
    li {
      &.red {
        color: red;
      }
      &.green {
        color: green;
      }
      &.blue {
        color: blue;
      }
    }
  }
}

ビルド結果(html)

<div class="outer">
  OUTER
  <div class="inner">INNER</div>
  <ul>
    <li class="red">RED</li>
    <li class="blue">BLUE</li>
    <li class="green">GREEN</li>
  </ul>
</div>

実行結果

この実装の問題点

Sample.scss に記述したスタイルのscopeはグローバルである。
すなわち、Sample.tsx と同時にロードされるコンポーネントに、
同じclassName(例えば.outer)が割りあたっていると、互いに影響を受け合いバグの原因となる

解決策

閉じたscopeを扱うことのできるsass loaderを導入する

導入手順

cra-sass を導入

npm install --save-dev cra-sass

cra-sass を実行

$(npm bin)/cra-sass

するとなんかいっぱいインストールしてプロジェクトが魔改造される

package.json
@ devDependencies
+    "cra-sass": "0.0.5",

@ dependencies
+    "node-sass-chokidar": "^1.4.0",
+    "npm-add-script": "^1.1.0",
+    "npm-run-all": "^4.1.5",

@ scripts
-    "start": "react-scripts start",
-    "build": "react-scripts --max-old-space-size=2048 build",
+    "start": "npm-run-all -p watch-css start-js",
+    "build": "npm run build-css && react-scripts build",
     "test": "react-scripts test",
-    "eject": "react-scripts eject"
+    "eject": "react-scripts eject",
+    "build-css": "node-sass-chokidar src/ -o src/",
+    "watch-css": "npm run build-css && node-sass-chokidar src/ -o src/ --watch --recursive",
+    "start-js": "react-scripts start"

--max-old-space-size=2048 とか無くなってぶっ壊れてんじゃん!
ってことで無駄にぶっこわされたとこは直しておく

package.json
-    "build": "react-scripts --max-old-space-size=2048 build",
+    "build": "npm run build-css && react-scripts --max-old-space-size=2048 build",

サンプルコード(リファクタ後)

Sample.scss

Sample.module.scss に改名する

Sample.tsx

  • scssのimport
  • classNameの割当てのしかた

だけを変更

Sample.tsx
import * as React from "react";
import styles from "./Sample.module.scss";

export const Sample: React.FC = () => {
  return (
    <div className={styles.outer}>
      OUTER
      <div className={styles.inner}>INNER</div>
      <ul>
        {["red", "blue", "green"].map((each, index) => (
          <li className={styles[each]} key={index}>
            {each.toUpperCase()}
          </li>
        ))}
      </ul>
    </div>
  );
};

export default Sample;

ビルド結果

<div class="Sample_outer__144wv">
  OUTER
  <div class="Sample_inner__EiBfI">INNER</div>
  <ul>
    <li class="Sample_red__1ktYQ">RED</li>
    <li class="Sample_blue__32ZOZ">BLUE</li>
    <li class="Sample_green__2OrZU">GREEN</li>
  </ul>
</div>
css部分の抜粋
.Sample_outer__144wv {
  font-size: 1.2rem; }
  .Sample_outer__144wv,
  .Sample_outer__144wv * {
    display: flex;
    flex-direction: column;
    padding: 8px;
    border-left: 1px solid gray; }
  .Sample_outer__144wv .Sample_inner__EiBfI {
    font-weight: bold; }
  .Sample_outer__144wv ul li.Sample_red__1ktYQ {
    color: red; }
  .Sample_outer__144wv ul li.Sample_green__2OrZU {
    color: green; }
  .Sample_outer__144wv ul li.Sample_blue__32ZOZ {
    color: blue; }

その他やったこと

scriptsが壊されてないかチェックしよう

start, build, test が、 cra-sass によって破壊されている恐れがある
特にdefaultから変更している場合注意しよう

.cssが.scssと同階層に出力されるようになってうっおとしい

  • node-sass-chokidar のしわざくさい
  • でもoutputしなくするオプションとかなさげ
  • めんどいから、別階層に吐かせて、ignoreすることにした
package.json
-    "build-css": "node-sass-chokidar src/ -o src/",
+    "build-css": "node-sass-chokidar src/ -o built-css/",
-    "watch-css": "npm run build-css && node-sass-chokidar src/ -o src/ --watch --recursive",
+    "watch-css": "npm run build-css && node-sass-chokidar src/ -o built-css/ --watch --recursive",
.gitignore
+/built-css

node-sass をすでに使っていた場合、不要になる

npm r node-sass

おしまい

これで快適な React x Scoped SASS 生活が始まる

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

CSSだってJavaScriptの値が欲しい!

インターン先でreact + typescript + scssのお仕事をしている @haduki1208 と申します。
最近reactよりscssを多く書いてる気がします。割と楽しいです。

TL;DR

  • CSS変数をインラインスタイルで書く
  • var()で読み込む

CSSでJavaScriptの値を取得する

趣味で「任意の大きさ・色・向きを指定できる矢印アイコン:arrow_up::arrow_right::arrow_down::arrow_left:」を作っていた時に

:thinking:「CSSでJavaScriptの値を取得できないのか?」

と思ったのでやってみました。

cssにattr()という関数がありますが、content属性でしか動作しないようです。
今回はheight, width, background-color, rotateに任意の値を使いたいのですがダメです。微妙な仕様ですね。

環境

  • typescript + react + scss および typescript + react + styled-components
  • chromeで動作確認
  • 動いたコードをcodepenに移植(codepenを使ってみたかった :heart: )

ソースコード

See the Pen Arrow01 react + ts + scss by haduki1208 (@haduki1208) on CodePen.

最初に作った矢印アイコンのソースコードです。

このコードの弱点は以下になります。

  1. size, color, rotateの1つでも違う値を使いたい場合、新たにclassnameを作成しなければならない
  2. tsxとscssの両ファイルに変更を加える必要がある
  3. mixinを使っているため、トランスパイル後のcssの容量が大きくなる

1番、2番は面倒くさいです。
これらを全て解消してみます。

変更後のソースコード

See the Pen Arrow02 react + ts + scss by haduki1208 (@haduki1208) on CodePen.

CSS カスタムプロパティ (変数)を使いました。

ポイントは以下になります。

  1. style属性に指定したオブジェクトは、インラインスタイルに変換される
  2. インラインスタイルで記述されているため、CSS変数が他の要素に影響しない
  3. typescript環境の場合、React.CSSPropertie型以外のオブジェクトを代入するため「as any」する必要がある

これでscssを編集せず色々なアイコンを作ることができます!

おまけ styled-componentsに置き換えたソースコード

こんなことするなら、最初からstyled-componentsにすれば良いじゃないと思った人もいらっしゃるでしょう。
僕は今までstyled-componentsを使ったことがなかったので、勉強のため置き換えをしてみました。

See the Pen Arrow03 react + ts + styled by haduki1208 (@haduki1208) on CodePen.

なんて素晴らしいライブラリなのでしょう。
scss、インラインスタイル、as any 全て消え去りました。
(ついでにwebpack.config.jsからstyle-loader, css-loader, sass-loader, postcss-loaderも消え去りました)

styled-componentsに惚れてしまいそうです。

感想

react + scssで開発をしているプロジェクトでJavaScripの変数を取得したくなった時はこの方法で解決できそうです。

既にscssで開発しているプロジェクトでは、styled-componentsに移行するにも時間がかかってしまうので、もしcssからjavascriptの値を使いたくなったら、ご検討ください。

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

フロントエンドマスターへの道 その1 React Hooksを使ったQiitaの一覧、検索アプリ

せっかくなので作ってみようと思う。今回は 1 の Build a movie search app using React (with hooks) を作る。ただそのままだとただのコピペになってしまうので、今回取得するのは movie の取得ではなく Qiita の記事一覧にします。

どのフレームワークも何年か前にはさわっていたけど、最新の情報はなんとなく見たことある程度の知識しかありませんので詳しい解説をするものではありませんのでご注意ください。誤った理解のご指摘などがあれば大変助かります!

完成品はこちら(キャプチャの左はじがすこし切れた……)

Screenshot from 2019-12-05 08-20-14.png

CSS は基本的には今後も雑にしか作らずに、あまり本質的ではない Lint とかフォーマットの設定とかはデフォルトで入っていないものについては何もしない予定。

install

参考サイトのように npx create-react-app hooked でインストールしてもいいんだろうけど、大体の人は TS で書きたいと思うので、インストール時には npx create-react-app hooked --typescript としなければいけません。自分は普通に忘れたので今回は普通の JS で書きました。

hook

useEffectuseStateuseCallback というのは関数名だけは知っていた。

useEffect については

React のライフサイクルに馴染みがある場合は、useEffect フックを componentDidMount と componentDidUpdate と componentWillUnmount がまとまったものだと考えることができます。

昔の知識のおかげか上記解説のヒントにあったこの説明が一番分かりやすくて、こういうある特定のタイミングで実行したい処理があった場合に

上記の hook から探して適切なものを使う、ということなのだろうかと理解した。

useEffect(() => {
  (async () => {
    const params = new URLSearchParams()
    params.set('page', 1)
    params.set('per_page', perPage)
    try {
      const response = await fetch(`https://qiita.com/api/v2/items?${params.toString()}`, {
        headers: {
          'Authorization': `Bearer ${API_KEY}`
        }
      })
      setArticles(await response.json())
    } catch (err) {
      setErrorMessage(err)
    }

    setLoading(false)
  })()
})

実際に useEffect を使ったコードはこうなった。async/await に書き換えたんだけど、戻りが Promise<T> になっているとまずいようで

useEffect(async() => {

こうすることはできない。そのため即時実行関数を使うしかないのかなと思いこうしておいた。多少ググってもみたのですが、似たような感じのコードしかなかったのでこうするしかなさそう。

dotenv

たぶん一番苦労した。

最初に答えを書いておくと、create-react-app には最初から dotenv が含まれているため、開発環境では .env.development.localREACT_APP_ から始まる値で設定しないといけない。

ぐぐれば結構でてくるし、当然ドキュメントにも書いてある。

yarn add dotenv とかやってしまってハマった。

感想

流石に1とあって簡単。後半きつそうなので不安しかない

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

フロントエンドマスターへの道 その1

せっかくなので作ってみようと思う。今回は 1 の Build a movie search app using React (with hooks) を作る。ただそのままだとただのコピペになってしまうので、今回取得するのは movie の取得ではなく Qiita の記事一覧にします。

どのフレームワークも何年か前にはさわっていたけど、最新の情報はなんとなく見たことある程度の知識しかありませんので詳しい解説をするものではありませんのでご注意ください。誤った理解のご指摘などがあれば大変助かります!

完成品はこちら(キャプチャの左はじがすこし切れた……)

Screenshot from 2019-12-05 08-20-14.png

CSS は基本的には今後も雑にしか作らずに、あまり本質的ではない Lint とかフォーマットの設定とかはデフォルトで入っていないものについては何もしない予定。

install

参考サイトのように npx create-react-app hooked でインストールしてもいいんだろうけど、大体の人は TS で書きたいと思うので、インストール時には npx create-react-app hooked --typescript としなければいけません。自分は普通に忘れたので今回は普通の JS で書きました。

hook

useEffectuseStateuseCallback というのは関数名だけは知っていた。

useEffect については

React のライフサイクルに馴染みがある場合は、useEffect フックを componentDidMount と componentDidUpdate と componentWillUnmount がまとまったものだと考えることができます。

昔の知識のおかげか上記解説のヒントにあったこの説明が一番分かりやすくて、こういうある特定のタイミングで実行したい処理があった場合に

上記の hook から探して適切なものを使う、ということなのだろうかと理解した。

useEffect(() => {
  (async () => {
    const params = new URLSearchParams()
    params.set('page', 1)
    params.set('per_page', perPage)
    try {
      const response = await fetch(`https://qiita.com/api/v2/items?${params.toString()}`, {
        headers: {
          'Authorization': `Bearer ${API_KEY}`
        }
      })
      setArticles(await response.json())
    } catch (err) {
      setErrorMessage(err)
    }

    setLoading(false)
  })()
})

実際に useEffect を使ったコードはこうなった。async/await に書き換えたんだけど、戻りが Promise<T> になっているとまずいようで

useEffect(async() => {

こうすることはできない。そのため即時実行関数を使うしかないのかなと思いこうしておいた。多少ググってもみたのですが、似たような感じのコードしかなかったのでこうするしかなさそう。

dotenv

たぶん一番苦労した。

最初に答えを書いておくと、create-react-app には最初から dotenv が含まれているため、開発環境では .env.development.localREACT_APP_ から始まる値で設定しないといけない。

ぐぐれば結構でてくるし、当然ドキュメントにも書いてある。

yarn add dotenv とかやってしまってハマった。

感想

流石に1とあって簡単。後半きつそうなので不安しかない

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

Awesome React - Reactのオススメ記事・ライブラリ・ツール一覧 -

:star: Awesome React

公式

コミュニティ

オンラインでReactのコードを試せるサービス

チュートリアル

Reactのチュートリアル
React Hooksのチュートリアル
React & TypeScript
パフォーマンス
React内部の実装ついて
ReactについてのQ&A

React Tools

React 開発ツール
  • create-react-app - 1つのコマンドで最新のWebアプリをセットアップする
  • react-starter-kit - webアプリ用テンプレート
  • react-devtools - ChromeおよびFirefox開発ツールでのReactコンポーネント階層の検査
  • react-hot-loader - Reactコンポーネントをリアルタイムで調整する
  • react-loadable - promiseでコンポーネントを読み込むための高階層コンポーネント
  • loadable-components - Reactコード分割を簡単にする、bundleサイズを縮小する
  • reactotron - ReactおよびReact Nativeプロジェクトを検査するためのデスクトップアプリ
  • storybook - UIコンポーネントの開発とテスト
  • react-styleguidist - スタイルガイド付きのReactコンポーネント開発環境
  • react-cosmos - 再利用可能なReactコンポーネントを作成するための開発ツール
  • eslint-plugin-react - ESLintの特定のlintルールのプラグイン
  • eslint-plugin-jsx-a11y - JSX要素のa11yルールの静的ASTチェッカー
  • react-axe - Reactアプリケーションのアクセシビリティ監査
React フレームワーク
  • next.js - SSRのためのReactフレームワーク
  • gatsby.js - Reactの静的サイトジェネレーター&フレームワーク
React スタイル
  • styled-components - コンポーネントのスタイルに実際のCSSコードを記述できるようにするライブラリ
  • emotion - JavaScriptを使用してCSSスタイルを記述するために設計されたライブラリ
  • radium - Reactのインラインスタイルを管理するツールのセット
  • jss - JavaScriptでスタイルシートを生成するためのライブラリ
React ルーティング
  • react-router - Reactの宣言型ルーティング
  • navi - Reactの宣言的な非同期ルーティング
  • curi - 単一ページアプリケーション用のJavaScriptルーター
React UIコンポーネントライブラリ
  • material-ui - Web開発を迅速かつ簡単にするためのコンポーネント
  • ant-design - エンタープライズクラスのUIデザイン言語とReact UIライブラリ
  • blueprint - Web用のReactベースのUIツールキット
  • office-ui-fabric-react - Microsoft Webエクスペリエンスを構築するためのReactコンポーネント
  • react-bootstrap - Reactで構築されたbootstrapコンポーネント
  • reactstrap - Bootstrap 4のステートレスReactコンポーネント
  • semantic-ui-react - Semantic-UIのReactコンポーネント
  • react-fontawesome - Font Awesome 5 のReactコンポーネント
  • Reakit - Reactのアクセス可能、構成可能、カスタマイズ可能なコンポーネント
  • rsuite - エンタープライズシステム製品用の一連のReactコンポーネントライブラリ
  • atlaskit - Atlassianデザインガイドラインに従って構築されたAtlassianの公式UIライブラリ
Reactの素晴らしいコンポーネント
  • Awesome React Components list - 本当に素晴らしいReactコンポーネントとライブラリ
  • react-select - Reactの選択コンポーネント
  • react-dnd - Reactのドラッグアンドドロップ
  • react-grid-layout - レスポンシブブレークポイントを備えたドラッグ可能でサイズ変更可能なグリッドレイアウト
  • react-table - React向けの軽量で高速で拡張可能なデータグリッド
  • react-data-grid - Reactで構築されたExcelのようなグリッドコンポーネント
  • react-draggable - ドラッグ可能なReactコンポーネント
  • react-resizable-and-movable - Reactのサイズ変更およびドラッグ可能なコンポーネント
  • react-resizable - ハンドルでサイズ変更可能な単純なReactコンポーネント
  • react-resizable-box - Reactのサイズ変更可能なコンポーネント
  • react-sortable-pane - React用のソートおよびサイズ変更可能なペインコンポーネント
  • react-dates - ウェブ向けの簡単に国際化可能でモバイル対応の日付選択ライブラリ
  • react-big-calendar - カレンダーコンポーネント
  • react-datepicker - Reactの日付選択ライブラリ
  • react-list - 多用途の無限スクロールReactコンポーネント
  • react-intl - Reactアプリの国際化
  • react-i18next - Reactの国際化を正しく行うライブラリ
  • react-aria-modal - 完全にアクセス可能なReactモーダル
  • react-hotkeys - Reactの宣言的なホットキーとフォーカスエリア管理
  • react-keydown - Reactコンポーネント用の軽量キーダウンラッパー
  • react-joyride - アプリのガイドを作成するライブラリ
  • react-virtualized - 大きなリストと表形式のデータを効率的にレンダリングするためのReactコンポーネント
  • react-window - 大きなリストと表形式のデータを効率的にレンダリングするためのReactコンポーネント
  • react-text-mask - React用Inputマスク
  • react-loading-skeleton - アプリに自動的に適応するスケルトン画面を作成するライブラリ
  • react-spinkit - 読み込み表示のCSSアニメーションのReactライブラリ
  • rheostat - Reactで構築されたアクセス可能なスライダーコンポーネント
  • qrcode.react - Reactで使用するQRコードのコンポーネント
Reactコマンドラインツール
  • ink - React用のインタラクティブなコマンドラインアプリ
  • react-blessed - ターミナルインターフェイスライブラリのReactレンダラー
Reactテストツール
  • jest - JavaScriptテストフレームワーク
  • enzyme - React用のJavaScriptテストユーティリティ
  • react-testing-library - シンプルで完全なReact DOMテストユーティリティ
  • react-hooks-testing-library - 優れたテストプラクティスを促進するReact Hooksテストユーティリティ
  • majestic - JestのためのGUI
React Libraries
  • react-border-wrapper - Reactのdiv境界に沿って要素を配置するためのラッパー
  • react-magic - ReactでプレーンなHTMLにAJAXを使用できるようにするライブラリ
  • react-toolbox - Googleのマテリアルデザイン仕様を実装する一連のReactコンポーネント
  • tcomb-react - Reactコンポーネントのすべてのpropsをチェックできるライブラリ
  • react-responsive - レスポンシブデザインに対応するメディアクエリ
  • react-cursor - Reactで使用するためのFunctional Stateの管理の抽象化
  • Omniscient.js - 不変データの高速トップダウンレンダリングのためのReactコンポーネントの抽象化
  • Touchstonejs - 美しいハイブリッドモバイルアプリを開発するためのReact.jsを搭載したUIフレームワーク。
  • Elemental - React.js WebサイトおよびアプリのUIツールキット
  • StateTrooper - CSPでReactアプリケーションの状態を一元管理
  • Preact - ReactのモダンなAPIを使用できる高速3kbのReactの代替ライブラリ
  • riotjs - Reactのような3.5KBのユーザーインターフェイスライブラリ
  • Maple.js - Webコンポーネントの概念をReactにもたらす
  • react-i13n - Reactアプリケーションを計装するための、高性能でスケーラブルでプラグ可能なアプローチ
  • react-icons - 人気のあるアイコンパックのsvgアイコン
  • Keo - Reactコンポーネントを作成するためのより機能的なDekuアプローチのためのプレーン関数
  • Bit - アプリケーション間でリアクションおよびその他のWebコンポーネントを管理および使用するための仮想リポジトリ
  • AtlasKit - AtlassianのReact UIライブラリ
  • ReactiveSearch - ElasticsearchのためのUIコンポーネントライブラリ
  • Slate - リッチテキストエディターを構築するための完全にカスタマイズ可能なフレームワーク
  • react-json-schema - JSON定義を公開するReactコンポーネントにマッピングして、JSONからReact要素を構築する
  • compose-state - Reactで複数のsetStateまたはgetDerivedStateFromPropsアップデーターを作成する
  • PrimeReact - Reactの最も完全なUIフレームワーク
  • react-lodash - ReactコンポーネントとしてのLodash
  • react-helmet - Reactのドキュメントヘッドマネージャー
  • Stator - Reactの組み込みサポートを備えたシンプルでプレーンなJavaScript状態管理
  • ClearX - 学習曲線がゼロでReact向けの高速で簡単な状態管理
  • react-snap - SPAのためのゼロ構成フレームワークに依存しない静的事前レンダリング
  • Draft.js - テキストエディターを構築するためのReactフレームワーク
  • refract - リアクティブプログラミングのパワーを制御して、コンポーネントを満たす
  • react-desktop - Reactで構築されたOS XおよびWindows UIコンポーネント
  • Reapop - React&Redux通知システム
  • react-extras - Reactを使用するための便利なコンポーネントとユーティリティ
  • react-instantsearch - AlgoliaによるReactおよびReact Nativeアプリケーションの超高速検索
  • uppy - Webブラウザー用の次のオープンソースファイルアップローダー
  • react-motion - アニメーションの問題を解決するバネ
  • react-esi - ReactおよびNext.jsの非常に高速なサーバー側レンダリング
Reactインテグレーション
フォーム
  • React Forms
  • react-formal - Reactのフォーム検証と値管理の改善、最小限の配線の提供
  • react-forms - Reactのフォームライブラリ
  • valuelink - 拡張されたReactリンクを備えたフル機能の双方向データバインディング
  • wingspan-forms - Reactの動的フォームライブラリ
  • newforms - Reactの同形フォーム処理
  • formjs - Reactjsのフォームジェネレーター
  • react-form-builder - React.jsのフォームビルダー
  • plexus-form - JSONスキーマを使用して反応するための動的フォームコンポーネント
  • tcomb-form - より少ないコードを記述するフォームを開発するためのUIライブラリ
  • formsy-react - React JSのフォーム入力ビルダーおよびバリデーター
  • Learn Raw React: Ridiculously Simple Forms
  • Winterfell - Reactで検証済みで拡張可能な複雑なJSONベースのフォームを生成する
  • Redux-Autoform - メタデータからRedux-Formsを動的に作成する
  • uniforms - フォームを簡単に生成および検証するためのReactコンポーネントとヘルパーの束
  • formik - Reactフォーム
  • NeoForm - フォーム状態の管理と検証のためのモジュラーHOC
  • react-jsonschema-form - JSONスキーマからWebフォームを構築するためのReactコンポーネント
  • List View Select - React Nativeとネイティブコンポーネントの切り替え可能な選択ボックス
  • Final Form ?
  • formland - シンプルで柔軟性が高く、拡張可能な構成ベースのフォームジェネレーター
  • react-reactive-form - ReactのAngularのようなリアクティブフォーム
  • unform - ネストされたフィールド、検証など、制御されていないフォーム構造を作成するReactJSフォームライブラリ
  • Formal - React Hook時代のエレガントなフォーム管理プリミティブ
オートコンプリート(入力補完)
グラフィック
  • react-art - Reactを使用してベクターグラフィックスを描画するためのJavaScriptライブラリ
  • react-canvas - Reactコンポーネントのための高性能canvasレンダリング
  • react-famous - Famo.usを使用した60 FPSの複雑な3DアニメーションUI
  • react-kinetic - Reactを使用したKineticJS経由のHTML5 Canvas
  • react-svg-morph - svgコンポーネントを別のものにモーフィングする
  • react-hooks-svgdrawing - Reactフックを使用したSVG描画
モデルライブラリ
  • mori - ClojureScriptの永続データ構造とサポートAPI
  • NestedTypes - 「純粋なレンダリング」をサポートする高速可変モデル
  • swarm - JavaScript複製モデル(MVCのM)ライブラリ
  • caplet - JavaScriptモデルライブラリ
データ管理
  • Immutable.js - JavaScript用の不変のデータコレクション
  • cortex - Reactでデータを集中管理するためのJavaScriptライブラリ
  • avers - 最新のクライアント側モデル抽象化ライブラリ
  • imvvm - Reactの不変のModel-View-ViewModel
  • morearty.js - 純粋なJavaScriptでのReactの状態管理の改善
  • valuable - Reactの不変のデータストア
  • react-resolver - Reactコンポーネントのデータを再帰的に遅延ロードする同形ライブラリ
  • freezer-js - Reactの軽量でリアクティブな不変のデータ構造
  • MobX - シンプルでスケーラブルな状態管理
  • baobab - カーソルを使用したJavaScript永続的でオプションの不変データツリー
  • baobab-react - BaobabのReact統合
  • datascript - ClojureScriptの不変データベースとデータログクエリエンジン
  • immstruct - Reactのようなコンポーネントベースのライブラリのトップからボトムのプロパティの履歴を持つ不変のデータ構造
  • seamless-immutable - 通常のJS配列およびオブジェクトと後方互換性のあるJavaScriptの不変のデータ構造
  • tydel - 型付きモデルとコレクション、Reactバインディング
  • extendable-immutable - Immutable.jsデータ構造を拡張する
  • statty - ReactおよびPreactアプリ用の小さくて控えめな状態管理ライブラリ
  • Hydux - 「バッテリーを含む」ReactのElmライクステートマネージャー
  • ReSub - より良いReactコンポーネントとデータストアを作成するためのライブラリ
  • ProppyJS - Functional props構成のための小さなライブラリ
  • WatermelonDB - 強力なReactおよびReact Nativeアプリ向けの次世代データベースで、数万件のレコードに対応し、高速性を維持
  • Effector - 高速で強力なリアクティブ状態マネージャー。 シンプルで高速かつタイプセーフなコードを記述し、状態を簡単に管理
  • reactn - 組み込みのグローバル状態管理
  • immer - 現在の状態を変更し、次の不変状態を作成
地図
  • react-googlemaps - GoogleマップへのReactインターフェース
  • react-maps - Reactのマップコンポーネント
  • react-google-maps - React.js Google Maps統合コンポーネント
  • react-gmaps - React.js用のGoogleマップコンポーネント
  • react-map-gl - MapboxGL JSのReactラッパー
  • google-map-react - Googleマップと同形のReactコンポーネント
  • react-mapbox-gl - Reactのためのmapbox-gl-jsラッパー
  • google-maps-react - React、遅延読み込みの依存関係、現在位置ファインダー、およびフルスタックReactチームによるテスト駆動型アプローチを使用した宣言的なGoogle Map Reactコンポーネント
  • react-leaflet - リーフレットマップのReactコンポーネント
  • react-geo - react、antd、およびolを使用した地理関連コンポーネントのセット
  • pigeon-maps - 外部依存関係のないReactJSマップ
統計・グラフ
  • DevExtreme React Chart - BootstrapおよびMaterialデザイン用の高性能プラグインベースのReactチャート
  • react-chartjs - chart.jsを使用した一般的なReactチャートコンポーネント
  • react-stockcharts - ReactJSとd3による高度にカスタマイズ可能な株価チャート
  • Number Picture - React&D3でアニメーション化された視覚化を構築するための低レベルのビルディングブロック
  • Victory - インタラクティブなデータ視覚化を構築するための構成可能なReactコンポーネントのコレクション
  • Recharts - すばらしい宣言型APIを備えたD3上に構築されたチャートライブラリ
  • React-ApexCharts - ApexChartsのReactコンポーネント(インタラクティブなSVGチャートライブラリ)
  • reaviz - D3.jsに基づくReactでの視覚化ライブラリ
  • react-vis - Reactフレンドリー、高レベルでカスタマイズ可能で、表現力があり、業界に強いReact視覚化ライブラリー
  • nivo - D3およびReactライブラリの上に構築された、豊富なデータ視覚化コンポーネントのセットを提供
  • vx - 再利用可能な低レベルの視覚化コンポーネントのコレクション
  • echarts-for-react - Reactの非常にシンプルなEChartsラッパー
  • Chartify - CSSを使用してチャートを作成するためのReactプラグイン
  • Semiotic - ReactとD3を組み合わせたデータ視覚化フレームワーク
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Awesome React - Reactのオススメ記事・ライブラリ・ツール まとめ -

:star: Awesome React

公式

コミュニティ

オンラインでReactのコードを試せるサービス

チュートリアル

Reactのチュートリアル
React Hooksのチュートリアル
React & TypeScript
パフォーマンス
React内部の実装ついて
ReactについてのQ&A

React Tools

React 開発ツール
  • create-react-app - 1つのコマンドで最新のWebアプリをセットアップする
  • react-starter-kit - webアプリ用テンプレート
  • react-devtools - ChromeおよびFirefox開発ツールでのReactコンポーネント階層の検査
  • react-hot-loader - Reactコンポーネントをリアルタイムで調整する
  • react-loadable - promiseでコンポーネントを読み込むための高階層コンポーネント
  • loadable-components - Reactコード分割を簡単にする、bundleサイズを縮小する
  • reactotron - ReactおよびReact Nativeプロジェクトを検査するためのデスクトップアプリ
  • storybook - UIコンポーネントの開発とテスト
  • react-styleguidist - スタイルガイド付きのReactコンポーネント開発環境
  • react-cosmos - 再利用可能なReactコンポーネントを作成するための開発ツール
  • eslint-plugin-react - ESLintの特定のlintルールのプラグイン
  • eslint-plugin-jsx-a11y - JSX要素のa11yルールの静的ASTチェッカー
  • react-axe - Reactアプリケーションのアクセシビリティ監査
React フレームワーク
  • next.js - SSRのためのReactフレームワーク
  • gatsby.js - Reactの静的サイトジェネレーター&フレームワーク
React スタイル
  • styled-components - コンポーネントのスタイルに実際のCSSコードを記述できるようにするライブラリ
  • emotion - JavaScriptを使用してCSSスタイルを記述するために設計されたライブラリ
  • radium - Reactのインラインスタイルを管理するツールのセット
  • jss - JavaScriptでスタイルシートを生成するためのライブラリ
React ルーティング
  • react-router - Reactの宣言型ルーティング
  • navi - Reactの宣言的な非同期ルーティング
  • curi - 単一ページアプリケーション用のJavaScriptルーター
React UIコンポーネントライブラリ
  • material-ui - Web開発を迅速かつ簡単にするためのコンポーネント
  • ant-design - エンタープライズクラスのUIデザイン言語とReact UIライブラリ
  • blueprint - Web用のReactベースのUIツールキット
  • office-ui-fabric-react - Microsoft Webエクスペリエンスを構築するためのReactコンポーネント
  • react-bootstrap - Reactで構築されたbootstrapコンポーネント
  • reactstrap - Bootstrap 4のステートレスReactコンポーネント
  • semantic-ui-react - Semantic-UIのReactコンポーネント
  • react-fontawesome - Font Awesome 5 のReactコンポーネント
  • Reakit - Reactのアクセス可能、構成可能、カスタマイズ可能なコンポーネント
  • rsuite - エンタープライズシステム製品用の一連のReactコンポーネントライブラリ
  • atlaskit - Atlassianデザインガイドラインに従って構築されたAtlassianの公式UIライブラリ
Reactの素晴らしいコンポーネント
  • Awesome React Components list - 本当に素晴らしいReactコンポーネントとライブラリ
  • react-select - Reactの選択コンポーネント
  • react-dnd - Reactのドラッグアンドドロップ
  • react-grid-layout - レスポンシブブレークポイントを備えたドラッグ可能でサイズ変更可能なグリッドレイアウト
  • react-table - React向けの軽量で高速で拡張可能なデータグリッド
  • react-data-grid - Reactで構築されたExcelのようなグリッドコンポーネント
  • react-draggable - ドラッグ可能なReactコンポーネント
  • react-resizable-and-movable - Reactのサイズ変更およびドラッグ可能なコンポーネント
  • react-resizable - ハンドルでサイズ変更可能な単純なReactコンポーネント
  • react-resizable-box - Reactのサイズ変更可能なコンポーネント
  • react-sortable-pane - React用のソートおよびサイズ変更可能なペインコンポーネント
  • react-dates - ウェブ向けの簡単に国際化可能でモバイル対応の日付選択ライブラリ
  • react-big-calendar - カレンダーコンポーネント
  • react-datepicker - Reactの日付選択ライブラリ
  • react-list - 多用途の無限スクロールReactコンポーネント
  • react-intl - Reactアプリの国際化
  • react-i18next - Reactの国際化を正しく行うライブラリ
  • react-aria-modal - 完全にアクセス可能なReactモーダル
  • react-hotkeys - Reactの宣言的なホットキーとフォーカスエリア管理
  • react-keydown - Reactコンポーネント用の軽量キーダウンラッパー
  • react-joyride - アプリのガイドを作成するライブラリ
  • react-virtualized - 大きなリストと表形式のデータを効率的にレンダリングするためのReactコンポーネント
  • react-window - 大きなリストと表形式のデータを効率的にレンダリングするためのReactコンポーネント
  • react-text-mask - React用Inputマスク
  • react-loading-skeleton - アプリに自動的に適応するスケルトン画面を作成するライブラリ
  • react-spinkit - 読み込み表示のCSSアニメーションのReactライブラリ
  • rheostat - Reactで構築されたアクセス可能なスライダーコンポーネント
  • qrcode.react - Reactで使用するQRコードのコンポーネント
Reactコマンドラインツール
  • ink - React用のインタラクティブなコマンドラインアプリ
  • react-blessed - ターミナルインターフェイスライブラリのReactレンダラー
Reactテストツール
  • jest - JavaScriptテストフレームワーク
  • enzyme - React用のJavaScriptテストユーティリティ
  • react-testing-library - シンプルで完全なReact DOMテストユーティリティ
  • react-hooks-testing-library - 優れたテストプラクティスを促進するReact Hooksテストユーティリティ
  • majestic - JestのためのGUI
React Libraries
  • react-border-wrapper - Reactのdiv境界に沿って要素を配置するためのラッパー
  • react-magic - ReactでプレーンなHTMLにAJAXを使用できるようにするライブラリ
  • react-toolbox - Googleのマテリアルデザイン仕様を実装する一連のReactコンポーネント
  • tcomb-react - Reactコンポーネントのすべてのpropsをチェックできるライブラリ
  • react-responsive - レスポンシブデザインに対応するメディアクエリ
  • react-cursor - Reactで使用するためのFunctional Stateの管理の抽象化
  • Omniscient.js - 不変データの高速トップダウンレンダリングのためのReactコンポーネントの抽象化
  • Touchstonejs - 美しいハイブリッドモバイルアプリを開発するためのReact.jsを搭載したUIフレームワーク。
  • Elemental - React.js WebサイトおよびアプリのUIツールキット
  • StateTrooper - CSPでReactアプリケーションの状態を一元管理
  • Preact - ReactのモダンなAPIを使用できる高速3kbのReactの代替ライブラリ
  • riotjs - Reactのような3.5KBのユーザーインターフェイスライブラリ
  • Maple.js - Webコンポーネントの概念をReactにもたらす
  • react-i13n - Reactアプリケーションを計装するための、高性能でスケーラブルでプラグ可能なアプローチ
  • react-icons - 人気のあるアイコンパックのsvgアイコン
  • Keo - Reactコンポーネントを作成するためのより機能的なDekuアプローチのためのプレーン関数
  • Bit - アプリケーション間でリアクションおよびその他のWebコンポーネントを管理および使用するための仮想リポジトリ
  • AtlasKit - AtlassianのReact UIライブラリ
  • ReactiveSearch - ElasticsearchのためのUIコンポーネントライブラリ
  • Slate - リッチテキストエディターを構築するための完全にカスタマイズ可能なフレームワーク
  • react-json-schema - JSON定義を公開するReactコンポーネントにマッピングして、JSONからReact要素を構築する
  • compose-state - Reactで複数のsetStateまたはgetDerivedStateFromPropsアップデーターを作成する
  • PrimeReact - Reactの最も完全なUIフレームワーク
  • react-lodash - ReactコンポーネントとしてのLodash
  • react-helmet - Reactのドキュメントヘッドマネージャー
  • Stator - Reactの組み込みサポートを備えたシンプルでプレーンなJavaScript状態管理
  • ClearX - 学習曲線がゼロでReact向けの高速で簡単な状態管理
  • react-snap - SPAのためのゼロ構成フレームワークに依存しない静的事前レンダリング
  • Draft.js - テキストエディターを構築するためのReactフレームワーク
  • refract - リアクティブプログラミングのパワーを制御して、コンポーネントを満たす
  • react-desktop - Reactで構築されたOS XおよびWindows UIコンポーネント
  • Reapop - React&Redux通知システム
  • react-extras - Reactを使用するための便利なコンポーネントとユーティリティ
  • react-instantsearch - AlgoliaによるReactおよびReact Nativeアプリケーションの超高速検索
  • uppy - Webブラウザー用の次のオープンソースファイルアップローダー
  • react-motion - アニメーションの問題を解決するバネ
  • react-esi - ReactおよびNext.jsの非常に高速なサーバー側レンダリング
Reactインテグレーション
フォーム
  • React Forms
  • react-formal - Reactのフォーム検証と値管理の改善、最小限の配線の提供
  • react-forms - Reactのフォームライブラリ
  • valuelink - 拡張されたReactリンクを備えたフル機能の双方向データバインディング
  • wingspan-forms - Reactの動的フォームライブラリ
  • newforms - Reactの同形フォーム処理
  • formjs - Reactjsのフォームジェネレーター
  • react-form-builder - React.jsのフォームビルダー
  • plexus-form - JSONスキーマを使用して反応するための動的フォームコンポーネント
  • tcomb-form - より少ないコードを記述するフォームを開発するためのUIライブラリ
  • formsy-react - React JSのフォーム入力ビルダーおよびバリデーター
  • Learn Raw React: Ridiculously Simple Forms
  • Winterfell - Reactで検証済みで拡張可能な複雑なJSONベースのフォームを生成する
  • Redux-Autoform - メタデータからRedux-Formsを動的に作成する
  • uniforms - フォームを簡単に生成および検証するためのReactコンポーネントとヘルパーの束
  • formik - Reactフォーム
  • NeoForm - フォーム状態の管理と検証のためのモジュラーHOC
  • react-jsonschema-form - JSONスキーマからWebフォームを構築するためのReactコンポーネント
  • List View Select - React Nativeとネイティブコンポーネントの切り替え可能な選択ボックス
  • Final Form ?
  • formland - シンプルで柔軟性が高く、拡張可能な構成ベースのフォームジェネレーター
  • react-reactive-form - ReactのAngularのようなリアクティブフォーム
  • unform - ネストされたフィールド、検証など、制御されていないフォーム構造を作成するReactJSフォームライブラリ
  • Formal - React Hook時代のエレガントなフォーム管理プリミティブ
オートコンプリート(入力補完)
グラフィック
  • react-art - Reactを使用してベクターグラフィックスを描画するためのJavaScriptライブラリ
  • react-canvas - Reactコンポーネントのための高性能canvasレンダリング
  • react-famous - Famo.usを使用した60 FPSの複雑な3DアニメーションUI
  • react-kinetic - Reactを使用したKineticJS経由のHTML5 Canvas
  • react-svg-morph - svgコンポーネントを別のものにモーフィングする
  • react-hooks-svgdrawing - Reactフックを使用したSVG描画
モデルライブラリ
  • mori - ClojureScriptの永続データ構造とサポートAPI
  • NestedTypes - 「純粋なレンダリング」をサポートする高速可変モデル
  • swarm - JavaScript複製モデル(MVCのM)ライブラリ
  • caplet - JavaScriptモデルライブラリ
データ管理
  • Immutable.js - JavaScript用の不変のデータコレクション
  • cortex - Reactでデータを集中管理するためのJavaScriptライブラリ
  • avers - 最新のクライアント側モデル抽象化ライブラリ
  • imvvm - Reactの不変のModel-View-ViewModel
  • morearty.js - 純粋なJavaScriptでのReactの状態管理の改善
  • valuable - Reactの不変のデータストア
  • react-resolver - Reactコンポーネントのデータを再帰的に遅延ロードする同形ライブラリ
  • freezer-js - Reactの軽量でリアクティブな不変のデータ構造
  • MobX - シンプルでスケーラブルな状態管理
  • baobab - カーソルを使用したJavaScript永続的でオプションの不変データツリー
  • baobab-react - BaobabのReact統合
  • datascript - ClojureScriptの不変データベースとデータログクエリエンジン
  • immstruct - Reactのようなコンポーネントベースのライブラリのトップからボトムのプロパティの履歴を持つ不変のデータ構造
  • seamless-immutable - 通常のJS配列およびオブジェクトと後方互換性のあるJavaScriptの不変のデータ構造
  • tydel - 型付きモデルとコレクション、Reactバインディング
  • extendable-immutable - Immutable.jsデータ構造を拡張する
  • statty - ReactおよびPreactアプリ用の小さくて控えめな状態管理ライブラリ
  • Hydux - 「バッテリーを含む」ReactのElmライクステートマネージャー
  • ReSub - より良いReactコンポーネントとデータストアを作成するためのライブラリ
  • ProppyJS - Functional props構成のための小さなライブラリ
  • WatermelonDB - 強力なReactおよびReact Nativeアプリ向けの次世代データベースで、数万件のレコードに対応し、高速性を維持
  • Effector - 高速で強力なリアクティブ状態マネージャー。 シンプルで高速かつタイプセーフなコードを記述し、状態を簡単に管理
  • reactn - 組み込みのグローバル状態管理
  • immer - 現在の状態を変更し、次の不変状態を作成
地図
  • react-googlemaps - GoogleマップへのReactインターフェース
  • react-maps - Reactのマップコンポーネント
  • react-google-maps - React.js Google Maps統合コンポーネント
  • react-gmaps - React.js用のGoogleマップコンポーネント
  • react-map-gl - MapboxGL JSのReactラッパー
  • google-map-react - Googleマップと同形のReactコンポーネント
  • react-mapbox-gl - Reactのためのmapbox-gl-jsラッパー
  • google-maps-react - React、遅延読み込みの依存関係、現在位置ファインダー、およびフルスタックReactチームによるテスト駆動型アプローチを使用した宣言的なGoogle Map Reactコンポーネント
  • react-leaflet - リーフレットマップのReactコンポーネント
  • react-geo - react、antd、およびolを使用した地理関連コンポーネントのセット
  • pigeon-maps - 外部依存関係のないReactJSマップ
統計・グラフ
  • DevExtreme React Chart - BootstrapおよびMaterialデザイン用の高性能プラグインベースのReactチャート
  • react-chartjs - chart.jsを使用した一般的なReactチャートコンポーネント
  • react-stockcharts - ReactJSとd3による高度にカスタマイズ可能な株価チャート
  • Number Picture - React&D3でアニメーション化された視覚化を構築するための低レベルのビルディングブロック
  • Victory - インタラクティブなデータ視覚化を構築するための構成可能なReactコンポーネントのコレクション
  • Recharts - すばらしい宣言型APIを備えたD3上に構築されたチャートライブラリ
  • React-ApexCharts - ApexChartsのReactコンポーネント(インタラクティブなSVGチャートライブラリ)
  • reaviz - D3.jsに基づくReactでの視覚化ライブラリ
  • react-vis - Reactフレンドリー、高レベルでカスタマイズ可能で、表現力があり、業界に強いReact視覚化ライブラリー
  • nivo - D3およびReactライブラリの上に構築された、豊富なデータ視覚化コンポーネントのセットを提供
  • vx - 再利用可能な低レベルの視覚化コンポーネントのコレクション
  • echarts-for-react - Reactの非常にシンプルなEChartsラッパー
  • Chartify - CSSを使用してチャートを作成するためのReactプラグイン
  • Semiotic - ReactとD3を組み合わせたデータ視覚化フレームワーク
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Svelte3入門】ToDoリストをチュートリアルと照らし合わせて作ろみゃあ!

はじめよっけ!

この記事はAteam Brides Inc. Advent Calendar 2019 6日目の記事です。

こんにちは。株式会社エイチームブライズ@mkin です。

さて、今日は React や Vue よりも早いと言われている Svelte を使って簡単なアプリを作っていきます。

この記事は実際に私が ToDoリストを作った順番に、チュートリアルを逆引きしながら作った実録でもあるので、読むだけでも開発体験をトレースできるんじゃないかと、淡く期待しています :blush:

React も Vue も好きな筆者が Svelte を触った感想は「...めっちゃ分かりやすいがぁ!! :rage1:」というもの。この開発体験を少しでも多くの方に共有したくてこの記事を書きます。

※Svelte は2019-04-23にリリースされ大幅に改善したバージョン3を指します。

Svelte ってなんなのー?

私も数週間前までその存在を知りませんでした。
職場のフレンズたちが Svelte で盛り上がっているとき、私は冷めた目をしていました。
でも、知らないものを批判しちゃいかんと思い直し、まず調べたのがこちら。

image.png

Svelte がホームページで謳っていることは次の3つ...

  1. Write less code: より少ないコードで
  2. No virtual DOM: 仮想DOMを使わずに
  3. Truely reactive: 真のリアクティブ

順に説明しますね。

1. Write less code: より少ないコードで

まずは以下の動画をみてください。
calc.gif

単純な計算機能ですが、これを Svelte は React と比べて32%、Vue と比べて55% のコード量で実装できます。

  • 442文字: React
  • 263文字: Vue
  • 145文字: Svelte

2. No virtual DOM: 仮想DOMを使わずに

これは大事なポイントですが、 Svelte はコンパイラーであり、React や Vue のようなフロントエンドフレームワークではありません。これにより、クライアントへ渡されるのは純粋な HTML と javascript と css になり、余分なオーバーヘッドを無くして高速に動きます。余分なオーバーヘッドとはフレームワーク自体の転送容量や、仮想DOMの生成時間、その使用メモリを指します。

3. Truely reactive: 真のリアクティブ

Svelte では 1と2のおかげで React / Vue のように複雑な状態管理を意識せずに書けます。実際、チュートリアルを幾つかやるだけで、その簡潔な書き方におったまげるでしょう。

参考までに、@so99ynoodles さんの記事「ReactとVueを改善したSvelteというライブラリーについて」によると

Svelteは速く、軽いです。
ベンチマークでReactの35倍、Vueの50倍速いです。
Svelteはコンパイラーであるため、実質ライブラリーとしての容量は0kbです。

とのこと:open_mouth:

開発環境を作ったるがや!

前置きはここまでにして、さっそく開発を始めていきましょう。

  • 私の開発環境はこちらです。
    • mscOS Mojave
    • Visual Studio Code
    • npx v6.13.1

Svelte プロジェクトを作成しよっけ!

簡単に開発環境を作るために「The Svelte 3 Quickstart Tutorial」を見ながら進めました。
まずは、以下のコマンドを実行してください。

console
# ワークスペースへ移動(好きな場所でOK)
mkdir svelte
cd svelte

# Svelteプロジェクトを作成して関連パッケージをインストールする
npx degit sveltejs/template todolist
cd todolist/
npm install

# VSCodeで開く
code .

そしたら、こんなプロジェクトができているので、、、
image.png
さっそく動かしてみましょう。
npm run devをコンソールで実行するとローカルホストが起動するので
http://localhost:5000/ にブラウザでアクセスしてください。
下記のように表示されたら成功。あなたの Svelte 生活の始まりですっ:rocket:

image.png

実装に入る前に...Svelte の拡張機能を入れとこっけ!

VSCode で開発するようなら、以下の拡張機能を入れておくとインテリセンスやシンタックスハイライトが効き、開発が捗るでしょう。

Svelte

image.png

Svelte 3 Snippets

image.png

ToDoリストの大枠を組み立ててこっけー

ToDoリストに求める機能はそれほど多くないので、大枠からざっくり作っていこうと思いました。
(逆に大きな機能のときは、それらを構成する機能をテスタブルに作り、終盤でつないでいくことが多いです。)
ということで、まず手始めに知っておいてほしいことは...

基本構成、それは HTML、script、そして style だがや!

/src/App.svelte
<!-- 自動生成された元の内容はガバっと変えていいよ! -->
<!-- HTML部 -->
<div>
  絞り込み:
  <button>すべて</button>
  <button>未完了</button>
  <button>完了</button>
</div>
<div>
  <input type="text">
  <button>タスク追加</button>
</div>
<div>
  <ul>
    <li>
      <input type="checkbox"> レストランを予約する
    </li>
    <li>
      <input type="checkbox"> サプライズ用の指輪を買う
    </li>
    <li>
      <input type="checkbox"> フラッシュモブダンスを練習する
    </li>
  </ul>
</div>

<script>
  // 後で記述するよ
</script>

<style>
  /* TODO: CSS得意なフレンズに頼む */
  /* https://svelte.dev/tutorial/styling */
</style>

ここまでは特に何も特別なことはありません。ただ、チュートリアルではscript、style、HTMLの構成でしたが、私が少し Nuxt.js を触っていたこともあり、順番を変えています。

さて、上記の実装をしたら、こんなシンプルでハッピーなToDoリストの雛形ができます:fork_and_knife::ring::dancers:

image.png

Svelte の機能を使ってToDoリストに機能をつけてこっかー

大枠が完成したところで、以下の手順で機能を実装していきたいと思います。

  1. タスクの直書きを配列に変更する
  2. 「タスク追加」ボタンでタスクを追加する
  3. 「絞り込み」機能で表示対象を切り替える

1. タスクの直書きを配列に変更する

実際のシステムであればサーバからデータを取得するのですが、
それは一旦置いといて、サクッと小さく動くものを作りたいと思いました。

/src/App.svelte
  <ul>
-    <li>
-      <input type="checkbox"> レストランを予約する
-    </li>
-    <li>
-      <input type="checkbox"> サプライズ用の指輪を買う
-    </li>
-    <li>
-      <input type="checkbox"> フラッシュモブダンスを練習する
-    </li>
+    {#each todoList as todo (todo.id)}
+      <li>
+        <input type="checkbox" bind:checked={todo.done}> {todo.title}
+      </li>
+    {/each}
  </ul>

<script>
-  // 後で記述するよ
+  let todoList = [
+    { id: 0, done: false, title: 'レストランを予約する'},
+    { id: 1, done: false, title: 'サプライズ用の指輪を買う'},
+    { id: 2, done: false, title: 'フラッシュモブダンスを練習する'},
+  ]

</script>

ここでは、以下のチュートリアルが役に立ちます。

  • Binding / Text inputs: {todo.title} 変数の値を表示してみよう。
  • Binding / Checkbox inputs: {todo.done} 変数の値と checkbox の値を紐付けてみよう。
  • Logic / Keyed each blocks: ループ処理をしてみよう。#each句で各ToDoタスクと(todo.id)を紐付けることで、Svelte に変化を検知させることができるよ。

動かしてみると、先程と表示は変わりませんが、配列からToDoリストを表示するように変更できました。

2. 「タスク追加」ボタンでタスクを追加する

次に「タスク追加」ボタンに対してクリックイベントを追加していきたい。(自分に負けない!)
image.png

/src/App.svelte
<div>
-  <input type="text">
-  <button>タスク追加</button>
+  <input type="text" bind:value={title}>
+  <button on:click={() => add()}>タスク追加</button>
</div>

・・・

<script>
  ...
+  let title = ''
+  function add() {
+    todoList = [...todoList,
+    {
+      id: todoList.length,
+      done: false,
+      title
+    }]
+  }

</script>

ここでは、以下のチュートリアルが役に立ちます。

3. 「絞り込み」機能で表示対象を切り替える

さぁ、最後の工程です! フィルタリングして見たい情報に絞り込んでやろうではありませんかっ!
(おーっ!!!!!)
image.png

/src/App.svelte
<div>
  絞り込み:
-  <button>すべて</button>
-  <button>未完了</button>
-  <button>完了</button>
+  <button on:click={() => { condition = null }}>すべて</button>
+  <button on:click={() => { condition = false }}>未完了</button>
+  <button on:click={() => { condition = true }}>完了</button>
+  
</div>

-   {#each todoList as todo (todo.id)}
+   {#each filteredTodoList(todoList, condition) as todo (todo.id)}
      <li>
        <input type="checkbox" bind:checked={todo.done}> {todo.title}
      </li>
    {/each}

<script>
...

+  let condition = null
+
+  $: filteredTodoList = (todoList, condition) => {
+    return condition === null ? todoList : todoList.filter(t => t.done === condition)
+  }
+  
</script>

ここでは、以下のチュートリアルが役に立ちます。

ここまでできたらToDoも完成です!
さぁ!みなさんもToDoを追加していきましょう!!:movie_camera:

ezgif.com-video-to-gif.gif

ちょっと待って!! 何かおかしいがー

さっきの動画で何かマズい点あったの気づきました?
そうです。そもそも恋人がいないんですよ!
そうです。動かして分かったんですが、このToDoリストには具合が悪い点がいくつかあったんです。それは...

  1. 初期カーソルが タスク入力欄にフォーカスされてないから入力しづらい
  2. 「タスク追加」ボタンをクリックした後にタスク入力欄がクリアされない

そこで、最後のお仕置きをしたいと思いました。

初期化処理で操作性を高めたいがー

上記の問題は初期化処理を追加することで解決しました。
では、さっそく...

/src/App.svelte
<div>
- <input type="text" bind:value={title}>
+ <input type="text" bind:value={title} bind:this={initFocus}>
  <button on:click={add}>タスク追加</button>
</div>

<script>
+  import { onMount } from 'svelte'
+
+  onMount(() => {
+    init()
+  })
+  
+  let initFocus = null
+
+  function init() {
+    title = ''
+    initFocus.focus()
+  }

  function add() {
    todoList = [...todoList,
    {
      id: todoList.length,
      done: false,
      title
    }]
+   init()
  }

</script>

※うまく反映されない場合はブラウザのキャッシュクリアをしてみてください。

ここでは、以下のチュートリアルが役に立ちます。

  • Lifecycle / onMount: コンポーネントのライフサイクルを理解しましょう。コンポーネントが最初にDOM描画された後に onMountハンドラで書いた処理を実行するよ。
  • Bindings / This: bind:this={変数} で何でも好きな要素と紐付けてみよう。

完成版だぎゃー!!!!!!!!!

/src/App.svelte(完成版)
<!-- HTML部 -->
<div>
  絞り込み:
  <button on:click={() => { condition = null }}>すべて</button>
  <button on:click={() => { condition = false }}>未完了</button>
  <button on:click={() => { condition = true }}>完了</button>
</div>
<div>
  <input type="text" bind:value={title} bind:this={initFocus}>
  <button on:click={() => add()}>タスク追加</button>
</div>
<div>
  <ul>
    {#each filteredTodoList(todoList, condition)  as todo (todo.id)}
      <li>
        <input type="checkbox" bind:checked={todo.done}> {todo.title}
      </li>
    {/each}
  </ul>
</div>

<script>
  import { onMount } from 'svelte'

  let title = ''
  let initFocus = null
  let condition = null
  let todoList = [
    { id: 0, done: false, title: 'レストランを予約する'},
    { id: 1, done: false, title: 'サプライズ用の指輪を買う'},
    { id: 2, done: false, title: 'フラッシュモブダンスを練習する'},
  ]

  onMount(() => {
    init()
  })

  function init() {
    title = ''
    initFocus.focus()
  }

  function add() {
    todoList = [...todoList,
    {
      id: todoList.length,
      done: false,
      title
    }]
    init()
  }

  $: filteredTodoList = (todoList, condition) => {
    return condition === null ? todoList : todoList.filter(t => t.done === condition)
  }
</script>

<style>
  /* TODO: CSS得意なフレンズ絶賛募集中!! */
  /* https://svelte.dev/tutorial/styling */
</style>

頑張れ!自分に負けるな!
todolist.gif

最後に、Svelte いいがやっ!

コード量が少ないから見晴らしがよくて、とっつきやすいんじゃないかなと思います:two_hearts:
Svelte でもコンポーネント管理できるし、Storybook for Svelteもあるのでデザインシステムも作れる。そのうえ、SSRとしてSapper という Next/Nuxt のようなフレームワークも用意されている。React Native / Vue Native よろしく Svelte Naitveというのもある。(誰だ!やり過ぎって言ったやつ! 俺も同感だよっ!)
React / Vue を触ったことがある方にはすごく馴染みやすい技術だと思うので、試してみてもらえると嬉しいです!

また、昨日の @oekazuma の記事「君はVue,Reactの次に来るSvelteを知っているか?」も併せて読んでいただけると嬉しいです:relaxed:

それでは、明日は @fussy113 の「サイト速度改善、実装編」(仮)の予定です。どうぞご期待ください:laughing:

私たちのチームで働こまい?

alt
エイチームは、インターネットを使った多様な技術を駆使し、幅広いビジネスの領域に挑戦し続ける名古屋の総合IT企業です。
そのグループ会社である株式会社エイチームブライズでは、一緒に働く仲間を募集しています!

上記求人をご覧いただき、少しでも興味を持っていただけた方は、まずはチャットでざっくばらんに話をしましょう。
技術的な話だけでなく、私たちが大切にしていることや、お任せしたいお仕事についてなどを詳しくお伝えいたします!

Qiita Jobsよりメッセージお待ちしております!

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

Figma ✕ React Storybook で作るプロトタイピング & スタイルガイド

アプリを開発する前に予め、開発の全体像を把握するためにプロトタイプを作成します。
プロトタイピングツールで作成したUIは、実際のアプリ開発にも流用できるため効率的です。

今回は簡単な写真共有Webブラウザアプリを制作すると仮定して、

  1. プロトタイピングツールFigmaでプロトタイプ作成
  2. Reactコンポーネントへの落とし込み
  3. Storybookでの管理

という一連の流れを実際に実装しながらFigmaStorybookを導入することでどういったメリットがあるのか・どういったことができるのかの検証を行います。

作成したプロトタイプの全体像

最初にどんなアプリなのかをスクリーンショットで提示してから、詳細な解説を進めていきます。
基本的なアプリに最低限必要な認証一覧表示登録編集 機能をプロトタイピングで再現しました。
マテリアルデザイン(Material-ui)をベースとしています。

ログイン画面

  • ユーザーサインイン
  • ユーザー新規登録
  • パスワードリセット

アルバム一覧・投稿画面

  • アルバム一覧表示
  • アルバム投稿
  • アルバム編集
  • アルバム削除


なぜFigmaを使うのか

Figma公式サイト
https://www.figma.com/

プロトタイピングツールにはSketchAdobeXDなど様々な種類がありますが、今回Figmaを選択したのは以下の理由からです。

  • ブラウザからアクセスしサインインするだけで、ほとんどの機能を無料で使える
  • コンポーネント・スタイルの管理が楽そう
  • 機能面でSketchやXDと遜色なさそう
  • リアルタイムコラボレーションを試してみたかった
  • 話題のweb assemblyで記述されているwebアプリの操作性を実際に体験してみたかった

Figmaで作るスタイルガイド

若干我流かもしれませんがAtomic Designを意識して、スタイルガイドを作成してみました。後ほど、Material-uiでコードを組む予定だったので、フォント・カラーなどの命名・パターンは極力Material-uiデフォルトのものを使用し齟齬が出ないようにしました。
Figmaではカラー・フォントはローカルスタイルとして定義することができ、定義されたスタイルをボタンやフォームなどに適用すれば、同一のスタイルを一括で変更したりと変更に強いデータを作成することが可能です。

フォント

Material-uiにもともと定義されているタイポグラフィ+タイトルのフォントを定義。

カラー

  • ベースカラー → Material-uiのオリジナルカラーgreyのカラーパターン
  • メインカラー → 独自のネイビー系の配色darkmainlightの3パターン
  • アクセントカラー → 独自のオレンジ系の配色darkmainlightの3パターン

アイコン・ボタン・フォーム

Figma便利機能

Figmaは奥が深く、いろいろと便利機能があるのですが、私が利用するなかで特に便利だと思った機能について解説を行います。

コンポーネント

図形やテキストなどのパーツをまとめたものをコンポーネントとして定義することができます。
画面上部メニューにある菱形アイコンをクリックするか、パーツを全て選択右クリックCreate Componentで簡単にコンポーネントを作成できます。

コンポーネントはコピーすることで、インスタンスを生成することができ、コピー元コンポーネント(マスターコンポーネント)の変更がリアルタイムで反映されるようになります。
生成されたインスタンスはパーツの位置のみは固定で、以下項目についてはオーバーライドして独自のコンポーネントを作成することが可能です。

  • カラー
  • 画像
  • テキストの内容
  • コンポーネントのサイズ

コンストレイント

上記のコンポーネント機能で、インスタンスを作成しコンポーネントのサイズ感を変更したい。しかし、コンポーネントのサイズを変更すると図形の比率や配置が変更されてしまった。そんなときに使えるのがコンストレイント機能です。コンストレイント機能を使うと、「左上」「右上」「左下」「右下」「中央」の中から、位置固定の基準位置を設定することができます。

コンポーネントの整列

プロトタイプを作成していると、複数のコンポーネント間を一発で調整したい時があるはずです。Figmaなら整列ボタンを使わなくても直感的にドラックアンドドロップで調整できるので作業効率が上がります。Illustratorやphotoshopではこんな機能なかったのですごく便利に感じました。

適当に配置した、オブジェクトをいい感じに整列してくれる「Tidy Up」もそこそこ便利です。整列ボタンから選択できます。

画像の一括配置

画像も複数画像を選択してサクサク配置することができます。アプリのイメージを変えたいなと思った時も10秒とかからず変更することができます。ただ、Sketchのようにテキストの一括流し込みができないのは残念です。

プロトタイピング

Figmaでは画面のレイアウトを作成したら、そのままプロトタイピングも実装できます。サイドバーメニューからPrototypeタブを選択し、あとはトリガーとなるボタンまたはフォームから遷移先のフレームに以下画像のように動線を結べば「クリックしたら画面遷移」や「ホバーしたらコンポーネント表示」などを実現させることができます。

アニメーションについては複雑な動作はできませんが、プロトタイピングの段階では画面遷移だけわかれば良いということがほとんどだと思うので私は十分だと思いました。フェードイン・アウトやムーブイン・アウトなど基本的動作は普通にできます。

コラボレーション

Figmaの一番の強みコラボレーション機能についてです。今回のアプリ制作は1人で作業していたためあまり効力を発揮しませんでしたが、試しに2台PCと2アカウントを用意してコラボレーション機能を試してみました。タイムラグが発生して使いにくいのではないかと思っていましたが、Google Documentのようにデザイン変更はリアルタイムで反映されストレスなく作業を行うことができました。何より、ファイルの受け渡しなく同時平行で複数人が作業・コメントできるのは作業効率や管理コスト面で相当便利な機能だと思います。


なぜStoryBookを使うのか

StoryBook公式サイト
https://storybook.js.org/

StoryBookはUI Component 管理ツールという位置づけです。スタイルガイドを作成するだけなら、上記で解説したFigmaでも可能です。しかし、実際運用・保守の観点で考えるとデザイン改修の際にFigmaファイルを修正して、その上でアプリのコードを修正するとなると2度手間になってしまいます。Storybookを利用するとアプリのコードをそのままスタイルガイドにできるので、スタイルガイドを修正する=アプリのコードを修正するということになり「アプリ側のコードだけしか修正されずスタイルガイドの意味がなくなってしまった」というリスクを防ぐことが可能になります。
Figmaはベータ版の段階でざっくりとスタイルガイドを作成する、ある程度案が固まって実際にコードを書き始めたらStorybookでしっかりとスタイルガイドを作り込んで運用するといった感じになると思います。StorybookはFigmaのようにGUIベースではなくコードベースなので、それなりに学習コストもかかりますが、作成したコンポーネントはそのままアプリで使えますのでスタイルガイド作成作業が全く無駄になってしまうということはないと思います。

Storybook for Reactの導入

Storybookを導入したいreactアプリケーションのプロジェクトルートで以下コマンドを入力すると自動でStorybookのsettingを行ってくれます。

shell
npx -p @storybook/cli sb init --type react

以下コマンドでStorybookを起動するとブラウザでスタイルガイドが開きます。

shell
yarn storybook

6074184e7a098f7a94042679654ca0e8.png

typescriptを使用する場合は、まず.storybook配下の2つのファイルに変更を加えます。
(ファイルが存在しなければ追加します)

.storybook/webpack.config.js
module.exports = ({ config }) => {
  config.module.rules.push({
    test: /\.(ts|tsx)$/,
    use: [
      {
        loader: require.resolve('babel-loader'),
        options: {
          presets: [['react-app', { flow: false, typescript: true }]],
        },
      },
      {
        loader: require.resolve('react-docgen-typescript-loader'),
      },
    ],
  });
  config.resolve.extensions.push('.ts', '.tsx');
  return config;
 };
.storybook/config.js
import { configure } from '@storybook/react';

// automatically import all files ending in *.stories.js
configure(require.context('../stories', true, /\.stories\.tsx$/), module);

次にプロジェクトルートに存在するtsconfig.jsonファイルを書き換えます

tsconfig.json
{
  "compilerOptions": {
    "outDir": "build/lib",
    "module": "commonjs",
    "target": "es5",
    "lib": ["es5", "es6", "es7", "es2017", "dom"],
    "sourceMap": true,
    "allowJs": false,
    "jsx": "react",
    "moduleResolution": "node",
    "rootDirs": ["src", "stories"],
    "baseUrl": "src",
    "forceConsistentCasingInFileNames": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "suppressImplicitAnyIndexErrors": true,
    "noUnusedLocals": true,
    "declaration": true,
    "allowSyntheticDefaultImports": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "build", "scripts"]
}

Storybookアドオン

Storybook単体ではスタイルガイドの機能としては不十分なので、アドオンを追加していきます。
アドオンの追加は基本的には以下の手順です。
アドオンのインストールを行い

shell
npm i -D @storybook/{ アドオン名 }

addons.jsファイルに以下コードを記述

.storybook/addons.js
import '@storybook/{ アドオン名 }/register';

addon-knobs

スタイルガイドの対象コンポーネントにpropsを渡して、コンポーネントの状態を変化させることができます。GUIで簡単に確認できるので、非エンジニアでもコンポーネントのデザインチェックを行うことができます。

コード一例
import React from 'react';
import { withKnobs, text, boolean, select } from '@storybook/addon-knobs';

import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';

import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
import StarBorderIcon from '@material-ui/icons/StarBorder';

import { storiesOf } from '@storybook/react';

const colorOptions: any = {
  primary: 'primary',
  secondary: 'secondary',
  inherit: 'inherit',
  default: 'default',
};

const sizeOptions: any = {
  small: 'small',
  medium: 'medium',
  large: 'large',
};

const ButtonComponent: React.FC = () => {
  return (
    <List>
      <ListItem>
        <Button
          color={select('Color', colorOptions, colorOptions.primary)}
          size={select('Size', sizeOptions, sizeOptions.medium)}
          disabled={boolean('disabled', false)}
        >
          {text('Label', 'Menu Button')}
        </Button>
      </ListItem>
      <ListItem>
        <IconButton
          color={select('Color', colorOptions, colorOptions.primary)}
        >
          <StarBorderIcon />
        </IconButton>
      </ListItem>
    </List>
  );
};

storiesOf('./Button', module)
  .addDecorator(withKnobs)
  .add('./Button', () => <ButtonComponent />);

addon-actions

onClickなどのイベントログを表示することができます。

コード一例
import React from 'react';
import { action } from '@storybook/addon-actions';

import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';

import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
import StarBorderIcon from '@material-ui/icons/StarBorder';

import { storiesOf } from '@storybook/react';

const ButtonComponent: React.FC = () => {
  return (
    <List>
      <ListItem>
        <Button
          onClick={action('button-click')}
        >
          Menu Button
        </Button>
      </ListItem>
      <ListItem>
        <IconButton
          onClick={action('icon-button-click')}
        >
          <StarBorderIcon />
        </IconButton>
      </ListItem>
    </List>
  );
};

storiesOf('./Button', module)
  .add('./Button', () => <ButtonComponent />);

addon-docs

addon-infoの上位互換のアドオンです。markdownを使ったドキュメントを作ったり、MDX形式でスタイルガイドを作成したりできるようです。
f11fb2d33d7d27652c18d232480cb4f9.png

導入にはnpmモジュールのインストールの他に一手間かける必要があります。
まず、.storybook配下にpresets.jsを作成し、以下コードを記述します。

.storybook/presets.js
module.exports = ['@storybook/addon-docs/react/preset'];

mdxを使いたい場合config.jsを書き換えます

.storybook/config.js
configure(require.context('../stories', true, /\.stories\.(tsx|mdx)$/), module);

以下のように.mdx形式ファイルはmarkdownが使えるため、簡単にきれいなドキュメントを作成できます。

storyコード一例(mdx使用)
import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';
import { ButtonComponent } from './Button'; //reactコンポーネントを読み込み

<Meta title="MDX|ButtonComponent" component={ButtonComponent} />

# ButtonComponent

With `MDX` we can define a story for `ButtonComponent` right in the middle of our
markdown documentation.

<Preview>
  <Story name="ButtonComponent">
    <ButtonComponent />
  </Story>
</Preview>

|  TH  |  TH  |
| ---- | ---- |
|  TD  |  TD  |
|  TD  |  TD  |

addon-storyshots

addon-storyshotsを使えば、jestと組合あせてStorybookに登録されているコンポーネントのテストを行うことができます。Storybookに登録されているコンポーネントに何かしらの変更を加えるとエラーとして検出され異常をすぐに検知することができます。
こちらもaddon-docs同様、導入にはnpmモジュールのインストールの他に一手間かける必要があります。
ただ、今回はaddon.jsへのregister記述は必要ありません。

まず、追加で以下のモジュールをインストールします。jestスナップショットを行うために必要です。

npm i -D require-context.macro react-test-renderer

次にconfig.jsを書き換えます

.storybook/config.js
import { configure } from '@storybook/react';
import requireContext from 'require-context.macro';

// automatically import all files ending in *.stories.js
const req = requireContext('../stories', true, /\.stories\.(tsx|mdx)$/);

function loadStories() {
  req.keys().forEach(filename => req(filename));
}

configure(loadStories, module);

最後にjestが検知されるパス(おそらくデフォルトは.src/)にstoryshots.test.tsxを作成し、以下のコードを記述します。

src/storyshots.test.tsx
import initStoryshots from '@storybook/addon-storyshots';

initStoryshots();

jestテストを実行すると、コンポーネントテストが行われ、src配下__snapshots__フォルダ内にスナップショットが記録されます。

yarn test

 PASS  src/storyshots.test.tsx
  Storyshots
    ./Button
      ✓ ./Button (64ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 passed, 1 total
Time:        3.563s
Ran all test suites.

少しでも変更するとエラーになります。スタイルガイドに変更を加えたい場合は、__snapshots__を一度削除してjestテストを再度行うことで、変更したコンポーネントが正しいものとして登録されます。

FAIL  src/storyshots.test.tsx
  Storyshots
    ./Button
      ✕ ./Button (57ms)

  ● Storyshots › ./Button › ./Button

    expect(received).toMatchSnapshot()

    Snapshot name: `Storyshots ./Button ./Button 1`

    - Snapshot
    + Received

    @@ -23,11 +23,11 @@
            type="button"
          >
            <span
              className="MuiButton-label"
            >
    -         Menu Button
    +         Menu But
            </span>          </button>
        </li>
        <li
          className="MuiListItem-root MuiListItem-gutters"

      at match (node_modules/@storybook/addon-storyshots/dist/test-bodies.js:27:20)
      at node_modules/@storybook/addon-storyshots/dist/test-bodies.js:39:10
      at Object.<anonymous> (node_modules/@storybook/addon-storyshots/dist/api/snapshotsTestsTemplate.js:44:33)

 › 1 snapshot failed.
Snapshot Summary
 › 1 snapshot failed from 1 test suite. Inspect your code changes or press `
u` to update them.

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   1 failed, 1 total
Time:        4.575s
Ran all test suites.

まとめ

プロトタイピングやスタイルガイド作成は正直面倒です。ですが、全体イメージ・根底ルールをしっかりと定めておくと、改修フェーズで圧倒的に手戻りが少なく楽をすることができます。また、「Storybookに乗せるためにReactのコードをきれいに書かなくては」という衝動にもかられ、コードを書いていく上でもガイドラインがあるとないとでは全く意識が変わってきます。私自身まだ手探り状態なところではありますが、今回の学びを業務にも活かしていこうと思います。
ちなみに今回プロトタイプで作った、写真共有WebブラウザアプリをReactで実装する過程を次回以降のアドベントカレンダーの投稿で記述するつもりです。

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