返回Storybook 簡介
章節
  • 開始使用
  • 簡單元件
  • 複合元件
  • 資料
  • 結論
  • 貢獻

組裝一個複合元件

從較簡單的元件組裝一個複合元件
此社群翻譯尚未更新至最新版本的 Storybook。請協助我們將英文指南中的變更套用至此翻譯,以更新此翻譯。 歡迎提交 Pull Request.

在上一章中,我們建立了第一個元件;本章將擴展我們所學的內容,以建立 TaskList,即 Tasks 的清單。讓我們將元件組合在一起,看看引入更多複雜性時會發生什麼。

任務清單

Taskbox 強調固定任務,方法是將它們放置在預設任務上方。這會產生兩種 TaskList 的變體,您需要為其建立故事:預設項目和預設與固定項目。

default and pinned tasks

由於 Task 資料可以非同步傳送,因此我們在沒有連線時**也**需要一個載入狀態才能呈現。此外,當沒有任務時,需要一個空狀態。

empty and loading tasks

開始設定

複合元件與其包含的基本元件沒有太大差異。建立一個 TaskList 元件和一個隨附的故事檔案:components/TaskList.jsxcomponents/TaskList.stories.jsx

TaskList 的粗略實作開始。您需要從先前匯入 Task 元件,並將屬性和動作當作輸入傳遞。

複製
components/TaskList.jsx
import { Task } from './Task';
import { FlatList, StyleSheet, Text, View } from 'react-native';
import { styles } from './styles';

export const TaskList = ({ loading, tasks, onPinTask, onArchiveTask }) => {
  const events = {
    onPinTask,
    onArchiveTask,
  };

  if (loading) {
    return (
      <View style={styles.listItems}>
        <Text>loading</Text>
      </View>
    );
  }

  if (tasks.length === 0) {
    return (
      <View style={styles.listItems}>
        <Text>empty</Text>
      </View>
    );
  }

  return (
    <View style={styles.listItems}>
      <FlatList
        data={tasks}
        keyExtractor={(task) => task.id}
        renderItem={({ item }) => (
          <Task key={item.id} task={item} {...events} />
        )}
      />
    </View>
  );
};

接下來,在故事檔案中建立 Tasklist 的測試狀態。

複製
components/TaskList.stories.jsx
import { TaskList } from './TaskList';
import { Default as TaskStory } from './Task.stories';
import { View } from 'react-native';

export default {
  component: TaskList,
  title: 'TaskList',
  decorators: [
    (Story) => (
      <View style={{ padding: 42, flex: 1 }}>
        <Story />
      </View>
    ),
  ],
  argTypes: {
    onPinTask: { action: 'onPinTask' },
    onArchiveTask: { action: 'onArchiveTask' },
  },
};

export const Default = {
  args: {
    // Shaping the stories through args composition.
    // The data was inherited from the Default story in Task.stories.js.
    tasks: [
      { ...TaskStory.args.task, id: '1', title: 'Task 1' },
      { ...TaskStory.args.task, id: '2', title: 'Task 2' },
      { ...TaskStory.args.task, id: '3', title: 'Task 3' },
      { ...TaskStory.args.task, id: '4', title: 'Task 4' },
      { ...TaskStory.args.task, id: '5', title: 'Task 5' },
      { ...TaskStory.args.task, id: '6', title: 'Task 6' },
    ],
  },
};

export const WithPinnedTasks = {
  args: {
    tasks: [
      ...Default.args.tasks.slice(0, 5),
      { id: '6', title: 'Task 6 (pinned)', state: 'TASK_PINNED' },
    ],
  },
};

export const Loading = {
  args: {
    tasks: [],
    loading: true,
  },
};

export const Empty = {
  args: {
    // Shaping the stories through args composition.
    // Inherited data coming from the Loading story.
    ...Loading.args,
    loading: false,
  },
};

裝飾器是一種為故事提供任意包裝的方式。在這種情況下,我們使用裝飾器在清單周圍新增間距,使其更容易進行視覺驗證。它們也可以用於將故事包裝在「提供者」中,例如:設定 React 上下文的程式庫元件。

TaskStory.args.task 提供我們從 Task.stories.js 檔案建立和匯出的 Task 形狀。同樣地,我們為 onPinTaskonArchiveTask 新增的 argTypes 會告知 Storybook 提供 TaskList 元件需要的動作(模擬回呼)。

