# dumi_app **Repository Path**: longmo666/dumi_app ## Basic Information - **Project Name**: dumi_app - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-03-29 - **Last Updated**: 2024-05-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # dumi_app [![NPM version](https://img.shields.io/npm/v/dumi_app.svg?style=flat)](https://npmjs.org/package/dumi_app) [![NPM downloads](http://img.shields.io/npm/dm/dumi_app.svg?style=flat)](https://npmjs.org/package/dumi_app) ## 背景 由于团队缺少一个能够通用的中后台业务组件库 由于项目中通用的组件,在开发过程中经常被不同的人修改,导致线上出现全局性的保错,提取组件减少该事件发生 由于经过长年累月对组件无规范的修改,导致代码变得臃肿,难以维护。 希望通过该组件库统一多端的样式,减少不合理的 UI 设计,提升用户体验 希望通过该组件库提升研发效率 我们在项目的开发过程中都会有大量的组件,然而在多个项目中,会导致这些组件来回 copy。所以我们需要搭建自己的组件库。 ## Development ```bash # install dependencies $ pnpm install # develop library by docs demo $ pnpm start # build library source code $ pnpm run build # build library source code in watch mode $ pnpm run build:watch # build docs $ pnpm run docs:build # check your project for potential problems $ pnpm run doctor ``` ## 一个普通组件的文件构成 ![](.README_images/89326bb8.png) index.ts 来编写组件的逻辑代码,组件的入口文件 md 文件负责文档的展示,使用``标签引入 demo 中的代码展示 demo 文件包含要在文档中展示的例子,在头部注释写`title,description` dumi 会自动解析。 style 文件包含该组件对应的样式,引入我们编写的主题样式,使用`@import url(../..)`样式来引入 ## dropdown 下拉框编写 使用 rc-trigger 进行二次开发。 有两个必要属性:1.`{children必要}`2.popup 弹出层内容必要 ## Alert 警示框编写 浮层元素是指在网页或应用程序中以覆盖式展示,并且需要用户进行操作才能关闭的 UI 组件。 通常,浮层元素会在当前页面的顶部弹出一个层级较高的容器,使用户无法与页面的其他内容进行交互,直到浮层关闭或完成相应操作。 描述:用于页面展示重要的提示消息。Alert 组件不属于浮层元素,不会自动消失或关闭。 样式 CSS:width:max-content 元素会根据其内容宽度自动扩展(不受限于父元素宽度或其他因素) ## drawer 抽屉 点击一个按钮,从屏幕边缘滑出浮层面板。需要点击遮罩区或者“x”图标来关闭。 改变 open 来控制 wrapper 组件显示与隐藏,open 也控制遮罩层是否显示。 open 和 masked 初始值都是 false。 这里打印的 open 是最新改变的值,但打印的 masked 是原始记忆值。 ![](.README_images/85bf39f0.png) 如果动画不生效,考虑是否因为其他元素覆盖。 ## modal 对话框 react-transition-group 库(有三个可以引用: CSSTransition 控制显示与隐藏、SwitchTransition 控制内容切换、TransitionGroup 控制列表)。 使用 SwitchTransition 包裹 CSSTransition。 这个库让动画效果的完成方便很多。 引入:import {CSSTransition} from 'react-transition-group' 使用: ```text
``` in 是触发条件,timeout 是持续时间。 // 动画编写--直接绑定在指定元素上了。 具体 CSS 代码编写: ```css .cherry-modal-mask-enter { opacity: 0; } .cherry-modal-mask-active { opacity: 1; transition: opacity 300ms; } .cherry-modal-mask-enter-done, .cherry-modal-mask-exit { opacity: 1; } .cherry-modal-mask-exit-active { opacity: 0; transition: opacity 300ms; } ``` 默认页脚按钮定义: ```text // footer按钮类型--定义一个对象包含两个模式(即default和simple,他们的值都是ReactNode) const buttonArea: {default:ReactNode;simple:ReactNode} = { default:( <> ), simple:( <> ) } ``` 应用: ```text ``` ?? 是空值合并运算符(nullish coalescing operator)。 它的作用是判断左侧的操作数是否为 null 或 undefined,如果是,则返回右侧的操作数。否则,返回左侧的操作数。 ## menu 菜单 在 index.tsx 中定义 MenuContext 并暴露。 使用 createContext 定义一个名为 MenuContext 的上下文对象, 这个上下文的初始值是一个实现了 MenuContext 接口的对象,其中 index 属性被设置为 0。 导出这个上下文对象,其他模块可以引入并使用这个上下文对象来共享数据。 ```text import React, { createContext } from 'react'; interface IMenuContext { index:string; onSelect?:(selectedIndex: string) => void; mode?:'horizontal' | 'vertical'; defaultOpenSubMenus?:string[]; } export const MenuContext = createContext({index:'0'}) return ( ) ``` 在其他模块中引入并使用 ```text import React, { useContext } from "react"; import { MenuContext } from "."; const context = useContext(MenuContext) ``` 在 index.tsx 文件中循环遍历 menuItem 加工生成对应的元素 ```text const childrenRender = () => { return React.Children.map(children,(child,index)=>{ const childElement = child as React.FunctionComponentElement console.log(childElement); const {displayName} = childElement.type//解构幅值 if(displayName == 'MenuItem'){ return React.cloneElement(childElement,{ index: index.toString(), }) }else{ return Menu has a child which is not a MenuItem component } }) } ``` React.cloneElement 是 React 提供的一个函数,用于克隆并返回一个已有的 React 元素,并可以传递新的属性给克隆后的元素。 其语法如下: ```text React.cloneElement(element, props, ...children) ``` element:要克隆的 React 元素。 props:要添加或替换的新属性对象。 ...children:可选参数,额外的子元素。 所以父组件给子组件加了 index 属性并传递 index(通过 cloneElement),子组件中调用留在父组件的回调函数来更新 index。 subMenuItem 二级菜单,title 是一级菜单名,里面遍历生成带有 xx-xx 的 index。 ```text const renderChildren = () => { const subMenuClasses = classNames('cherry-submenu', { 'menu-opened': menuOpen, 'cherry-submenu-vertical': context.mode == 'vertical', 'submenu-item-horizontal': context.mode !== 'vertical' }) // 循环遍历生成带有index属性的元素 const childrenComponent = React.Children.map(children, (child, i) => { const childElement = child as FunctionComponentElement if (childElement.type.displayName == 'MenuItem') { return React.cloneElement(childElement, { index: `${index}-${i}` }) } else { console.error('Menu has a child which is not a MenuItem component') } }) // 返回子菜单 return ( <>{ menuOpen && (
    {childrenComponent}
)} ) } ``` ## breadcrumb 面包屑 a 标签的 target 控制是否在当前页面跳转链接,默认\_self 在当前页面打开,\_blank 在新标签页面打开。 ```text const Breadcrumb:React.FC = (props)=>{ const {objects, target='_self', ...restProps} = props; return (
    {objects?.map((obj,index)=>{ return ( <>
  • {obj.text}
  • {index !== objects.length-1 &&
  • /
  • } ) })}
) } ``` ## input 输入框 类名为 icon-wrapper 元素的下一个兄弟元素 input-inner(紧挨) ```text .icon-wrapper+.input-inner { padding-right: 35px; } ``` `outline: 0;` 是一种 CSS 属性,用于移除元素获取焦点时的默认轮廓样式。 在某些浏览器中,当元素获得焦点时,会出现一个默认的轮廓样式,通常是蓝色阴影或边框。 ```text /* 移除所有元素的焦点轮廓 */ * { outline: 0; } /* 移除特定元素的焦点轮廓 */ input:focus, button:focus { outline: 0; } ``` ``,占位文本的样式用`input{&::placeholder}`伪元素选择。 没有空格--多类名选择器 有空格--父子选择器 ## tree store 设置一个 store 文件夹,包含 index.tsx 和 node.tsx 文件。 node.tsx node.tsx 文件主要定义 NodeOptions 和 Node 类 定义 node 上的属性 如果没有子节点,那么自己选中了就是返回 true,如果有子节点,每个子节点都选中了则返回 true。 半选:在没有没全选中的情况下,有子节点被选中/半选中,则返回 true。 ![](.README_images/c9cbbc03.png) 自上向下来更新:选中/取消了父节点,要更新它下方所有子节点的状态。 自下向上来更新:选中/取消了子节点,要更新它上方所有父节点的状态。 ![](.README_images/c8231cd4.png) store 的 accordion 来控制是否只能展开一个节点。 如果为 true 的话则判断,false 的话传入什么就设置这个 node 的 collapse 是什么。 ![](.README_images/dd08ec8c.png) 封装子节点。还是采用 dfs 形式。比 index.ts 中的 createTree 多了判断 append 的步骤。 ![](.README_images/6acd8dfe.png) 追加和删除子节点 ![](.README_images/39722504.png) index.tsx Record 接受两个类型参数 K 和 T,其中 K 是一个键的集合(通常是字符串字面量类型或联合类型),T 是对应键的值的类型。 返回的类型是一个对象,其键是 K 中的每个元素,值的类型是 T。 ![](.README_images/38ea83d8.png) 创建根节点/子节点的方法,都要把 store 的值绑定成当前 Store 实例,即当前的 Store 实例作为 store 属性的值赋给新创建的 Node 对象。 `Omit`表示从原始类型 A 中排除 B 属性。 ![](.README_images/2766b9eb.png) 递归 dfs 创建树 ![](.README_images/25a942c9.png) 拍平数组(把 children 拍平) ![](.README_images/67ef7457.png) 激活 node 的 checked 属性。new Map()可以接收一个伪数组作为参数,例如[[1,1],[2,3]]就是 1->1,2->3。 ![](.README_images/b464a883.png) 基本一样的逻辑。 ![](.README_images/a838f01c.png) index.tsx 使用 useState 和 useEffect 配合,当 data 改变时重新构建树,当 tree 改变时重新创建拍平的树的列表。新建一个传入 data 的 store 实例来控制状态。 ![](.README_images/be347868.png) 根据初始化的默认点击/打开来调用 store 中的方法 ![](.README_images/220951c1.png) 判断一个节点是否展示,取决于它的父节点(祖先节点)的 collapse,如果设置为 false 则不显示(有一个折叠就是折叠了),如果遍历到最上面的父节点都是设置为展开则该节点是显示的。 ![](.README_images/09e455bf.png) 自适应高度功能。 ![](.README_images/b74e13b3.png) MutationObserver 是一个在 JavaScript 中用于监测 DOM 变化的接口。 它可以观察指定的 DOM 元素或 DOM 树的变化,并在变化发生时触发回调函数执行相应的操作。 MutationObserver 的常用方法和属性有: observe(target, options): 启动对目标元素的观察。 target 参数指定要观察的 DOM 元素,而 options 参数是一个配置对象,用于指定观察的类型(如子节点变化、属性变化等)。 通过此方法开启观察后,MutationObserver 会在目标元素变化时触发回调函数。 disconnect(): 停止对目标元素的观察。调用该方法后,MutationObserver 将不再触发回调函数,并且不再监听任何 DOM 变化。 takeRecords(): 返回当前已观察到但尚未处理的所有 DOM 变化记录的数组。 observe(...) 方法的返回值是一个 MutationObserver 实例,可以使用此实例的其他方法和属性。 在给定的代码中,MutationObserver 被用来监测 treeRef.current 的父元素的变化。 具体来说,它会检测父元素的子节点变化以及子树中任何节点的变化。 一旦变化发生,回调函数将被触发,并执行相应的操作。 offsetHeight 是元素的实际高度,经常用于动态布局、元素定位和响应式设计。 把元素实际的高度定义给这个元素达到自适应高度的效果。 创建 treeNode。 ![](.README_images/7e19b978.png) 创建每个小 li 作为 node 节点。用 style 来控制节点的显示与隐藏(showNode 函数的返回结果)。 缩进是根据 node.depth 来调节。设计一个标签来控制左边箭头的显示与隐藏。 点击箭头,这个 node 的 collapse 取反,重新构建树。 点击 CheckBox 的 checked 是根据 node.checked 设置的,点击则调用 node 绑定的 setChecked 方法,实现父节点和子节点对应的更新。 ![](.README_images/9ac6c9a7.png) 最后把拍平树的结果过滤一遍 showNode 作为中的 itemData。 ` `是由 react-window 库提供的一个组件,用于高效地渲染大量数据时进行虚拟化。 它可以帮助优化性能,减少内存消耗,并提供平滑的滚动体验。 通过将 itemCount 属性设置为渲染项的总数,再结合 itemData、itemSize 和 innerElementType 等属性,` ` 将仅渲染可见区域的项,而不是一次渲染整个列表。 这种虚拟化的方式使得在处理大数据集时能够显著提高性能和响应速度。 ![](.README_images/bf78f35b.png) ## 组件库在项目中引入组件但样式不生效 遇到这个问题说明组件库文档中引入的组件是有样式的,需要先确认文档中样式生效的原因,通常有 3 种可能: 借助 .dumi/global.less 加载了组件库样式表 借助 .dumirc.ts 中的 styles 配置项加载了组件库样式表 借助 babel-plugin-import 并将其配置到 .dumirc.ts 中按需加载了组件样式 实际上,这些样式引入方案均只对文档构建生效,也就是说它们都是依托于 dumi 框架提供的能力,而组件库发布为 NPM 包以后,组件库的编译将由实际使用组件库的项目负责。 因此,我们需要根据项目使用的开发框架做等价配置,才能确保样式生效,此处以 Umi 项目为例,上述 3 种方案的等价配置方式如下: 借助 src/global.less 加载组件库样式表 借助 .fatherrc.ts 中的 styles 配置项加载组件库样式表 借助 babel-plugin-import 并将其配置到 .fatherrc.ts 中按需加载组件样式 最佳解决方案: 在 .fatherrc.ts 中添加配置 ```text extraBabelPlugins: [ [ 'babel-plugin-import', { libraryName: 'antd', libraryDirectory: 'es', style: true, }, ], ], ``` ## 部署文档 文档部署在哪里是一个问题,对于大部分的人来说,可能没有精力去维护一个静态服务器, 所以我们的目标是将文档部署到 githubPages,根据 dumi 文档描述,这样的情况属于非根目录部署。 部署到 githubPages 非根目录部署需要修改 Umi 的 base 配置项 和 视实际情况 修改 publicPath 配置项。 意思就是说,在 github 上的项目名称为 tst-design,那么就要在.dumirc 文件中的属性 base 和 publicPath 改成: ```text export default defineConfig({ base: '/tst-design', publicPath: '/tst-design/', }) ``` ### 手动步骤 注意手动部署文档时操作如下: 先安装 gh-pages ```sh yarn add gh-pages -D ``` package.json 中添加 ```text "scripts": { "deploy": "gh-pages -d docs-dist" } ``` 运行 yarn docs:build 运行 deploy 运行完后你在看下 github 上会多出一个 gh-pages 分支。 部署成功后的文档地址位置如下图,找到项目点击 Actions,成功会给你一个地址: ![](.README_images/939acb39.png) 只有这样才能访问文档页面。 ### 自动化部署 当项目有多个贡献者时,一些贡献者不可避免会犯一些流程错误。 比如提交代码时,没有尝试对项目进行生产模式的构建等等,我们需要在提交代码到 git 远程仓库时去做一些流程性的任务,也就是我们常说的 ci/cd 或者流水线。 所以我们将采用自动化部署也就是 Git Actions,让它帮执行单元测试和代码校验、github 的同步、文档的部署。 具体操作如下: 创建 .github/workflows/gh-pages.yml 文件 gh-pages.yml 模板如下: ```yml name: github pages on: push: branches: - main # default branch jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - run: yarn # 文档编译命令,如果是 react 模板需要修改为 npm run docs:build - run: yarn docs:build - name: Deploy uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.MY_TOKEN }} # 文档目录,如果是 react 模板需要修改为 docs-dist publish_dir: ./docs-dist ``` ## 发布 https://github.com/umijs/father/blob/master/docs/guide/release.md 通常仅需 4 步即可完成普通 NPM 包发布,monorepo 项目请参考各自 monorepo 方案的发包实践。 前置工作 执行 npm whoami 查看当前用户是否已经登录,如果未登录则执行 npm login 检查 package.json 中的 NPM 包名及 publishConfig 是否符合预期 更新版本号 使用 npm version 命令更新版本号,例如: # 发布一个 patch 版本 $ npm version patch -m "build: release %s" 该命令将会自动生成 git tag 及 git commit,并将版本号更新到 package.json 中。更多用法可参考 NPM 文档:https://docs.npmjs.com/cli/v8/commands/npm-version 构建及发布 father 4 的脚手架默认已将 项目体检命令 及构建命令配置到 prepublishOnly 脚本中: ```markdown "scripts": { ... - "prepublishOnly": "father doctor && npm run build" }, ``` 所以我们只需要执行发布即可: # NPM 会自动执行 prepublishOnly 脚本然后完成发布 $ npm publish 后置工作 **功能验证:**使用测试项目下载新发布的 NPM 包,验证功能是否正常 **更新日志:**将本次发布的变更通过 GitHub 的 Release Page 进行描述,也可以选择在前置工作中将变更描述写入 CHANGELOG.md 文件(未来 father 会提供自动化的更新日志生成能力,敬请期待) ## 主题美化 ```sh pnpm i dumi-theme-antd-style -D pnpm i classnames ahooks eventemitter3 ``` ## 实现对 less 的处理 ```sh pnpm i postcss-less -D ```