当前位置: 首页 > Web前端 > HTML

从头开始构建React组件库

时间:2023-03-28 18:20:52 HTML

介绍为了统一维护之前业务开发的组件,方便后续在其他项目中复用,为此构建了组件库。由于之前开发的项目是基于React实现的,经过研究,决定选择比较常用的Dumi作为组件库文档工具,Father作为组件库打包工具。Dumi:https://d.umijs.org/zh-CNF父亲:https://github.com/umijs/fatherprojectbuildmkdircomponent-lib-democdcomponent-lib-demonpx@umijs/create-dumi-lib--site——初始化一个站点模式的组件库开发脚手架初始目录如下,其中.fatherrc.ts为打包配置文件,.umirc.ts为组件库文档配置文件。组件编写通用组件以开发排行榜RankList组件为例。编写组插件/src/RankList/index.tsximportReactfrom'react';import'./index.less';interfaceRankListProps{data:{label:string;值:字符串|number}[];}functionRankList({data}:RankListProps){return({data.length?(

    {data.filter((_,index)=>索引<10).map(({label,value},index)=>({index+1}
{label||'--'}
{value}
))}):(无数据
)});}导出默认RankList;写组件样式/src/RankList/index.less.rank-list{position:relative;height:100%;ul{margin:0px;padding:0px;li{height:24px;margin:16px0px;display:flex;align-items:center;.rank{flex-shrink:0;display:flex;justify-content:center;width:20px;height:20px;line-height:20px;radius:50%;}.name{flex:1;}}}.empty{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);}}at导出组件在入口文件/src/index.tsexport{defaultasRankList}from'./RankList';编写组件文件/src/RankList/index.md---title:RankList排行榜nav:title:组件路径:/componentsgroup:路径:/components---#RankList排行榜组件用于简单的排行榜业务场景##基本使用/src/RankList/demos/index.tsximportReactfrom'react';import{RankList}from'component-lib-demo';functionRankListDemo(){constdata=Array.from(newArray(10)).map((_,idx)=>({标签:`选项${idx+1}`,值:10-idx,}));return(
);}exportdefaultRankListDemo;配置别名,防止在demo中通过项目名引入组件时显示编译错误。/tsconfig.json{"compilerOptions":{//..."paths":{"@/*":["src/*"],"@@/*":["src/.umi/*"],"component-lib-demo":["src/index.ts"]},},//...}效果基于Antd封装的组件以开发一个倒计时按钮CountdownButton组件为例。安装配置相关依赖安装antd相关依赖npmi-Dantdbabel-plugin-import配置按需加载/.umirc.tsexportdefaultdefineConfig({//...extraBabelPlugins:[['babel-plugin-import',{libraryName:'antd',libraryDirectory:'es',style:true,},],],//...});编写组件逻辑/src/CountdownButton/index.tsximportReact,{useState,useEffect}from'react';import{Button}from'antd';import{ButtonProps}from'antd/es/button';constMAX_SECOND_NUM=60;接口CountdownButtonTypeextendsOmit{/***最大秒数*/maxSecondNum?:number;/***按钮默认文本*/txt?:string;/***加载按钮文本*/loadingTxt?:string;/***禁用按钮文本*/disabledTxt?:(s:number)=>string;/***点击按钮时触发的函数,其参数completeCallback需要在接口请求完成后调用,用于通知组件接口请求已经完成。*/onClick:(completeCallback:()=>void)=>void;}functionCountdownButton({maxSecondNum=MAX_SECOND_NUM,txt='获取验证码',loadingTxt='发送中',disabledTxt=(s)=>`${s}秒后重试`,onClick=(completeCallback)=>{completeCallback();},...rest}:CountdownButtonType){const[authCodeArgs,setAuthCodeArgs]=useState({timing:false,count:maxSecondNum,});useEffect(()=>{lettimer:number|undefined=undefined;if(authCodeArgs.timing){timer=window.setInterval(()=>{setAuthCodeArgs((pre)=>{const{count,timing}=pre;if(count===1){window.clearInterval(timer);return{timing:false,count:maxSecondNum};}return{timing,count:count-1};});},1000);}返回()=>window.clearInterval(timer);},[authCodeArgs.timing]);constcompleteCallback=()=>{setAuthCodeArgs({...authCodeArgs,计时:真,});};让按钮文本;如果(rest.loading){buttonText=loadingTxt;}elseif(authCodeArgs.timing){buttonText=disabledTxt(authCodeArgs.count);}else{buttonText=txt;}return({onClick&&onClick(completeCallback);}}{...rest}>{buttonText});}exportdefaultCountdownButton;导出入口文件中的组件/src/index.tsexport{defaultasRankList}from'./RankList';export{defaultasCountdownButton}from'./CountdownButton';编写组件文档/src/CountdownButton/index.md---title:CountdownButton倒计时按钮nav:title:componentpath:/componentsgroup:path:/components---#CountdownButton倒计时按钮倒计时按钮常用于业务场景如如获取手机和邮箱验证码。##基本使用除了以上API,倒计时按钮还支持Button组件(AntDesign)的所有API。/src/CountdownButton/demos/index.tsximportReact,{useState}from'react';import{CountdownButton}from'component-lib-demo';functionCountdownButtonDemo(){const[loading,setLoading]=useState(错误的);constgetCode=async()=>{setLoading(true);try{returnawaitnewPromise((resolve)=>setTimeout(()=>{resolve(123);},1000),);}catch(err){thrownewError('失败');}最后{setLoading(false);}};return({constcode=awaitgetCode();console.log(`验证码:${code}`);completeCallback();}}>获取验证码);}exportdefaultCountdownButtonDemo;修改.umirc.ts配置,过滤掉antd组件的interface属性,防止最终生成的API文档包含antd组件自身的属性。从'dumi'导入{defineConfig};exportdefaultdefineConfig({//...extraBabelPlugins:[['babel-plugin-import',{libraryName:'antd',libraryDirectory:'es',style:true,},],],apiParser:{//自定义属性过滤配置,也可以是函数,用法参考:https://github.com/styleguidist/react-docgen-typescript/#propfilterpropFilter:{//是否忽略propFilter从node_modules继承的属性,默认值为假skipNodeModules:true,},},//...});效果组件打包发布打包配置/src/.fatherrc.tsexportdefault{esm:'babel',//可以通过babel编译相关组件,不用打包成文件,这样使用的时候可以按需加载。cjs:'babel',lessInBabelMode:true,//lesstocss//打包后的产品如果需要导入antd,应该通过按需加载的方式导入。extraBabelPlugins:[['babel-plugin-import',{libraryName:'antd',libraryDirectory:'es',style:true,},],],};为防止打包时可能出现类型错误,建议安装@Types/react、@types/react-dom等相关依赖:npmi-D@types/react@types/react-dom配置声明选项在tsconfig.json的compilerOptions字段中设置为true,以便在打包文档时生成相应的类型声明。/tsconfig.json{"compilerOptions":{//..."declaration":true,//...},//...}执行打包操作npmrunbuild发布package.json配置{"name":"component-lib-demo","version":"1.0.0","scripts":{"start":"dumidev","docs:build":"dumibuild","docs:deploy":"gh-pages-ddocs-dist","build":"father-build","deploy":"npmrundocs:build&&npmrundocs:deploy","release":"npmrunbuild&&npmpublish","prettier":"prettier--write\"**/*.{js,jsx,tsx,ts,less,md,json}\"","test":"umi-test","test:coverage":"umi-test--coverage"},"main":"lib/index.js","module":"es/index.esm.js","typings":"lib/index.d.ts","gitHooks":{"pre-commit":"lint-staged"},"lint-staged":{"*.{js,jsx,less,md,json}":["prettier--write"],"*.ts?(x)":["prettier--parser=typescript--write"]},"files":["es","lib"],"peerDependencies":{"antd":">=4.0.0","反应":">=16.9.0","react-dom":">=16.9.0"},"dependencies":{"react":"^16.12.0||^17.0.0"},"devDependencies":{"@types/react":"^17.0.37","@types/react-dom":"^17.0.11","@umijs/test":"^3.0.5","antd":"^4.17.2","babel-plugin-import":"^1.13.3","dumi":"^1.0.17","father-build":"^1.17.2","gh-pages":"^3.0.0","lint-staged":"^10.0.7","prettier":"^2.2.1","yorkie":"^2.0.0"}}关注以下字段:main:指定包入口文件,这里指定为lib/index.jsmodule:基于ESM规范的指定包入口文件,这里指定为es/index。esm.js.typings:指定包的类型声明文件,这里指定为lib/index.d.ts.files:指定需要推送到npm的文件,这里指定为["es","lib".peerDependencies:指定使用包的项目需要依赖的模块,由于项目是React项目,并且有依赖Ant-Design组件库的组件,所以这里指定为react,react-dom,andantd.如果包npmpublish是第一个发布包,需要先通过npmadduser添加用户。如果没有登录,需要先使用npmlogin登录,然后才能执行npmpublish操作。组件文档deployment.umirc.tsimport{defineConfig}from'dumi';exportdefaultdefineConfig({//...base:'/component-lib-demo/docs-dist/',publicPath:'/component-lib-demo/docs-dist/',history:{type:'hash',//设置路由模式为hash模式,防止部署到GitHubPages后刷新网页404。},//...});设置base、publicPath、history,方便后续部署到GithubPages。生成组件文档npmrundocs:build取消忽略/docs-dist目录,后续GithubPages需要这个目录。同时将打包后的产品(lib、es)添加到ignore文件中(这两个目录不需要push到Git仓库)。.gitignore#/docs-dist/lib/es提交更改并推送到GitHub存储库。配置GithubPages访问https://hwjfqr.github.io/component-lib-demo/docs-dist/#/,当页面显示如下时,说明大功告成!除了以上基本用法,嘟嘟和爸爸还有更多个性化的配置操作,具体请参考相关官方文档。Dumi:https://d.umijs.org/zh-CNF父亲:https://github.com/umijs/father本文演示的项目源码地址为https://github.com/hwjfqr/component-lib-demo如果您有任何问题,请评论或提出问题。如果您觉得本文对您有帮助,不妨在离开前点个赞,谢谢!同时安利上一波基于AntDesign封装的业务组件库:https://github.com/hwjfqr/ant-design-power,欢迎使用。