6/21 コンポーネント駆動開発、簡易な導入手順まとめ
使うライブラリ・パッケージと概要
- React, TypeScript, Storybook: ReactはJavaScriptライブラリであり、ユーザーインターフェイスの構築を容易にします。TypeScriptはJavaScriptのスーパーセットで、型安全性を提供し、バグを早期に発見し、コードのリーダブル性と保守性を向上します。StorybookはUIコンポーネントの開発とドキュメンテーションを支援するツールです。Storybookを使用すると、各コンポーネントの異なる状態を視覚的に確認することができます。
- Jest: JestはJavaScriptのテストフレームワークで、Reactコンポーネントのテストに特に適しています。Jestはユニットテストと結合テストの両方をサポートしており、モック関数やスナップショットテストなどの機能も提供しています。
- Plop: Plopはボイラープレートコードを生成するためのツールです。あらかじめ定義したテンプレートを元に新しいコンポーネントやテストの雛形を簡単に生成することができます。
- MSW (Mock Service Worker): MSWはブラウザとNode.jsで動作するモック/スパイライブラリです。MSWはネットワークリクエストを補足し、定義したモックレスポンスを返します。これにより、APIの実装が未完成でもフロントエンドの開発やテストを進めることができます。
導入手順
React TypeScriptプロジェクトの作成
npx create-react-app my-app --template typescript
TypeScriptのオプションをつけることで、作成時からTypeScriptの初期設定やファイル名になります。
Storybookのインストール
npx sb init
.storybook
ディレクトリが作成される。
また、既存のデフォルトのストーリーは src/stories
ディレクトリ内に格納される。
Jestのインストール:
Jestは create-react-app
にデフォルトで含まれていますが、追加の設定や型定義が必要な場合は次のコマンドを実行Jestは create-react-app
にデフォルトで含まれていますが、追加の設定や型定義が必要な場合は次のコマンドを実行
Plopのインストールと設定:
npm i -D plop
プロジェクトのルートディレクトリに plopfile.js
ファイルを作成
touch plopfile.js
plopfile.js
module.exports = function (plop) { // コンポーネントジェネレータを作成します plop.setGenerator('component', { description: 'Create a component', prompts: [{ type: 'input', name: 'name', message: 'What is your component name?' }], actions: [ { type: 'add', path: 'src/components/{{pascalCase name}}/{{pascalCase name}}.tsx', templateFile: 'plop-templates/Component.tsx.hbs' }, { type: 'add', path: 'src/components/{{pascalCase name}}/{{pascalCase name}}.stories.tsx', templateFile: 'plop-templates/Component.stories.tsx.hbs' } ] }); };
- Plopの設定ファイルで、このファイルによってPlopがどのように動作すべきかを定義
- このファイル内で、生成されるファイルのタイプや名前、テンプレートとなるファイルへのパス、プロンプトの質問などを設定している
plop-templatesというディレクトリをプロジェクトのルートに作成
その中にテンプレートとなるComponent.tsx.hbs
とComponent.stories.tsx.hbs
という2つのファイルを作成
これらの.hbs
ファイルはHandlebarsテンプレートと呼ばれ、特定の箇所を置換して新しいファイルを生成するために使用される
基本的な例
Component.tsx.hbs
import React from 'react'; export interface {{pascalCase name}}Props { } export const {{pascalCase name}}: React.FC<{{pascalCase name}}Props> = (props) => { return ( <div> {/* Implement your component here */} </div> ); }
Component.stories.tsx.hbs
import React from 'react'; import { {{pascalCase name}}, {{pascalCase name}}Props } from './{{pascalCase name}}'; export default { title: 'Example/{{pascalCase name}}', component: {{pascalCase name}}, }; const Template: React.VFC<{{pascalCase name}}Props> = (args) => <{{pascalCase name}} {...args} />; export const Primary = Template.bind({}); Primary.args = { /* Fill in your default args here */ };
npx plop component
コマンドを実行すると、ファイル名を聞かれるので、例としてbuttonと答えたとすると、Button.tsx
とButton.stories.tsx
がsrc/components/Button/
ディレクトリに作成される
ストーリーブックのテスト設定
ストーリーブックのテストには、@storybook/addon-storyshots
を使用する。これをインストールして、自動的にStorybookのスナップショットテストを作成できる。
npm install --save-dev @storybook/addon-storyshots react-test-renderer
src
ディレクトリにstoryshots.test.js
という新しいファイルを作成
touch src/storyshots.test.js
storyshots.test.js
ファイルに以下の内容を追加
import initStoryshots from '@storybook/addon-storyshots'; initStoryshots();
この設定により、Jestがテストを行うときにStorybookの各ストーリーのスナップショットテストを自動的に行う
テストの実行:
npm test
- start test などは、package.jsonのscriptsに書かれていて、runをつけなくてもいい
- このコマンドでjestのテストが走ります
storybook起動
npm run storybook
- このコマンドをするとブラウザにstorybookが立ち上がるので、現状のstorybook(今回の例だとbuttonコンポーネント)が反映される
mswの設定:
npm install msw
mswを設定します。以下のコードをsrc/mocks/handlers.js
に追加します。
import { rest } from 'msw' export const handlers = [ rest.post('https://example.com/api/submit', (req, res, ctx) => { return res(ctx.json({ message: 'Success' })) }), ]
このハンドラをテスト環境で使用できるように設定します。以下のコードをsrc/mocks/browser.js
に追加します。
import { setupWorker } from 'msw' import { handlers } from './handlers' export const worker = setupWorker(...handlers)
テストのセットアップとクリーンアップでmswサーバーを起動と停止します。以下のコードをsrc/setupTests.js
に追加します。
import { server } from './mocks/server.js' beforeAll(() => server.listen()) afterEach(() => server.resetHandlers()) afterAll(() => server.close())
これでmswがテストで利用可能になりました。
基本的なコード例
import React from 'react' import { render, fireEvent, waitFor } from '@testing-library/react' import { rest } from 'msw' import { server } from './mocks/server' import Button from './Button' test('submits the form when the button is clicked', async () => { server.use( rest.post('https://example.com/api/submit', (req, res, ctx) => { return res(ctx.json({ message: 'Success' })) }) ) const mockOnClick = jest.fn() const { getByText } = render(<Button
コメント
実務でフロントエンドをCDDで開発することになったので、一回自分で小さく動かしてみるために、設定方法を簡単にまとめました。
6/20 Growthマインドセット
前提として成長の定義は
- できなかったことを、できるようになること
Growth(成長)マインドセットとは
- 会社の成功に必要なスキルを自分から積極的に学び、習得しようとするマインドセット
ある人とない人の違い
- 新しい挑戦に対して、チャンスと捉えるか、それとも面倒だと感じるか
持っているかの見極めポイント
- 過去18ヶ月で、元々のジョブディスクリプションには含まれていなかったけれど、会社の成功のために新たに身につけたスキルはありますかとストレートに聞く
持っている人の共通点
- 「自分が正しい」といった固定概念を捨てられること
- 一定の成功体験はもちろん自信につながるが、世の中はどんどん変化していくから、もっと良い方法はないか、もっと面白いアクションはないかという思考プロセスを繰り返す能力が重要
- 一次情報を取りに行き、新しいものを学ぶ姿勢があること
- それを楽しんで「こんな新しいものを見つけたよ」「これが面白かったよ」と新しい発見を共有してくれる
- 学んだことを言語化することも得意
- 自身の学びや新しい発見を、他者に対して還元していこうというマインドが強い
Growthマインドセット」を持つ人材を増やすために、工夫や大切にしていること
- 痛みを伴うような経験をポジティブに乗り越えようとする考え方
- フィードバックを「自分が攻撃されている」と受け取るのではなく、「自分の成長のために何かをしてもらっている」と前向きに捉える
参考
Jest DOMの主なカスタムマッチャー
例えばこちらですと、test1 test2 が表示されている(見える)ことを確認しています。
describe('TopPage', () => { test('デフォルト(テストケース)', async () => { render(<Default />); await waitFor(() => { expect(screen.getByText('test1')).toBeVisible(); expect(screen.getByText('test2')).toBeVisible(); }); });
toBeInTheDocument()
: 要素がDOMに存在することを確認します。toBeVisible()
: 要素が表示されている(見える)ことを確認します。toBeEmpty()
: 要素が空(子要素を持たない)ことを確認します。toBeEmptyDOMElement()
: 要素が空(子要素やテキストを持たない)ことを確認します。toBeInvalid()
/toBeValid()
: 要素が無効または有効な状態であることを確認します。toBeRequired()
: 要素が必須(required
属性を持つ)であることを確認します。toBeEnabled()
/toBeDisabled()
: 要素が有効または無効化(disabled
)されていることを確認します。toContainElement(element)
: 要素が特定の他の要素を含むことを確認します。toContainHTML(html)
: 要素が特定のHTMLを含むことを確認します。toHaveAttribute(attributeName, value?)
: 要素が特定の属性を持つことを確認します。オプションで属性の値も指定できます。toHaveClass(className)
: 要素が特定のクラス名を持つことを確認します。toHaveFocus()
: 要素がフォーカスされていることを確認します。toHaveFormValues(expectedValues)
: フォーム要素が特定の値を持つことを確認します。toHaveStyle(style)
: 要素が特定のスタイルを持つことを確認します。スタイルは文字列またはオブジェクトで指定できます。toHaveTextContent(text)
: 要素が特定のテキスト内容を持つことを確認します。toHaveValue(value)
: フォームコントロール要素(<input>
や<textarea>
など)が特定の値を持つことを確認します。toHaveDisplayValue(value)
: フォームコントロール要素が特定の表示値を持つことを確認します。toBeChecked()
/toBePartiallyChecked()
: チェックボックスやラジオボタンがチェックされている、または部分的にチェックされていることを確認します。toHaveDescription(description)
: 要素が特指定のアクセシビリティの説明(aria-describedby
で参照される要素のテキスト)を持つことを確認します。
コメント
フロントエンドのテストを極めたいので、少しずつ理解を深めていきます。
6/17 lodashのvalues
見るたびに何となく理解しては、時間が経つと忘れてしまっているのでまとめます。
インストール
npm i lodash @types/lodash
コード
import { values } from "lodash"; const obj = { a: 1, b: 2, c: 3, }; const valArray = values(obj);
デベロッパーツールで見た出力結果
Array(3) 0: 1 1: 2 2: 3 length: 3
コメント
最近、頭の中で完結させるより、簡単な実例に習って、ハンズオンを軽く行った方がいいような気がしている。
考えるより体験を!というような感覚です。
6/15 RuduxToolKitの忘れかけていた箇所のおさらい
- storeの中にreducerがある
- なので実際の実装では、configureStore内にreducerを設定する
- sliceにはstate reducer actioncreatorがある
- sliceの中にreducerがあるので、sliceからreducerを取り出して、storeに入れる
- なので実際の実装では、exportの際にreducerのみを取り出して、storeに入れている
- sliceの中にreducerがあるので、sliceからreducerを取り出して、storeに入れる
# 参考
Udemy
6/14 Storybook const Template = (args) => <Button {...args} />;について
const Template = (args) => <Button {...args} />;
がよくわからなかったので調べました
まずそれぞれの関係性の把握から
StorybookとTemplate関数、そしてButtonコンポーネントの関係を示す図
- Storybookは、異なる状態を表現するための
args
をTemplate関数に提供します。 - Template関数は、これらの
args
をプロパティとしてButtonコンポーネントに渡します。 - Buttonコンポーネントは、特定のプロパティでレンダリングされ、その結果がStorybookで視覚的に表現されます。
ここの関係性のイメージを持っていないと、コードを読んでいても、今ひとつ腑に落ちなかった私です。
コードでの例として
Buttonコンポーネントがcolor
とsize
という2つのプロパティを持っているとします。Storybookを使用して、これらのプロパティの異なる組み合わせを視覚的に確認したいと思います。
// Buttonコンポーネントの定義 const Button = ({ color, size }) => <button style={{ color, fontSize: size }}>Click me</button>; // Template関数の定義 const Template = (args) => <Button {...args} />; // Storybookのストーリー export const LargeBlueButton = Template.bind({}); LargeBlueButton.args = { color: 'blue', size: '20px' }; export const SmallRedButton = Template.bind({}); SmallRedButton.args = { color: 'red', size: '10px' };
LargeBlueButton
とSmallRedButton
という2つのストーリーを作成しています。
Template.bind({})は何をしているのか?
Template.bind({})
はJavaScriptのbind
メソッドを使用しています。bind
メソッドは、関数に特定のthis
値と前置引数を設定した新しい関数を作成します。
Storybookのコンテキストでは、Template.bind({})
は新しい関数を作成しますが、this
値は空のオブジェクト({}
)に設定されます。これは、Storybookが各ストーリーに対して新しい関数インスタンスを作成することを保証します。これにより、各ストーリーが独立して動作し、他のストーリーに影響を与えることなくargs
を変更できます。
const Template = (args) => <Button {...args} />; export const LargeBlueButton = Template.bind({}); LargeBlueButton.args = { color: 'blue', size: '20px' };
Template.bind({})
を呼び出すと、Template
関数の新しいインスタンスが作成されます。- この新しい関数は、
Template
関数と全く同じ動作をしますが、this
値が空のオブジェクト({}
)に設定されています。 - この新しい関数に
args
プロパティを設定すると、それらのargs
は新しい関数の呼び出しに使用されます。
独立したTemplateを生成して、argsの内容も変えられるようにするためにあるようです。