构建一个简单组件
我们将遵循组件驱动开发 (CDD) 方法论构建我们的 UI。这是一个从“自下而上”构建 UI 的过程,从组件开始,到屏幕结束。CDD 帮助您在构建 UI 时应对日益增长的复杂性。
任务
`Task` 是我们应用中的核心组件。每个任务的显示方式略有不同,具体取决于它所处的状态。我们显示一个已选中(或未选中)的复选框、一些关于任务的信息和一个“置顶”按钮,允许我们将任务在列表中上下移动。综上所述,我们将需要以下 props
- `title` – 描述任务的字符串
- `state` - 任务当前在哪个列表中,以及是否已选中?
当我们开始构建 `Task` 时,我们首先编写与上面草绘的不同类型任务相对应的测试状态。然后我们使用 Storybook,使用模拟数据隔离构建组件。我们将随着进行“视觉测试”组件在每种状态下的外观。
此过程类似于测试驱动开发 (TDD),我们可以称之为“视觉 TDD”。
设置
让我们打开 `.storybook/main.js` 并查看一下
module.exports = {
stories: ['../components/**/*.stories.?(ts|tsx|js|jsx)'],
addons: [
'@storybook/addon-ondevice-controls',
'@storybook/addon-ondevice-actions',
],
};
如果您查看 `stories` 属性,您将看到 Storybook 正在 `components` 文件夹中查找 stories。
在 React Native 的 Storybook 中,由于 Metro bundler 当前的限制,我们依赖于 `main.js` 中的配置来生成一个名为 `storybook.requires` 的文件,该文件用于加载我们项目中的所有 stories 和 addons。当您运行 `yarn storybook` 启动 Storybook 时,此文件会自动生成。
💡 您还可以通过运行 `yarn storybook-generate` 手动生成 `storybook.requires` 文件。但是,除非您看到某个 story 未被加载,或者注意到 `main.js` 配置中的更改未反映在您的 Storybook 中,否则您无需重新创建此文件。要了解有关 `storybook.requires` 文件如何生成的更多信息,您可以查看 `metro.config.js` 文件中的 `withStorybook` 函数。
现在让我们创建 task 组件及其附带的 story 文件:`components/Task.jsx` 和 `components/Task.stories.jsx`。
我们将从 `Task` 的基本实现开始,只需接收我们知道需要的属性以及您可以对任务执行的两个操作(在列表之间移动它)
import { StyleSheet, TextInput, View } from 'react-native';
import { styles } from './styles';
export const Task = ({
task: { id, title, state },
onArchiveTask,
onPinTask,
}) => {
return (
<View style={styles.listItem}>
<TextInput value={title} editable={false} />
</View>
);
};
现在添加 story 文件
import { Task } from './Task';
export default {
title: 'Task',
component: Task,
argTypes: {
onPinTask: { action: 'onPinTask' },
onArchiveTask: { action: 'onArchiveTask' },
},
};
export const Default = {
args: {
task: {
id: '1',
title: 'Test Task',
state: 'TASK_INBOX',
},
},
};
export const Pinned = {
args: { task: { ...Default.args.task, state: 'TASK_PINNED' } },
};
export const Archived = {
args: { task: { ...Default.args.task, state: 'TASK_ARCHIVED' } },
};
在我们的 stories 文件中,我们使用一种名为组件 Story Format (CSF) 的语法。此语法使编写 stories 更容易,并且受到最新版本 Storybook 的支持。
Storybook 中有两个基本的组织级别:组件及其子 stories。将每个 story 视为组件的一种排列。您可以根据需要为每个组件设置任意数量的 stories。
- 组件
- Story
- Story
- Story
为了告诉 Storybook 我们正在记录的组件,我们创建一个 default
export,其中包含
- `component` -- 组件本身
- `title` -- 如何在 Storybook 应用的侧边栏中引用该组件
- `argTypes` -- 允许我们指定 args 的类型,在这里我们使用它来定义操作,这些操作将在每次发生交互时记录日志
为了定义我们的 stories,我们导出一个带有 `args` 属性的对象。参数或 args
简称,允许我们使用 controls addon 实时编辑组件,而无需重启 Storybook。一旦 args
值发生更改,组件也会随之更改。
在创建 story 时,我们使用基本的 `task` arg 来构建组件期望的任务形状。通常根据实际数据的外观建模。同样,导出此形状将使我们能够在以后的 stories 中重用它,正如我们将看到的。
现在我们已经设置了基础知识,让我们重新运行 `yarn storybook` 并查看我们的更改。如果您已经运行了 Storybook,您也可以运行 `yarn storybook-generate` 以重新生成 `storybook.requires` 文件。
您应该看到如下所示的 UI:
您可以使用屏幕底部的菜单打开导航菜单,然后点击以在 stories 之间切换。然后您可以点击离开或向下拉动以关闭菜单。您可以通过按底部栏最右侧的图标找到 addons。
构建状态
现在我们可以开始构建我们的组件以匹配设计。
该组件目前仍然很基础。首先编写实现设计效果的代码,而无需过多细节
import { MaterialIcons } from '@expo/vector-icons';
import { StyleSheet, TextInput, TouchableOpacity, View } from 'react-native';
import { styles } from './styles';
export const Task = ({
task: { id, title, state },
onArchiveTask,
onPinTask,
}) => {
return (
<View style={styles.listItem}>
<TouchableOpacity onPress={() => onArchiveTask(id)}>
{state !== "TASK_ARCHIVED" ? (
<MaterialIcons
name="check-box-outline-blank"
size={24}
color="#26c6da"
/>
) : (
<MaterialIcons name="check-box" size={24} color="#26c6da" />
)}
</TouchableOpacity>
<TextInput
placeholder="Input Title"
value={title}
editable={false}
style={
state === "TASK_ARCHIVED"
? styles.listItemInputTaskArchived
: styles.listItemInputTask
}
/>
<TouchableOpacity onPress={() => onPinTask(id)}>
<MaterialIcons
name="star"
size={24}
color={state !== "TASK_PINNED" ? "#eee" : "#26c6da"}
/>
</TouchableOpacity>
</View>
);
};
完成后,它应该看起来像这样
组件已构建!
我们现在已经成功构建了一个组件,而无需服务器或运行整个应用程序。下一步是以类似的方式逐个构建剩余的 Taskbox 组件。
正如您所看到的,开始隔离构建组件既简单又快速。我们可以期望生产出更高质量、更少错误、更精细的 UI,因为可以深入挖掘并测试每种可能的状态。