UNPKG

utquidem

Version:

The meta-framework suite designed from scratch for frontend-focused modern web development.

692 lines (514 loc) 18.9 kB
--- sidebar_position: 10 --- # 开发 UI 组件 本章将介绍如何使用 Modern.js 进行 UI 组件项目的开发。本章对应的代码仓库地址:[独立项目场景](https://github.com/modern-js-dev/modern-js-examples/tree/main/quick-start/components-project) 、[Monorepo 场景](https://github.com/modern-js-dev/modern-js-examples/tree/main/quick-start/monorepo-component)。 :::info 注 在 Modern.js 中,UI 组件项目是指基于 React 开发组件类型可复用模块的项目。 ::: 通过本章你可以了解到: - 如何创建一个 UI 组件项目。 - 如何在 UI 组件项目中进行测试。 - 如何为 UI 组件项目开启 Storybook 功能并使用它进行调试。 - 如何开发 UI 组件样式。 - 如何在 UI 组件项目中使用[运行时 API](/docs/apis/runtime/overview)。 - 如何发布 UI 组件项目。 - 在 Monorepo 中,UI 组件项目与应用项目如何联调。 ## 环境准备 import EnvPrepare from '@site/docs/components/env-prepare.md'; <EnvPrepare /> ## 创建项目 使用 `@modern-js/create` 创建新项目,运行命令如下: ```bash npx @modern-js/create components-project ``` :::info 注 components-project 为创建项目的目录名称。 ::: 按照如下选择,生成项目: ```bash ? 请选择你想创建的工程类型 模块 ? 请填写项目名称 components ? 请选择开发语言 TS ? 请选择包管理工具 pnpm ? 是否需要调整默认配置? 否 ``` :::info 注 项目名称为 `package.json` 中的 `"name"` 字段值。 ::: ### 修改默认文件和代码 1. 将 `src/index.ts` 文件重命名为 `src/index.tsx`,以支持 JSX 语法。 2. 将文件代码替换为: ``` tsx export default function () { return <div>This is a UI Component</div>; } ``` ### 修改默认测试文件和代码 1. 将 `tests/index.test.ts` 文件重命名为 `tests/index.test.tsx`,以支持 JSX 语法。 2. 将测试代码替换为: ``` tsx import { render, screen } from '@modern-js/runtime/testing'; import Component from '@/index'; describe('默认值 cases', () => { test('Rendered', () => { render(<Component />); expect(screen.getByText('This is a UI Component')).toBeInTheDocument(); }); }); ``` ### 新增 `styles/` 目录 在项目根目录创建 `styles/` 目录,该目录用于存放[独立样式](#开发独立样式)文件。 到此为止,一个组件项目创建成功。 ## 测试 项目创建成功之后,我们可以使用 Modern.js 提供的 `test` 命令对替换的组件代码进行测试,命令如下: ```bash pnpm run test ``` ![component-test-result](https://lf3-static.bytednsdoc.com/obj/eden-cn/aphqeh7uhohpquloj/modern-js/start/component/test-result.png) ## IDE import DevIDE from '@site/docs/components/dev-ide.md' <DevIDE/> ## 添加组件代码 接下来添加真正的组件代码。这里以一个 `TableList` 组件举例。 ### 准备工作 :::info 注 这里基于 [Ant Design](https://ant.design/) 组件库进行 UI 组件的开发。你也可以使用其他组件库,或不使用任何组件库。 ::: 1. 安装 [Ant Design](https://ant.design/) 依赖:`pnpm add antd`2. 由于需要使用 [Antd](https://ant.design/index-cn) 的 Less 样式,需要先开启 Modern.js 对 Less 的支持: ``` ? 请选择你想要的操作: 启用可选功能 ? 启用可选功能: 启用 Less 支持 ``` ### 新增 `TableList` 组件 修改 `src/index.tsx` 文件,增加以下代码: ``` tsx import type React from 'react'; import { Table } from 'antd'; export const TableList: React.FC = () => { const columns = [ { title: 'Name', dataIndex: 'name', key: 'name', }, { title: 'Age', dataIndex: 'age', key: 'age', }, { title: 'Country', dataIndex: 'country', key: 'country', }, ]; const data = [ { key: '1', name: 'John Brown', age: 32, country: 'America', }, { key: '2', name: 'Jim Green', age: 42, country: 'England', }, { key: '3', name: 'Ming Li', age: 30, country: 'China', }, ]; return ( <div className="table-list table-theme"> <Table columns={columns} dataSource={data} /> </div> ); }; ``` ### 新增 `TableList` 组件样式 创建 `src/tableList.less` 文件,并增加以下内容: ``` less .table-list { border: 1px solid #ccc; padding: 5px; } ``` > 这里为表格的父容器增加了内边距以及边框 然后更新 `src/tableList.tsx` 文件。将 `tableList.less` 样式文件导入到 `src/tableList.tsx` 文件中: ``` tsx {4} import type React from 'react'; import { Table } from 'antd'; import './tableList.less'; // ... ``` ### 新增 `styles/tableTheme.less` 文件 创建 `styles/tableTheme.less` 样式文件,并增加以下内容: ``` less .table-theme { background: beige; } ``` ## 启用 Storybook 调试 在添加完组件代码后,可以使用 Modern.js 提供的 Storybook 功能,查看效果并进行调试。 在项目根目录下,执行 `pnpm run new`,可以开启 Storybook 功能: ```bash ? 请选择你想要的操作 启用可选功能 ? 启用可选功能 启用「Storybook」 ``` 启用成功后,会自动创建 `stories/` 目录。 然后更改默认的 Story 代码: ``` tsx import { TableList } from '@/index'; import '@styles/tableTheme.less'; export const YourStory = () => <TableList />; export default { title: 'Your Stories', }; ``` 执行 `pnpm run dev` 查看运行结果: ![调试 storybook](https://lf3-static.bytednsdoc.com/obj/eden-cn/aphqeh7uhohpquloj/modern-js/start/component/storybook.png) ## 开发组件样式 在开发 `TableList` 组件的过程中,通过三种方式为组件添加样式。接下来介绍这三种方式。 ### 使用组件库样式 在开发 `TableList` 组件过程中,使用 [Ant Design](https://ant.design/index-cn) 组件库中的 `Table` 组件,基于 `Table` 组件提供基础的样式进行二次开发。像这类第三方 UI 组件库中包含一些可复用的样式,使用这类组件库提供的组件在开发中可以节省很多时间。 ### 开发内置样式 在 Modern.js 中,位于 `src` 目录下的样式称为组件的内置样式。 :::info 注 关于更多内置样式的内容,请参考【[什么是内置样式](/docs/guides/features/modules/code-style#什么是内置样式)】章节。 ::: 在实现 `TableList` 组件的过程中,`src/tableList.less` 样式文件就是 `TableList` 组件的内置样式。在 Modern.js 中推荐内置样式在组件代码中引用,例如 `tableList.tsx` 文件中有如下一段代码: ``` tsx import './tableList.less'; ``` :::info 注 Modern.js 对于在组件代码中引用内置样式文件的情况,在构建组件代码的过程中会进行一些特殊的处理,更多内容请参考【[内置样式与独立样式的构建产物](/docs/guides/features/modules/code-style#内置样式与独立样式的构建产物)】。 ::: ### 开发独立样式 在 Modern.js 中,位于 `styles` 目录下的样式称为组件的独立样式。 :::info 注 关于更多独立样式的内容,请参考【[什么是独立样式](/docs/guides/features/modules/code-style#什么是独立样式)】章节。 ::: 在实现 `TableList` 组件的过程中,`styles/tableTheme.less` 样式文件就是 `TableList` 组件的独立样式。独立样式的使用方式类似 Ant Design 组件库中组件样式的使用方式,分为: - 通过 [babel-plugin-import](https://github.com/ant-design/babel-plugin-import) **按需加载**组件的独立样式文件产物。 - 手动导入组件的独立样式文件产物。 :::info 注 关于 Ant Design 的按需加载,可以参考 Ant Design 官网【[按需加载](https://ant.design/docs/react/getting-started-cn#%E6%8C%89%E9%9C%80%E5%8A%A0%E8%BD%BD)】章节内容。 ::: ## 转换为组件库 目前项目只包含一个组件。如果要创建组件库项目,只需要重新组织项目的目录结构,增加更多的组件即可。例如将当前 `src` 目录结构更改为: ```md . ├── src/ │ └── index.tsx | └── tableList | ├── tableList.less | └── index.tsx | └── avatar | └── index.tsx | ... ``` 其中 `src/tableList/index.tsx` 包含了 `TableList` 组件的代码。 新建 `src/avatar/index.tsx` 文件,并添加组件代码: ``` tsx import { Avatar } from 'antd'; export default ({ name }: {name: string}) => { return <Avatar>{name}</Avatar> } ``` 修改 `src/index.tsx` 文件,将所有组件导出: ``` tsx export { TableList } from './tableList'; export { default as Avatar } from './avatar' ``` 此时组件项目就变成了组件库项目。 ## 使用 Runtime API Modern.js 通过 `@modern-js/runtime` 模块提供运行时 API。下面通过创建状态管理层,即 Model 层,演示如何在 UI 组件项目中使用 Modern.js Runtime API。接下来将新建一个 Model 对象,调用网络 API 动态获取数据,并提供给 `TableList` 组件使用。 首先,在 `TableList` 组件同一层级的目录下面创建 `./tableListModel.tsx` 文件,定义 Model 对象,代码如下: ``` tsx import { model } from '@modern-js/runtime/model'; type State = { data: { key: string; name: string; age: number; country: string; }[]; }; export default model<State>('tableList').define({ state: { data: [], }, actions: { load: { fulfilled(state, payload) { return { data: payload }; }, } }, effects: { async load() { const data = await (await fetch('https://lf3-static.bytednsdoc.com/obj/eden-cn/beeh7uvzhq/users.json')).json(); return data; }, }, }); ``` 然后,在 `TableList` 组件文件中导入 `tableListModel.tsx` 文件,并使用导入的 `tableListModel` 对象: ``` tsx import type React from 'react'; import { useEffect } from 'react'; import { Table } from 'antd'; import { useModel } from '@modern-js/runtime/model'; import tableListModel from './tableListModel'; import './tableList.less'; export const TableList: React.FC = () => { const columns = [ { title: 'Name', dataIndex: 'name', key: 'name', }, { title: 'Age', dataIndex: 'age', key: 'age', }, { title: 'Country', dataIndex: 'country', key: 'country', }, ]; const [{data}, {load}] = useModel(tableListModel); useEffect(() => { load(); }, []) return ( <div className='table-list table-theme'> <Table columns={columns} dataSource={data} /> </div> ); }; ``` :::info 补充信息 关于 Model 的详细介绍,请参考【[添加业务模型](/docs/guides/tutorials/c10-model/10.1-application-architecture)】。 ::: 接着新增 `modern.config.js` 文件并添加配置 [`runtime.state`](/docs/apis/config/runtime/state) 开启支持 Model 功能: ``` javascript title=modern.config.js export default defineConfig({ runtime: { state: true, }, }; ``` 最后重新启动 Storybook 调试程序观察效果,可以看到 `TableList` 组件上展示的数据内容是从接口返回的动态数据: ![component-with-model](https://lf3-static.bytednsdoc.com/obj/eden-cn/aphqeh7uhohpquloj/modern-js/start/component/storybook-with-fetch.png) ## 发布 组件功能开发完成后,可对 UI 组件项目进行发布。 发布分以下四个步骤: 1. 添加 changeset 执行 `pnpm run change`,根据提示选择升级的版本,并填写变更信息。 ![填写变更信息](https://lf3-static.bytednsdoc.com/obj/eden-cn/aphqeh7uhohpquloj/modern-js/start/component/changeset.png) 2. 升级对应版本号,并生成 changelog 执行 `pnpm run bump`,该命令会根据上述生成的 changeset 自动更新版本号和 CHANGELOG 信息,检查信息无误后提交。 3. 发布 执行 `pnpm run release`, 发布该 UI 组件包。 4. 推送 tags 发布完成之后执行 `git push --follow-tags`,推送当前发布对应生成的 git tag。 ## 迁移到 Monorepo 在团队协作开发中,也会存在使用 Monorepo 进行项目开发的情况。接下来讲一下如何将组件项目在 monorepo 中的一些使用方式。 ### 创建 Monorepo 使用 `@modern-js/create` 创建 Monorepo 项目,运行命令如下: ```bash npx @modern-js/create monorepo ``` :::info 注 monorepo 即为创建的 Monorepo 目录名称,又是项目的名称。 ::: 按照如下选择,生成项目: ```bash ? 请选择你想创建的工程类型 Monorepo ? 请选择包管理工具 pnpm ``` 生成的项目目录结构如下: ```bash . ├── .changeset │ └── config.json ├── .editorconfig ├── .gitignore ├── .npmrc ├── .nvmrc ├── .pnpmfile.cjs ├── .vscode │ ├── extensions.json │ └── settings.json ├── README.md ├── apps │ └── .gitkeep ├── features │ └── .gitkeep ├── monorepo.code-workspace ├── package.json ├── packages │ └── .gitkeep ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── tsconfig.json ``` ### 迁移组件项目 这里只需要将之前创建的组件项目复制到 `packages` 目录下面,并删除 `.change/``.vscode/``.git/` 目录及 `.editorconfig``.gitignore``.nvmrc``.npmrc` 文件。 ```md . ├── packages/ │ └── components/ │ ├── README.md │ ├── modern.config.js │ ├── package.json │ ├── src │ │ ├── avatar │ │ │ └── index.tsx │ │ ├── index.ts │ │ ├── modern-app-env.d.ts │ │ └── tableList │ │ ├── index.tsx │ │ ├── tableListModel.tsx │ │ └── tableList.less │ ├── stories │ │ ├── index.stories.tsx │ │ └── tsconfig.json │ ├── styles │ │ └── tableTheme.less │ ├── tests │ │ ├── index.test.tsx │ │ ├── modern-app-env.d.ts │ │ └── tsconfig.json │ └── tsconfig.json │ ... ``` ### 创建子项目 Modern.js 支持 Monorepo 工程方案的管理,可以在 Monorepo 项目下通过 `new` 命令创建不同类型的子项目。例如在刚刚创建的 Monorepo 项目根目录下执行: ``` pnpm run new ``` 在刚刚创建的 Monorepo 项目根目录下执行 `pnpm run new`,然后分别选择创建 「应用」,「模块(内部)」项目: ```bash ? 请选择你想创建的工程类型 应用 ? 请填写子项目名称 app ? 请填写子项目目录名称 app ? 请选择开发语言 TS ? 是否需要支持以下类型应用 不需要 ? 是否需要调整默认配置? 否 ``` ```bash ? 请选择你想创建的工程类型 模块(内部) ? 请填写子项目名称 internal-lib ? 请填写子项目目录名称 internal-lib ? 请选择开发语言 TS ? 是否需要调整默认配置? 否 ``` ### 在应用项目中使用组件以及内部模块 接下来在 `app` 项目中通过以下方式把组件项目加到依赖中: ```bash cd ./apps/app pnpm add components ``` 此时可以观察到 app 项目的 `package.json` 内容更新如下: ``` json {4} { "dependencies": { "@modern-js/runtime": "^1", "components": "workspace:^0.1.0", "react": "^17.0.1", "react-dom": "^17.0.1" } } ``` 接下来导入内部模块 `internal-lib`,由于内部模块并不需要进行发布,因此通过如下方式添加到项目中: > 内部模块是指不需要发布到 npm 上的项目,它们只提供源码给应用项目使用,应用项目会将它们打包到构建产物中。 ```bash cd ./apps/app pnpm add internal-lib -D ``` 此时可以观察到 app 项目的 `package.json` 内容更新如下: ``` json {9} { "devDependencies": { "@modern-js/app-tools": "^1", "@modern-js/plugin-jarvis": "^1", "@types/jest": "^26.0.9", "@types/node": "^14", "@types/react": "^17", "@types/react-dom": "^17", "internal-lib": "workspace:^0.1.0", "typescript": "^4" }, } ``` 此时在 `app` 项目下的 `src/App.tsx` 文件引用 `components``TableList` 组件以及 `internal-lib` 模块,并使用它们: ``` tsx import { Switch, Route } from '@modern-js/runtime/router'; import { TableList } from 'components'; import sayHelloWorld from 'internal-lib'; import './App.css'; const App = () => ( <Switch> <Route exact={true} path="/"> <div className="container"> <main> {/* //... */} </main> <TableList /> {sayHelloWorld()} <footer className="footer"> <a href="#" target="_blank" rel="noopener noreferrer"> Powered by Modern.JS </a> </footer> </div> </Route> <Route path="*"> <div>404</div> </Route> </Switch> ); export default App; ``` 然后我们在 `apps/app` 目录下执行 `dev` 命令,可以看到组件被正确渲染出来: ![app-tools](https://lf3-static.bytednsdoc.com/obj/eden-cn/aphqeh7uhohpquloj/modern-js/start/component/app-tools.png) ### 批量发布 我们可以在 Monorepo 中再创建一个 UI 组件项目(以创建 Button 组件为例): 在 Monorepo 项目根目录下执行: ```bash pnpm run new ``` ```bash ? 请选择你想创建的工程类型 模块 ? 请填写子项目名称 component2 ? 请填写子项目目录名称 compomemt2 ? 请选择开发语言 TS ? 是否需要调整默认配置? 否 ``` 删除 `packages/component2/src/index.ts` 文件,新建 `packages/component2/src/index.tsx` 文件,增加 Button 组件: ```ts import Button from 'antd/es/button'; export default function () { return <Button />; } ``` 开发完成后可以在 Monorepo 的场景下对所有 UI 组件库做批量发布: #### 添加 changeset 在 Monorepo 根目录执行 `pnpm run change`,根据提示选择发布的包(注意这里只选择 UI 组件包名)和升级的版本,并填写变更信息。 ![monorepo 添加 changeset](https://lf3-static.bytednsdoc.com/obj/eden-cn/aphqeh7uhohpquloj/modern-js/start/component/monorepo-changeset.png) #### 升级发布包对应版本号,并生成 changelog 执行 `pnpm run bump`,该命令会根据上述生成的 changeset 自动更新版本号和 CHANGELOG 信息,检查信息无误后提交。 #### 发布 执行 `pnpm run release`, 发布对应的多个 UI 组件包。 #### 推送 tags 发布完成之后执行 `git push --follow-tags`,推送当前发布对应生成的 [Git Tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging)。