Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update intro-to-storybook/ja #765

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Prev Previous commit
Next Next commit
Update data.md
  • Loading branch information
UHAsikakutou committed May 16, 2024
commit 2f9468d93005f28063796023ddc6add1430cab44
151 changes: 76 additions & 75 deletions content/intro-to-storybook/react/ja/data.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/) といった他のデータ管理用のライブラリーでもここでのパターンが使用できます。

以下のコマンドを実行し必要な依存関係を追加しましょう:

Expand All @@ -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' },
Expand All @@ -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({
Expand All @@ -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({
Expand All @@ -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'),
Expand All @@ -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 = (
Expand Down Expand Up @@ -139,8 +139,8 @@ export default function TaskList() {
<div className="list-items" key={"empty"} data-testid="empty">
<div className="wrapper-message">
<span className="icon-check" />
<div className="title-message">You have no tasks</div>
<div className="subtitle-message">Sit back and relax</div>
<p className="title-message">You have no tasks</p>
<p className="subtitle-message">Sit back and relax</p>
</div>
</div>
);
Expand All @@ -161,29 +161,27 @@ 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';

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' },
Expand All @@ -197,7 +195,7 @@ export const MockedState = {
error: null,
};

// A super-simple mock of a redux store
// 超シンプルなreduxストアのモック
const Mockstore = ({ taskboxState, children }) => (
<Provider
store={configureStore({
Expand Down Expand Up @@ -225,80 +223,83 @@ const Mockstore = ({ taskboxState, children }) => (
export default {
component: TaskList,
title: 'TaskList',
decorators: [(story) => <div style={{ padding: "3rem" }}>{story()}</div>],
decorators: [(story) => <div style={{ padding: '3rem' }}>{story()}</div>],
tags: ['autodocs'],
excludeStories: /.*MockedState$/,
};

const Template = () => <TaskList />;

export const Default = Template.bind({});
Default.decorators = [
(story) => <Mockstore taskboxState={MockedState}>{story()}</Mockstore>,
];
export const Default = {
decorators: [
(story) => <Mockstore taskboxState={MockedState}>{story()}</Mockstore>,
],
};

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 (
<Mockstore
taskboxState={{
...MockedState,
tasks: pinnedtasks,
}}
>
{story()}
</Mockstore>
);
},
],
};

return (
export const Loading = {
decorators: [
(story) => (
<Mockstore
taskboxState={{
...MockedState,
tasks: pinnedtasks,
status: 'loading',
}}
>
{story()}
</Mockstore>
);
},
];

export const Loading = Template.bind({});
Loading.decorators = [
(story) => (
<Mockstore
taskboxState={{
...MockedState,
status: 'loading',
}}
>
{story()}
</Mockstore>
),
];
),
],
};

export const Empty = Template.bind({});
Empty.decorators = [
(story) => (
<Mockstore
taskboxState={{
...MockedState,
tasks: [],
}}
>
{story()}
</Mockstore>
),
];
export const Empty = {
decorators: [
(story) => (
<Mockstore
taskboxState={{
...MockedState,
tasks: [],
}}
>
{story()}
</Mockstore>
),
],
};
```

<div class="aside">
💡 <code>excludeStories</code> は Storybook の設定のフィールドで、モックされた状態がストーリーとして扱われるのを防ぐためのものです。このフィールドについては <a href="https://storybook.js.org/docs/react/api/csf">Storybook documentation</a> で詳しく説明されています。
💡 <code>excludeStories</code> は Storybook の設定のフィールドで、モックされた状態がストーリーとして扱われるのを防ぐためのものです。このフィールドについては <a href="https://storybook.js.org/docs/react/api/csf">ドキュメント</a> で詳しく説明されています。
</div>

<video autoPlay muted playsInline loop>
<source
src="/intro-to-storybook/finished-tasklist-states-6-4-optimized.mp4"
src="/intro-to-storybook/finished-tasklist-states-7-0-optimized.mp4"
type="video/mp4"
/>
</video>

<div class="aside">
💡 この変更により、全てのテストはアップデートが必要になります。<code>-u</code> フラグをつけてテストを再実行し、アップデートしてください。 Git へのコミットを忘れずに行ってください!
💡 Git へのコミットを忘れずに行ってください!
</div>

成功です! Storybook が動作し、接続されたコンポーネントにデータを渡す方法を確認することができました。次の章では、ここで学んだことを画面に適用してみましょう。