From cfd7bdeeb44183d8162aee6e5071c886de930934 Mon Sep 17 00:00:00 2001 From: UHAsikakutou <91722200+UHAsikakutou@users.noreply.github.com> Date: Thu, 16 May 2024 19:33:00 +0900 Subject: [PATCH 01/14] Update get-started.md --- .../react/ja/get-started.md | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/content/intro-to-storybook/react/ja/get-started.md b/content/intro-to-storybook/react/ja/get-started.md index c84ecd3e4..60cd0fa5e 100644 --- a/content/intro-to-storybook/react/ja/get-started.md +++ b/content/intro-to-storybook/react/ja/get-started.md @@ -16,12 +16,12 @@ Storybook を開発プロセスに組み込むにあたり、いくつかの手 それでは、次のコマンドを実行してください: ```shell:clipboard=false -# Clone the template +# テンプレートをクローンする npx degit chromaui/intro-storybook-react-template taskbox cd taskbox -# Install dependencies +# 依存関係をインストールする yarn ``` @@ -32,23 +32,16 @@ yarn それでは、アプリケーションのさまざまな環境が問題なく動くことを次のコマンドで確認しましょう: ```shell:clipboard=false -# Run the test runner (Jest) in a terminal: -yarn test --watchAll - -# Start the component explorer on port 6006: +# コンポーネントエクスプローラを6006番ポートで起動する yarn storybook -# Run the frontend app proper on port 3000: -yarn start +# フロントエンドアプリケーションを5173番ポートで起動する +yarn dev ``` -
-テストのコマンドに --watchAll フラグを付けているのに気づいたでしょうか。これは間違いではありません。このフラグを付けることにより、すべてのテストが実行され、アプリケーションに問題ないことを確実にできます。チュートリアルを進めると、別のテストシナリオも出てきます。必要ならばこのフラグを package.json のテストコマンドに追加することで、テストスイートのすべてのテストが実行されるようになります。 -
- -フロントエンド開発の 3 つのモード: 自動化されたテスト (Jest)、コンポーネント開発 (Storybook)、アプリケーション自体 +フロントエンド開発の 2 つのモード: コンポーネント開発 (Storybook)、アプリケーション自体 -![3 つのモード](/intro-to-storybook/app-three-modalities.png) +![2つのモード](/intro-to-storybook/app-main-modalities-react.png) 作業をする対象に応じて、このモードのうち 1 つまたは複数を同時に動かしながら作業します。今は単一の UI コンポーネントを作るのに集中するため、Storybook を動かすことにしましょう。 @@ -75,7 +68,7 @@ git commit -m "first commit" 最後に: ```shell -$ git branch -M main +git branch -M main ``` それでは最初のコンポーネントを作り始めましょう! From ecd22c2f8742771a14bdd3252cf549470c8b61bf Mon Sep 17 00:00:00 2001 From: UHAsikakutou <91722200+UHAsikakutou@users.noreply.github.com> Date: Thu, 16 May 2024 20:07:38 +0900 Subject: [PATCH 02/14] Update simple-component.md --- .../react/ja/simple-component.md | 214 +++++++++++------- 1 file changed, 126 insertions(+), 88 deletions(-) diff --git a/content/intro-to-storybook/react/ja/simple-component.md b/content/intro-to-storybook/react/ja/simple-component.md index 45888586e..ff98d65c5 100644 --- a/content/intro-to-storybook/react/ja/simple-component.md +++ b/content/intro-to-storybook/react/ja/simple-component.md @@ -5,7 +5,7 @@ description: '単純なコンポーネントを切り離して作りましょう commit: '9b36e1a' --- -それでは、[コンポーネント駆動開発](https://www.componentdriven.org/) (CDD) の手法にのっとって UI を作ってみましょう。コンポーネント駆動開発とは、UI を最初にコンポーネントから作り始めて、最後に画面を作り上げる「ボトムアップ」の開発プロセスです。CDD を用いれば UI を作る際に直面する複雑性を軽減することができます。 +それでは、[コンポーネント駆動開発](https://www.componentdriven.org/) (CDD) の手法にのっとって UI を作ってみましょう。コンポーネント駆動開発とは、UI を最初にコンポーネントから作り始めて、最後に画面を作り上げる「ボトムアップ」の開発プロセスです。CDD を用いれば、 UI を作る際に直面する複雑性を軽減できます。 ## Task (タスク) @@ -20,17 +20,19 @@ commit: '9b36e1a' ## セットアップする -まずは、タスクのコンポーネントと、対応するストーリーファイル `src/components/Task.js` と `src/components/Task.stories.js` を作成しましょう。 +まずは、タスクのコンポーネントと、対応するストーリーファイル `src/components/Task.jsx` と `src/components/Task.stories.jsx` を作成しましょう。 `Task` の基本的な実装から始めます。`Task` は上述したプロパティと、タスクに対して実行できる 2 つの (リスト間を移動させる) アクションを引数として取ります: -```js:title=src/components/Task.js +```js:title=src/components/Task.jsx import React from 'react'; export default function Task({ task: { id, title, state }, onArchiveTask, onPinTask }) { return (
- +
); } @@ -40,46 +42,45 @@ export default function Task({ task: { id, title, state }, onArchiveTask, onPinT 下のコードは `Task` に対する 3 つのテスト用の状態をストーリーファイルに書いています: -```js:title=src/components/Task.stories.js -import React from 'react'; - +```js:title=src/components/Task.stories.jsx import Task from './Task'; export default { component: Task, title: 'Task', + tags: ['autodocs'], }; -const Template = args => ; - -export const Default = Template.bind({}); -Default.args = { - task: { - id: '1', - title: 'Test Task', - state: 'TASK_INBOX', - updatedAt: new Date(2021, 0, 1, 9, 0), +export const Default = { + args: { + task: { + id: '1', + title: 'Test Task', + state: 'TASK_INBOX', + }, }, }; -export const Pinned = Template.bind({}); -Pinned.args = { - task: { - ...Default.args.task, - state: 'TASK_PINNED', +export const Pinned = { + args: { + task: { + ...Default.args.task, + state: 'TASK_PINNED', + }, }, }; -export const Archived = Template.bind({}); -Archived.args = { - task: { - ...Default.args.task, - state: 'TASK_ARCHIVED', +export const Archived = { + args: { + task: { + ...Default.args.task, + state: 'TASK_ARCHIVED', + }, }, }; ``` -Storybook には基本となる 2 つの階層があります。コンポーネントとその子供となるストーリーです。各ストーリーはコンポーネントに連なるものだと考えてください。コンポーネントには必要なだけストーリーを記述することができます。 +Storybook には基本となる 2 つの階層があります。コンポーネントとその子供となるストーリーです。各ストーリーはコンポーネントに連なるものだと考えてください。コンポーネントには必要なだけストーリーを記述できます。 - **コンポーネント** - ストーリー @@ -89,52 +90,41 @@ Storybook には基本となる 2 つの階層があります。コンポーネ Storybook にコンポーネントを認識させるには、以下の内容を含む `default export` を記述します: - `component` -- コンポーネント自体 -- `title` -- Storybook のサイドバーにあるコンポーネントを参照する方法 - -ストーリーを定義するには、テスト用の状態ごとにストーリーを生成する関数をエクスポートします。ストーリーとは、特定の状態で描画された要素 (例えば、プロパティを指定したコンポーネントなど) を返す関数で、React の[状態を持たない関数コンポーネント](https://reactjs.org/docs/components-and-props.html#function-and-class-components)のようなものです。 +- `title` -- Storybookのサイドバーでコンポーネントをグループ化または分類するためのタイトル +- `tags` -- このコンポーネントのドキュメントを自動生成するためのタグ -コンポーネントにストーリーが複数連なっているので、各ストーリーを単一の `Template` 変数に割り当てるのが便利です。このパターンを導入することで、書くべきコードの量が減り、保守性も上がります。 - -
-💡 Template.bind({}) は関数のコピーを作成する JavaScript の標準的な テクニックで、同じ実装を使いながら、エクスポートされたそれぞれのストーリーに独自のプロパティを設定することができます。 -
+ストーリーを定義するには、コンポーネント ストーリー フォーマット 3 ( [CSF3](https://storybook.js.org/docs/react/api/csf) )を使用してテストケースを構築します。このフォーマットは、各テストケースを簡潔に構築するために設計されています。各コンポーネントの状態を含むオブジェクトをエクスポートすることで、テストをより直感的に定義し、ストーリーをより効率的に作成・再利用できます。 Arguments (略して [`args`](https://storybook.js.org/docs/react/writing-stories/args)) を使用することで、コントロールアドオンを通して、Storybook を再起動することなく、コンポーネントを動的に編集することができるようになります。[`args`](https://storybook.js.org/docs/react/writing-stories/args) の値が変わるとコンポーネントもそれに合わせて変わります。 ストーリーを作る際には素となるタスク引数を使用してコンポーネントが想定するタスクの状態を作成します。想定されるデータは実際のデータと同じように作ります。さらに、このデータをエクスポートすることで、今後作成するストーリーで再利用することが可能となります。 -
-💡 アクションアドオンは切り離された環境で UI コンポーネントを開発する際の動作確認に役立ちます。アプリケーションの実行中には状態や関数を参照出来ないことがよくあります。action() はそのスタブとして使用できます。 -
- ## 設定する -作成したストーリーを認識させ、[前の章](/intro-to-storybook/react/ja/get-started)で変更した CSS ファイルを使用できるようにするため、Storybook の設定をいくつか変更する必要があります。 +作成したストーリーを認識させたり、CSS ファイル (`src/index.css`にあります) をStorybook上で使用できるようにするため、Storybook の設定をいくつか変更する必要があります。 まず、設定ファイル (`.storybook/main.js`) を以下のように変更してください: ```diff:title=.storybook/main.js -module.exports = { -- stories: [ -- '../src/**/*.stories.mdx', -- '../src/**/*.stories.@(js|jsx|ts|tsx)' -- ], -+ stories: ['../src/components/**/*.stories.js'], +/** @type { import('@storybook/react-vite').StorybookConfig } */ +const config = { +- stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], ++ stories: ['../src/components/**/*.stories.@(js|jsx)'], staticDirs: ['../public'], addons: [ '@storybook/addon-links', '@storybook/addon-essentials', - '@storybook/preset-create-react-app', '@storybook/addon-interactions', ], - features: { - postcss: false, + framework: { + name: '@storybook/react-vite', + options: {}, }, - framework: '@storybook/react', - core: { - builder: 'webpack4', + docs: { + autodocs: 'tag', }, }; +export default config; ``` 上記の変更が完了したら、`.storybook` フォルダー内の `preview.js` を、以下のように変更してください: @@ -142,19 +132,24 @@ module.exports = { ```diff:title=.storybook/preview.js + import '../src/index.css'; -//👇 Configures Storybook to log the actions( onArchiveTask and onPinTask ) in the UI. -export const parameters = { - actions: { argTypesRegex: '^on[A-Z].*' }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/, +//👇 Storybookのアクション(onArchiveTaskとonPinTask)をUIに記録するように設定します。 +/** @type { import('@storybook/react').Preview } */ +const preview = { + parameters: { + actions: { argTypesRegex: "^on[A-Z].*" }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, }, }, }; + +export default preview; ``` -[`parameters`](https://storybook.js.org/docs/react/writing-stories/parameters) は Storybook の機能やアドオンの振る舞いをコントロールするのに使用します。この例では、アクション (呼び出しのモック) がどのように扱われるかを設定しています。 +[`parameters`](https://storybook.js.org/docs/react/writing-stories/parameters) は Storybook の機能やアドオンの振る舞いをコントロールするのに使用します。この例では、アクション (`actions`、呼び出しのモック) がどのように扱われるかを設定しています。 アクションアドオンを使用することで、クリックした時などに Storybook の **actions** パネルにその情報を表示するコールバックを作成できます。これにより、ピン留めボタンを作成するとき、ボタンがクリックされたことがテスト用の UI 上で確認できます。 @@ -162,7 +157,7 @@ Storybook のサーバーを再起動すると、タスクの 3 つの状態の @@ -173,7 +168,7 @@ Storybook のサーバーを再起動すると、タスクの 3 つの状態の 今のところコンポーネントは簡素な状態です。まずはデザインを実現するために最低限必要なコードを書いてみましょう: -```js:title=src/components/Task.js +```jsx:title=src/components/Task.jsx import React from 'react'; export default function Task({ task: { id, title, state }, onArchiveTask, onPinTask }) { @@ -227,7 +222,7 @@ export default function Task({ task: { id, title, state }, onArchiveTask, onPinT @@ -236,27 +231,69 @@ export default function Task({ task: { id, title, state }, onArchiveTask, onPinT `propTypes` を使い React にコンポーネントが想定するデータ構造を示すのがベストプラクティスです。これにより想定するデータ構造がコードからわかるだけでなく、早期に問題を見つけるのに役立ちます。 -```diff:title=src/components/Task.js +```diff:title=src/components/Task.jsx import React from 'react'; + import PropTypes from 'prop-types'; export default function Task({ task: { id, title, state }, onArchiveTask, onPinTask }) { - // ... + return ( +
+ + + + + {state !== "TASK_ARCHIVED" && ( + + )} +
+ ); } + Task.propTypes = { -+ /** Composition of the task */ ++ /** タスクの構成 */ + task: PropTypes.shape({ -+ /** Id of the task */ ++ /** タスクのID */ + id: PropTypes.string.isRequired, -+ /** Title of the task */ ++ /** タスクのタイトル */ + title: PropTypes.string.isRequired, -+ /** Current state of the task */ ++ /** タスクの現在の状態 */ + state: PropTypes.string.isRequired, + }), -+ /** Event to change the task to archived */ ++ /** タスクの状態を「アーカイブ済」に変更するイベント */ + onArchiveTask: PropTypes.func, -+ /** Event to change the task to pinned */ ++ /** タスクのをの状態を「ピン留め済」に変更するイベント */ + onPinTask: PropTypes.func, + }; ``` @@ -269,48 +306,49 @@ export default function Task({ task: { id, title, state }, onArchiveTask, onPinT ## 完成! -これでサーバーを起動したり、フロントエンドアプリケーションを起動したりすることなく、コンポーネントを作りあげることができました。次の章では、Taskbox の残りのコンポーネントを、同じように少しずつ作成していきます。 +これでサーバーを起動したり、フロントエンドアプリケーションを起動したりすることなく、コンポーネントを作りあげることができました。次の章では、Taskbox の残りのコンポーネントを、同じように少しずつ作成します。 -見た通り、コンポーネントだけを切り離して作り始めるのは早くて簡単です。あらゆる状態を掘り下げてテストできるので、高品質で、バグが少なく、洗練された UI を作ることができることでしょう。 +見た通り、コンポーネントを切り離して開発を始めるのは、迅速かつ簡単です。あらゆる状態を掘り下げてテストできるので、高品質で、バグが少なく、洗練された UI を作ることができることでしょう。 ## アクセシビリティの問題の検知 -アクセシビリティテストとは、[WCAG](https://www.w3.org/WAI/standards-guidelines/wcag/) のルールと他の業界で認めれたベストプラクティスに基づく経験則に対して、自動化ツールを用いることでレンダリングされた DOM を監視することを指します。これは視覚障害、聴覚障害、認知障害などの障害をお持ちの方を含む、できるだけ多くのユーザーがアプリケーションを利用できるように、明らかなアクセシビリティの違反を検知するために QA の第一線として機能します。 +アクセシビリティテストとは、[WCAG](https://www.w3.org/WAI/standards-guidelines/wcag/) のルールと他の業界で認められたベストプラクティスに基づく経験則に対して、自動化ツールを用いることでレンダリングされた DOM を監視することを指します。これは視覚障害、聴覚障害、認知障害などの障害をお持ちの方を含む、できるだけ多くのユーザーがアプリケーションを利用できるように、明らかなアクセシビリティの違反を検知するために QA の第一線として機能します。 -Storybook には公式の[アクセシビリティアドオン](https://storybook.js.org/addons/@storybook/addon-a11y)があります。これは、Deque の [axe-core](https://github.com/dequelabs/axe-core) を使っており、[WCAG の問題の 57%](https://www.deque.com/blog/automated-testing-study-identifies-57-percent-of-digital-accessibility-issues/) に対応しています。 +Storybook には公式の[アクセシビリティアドオン](https://storybook.js.org/addons/@storybook/addon-a11y)があります。このアドオンは、Deque の [axe-core](https://github.com/dequelabs/axe-core) がベースで、[WCAG の問題の 57%](https://www.deque.com/blog/automated-testing-study-identifies-57-percent-of-digital-accessibility-issues/) に対応しています。 -それでは、どのように動かすのかみてみましょう! 以下のコマンドでアドオンをインストールします: +それでは、どのように動かすのか見てみましょう! 以下のコマンドでアドオンをインストールします。 ```shell yarn add --dev @storybook/addon-a11y ``` -アドオンを利用可能にするために、Storybook の設定ファイル(`.storybook/main.js`)を以下のように設定します: +アドオンを利用可能にするために、Storybook の設定ファイル(`.storybook/main.js`)を以下のように設定します。 ```diff:title=.storybook/main.js -module.exports = { - stories: ['../src/components/**/*.stories.js'], +/** @type { import('@storybook/react-vite').StorybookConfig } */ +const config = { + stories: ['../src/components/**/*.stories.@(js|jsx)'], staticDirs: ['../public'], addons: [ '@storybook/addon-links', '@storybook/addon-essentials', - '@storybook/preset-create-react-app', '@storybook/addon-interactions', + '@storybook/addon-a11y', ], - framework: '@storybook/react', - core: { - builder: '@storybook/builder-webpack5', + framework: { + name: '@storybook/react-vite', + options: {}, }, - features: { - interactionsDebugger: true, + docs: { + autodocs: 'tag', }, }; +export default config; ``` -![Task accessibility issue in Storybook](/intro-to-storybook/finished-task-states-accessibility-issue.png) +![タスクのアクセシビリティの問題をStorybookで表示する](/intro-to-storybook/finished-task-states-accessibility-issue-7-0.png) -ストーリーを一通り見てみると、このアドオンが一つのアクセシビリティの問題を検知したことがわかります。[**「Elements must have sufficient color contrast」**](https://dequeuniversity.com/rules/axe/4.4/color-contrast?application=axeAPI)というエラーメッセージは本質的にタイトルと背景に十分なコントラストがないことを指しています。そのため、アプリケーションの CSS (`src/index.css` にある)の中で、テキストカラーをより暗いグレーに修正する必要があります。 +ストーリーをひととおり見てみると、このアドオンがひとつのアクセシビリティの問題を検知したことがわかります。[**"Elements must have sufficient color contrast"**](https://dequeuniversity.com/rules/axe/4.4/color-contrast?application=axeAPI) というエラーメッセージは、タイトルと背景に十分なコントラストがないことを指しています。そのため、アプリケーションの CSS (`src/index.css`)を編集して、テキストのカラーをより暗いグレーに修正する必要があります。 ```diff:title=src/index.css .list-item.TASK_ARCHIVED input[type="text"] { @@ -320,7 +358,7 @@ module.exports = { } ``` -以上です!これで、UI のアクセシビリティ向上の最初のステップが完了です。アプリケーションをさらに複雑にしても、他の全てのコンポーネントに対してこのプロセスを繰り返すことができ、追加のツールやテスト環境を準備する必要はありません。 +以上です!これで、UI のアクセシビリティ向上の最初のステップが完了です。アプリケーションをさらに複雑にしても、他のすべてのコンポーネントに対してこのプロセスを繰り返すことができ、追加のツールやテスト環境を準備する必要はありません。
💡 Git へのコミットを忘れずに行ってください! From 734b40152392d27018c4fafacffaa8aa410b5b19 Mon Sep 17 00:00:00 2001 From: UHAsikakutou <91722200+UHAsikakutou@users.noreply.github.com> Date: Thu, 16 May 2024 20:19:01 +0900 Subject: [PATCH 03/14] Update composite-component.md --- .../react/ja/composite-component.md | 156 ++++++++++++------ 1 file changed, 101 insertions(+), 55 deletions(-) diff --git a/content/intro-to-storybook/react/ja/composite-component.md b/content/intro-to-storybook/react/ja/composite-component.md index 127172d7b..68b8952f2 100644 --- a/content/intro-to-storybook/react/ja/composite-component.md +++ b/content/intro-to-storybook/react/ja/composite-component.md @@ -9,21 +9,21 @@ commit: '429780a' ## TaskList (タスクリスト) -Taskbox はピン留めされたタスクを通常のタスクより上部に表示することで強調します。これにより `TaskList` に、タスクのリストが、通常のタスクのみである場合と、ピン留めされたタスクとの組み合わせである場合という、ストーリーを追加するべき 2 つのバリエーションができます。 +Taskbox はピン留めされたタスクを通常のタスクより上部に表示することで強調します。これにより `TaskList` に、タスクのリストが通常のタスクのみである場合とピン留めされたタスクとの組み合わせである場合という、ストーリーを追加するべき 2 つのバリエーションができます。 ![通常のタスクとピン留めされたタスク](/intro-to-storybook/tasklist-states-1.png) -`Task` のデータは非同期的に送信されるので、接続がないことを示すため、読み込み中の状態**も**必要となります。さらにタスクがない場合に備え、空の状態も必要です。 +`Task` のデータは非同期的に送信されるので、接続がないことを示すため、読み込み中の状態**も併せて**必要となります。さらにタスクがない場合に備え、空の状態も必要です。 ![空の状態と読み込み中の状態](/intro-to-storybook/tasklist-states-2.png) ## セットアップする -複合的なコンポーネントも基本的なコンポーネントと大きな違いはありません。`TaskList` のコンポーネントとそのストーリーファイル、`src/components/TaskList.js` と `src/components/TaskList.stories.js` を作成しましょう。 +複合的なコンポーネントも基本的なコンポーネントと大きな違いはありません。`TaskList` のコンポーネントとそのストーリーファイル、`src/components/TaskList.jsx` と `src/components/TaskList.stories.jsx` を作成しましょう。 まずは `TaskList` の大まかな実装から始めます。前の章で作成した `Task` コンポーネントをインポートし、属性とアクションを入力として渡します。 -```js:title=src/components/TaskList.js +```jsx:title=src/components/TaskList.jsx import React from 'react'; import Task from './Task'; @@ -54,61 +54,61 @@ export default function TaskList({ loading, tasks, onPinTask, onArchiveTask }) { 次に `Tasklist` のテスト状態をストーリーファイルに記述します。 -```js:title=src/components/TaskList.stories.js -import React from 'react'; - +```jsx:title=src/components/TaskList.stories.jsx import TaskList from './TaskList'; + import * as TaskStories from './Task.stories'; export default { component: TaskList, title: 'TaskList', - decorators: [story =>
{story()}
], + decorators: [(story) =>
{story()}
], + tags: ['autodocs'], }; -const Template = args => ; - -export const Default = Template.bind({}); -Default.args = { - // Shaping the stories through args composition. - // The data was inherited from the Default story in Task.stories.js. - tasks: [ - { ...TaskStories.Default.args.task, id: '1', title: 'Task 1' }, - { ...TaskStories.Default.args.task, id: '2', title: 'Task 2' }, - { ...TaskStories.Default.args.task, id: '3', title: 'Task 3' }, - { ...TaskStories.Default.args.task, id: '4', title: 'Task 4' }, - { ...TaskStories.Default.args.task, id: '5', title: 'Task 5' }, - { ...TaskStories.Default.args.task, id: '6', title: 'Task 6' }, - ], +export const Default = { + args: { + // argsによってストーリーを形成します。 + // データはTask.stories.jsxのDefaultストーリーから継承しています。 + tasks: [ + { ...TaskStories.Default.args.task, id: '1', title: 'Task 1' }, + { ...TaskStories.Default.args.task, id: '2', title: 'Task 2' }, + { ...TaskStories.Default.args.task, id: '3', title: 'Task 3' }, + { ...TaskStories.Default.args.task, id: '4', title: 'Task 4' }, + { ...TaskStories.Default.args.task, id: '5', title: 'Task 5' }, + { ...TaskStories.Default.args.task, id: '6', title: 'Task 6' }, + ], + }, }; -export const WithPinnedTasks = Template.bind({}); -WithPinnedTasks.args = { - // Shaping the stories through args composition. - // Inherited data coming from the Default story. - tasks: [ - ...Default.args.tasks.slice(0, 5), - { id: '6', title: 'Task 6 (pinned)', state: 'TASK_PINNED' }, - ], +export const WithPinnedTasks = { + args: { + tasks: [ + ...Default.args.tasks.slice(0, 5), + { id: '6', title: 'Task 6 (pinned)', state: 'TASK_PINNED' }, + ], + }, }; -export const Loading = Template.bind({}); -Loading.args = { - tasks: [], - loading: true, +export const Loading = { + args: { + tasks: [], + loading: true, + }, }; -export const Empty = Template.bind({}); -Empty.args = { - // Shaping the stories through args composition. - // Inherited data coming from the Loading story. - ...Loading.args, - loading: false, +export const Empty = { + args: { + // argsによってストーリーを形成します。 + // データは上のLoadingストーリーから継承しています。 + ...Loading.args, + loading: false, + }, }; ```
-💡 デコレーターを使ってストーリーに任意のラッパーを設定できます。上記のコードでは、decorators というキーをデフォルトエクスポートに追加し、描画するコンポーネントの周りに padding を設定してます。ストーリーで使用する「プロバイダー」(例えば、React のコンテキストを設定するライブラリコンポーネントなど) を使うためにも使用します。 +💡 デコレーターを使ってストーリーに任意のラッパーを設定できます。上記のコードでは、decorators というキーをデフォルトエクスポートに追加し、描画するコンポーネントの周りに padding を設定しています。ストーリーで使用する「プロバイダー」(例えば、React のコンテキストを設定するライブラリコンポーネントなど) を使うためにも使用します。
`TaskStories` をインポートすることで、ストーリーに必要な引数 (args) を最小限の労力で[組み合わせる](https://storybook.js.org/docs/react/writing-stories/args#args-composition)ことができます。そうすることで、2 つのコンポーネントが想定するデータとアクション (呼び出しのモック) の一貫性が保たれます。 @@ -117,16 +117,16 @@ Empty.args = { ## 状態を作りこむ -今のコンポーネントはまだ粗削りですが、ストーリーは見えています。単に `.list-items` だけのためにラッパーを作るのは単純すぎると思うかもしれません。実際にその通りです。ほとんどの場合単なるラッパーのためだけに新しいコンポーネントは作りません。`TaskList` の**本当の複雑さ**は `withPinnedTasks`、`loading`、`empty` といったエッジケースに現れているのです。 +今のコンポーネントはまだ粗削りですが、ストーリーは見えています。単に `.list-items` だけのためにラッパーを作るのは単純すぎると思うかもしれません。実際、その通りです。ほとんどの場合、単なるラッパーのためだけに新しいコンポーネントは作りません。`TaskList` の**本当の複雑さ**は `withPinnedTasks`、`loading`、`empty` といったエッジケース(ユーザーが遭遇する可能性のあるまれなバグ)に現れているのです。 -```js:title=src/components/TaskList.js +```jsx:title=src/components/TaskList.jsx import React from 'react'; import Task from './Task'; @@ -161,16 +161,16 @@ export default function TaskList({ loading, tasks, onPinTask, onArchiveTask }) {
-
You have no tasks
-
Sit back and relax
+

You have no tasks

+

Sit back and relax

); } const tasksInOrder = [ - ...tasks.filter((t) => t.state === "TASK_PINNED"), - ...tasks.filter((t) => t.state !== "TASK_PINNED"), + ...tasks.filter((t) => t.state === 'TASK_PINNED'), + ...tasks.filter((t) => t.state !== 'TASK_PINNED'), ]; return (
@@ -186,7 +186,7 @@ export default function TaskList({ loading, tasks, onPinTask, onArchiveTask }) { @@ -197,24 +197,70 @@ export default function TaskList({ loading, tasks, onPinTask, onArchiveTask }) { コンポーネントが大きくなるにつれ、入力の要件も増えていきます。`TaskList` のプロパティの要件を定義しましょう。`Task` が子供のコンポーネントなので、`Task` を表示するのに正しいデータ構造が渡されていることを確認しましょう。時間を節約するため、前の章で `Task` に定義した `propTypes` を再利用しましょう。 -```diff:title=src/components/TaskList.js +```diff:title=src/components/TaskList.jsx import React from 'react'; + import PropTypes from 'prop-types'; import Task from './Task'; export default function TaskList({ loading, tasks, onPinTask, onArchiveTask }) { - ... + const events = { + onPinTask, + onArchiveTask, + }; + const LoadingRow = ( +
+ + + Loading cool state + +
+ ); + if (loading) { + return ( +
+ {LoadingRow} + {LoadingRow} + {LoadingRow} + {LoadingRow} + {LoadingRow} + {LoadingRow} +
+ ); + } + if (tasks.length === 0) { + return ( +
+
+ +

You have no tasks

+

Sit back and relax

+
+
+ ); + } + + const tasksInOrder = [ + ...tasks.filter((t) => t.state === 'TASK_PINNED'), + ...tasks.filter((t) => t.state !== 'TASK_PINNED'), + ]; + return ( +
+ {tasksInOrder.map((task) => ( + + ))} +
+ ); } + TaskList.propTypes = { -+ /** Checks if it's in loading state */ ++ /** ローディング状態かどうかをチェックする */ + loading: PropTypes.bool, -+ /** The list of tasks */ ++ /** タスクの配列 */ + tasks: PropTypes.arrayOf(Task.propTypes.task).isRequired, -+ /** Event to change the task to pinned */ ++ /** タスクのをの状態を「ピン留め済」に変更するイベント */ + onPinTask: PropTypes.func, -+ /** Event to change the task to archived */ ++ /** タスクの状態を「アーカイブ済」に変更するイベント */ + onArchiveTask: PropTypes.func, + }; + TaskList.defaultProps = { From 2f9468d93005f28063796023ddc6add1430cab44 Mon Sep 17 00:00:00 2001 From: UHAsikakutou <91722200+UHAsikakutou@users.noreply.github.com> Date: Thu, 16 May 2024 20:34:19 +0900 Subject: [PATCH 04/14] Update data.md --- content/intro-to-storybook/react/ja/data.md | 151 ++++++++++---------- 1 file changed, 76 insertions(+), 75 deletions(-) diff --git a/content/intro-to-storybook/react/ja/data.md b/content/intro-to-storybook/react/ja/data.md index f48bb0e9d..fd3289a70 100644 --- a/content/intro-to-storybook/react/ja/data.md +++ b/content/intro-to-storybook/react/ja/data.md @@ -13,7 +13,7 @@ commit: 'c70ec15' `TaskList` コンポーネントは、今のところ「presentational (表示用)」として書かれており、その実装以外の外部とは何もやりとりをしません。データを中に入れるためにはデータプロバイダに繋ぐ必要があります。 -ここではデータを保存する際に使用される React で人気のライブラリーである [Redux](https://redux.js.org/) を使用し、アプリケーションにシンプルなデータモデルを作ります。[Apollo](https://www.apollographql.com/client/) や [MobX](https://mobx.js.org/) といった他のデータ管理用のライブラリーでもここでのパターンが使用できます。 +ここでは、[Redux](https://redux.js.org/) でデータを保存するためにもっとも効果的な開発用ツールセットである [Redux Toolkit](https://redux-toolkit.js.org/)を使用し、アプリケーションにシンプルなデータモデルを作ります。[Apollo](https://www.apollographql.com/client/) や [MobX](https://mobx.js.org/) といった他のデータ管理用のライブラリーでもここでのパターンが使用できます。 以下のコマンドを実行し必要な依存関係を追加しましょう: @@ -24,14 +24,14 @@ yarn add @reduxjs/toolkit react-redux まず、タスクの状態を変更するアクションを処理する単純な Redux のストアを作ります。`src/lib` フォルダの `store.js` というファイルを作ってください (あえて簡単にしています): ```js:title=src/lib/store.js -/* A simple redux store/actions/reducer implementation. - * A true app would be more complex and separated into different files. +/* シンプルなreduxのストア/アクション/リデューサーの実装です。 + * 本当のアプリケーションはもっと複雑で、異なるファイルに分けられます。 */ import { configureStore, createSlice } from '@reduxjs/toolkit'; /* - * The initial state of our store when the app loads. - * Usually, you would fetch this from a server. Let's not worry about that now + * アプリケーションのロード時のストアの初期状態です。 + * 通常、サーバーから取得しますが、今回は気にしないでください(ファイルに直書きしています)。 */ const defaultTasks = [ { id: '1', title: 'Something', state: 'TASK_INBOX' }, @@ -46,8 +46,8 @@ const TaskBoxData = { }; /* - * The store is created here. - * You can read more about Redux Toolkit's slices in the docs: + * ストアはここで作成されます。 + * Redux Toolkitのスライスについて詳しくはドキュメントを参照してください: * https://redux-toolkit.js.org/api/createSlice */ const TasksSlice = createSlice({ @@ -64,12 +64,12 @@ const TasksSlice = createSlice({ }, }); -// The actions contained in the slice are exported for usage in our components +// スライスに含まれるアクションはコンポーネントで使用するためにエクスポートされます export const { updateTaskState } = TasksSlice.actions; /* - * Our app's store configuration goes here. - * Read more about Redux's configureStore in the docs: + * アプリケーションのストアの設定はここにあります。 + * ReduxのconfigureStoreについて詳しくはドキュメントを参照してください: * https://redux-toolkit.js.org/api/configureStore */ const store = configureStore({ @@ -81,16 +81,16 @@ const store = configureStore({ export default store; ``` -次に、`TaskList` コンポーネントのデフォルトエクスポートを更新し、Redux のストアに 「connect (接続)」し、ストアから、気になるタスクのリストを描画します。 +次に、`TaskList` コンポーネントのデフォルトエクスポートを更新し、Redux のストアに「connect (接続)」し、ストアから気になるタスクのリストを描画します。 -```js:title=src/components/TaskList.js +```jsx:title=src/components/TaskList.jsx import React from 'react'; import Task from './Task'; import { useDispatch, useSelector } from 'react-redux'; import { updateTaskState } from '../lib/store'; export default function TaskList() { - // We're retrieving our state from the store + // ストアから状態を取得します const tasks = useSelector((state) => { const tasksInOrder = [ ...state.taskbox.tasks.filter((t) => t.state === 'TASK_PINNED'), @@ -107,11 +107,11 @@ export default function TaskList() { const dispatch = useDispatch(); const pinTask = (value) => { - // We're dispatching the Pinned event back to our store + // ストアにピン留めされたタスクを送信します dispatch(updateTaskState({ id: value, newTaskState: 'TASK_PINNED' })); }; const archiveTask = (value) => { - // We're dispatching the Archive event back to our store + // ストアにアーカイブされたタスクを送信します dispatch(updateTaskState({ id: value, newTaskState: 'TASK_ARCHIVED' })); }; const LoadingRow = ( @@ -139,8 +139,8 @@ export default function TaskList() {
-
You have no tasks
-
Sit back and relax
+

You have no tasks

+

Sit back and relax

); @@ -161,21 +161,19 @@ export default function TaskList() { } ``` -これで、Redux からデータを取得し、実際のデータでコンポ―ネントを生成できるようになりました。`src/app.js` に接続してコンポーネントを描画することも可能ですが、今のところはこのままにして、コンポーネント駆動の旅を続けましょう。 +これで、Redux からデータを取得し、実際のデータでコンポーネントを生成できるようになりました。`src/app.js` に接続してコンポーネントを描画することも可能ですが、今のところはこのままにして、コンポーネント駆動の旅を続けましょう。 -アプリケーションで表示する方法は次の章で説明しますのでご心配なく。 +アプリケーションで表示する方法は次の章で説明しますので心配ありません。 ## デコレーターにコンテキストを渡す この段階で、Storybook のテストが動かなくなりました。`TaskList` が繋がれたコンポーネントとなって、タスクを取得しアップデートするのに Redux ストアに依存しているからです。 -![壊れたタスクリスト](/intro-to-storybook/broken-tasklist-optimized.png) +![壊れたタスクリスト](/intro-to-storybook/broken-tasklist-7-0-optimized.png) -この問題を解決するために、さまざまなアプローチができます。しかし、私たちのアプリは非常に単純なので、[前の章](/intro-to-storybook/react/ja/composite-component)で行ったのと同様に、デコレーターに頼ることができ、Storybook の中でモックストアを利用できます: - -```js:title=src/components/TaskList.stories.js -import React from 'react'; +この問題を解決するために、さまざまなアプローチができます。しかし、このアプリは非常に単純なので、[前の章](/intro-to-storybook/react/ja/composite-component)で行ったのと同様にデコレーターに頼ることができ、Storybook の中でモックストアを利用できます。 +```jsx:title=src/components/TaskList.stories.jsx import TaskList from './TaskList'; import * as TaskStories from './Task.stories'; @@ -183,7 +181,7 @@ import { Provider } from 'react-redux'; import { configureStore, createSlice } from '@reduxjs/toolkit'; -// A super-simple mock of the state of the store +// 超シンプルなストアの状態のモック export const MockedState = { tasks: [ { ...TaskStories.Default.args.task, id: '1', title: 'Task 1' }, @@ -197,7 +195,7 @@ export const MockedState = { error: null, }; -// A super-simple mock of a redux store +// 超シンプルなreduxストアのモック const Mockstore = ({ taskboxState, children }) => ( ( export default { component: TaskList, title: 'TaskList', - decorators: [(story) =>
{story()}
], + decorators: [(story) =>
{story()}
], + tags: ['autodocs'], excludeStories: /.*MockedState$/, }; -const Template = () => ; - -export const Default = Template.bind({}); -Default.decorators = [ - (story) => {story()}, -]; +export const Default = { + decorators: [ + (story) => {story()}, + ], +}; -export const WithPinnedTasks = Template.bind({}); -WithPinnedTasks.decorators = [ - (story) => { - const pinnedtasks = [ - ...MockedState.tasks.slice(0, 5), - { id: '6', title: 'Task 6 (pinned)', state: 'TASK_PINNED' }, - ]; +export const WithPinnedTasks = { + decorators: [ + (story) => { + const pinnedtasks = [ + ...MockedState.tasks.slice(0, 5), + { id: '6', title: 'Task 6 (pinned)', state: 'TASK_PINNED' }, + ]; + + return ( + + {story()} + + ); + }, + ], +}; - return ( +export const Loading = { + decorators: [ + (story) => ( {story()} - ); - }, -]; - -export const Loading = Template.bind({}); -Loading.decorators = [ - (story) => ( - - {story()} - - ), -]; + ), + ], +}; -export const Empty = Template.bind({}); -Empty.decorators = [ - (story) => ( - - {story()} - - ), -]; +export const Empty = { + decorators: [ + (story) => ( + + {story()} + + ), + ], +}; ```
-💡 excludeStories は Storybook の設定のフィールドで、モックされた状態がストーリーとして扱われるのを防ぐためのものです。このフィールドについては Storybook documentation で詳しく説明されています。 +💡 excludeStories は Storybook の設定のフィールドで、モックされた状態がストーリーとして扱われるのを防ぐためのものです。このフィールドについては ドキュメント で詳しく説明されています。
-💡 この変更により、全てのテストはアップデートが必要になります。-u フラグをつけてテストを再実行し、アップデートしてください。 Git へのコミットを忘れずに行ってください! +💡 Git へのコミットを忘れずに行ってください!
成功です! Storybook が動作し、接続されたコンポーネントにデータを渡す方法を確認することができました。次の章では、ここで学んだことを画面に適用してみましょう。 From ec0ce21ebeaad3a732197907b81da7b930a3eabf Mon Sep 17 00:00:00 2001 From: UHAsikakutou <91722200+UHAsikakutou@users.noreply.github.com> Date: Thu, 16 May 2024 20:54:47 +0900 Subject: [PATCH 05/14] Update screen.md --- content/intro-to-storybook/react/ja/screen.md | 265 ++++++++++-------- 1 file changed, 147 insertions(+), 118 deletions(-) diff --git a/content/intro-to-storybook/react/ja/screen.md b/content/intro-to-storybook/react/ja/screen.md index 67cd29a67..3d5bd371a 100644 --- a/content/intro-to-storybook/react/ja/screen.md +++ b/content/intro-to-storybook/react/ja/screen.md @@ -5,7 +5,7 @@ description: 'コンポーネントをまとめて画面を作りましょう' commit: '2275632' --- -今までボトムアップ (小さく始めてから複雑性を追加していく) で UI の作成に集中してきました。ボトムアップで作業することで、Storybook で遊びながら、それぞれのコンポーネントを切り離された環境で、それぞれに必要なデータを考えながら開発することができました。サーバーを立ち上げたり、画面を作ったりする必要は全くありませんでした! +今までボトムアップ (小規模な状態から複雑さを追加していく) で UI の作成に集中してきました。ボトムアップで作業することで、Storybook で遊びながら、それぞれのコンポーネントを切り離された環境で、それぞれに必要なデータを考えながら開発することができました。サーバーを立ち上げたり、画面を作ったりする必要はまったくありませんでした! この章では Storybook を使用して、コンポーネントを組み合わせて画面を作り、完成度を高めていきます。 @@ -13,11 +13,11 @@ commit: '2275632' このアプリケーションはとても単純なので、作る画面は些細なものです。リモート API からデータを取得し、(Redux から自分でデータを取得する) `TaskList` をラップして、Redux からの `error` フィールドを追加するだけです。 -まず、リモート API に接続して様々な状態 (すなわち、`error`、`succeeded`) をアプリケーションで扱えるようにするために、Redux ストア (`src/lib/store.js` 内) をアップデートするところから始めましょう: +まず、リモート API に接続してさまざまな状態 (すなわち、`error`、`succeeded`) をアプリケーションで扱えるようにするために、Redux ストア (`src/lib/store.js` 内) をアップデートするところから始めましょう。 ```diff:title=src/lib/store.js -/* A simple redux store/actions/reducer implementation. - * A true app would be more complex and separated into different files. +/* シンプルなreduxのストア/アクション/リデューサーの実装です。 + * 本当のアプリケーションはもっと複雑で、異なるファイルに分けられます。 */ import { configureStore, @@ -26,19 +26,19 @@ import { } from '@reduxjs/toolkit'; /* - * The initial state of our store when the app loads. - * Usually, you would fetch this from a server. Let's not worry about that now + * アプリケーションのロード時のストアの初期状態です。 + * 通常、サーバーから取得しますが、今回は気にしないでください(ファイルに直書きしています)。 */ const TaskBoxData = { tasks: [], - status: "idle", + status: 'idle', error: null, }; /* - * Creates an asyncThunk to fetch tasks from a remote endpoint. - * You can read more about Redux Toolkit's thunks in the docs: + * AsyncThunkを使ってリモートエンドポイントからタスクを取得します。 + * Redux Toolkitのthunkについて詳しくはドキュメントを参照してください: * https://redux-toolkit.js.org/api/createAsyncThunk */ + export const fetchTasks = createAsyncThunk('todos/fetchTodos', async () => { @@ -55,8 +55,8 @@ const TaskBoxData = { + }); /* - * The store is created here. - * You can read more about Redux Toolkit's slices in the docs: + * ストアはここで作成されます。 + * Redux Toolkitのスライスについて詳しくはドキュメントを参照してください: * https://redux-toolkit.js.org/api/createSlice */ const TasksSlice = createSlice({ @@ -72,8 +72,8 @@ const TasksSlice = createSlice({ }, }, /* - * Extends the reducer for the async actions - * You can read more about it at https://redux-toolkit.js.org/api/createAsyncThunk + * 非同期アクション用のリデューサを追加します。 + * 詳しくは https://redux-toolkit.js.org/api/createAsyncThunk を参照してください。 */ + extraReducers(builder) { + builder @@ -96,12 +96,12 @@ const TasksSlice = createSlice({ + }, }); -// The actions contained in the slice are exported for usage in our components +// スライスに含まれるアクションはコンポーネントで使用するためにエクスポートされます export const { updateTaskState } = TasksSlice.actions; /* - * Our app's store configuration goes here. - * Read more about Redux's configureStore in the docs: + * アプリケーションのストアの設定はここにあります。 + * ReduxのconfigureStoreについて詳しくはドキュメントを参照してください: * https://redux-toolkit.js.org/api/configureStore */ const store = configureStore({ @@ -113,12 +113,15 @@ const store = configureStore({ export default store; ``` -リモート API エンドポイントからデータを取得するようにストアを更新し、アプリのさまざまな状態を処理できるように準備したので、`InboxScreen.js` を `src/components` ディレクトリに作成しましょう: +リモート API エンドポイントからデータを取得するようにストアを更新し、アプリのさまざまな状態を処理できるように準備したので、`InboxScreen.jsx` を `src/components` ディレクトリに作成しましょう: -```js:title=src/components/InboxScreen.js +```jsx:title=src/components/InboxScreen.jsx import React, { useEffect } from 'react'; + import { useDispatch, useSelector } from 'react-redux'; + import { fetchTasks } from '../lib/store'; + import TaskList from './TaskList'; export default function InboxScreen() { @@ -135,8 +138,8 @@ export default function InboxScreen() {
-
Oh no!
-
Something went wrong
+

Oh no!

+

Something went wrong

); @@ -144,9 +147,7 @@ export default function InboxScreen() { return (
@@ -156,9 +157,12 @@ export default function InboxScreen() { さらに、`App` コンポーネントを `InboxScreen` を描画するように変更します (いずれはルーターにどの画面を表示するか決めてもらいますが、今は気にしないでください): -```diff:title=src/App.js -- import logo from './logo.svg'; -- import './App.css'; +```diff:title=src/App.jsx +- import { useState } from 'react' +- import reactLogo from './assets/react.svg' +- import viteLogo from '/vite.svg' +- import './App.css' + + import './index.css'; + import store from './lib/store'; @@ -166,22 +170,29 @@ export default function InboxScreen() { + import InboxScreen from './components/InboxScreen'; function App() { +- const [count, setCount] = useState(0) return ( -
--
-- logo +- +-

Vite + React

+-
+- -

-- Edit src/App.js and save to reload. +- Edit src/App.jsx and save to test HMR -

-- -- Learn React -- --
+-
+-

+- Click on the Vite and React logos to learn more +-

-
+ + @@ -193,11 +204,9 @@ export default App; しかし、面白くなるのは Storybook でストーリーをレンダリングするときです。 -前回見たように、`TaskList` コンポーネントは現在 **接続された** コンポーネントで、タスクのレンダリングは Redux ストアに依存しています。`InboxScreen` も接続されたコンポーネントなので、同じように、ストーリーにストアを渡します。以下のように `InboxScreen.stories.js` でストーリーを設定します: - -```js:title=src/components/InboxScreen.stories.js -import React from 'react'; +前回見たように、`TaskList` コンポーネントは現在 **接続された** コンポーネントで、タスクのレンダリングは Redux ストアに依存しています。`InboxScreen` も接続されたコンポーネントなので、同じように、ストーリーにストアを渡します。以下のように `InboxScreen.stories.jsx` でストーリーを設定します: +```jsx:title=src/components/InboxScreen.stories.jsx import InboxScreen from './InboxScreen'; import store from '../lib/store'; @@ -207,25 +216,26 @@ export default { component: InboxScreen, title: 'InboxScreen', decorators: [(story) => {story()}], + tags: ['autodocs'], }; -const Template = () => ; +export const Default = {}; -export const Default = Template.bind({}); -export const Error = Template.bind({}); +export const Error = {}; +``` const Error = Template.bind({}); ``` -私たちは `error` ストーリーですぐに問題を発見することができます。正しい状態が表示されず、タスクのリストが表示されます。この問題を回避する 1 つの方法は、前章で行ったように各状態に対してモックされたバージョンを提供することです。その代わりに、よく知られた API モッキングライブラリを Storybook アドオンと一緒に使用して、この問題を解決するのに役立てます。 +私たちは `error` ストーリーですぐに問題を発見できます。正しい状態が表示されず、タスクのリストが表示されます。この問題を回避する 1 つの方法は、前章で行ったように各状態に対してモックされたバージョンを提供することです。その代わりに、よく知られた API モッキングライブラリを Storybook アドオンと一緒に使用して、この問題を解決するのに役立てます。 -![壊れた Inbox 画面の状態](/intro-to-storybook/broken-inbox-error-state-optimized.png) +![壊れた Inbox 画面の状態](/intro-to-storybook/broken-inbox-error-state-7-0-optimized.png) ## API をモックする -今回のアプリケーションは単純で、リモート API 呼び出しにあまり依存しないので、[Mock Service Worker](https://mswjs.io/) と [Storybook's MSW addon](https://storybook.js.org/addons/msw-storybook-addon) を使用することにします。Mock Service Worker は、API モックライブラリです。Service Worker に依存してネットワークリクエストを捕捉し、モックデータをレスポンスします。 +今回のアプリケーションは単純で、リモート API 呼び出しにあまり依存しないので、[Mock Service Worker](https://mswjs.io/) と [Storybook MSW アドオン](https://storybook.js.org/addons/msw-storybook-addon) を使用することにします。Mock Service Worker は、API モックライブラリです。Service Worker に依存してネットワークリクエストを捕捉し、モックデータをレスポンスします。 -[Get started section](/intro-to-storybook/react/en/get-started) でアプリケーションをセットアップすると、両方のパッケージともインストールされます。あとは、それらを設定しストーリーを更新して使用するのみです。 +[初めの章](/intro-to-storybook/react/ja/get-started) でアプリケーションをセットアップしたときに、これらのパッケージはすでにインストールされています。あとは、それらを設定しストーリーを更新して使用するのみです。 -ターミナルで以下のコマンドを実行し、`public` フォルダの中にサービスワーカーを生成します。: +ターミナルで以下のコマンドを実行し、`public` フォルダの中にサービスワーカーを生成します。 ```shell yarn init-msw @@ -236,32 +246,33 @@ yarn init-msw ```diff:title=.storybook/preview.js import '../src/index.css'; -+ // Registers the msw addon -+ import { initialize, mswDecorator } from 'msw-storybook-addon'; ++ // mswアドオンを登録 ++ import { initialize, mswLoader } from 'msw-storybook-addon'; -+ // Initialize MSW ++ // MSWを初期化 + initialize(); -+ // Provide the MSW addon decorator globally -+ export const decorators = [mswDecorator]; - //👇 Configures Storybook to log the actions( onArchiveTask and onPinTask ) in the UI. -export const parameters = { - actions: { argTypesRegex: '^on[A-Z].*' }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/, +/** @type { import('@storybook/react').Preview } */ +const preview = { + parameters: { + actions: { argTypesRegex: "^on[A-Z].*" }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, }, }, ++ loaders: [mswLoader], }; -``` -最後に、`InboxScreen` のストーリーを更新し、リモート API 呼び出しをモックする [parameter](https://storybook.js.org/docs/react/writing-stories/parameters) を組み込みます: +export default preview; +``` -```diff:title=src/components/InboxScreen.stories.js -import React from 'react'; +最後に、`InboxScreen` のストーリーを更新し、リモート API 呼び出しをモックする [parameter](https://storybook.js.org/docs/react/writing-stories/parameters) を組み込みます。 +```diff:title=src/components/InboxScreen.stories.jsx import InboxScreen from './InboxScreen'; import store from '../lib/store'; + import { rest } from 'msw'; @@ -272,12 +283,11 @@ export default { component: InboxScreen, title: 'InboxScreen', decorators: [(story) => {story()}], + tags: ['autodocs'], }; -const Template = () => ; - -export const Default = Template.bind({}); -+ Default.parameters = { +export const Default = { ++ parameters: { + msw: { + handlers: [ + rest.get( @@ -288,10 +298,10 @@ export const Default = Template.bind({}); + ), + ], + }, -+ }; - -export const Error = Template.bind({}); -+ Error.parameters = { ++ }, +}; +export const Error = { ++ parameters: { + msw: { + handlers: [ + rest.get( @@ -302,7 +312,8 @@ export const Error = Template.bind({}); + ), + ], + }, -+ }; ++ }, +}; ```
@@ -314,7 +325,7 @@ Storybook で `error` ストーリーが意図したように動作している @@ -331,13 +342,11 @@ Storybook の [`play`](https://storybook.js.org/docs/react/writing-stories/play- play 関数はタスクが更新されたときに UI に何が起こるかを検証するのに役立ちます。フレームワークに依存しない DOM API を使用しています。つまり、 play 関数を使って UI を操作し、人間の行動をシミュレートするストーリーを、フロントエンドのフレームワークに関係なく書くことができるのです。 -`@storybook/addon-interactions`は、一つ一つのステップごとに Storybook のテストを可視化するのに役立ちます。さらに、各インタラクションの一時停止、再開、巻き戻し、ステップ実行といった便利な UI の制御機能が備わっています。 +`@storybook/addon-interactions`は、ひとつひとつのステップごとに Storybook のテストを可視化するのに役立ちます。さらに、各インタラクションの一時停止、再開、巻き戻し、ステップ実行といった便利な UI の制御機能が備わっています。 -実際に動かしてみましょう!以下のようにして新しく作成された `InboxScreen` ストーリーを更新し、コンポーネント操作を追加してみましょう: - -```diff:title=src/components/InboxScreen.stories.js -import React from 'react'; +実際に動かしてみましょう!以下のようにして新しく作成された `InboxScreen` ストーリーを更新し、コンポーネント操作を追加してみましょう。 +```diff:title=src/components/InboxScreen.stories.jsx import InboxScreen from './InboxScreen'; import store from '../lib/store'; @@ -347,73 +356,93 @@ import { Provider } from 'react-redux'; + import { + fireEvent, -+ within, + waitFor, ++ within, + waitForElementToBeRemoved -+ } from '@storybook/testing-library'; ++ } from '@storybook/test'; export default { component: InboxScreen, title: 'InboxScreen', decorators: [(story) => {story()}], + tags: ['autodocs'], }; -const Template = () => ; - -export const Default = Template.bind({}); -Default.parameters = { - msw: { - handlers: [ - rest.get( - 'https://jsonplaceholder.typicode.com/todos?userId=1', - (req, res, ctx) => { - return res(ctx.json(MockedState.tasks)); - } - ), - ], +export const Default = { + parameters: { + msw: { + handlers: [ + rest.get( + 'https://jsonplaceholder.typicode.com/todos?userId=1', + (req, res, ctx) => { + return res(ctx.json(MockedState.tasks)); + } + ), + ], + }, }, -}; - -+ Default.play = async ({ canvasElement }) => { ++ play: async ({ canvasElement }) => { + const canvas = within(canvasElement); -+ // Waits for the component to transition from the loading state ++ // コンポーネントのローディング状態からの遷移を待機 + await waitForElementToBeRemoved(await canvas.findByTestId('loading')); -+ // Waits for the component to be updated based on the store ++ // ストアに基づいたコンポーネントの更新を待機 + await waitFor(async () => { -+ // Simulates pinning the first task ++ // 最初のタスクのピン止めをシミュレート + await fireEvent.click(canvas.getByLabelText('pinTask-1')); -+ // Simulates pinning the third task ++ // 3つ目のタスクのピン止めをシミュレート + await fireEvent.click(canvas.getByLabelText('pinTask-3')); + }); -+ }; ++ }, +}; +export const Error = { + parameters: { + msw: { + handlers: [ + rest.get( + 'https://jsonplaceholder.typicode.com/todos?userId=1', + (req, res, ctx) => { + return res(ctx.status(403)); + } + ), + ], + }, + }, +}; ``` -新しく作成したストーリーを確認します。`Interactions` パネルをクリックすると、ストーリーの play 関数内のインタラクションのリストが表示されます。 +
+ +💡 The `@storybook/test` パッケージは `@storybook/jest`と`@storybook/testing-library`を置き換えるものです。 +より小さなバンドルサイズと、VitestパッケージをベースにしたよりわかりやすいAPIを提供します。 + +
+ +`Default`ストーリーを確認します。`Interactions` パネルをクリックすると、ストーリーの play 関数内のインタラクションのリストが表示されます。 -### テスト自動化 +### test runnerによるテストの自動化 -play 関数を利用して、UI を操作し、タスクを更新した場合の反応を素早く確認することができます。これによって、余計な手間をかけずに UI の一貫性を保つことができます。 +play 関数を利用して、UI を操作し、タスクを更新した場合の反応を素早く確認できます。これによって、余計な手間をかけずに UI の一貫性を保つことができます。 -しかし、Storybook をよく見ると、ストーリーを見るときだけインタラクションテストが実行されることがわかります。そのため、変更時に各ストーリーを全てチェックしなければなりません。これは自動化できないのでしょうか? +しかし、Storybook をよく見ると、ストーリーを見るときだけインタラクションテストが実行されることがわかります。そのため、変更時に各ストーリーをすべてチェックしなければなりません。これは自動化できないのでしょうか? -可能です!Storybook の[テストランナー](https://storybook.js.org/docs/react/writing-tests/test-runner)は可能にしてくれます。それは [Playwright](https://playwright.dev/) によって実現されたスタンドアロンなパッケージで、全てのインタラクションテストを実行し、壊れたストーリーを検知してくれます。 +結論から言うと、それは可能です!Storybook の[テストランナー](https://storybook.js.org/docs/react/writing-tests/test-runner)は [Playwright](https://playwright.dev/) によって実現されたスタンドアロンなパッケージで、すべのインタラクションテストを実行し、壊れたストーリーを検知してくれます。 -それではどのように動くのかみてみましょう!次のコマンドでインストールして走らせます: +それではどのように動くのかみてみましょう!次のコマンドでインストールします。 ```bash yarn add --dev @storybook/test-runner ``` -次に、 `package.json` の `scripts` をアップデートし、新しいテストタスクを追加してください: +次に、 `package.json` の `scripts` を更新し、新しいテストタスクを追加してください。 -```json +```json:clipboard=false { "scripts": { "test-storybook": "test-storybook" @@ -421,7 +450,7 @@ yarn add --dev @storybook/test-runner } ``` -最後に、Storybook を起動し、新しいターミナルで以下のコマンドを実行してください: +最後に、Storybook を起動し、新しいターミナルで以下のコマンドを実行してください。 ```bash yarn test-storybook --watch @@ -435,11 +464,11 @@ yarn test-storybook --watch ![Storybook test runner successfully runs all tests](/intro-to-storybook/storybook-test-runner-execution.png) -成功です!これで、全てのストーリーがエラーなくレンダリングされ、全てのテストが自動的に通過するかどうか検証するためのツールができました。さらに、テストが失敗した場合、失敗したストーリーをブラウザで開くリンクを提供してくれます。 +成功です!これで、すべてのストーリーがエラーなくレンダリングされ、すべてのテストが自動的に通過するかどうか検証するためのツールができました。さらに、テストが失敗した場合、失敗したストーリーをブラウザで開くリンクを提供してくれます。 ## コンポーネント駆動開発 -まず、一番下の `Task` から始めて、`TaskList` を作り、画面全体の UI が出来ました。`InboxScreen` では繋がれたコンポーネントを含み、一緒にストーリーも作成しました。 +まず、一番下の `Task` から始めて、`TaskList` を作り、画面全体の UI ができました。`InboxScreen` では繋がれたコンポーネントを含み、一緒にストーリーも作成しました。 -[**コンポーネント駆動開発**](https://www.componentdriven.org/) (CDD) はコンポーネント階層を上がるごとに少しずつ複雑性を拡張していきます。利点としては、開発プロセスに集中できること、UI の組み合わせの網羅性を向上できること、が挙げられます。要するに、CDD によって、高品質で複雑な UI を作ることができます。 +[**コンポーネント駆動開発**](https://www.componentdriven.org/) (CDD) はコンポーネント階層を上がるごとに少しずつ複雑性を拡張します。利点としては、開発プロセスに集中できること、UI の組み合わせの網羅性を向上できること、が挙げられます。要するに、CDD によって、高品質で複雑な UI を作ることができます。 -まだ終わりではありません。UI を作成しても仕事は終わりません。長期間にわたり耐久性を維持できるようにしなければなりません。 +まだ終わりではありません。UI を作成しても作業は終わりません。長期間にわたり耐久性を維持できるようにしなければなりません。
💡 Git へのコミットを忘れずに行ってください! From 3811f28a92800f87585a1a9f555a6aa17ddd7632 Mon Sep 17 00:00:00 2001 From: UHAsikakutou <91722200+UHAsikakutou@users.noreply.github.com> Date: Thu, 16 May 2024 21:04:44 +0900 Subject: [PATCH 06/14] Update deploy.md --- content/intro-to-storybook/react/ja/deploy.md | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/content/intro-to-storybook/react/ja/deploy.md b/content/intro-to-storybook/react/ja/deploy.md index 94ba7cf97..1f2fcb4d7 100644 --- a/content/intro-to-storybook/react/ja/deploy.md +++ b/content/intro-to-storybook/react/ja/deploy.md @@ -11,27 +11,27 @@ commit: '59da1ac' Storybook をデプロイするには、まず静的サイトとしてエクスポートします。この機能はすでに組み込まれて、使える状態となっているので、設定について気にする必要はありません。 -`yarn build-storybook` を実行すると、`storybook-static` ディレクトリーに Storybook が静的サイトとして出力されますので、静的サイトのホスティングサービスのデプロイ出来ます。 +`yarn build-storybook` を実行すると、`storybook-static` ディレクトリに Storybook が静的サイトとして出力されますので、静的サイトのホスティングサービスにデプロイできます。 -## Storybook を発行する +## Storybook を公開する -このチュートリアルでは、Storybook のメンテナーが作成した、無料のホスティングサービスである Chromatic を使用します。Chromatic を使えば、クラウド上に Storybook を安全に、デプロイしホストすることができます。 +このチュートリアルでは、Storybook のメンテナーが作成した、無料のホスティングサービスである Chromatic を使用します。Chromatic を使えば、クラウド上に Storybook を安全にデプロイし、またホストできます。 ### GitHub にリポジトリーを作成する -デプロイの前に、リモートのバージョン管理サービスへローカルのコードを同期しなければなりません。[はじめにの章](/intro-to-storybook/react/ja/get-started/)で Create React App (CRA) でプロジェクトを初期化した際に、ローカルのリポジトリーはすでに作成されています。また、この段階でリモートリポジトリーにプッシュできるコミットがあるはずです。 +デプロイの前に、リモートのバージョン管理サービスへローカルのコードを同期しなければなりません。[はじめの章](/intro-to-storybook/react/ja/get-started/)でプロジェクトを初期化した際に、ローカルのリポジトリーはすでに作成されています。この段階に来れば、リモートリポジトリーにプッシュできるコミットがあるはずです。 -[ここから](https://github.com/new) GitHub にアクセスし、リポジトリーを作りましょう。リポジトリーの名前はローカルと同じく「taskbox」とします。 +[ここから](https://github.com/new) GitHub にアクセスし、リポジトリを作りましょう。リポジトリの名前はローカルと同じく「taskbox」とします。 ![GitHub のセットアップ](/intro-to-storybook/github-create-taskbox.png) -新しいリポジトリーを作ったら origin の URL をコピーして、次のコマンドを実行し、ローカルの Git プロジェクトにリモートを追加します: +新しいリポジトリを作ったら origin の URL をコピーして、次のコマンドを実行し、ローカルの Git リポジトリを GitHub のリモートリポジトリーに追加します。 ```shell -git remote add origin https://github.com//taskbox.git +git remote add origin https://github.com//taskbox.git ``` -最後にローカルリポジトリーを GitHub のリモートリポジトリーにプッシュします: +最後にローカルリポジトリを GitHub のリモートリポジトリにプッシュします: ```shell git push -u origin main @@ -45,9 +45,9 @@ git push -u origin main yarn add -D chromatic ``` -パッケージをインストールしたら、GitHub のアカウントを使用して [Chromatic にログイン](https://www.chromatic.com/start/?utm_source=storybook_website&utm_medium=link&utm_campaign=storybook)します。(Chromatic は一部のアクセス許可を要求します。) 「taskbox」という名前でプロジェクトを作成し、GitHub のリポジトリーと同期させます。 +パッケージをインストールしたら、GitHub のアカウントを使用して [Chromatic にログイン](https://www.chromatic.com/start/?utm_source=storybook_website&utm_medium=link&utm_campaign=storybook)します。(Chromatic は一部のアクセス許可を要求します。)「taskbox」という名前でプロジェクトを作成し、GitHub のリポジトリと同期させます。 -ログインしたら `Choose from GitHub` をクリックし、リポジトリーを選択します。 +ログインしたら `Choose GitHub repo` をクリックし、リポジトリを選択します。
-![Storybook test runner successfully runs all tests](/intro-to-storybook/storybook-test-runner-execution.png) +![Storybook のテストランナーがすべてのテストの実行を成功させる様子](/intro-to-storybook/storybook-test-runner-execution.png) 成功です!これで、すべてのストーリーがエラーなくレンダリングされ、すべてのテストが自動的に通過するかどうか検証するためのツールができました。さらに、テストが失敗した場合、失敗したストーリーをブラウザで開くリンクを提供してくれます。 diff --git a/content/intro-to-storybook/react/ja/simple-component.md b/content/intro-to-storybook/react/ja/simple-component.md index ff98d65c5..3dad1b504 100644 --- a/content/intro-to-storybook/react/ja/simple-component.md +++ b/content/intro-to-storybook/react/ja/simple-component.md @@ -22,7 +22,7 @@ commit: '9b36e1a' まずは、タスクのコンポーネントと、対応するストーリーファイル `src/components/Task.jsx` と `src/components/Task.stories.jsx` を作成しましょう。 -`Task` の基本的な実装から始めます。`Task` は上述したプロパティと、タスクに対して実行できる 2 つの (リスト間を移動させる) アクションを引数として取ります: +`Task` の基本的な実装から始めます。`Task` は上述したプロパティと、タスクに対して実行できる 2 つの (リスト間を移動させる) アクションを引数として取ります。 ```js:title=src/components/Task.jsx import React from 'react'; @@ -40,7 +40,7 @@ export default function Task({ task: { id, title, state }, onArchiveTask, onPinT 上のコードは Todo アプリケーションの HTML を基にした `Task` の簡単なマークアップです。 -下のコードは `Task` に対する 3 つのテスト用の状態をストーリーファイルに書いています: +下のコードは `Task` に対する 3 つのテスト用の状態をストーリーファイルに書くものです。 ```js:title=src/components/Task.stories.jsx import Task from './Task'; @@ -103,7 +103,7 @@ Arguments (略して [`args`](https://storybook.js.org/docs/react/writing-storie 作成したストーリーを認識させたり、CSS ファイル (`src/index.css`にあります) をStorybook上で使用できるようにするため、Storybook の設定をいくつか変更する必要があります。 -まず、設定ファイル (`.storybook/main.js`) を以下のように変更してください: +まず、設定ファイル (`.storybook/main.js`) を以下のように変更してください。 ```diff:title=.storybook/main.js /** @type { import('@storybook/react-vite').StorybookConfig } */ @@ -127,12 +127,12 @@ const config = { export default config; ``` -上記の変更が完了したら、`.storybook` フォルダー内の `preview.js` を、以下のように変更してください: +上記の変更が完了したら、`.storybook` フォルダー内の `preview.js` を、以下のように変更してください。 ```diff:title=.storybook/preview.js + import '../src/index.css'; -//👇 Storybookのアクション(onArchiveTaskとonPinTask)をUIに記録するように設定します。 +//👇 Configures Storybook to log the actions( onArchiveTask and onPinTask ) in the UI. /** @type { import('@storybook/react').Preview } */ const preview = { parameters: { @@ -166,7 +166,7 @@ Storybook のサーバーを再起動すると、タスクの 3 つの状態の ここまでで、Storybook のセットアップが完了し、スタイルをインポートし、テストケースを作りました。早速、デザインに合わせてコンポーネントの HTML を実装していきましょう。 -今のところコンポーネントは簡素な状態です。まずはデザインを実現するために最低限必要なコードを書いてみましょう: +今のところコンポーネントは簡素な状態です。まずはデザインを実現するために最低限必要なコードを書いてみましょう。 ```jsx:title=src/components/Task.jsx import React from 'react'; @@ -218,7 +218,7 @@ export default function Task({ task: { id, title, state }, onArchiveTask, onPinT } ``` -追加したマークアップとインポートした CSS により以下のような UI ができます: +追加したマークアップとインポートした CSS により以下のような UI ができます。 -### test runnerによるテストの自動化 +### test runner によるテストの自動化 play 関数を利用して、UI を操作し、タスクを更新した場合の反応を素早く確認できます。これによって、余計な手間をかけずに UI の一貫性を保つことができます。 From 17b2af48743d9a47365f80835b3a5babe822e472 Mon Sep 17 00:00:00 2001 From: UHAsikakutou <91722200+UHAsikakutou@users.noreply.github.com> Date: Sun, 21 Jul 2024 12:43:41 +0900 Subject: [PATCH 14/14] Fix: update ` actions/checkout` to v4 --- content/intro-to-storybook/react/ja/deploy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/intro-to-storybook/react/ja/deploy.md b/content/intro-to-storybook/react/ja/deploy.md index 2c05b1a30..b0712f465 100644 --- a/content/intro-to-storybook/react/ja/deploy.md +++ b/content/intro-to-storybook/react/ja/deploy.md @@ -94,7 +94,7 @@ jobs: runs-on: ubuntu-latest # Job steps steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - run: yarn