如果您沒有立即看到新的故事,請嘗試重新載入應用程式。如果仍然無效,您可以重新執行 yarn storybook-generate 以重新產生 storybook.requires 檔案。

現在檢查 Storybook 中新的 TaskList 故事。

a gif showing the task list component in storybook

建立狀態

我們的元件仍然很粗略,但現在我們有了要努力的方向的故事概念。您可能會認為 listitems 包裝器過於簡單。您說得對,在大多數情況下,我們不會為了新增包裝器而建立新的元件。但是 TaskList 元件的**真正複雜性**會在邊緣情況 withPinnedTasksloadingempty 中顯現出來。

對於載入案例,我們將建立一個新的元件,以顯示載入動畫。

建立一個名為 LoadingRow.jsx 的新檔案,內容如下

複製
components/LoadingRow.jsx
import { useState, useEffect } from 'react';
import { Animated, Text, View, Easing, StyleSheet } from 'react-native';
import { styles } from './styles';

const GlowView = ({ style, children }) => {
  const [glowAnim] = useState(new Animated.Value(0.3));

  useEffect(() => {
    Animated.loop(
      Animated.sequence([
        Animated.timing(glowAnim, {
          toValue: 1,
          duration: 1500,
          easing: Easing.ease,
          useNativeDriver: true,
        }),
        Animated.timing(glowAnim, {
          toValue: 0.3,
          duration: 1500,
          easing: Easing.ease,
          useNativeDriver: true,
        }),
      ])
    ).start();
  }, []);

  return (
    <Animated.View
      style={{
        ...style,
        opacity: glowAnim,
      }}
    >
      {children}
    </Animated.View>
  );
};

export const LoadingRow = () => (
  <View style={styles.container}>
    <GlowView>
      <View style={styles.loadingItem}>
        <View style={styles.glowCheckbox} />
        <Text style={styles.glowText}>Loading</Text>
        <Text style={styles.glowText}>cool</Text>
        <Text style={styles.glowText}>state</Text>
      </View>
    </GlowView>
  </View>
);

並將 TaskList.jsx 更新為以下內容

複製
components/TaskList.jsx
import { Task } from './Task';
import { FlatList, StyleSheet, Text, View } from 'react-native';
import { LoadingRow } from './LoadingRow';
import { MaterialIcons } from '@expo/vector-icons';
import { styles } from './styles';

export const TaskList = ({ loading, tasks, onPinTask, onArchiveTask }) => {
  const events = {
    onPinTask,
    onArchiveTask,
  };

  if (loading) {
    return (
      <View style={[styles.listItems, { justifyContent: "center" }]}>
        <LoadingRow />
        <LoadingRow />
        <LoadingRow />
        <LoadingRow />
        <LoadingRow />
        <LoadingRow />
      </View>
    );
  }

  if (tasks.length === 0) {
    return (
      <View style={styles.listItems}>
        <View style={styles.wrapperMessage}>
          <MaterialIcons name="check" size={64} color={"#2cc5d2"} />
          <Text style={styles.titleMessage}>You have no tasks</Text>
          <Text style={styles.subtitleMessage}>Sit back and relax</Text>
        </View>
      </View>
    );
  }

  const tasksInOrder = [
    ...tasks.filter((t) => t.state === 'TASK_PINNED'),
    ...tasks.filter((t) => t.state !== 'TASK_PINNED'),
  ];
  return (
    <View style={styles.listItems}>
      <FlatList
        data={tasksInOrder}
        keyExtractor={(task) => task.id}
        renderItem={({ item }) => (
          <Task key={item.id} task={item} {...events} />
        )}
      />
    </View>
  );
};

這些變更會產生下列 UI

TaskList with loading state

成功!我們完成了既定的目標。如果我們檢查更新的 UI,我們可以很快地看到固定任務現在顯示在清單的頂端,符合預期的設計。在接下來的章節中,我們將擴展所學的內容,並透過將這些原則應用於更複雜的 UI,來繼續為我們的應用程式新增複雜性。

💡 別忘了使用 git 提交您的變更!
這個免費指南對您有幫助嗎?請發推文表示讚許,並協助其他開發人員找到它。
下一章
資料
了解如何將資料連接到您的 UI 元件
✍️ 在 GitHub 上編輯 – 歡迎提交 PR!
加入社群
6,616位開發人員和計數
為什麼為何選擇 Storybook元件驅動式 UI
開放原始碼軟體
Storybook

由以下人員維護
Chromatic
特別感謝 Netlify CircleCI