前端框架,快速开发页面,函数式编程,与后端api快速搭建
ES-lint react的代码规范库
1 yarn add eslint eslint-plugin-react
如果是typescript项目按照ts相关插件
1 yarn add @typescript-eslint/eslint-plugin @typescript-eslint/parser
使用yarn eslint –lint向导来完成配置,或者手动创建eslintrc。json填入如下配置
1 2 3 4 5 6 7 8 { "extends" : ["eslint:recommended" ,"plugin:react/recommended" ], "parser" : "@typescript-eslint/parser" , "plugins" : ["react" ,"@typescript-eslint" ], "rules" : { "react/self-closing-comp" : ["error" ] } }
在vscode中配置
1 2 3 4 5 6 "eslint.validate": [ "javascript", "javascriptreact", "typescript", "typescriptreact" ]
Prettier Prettier是
一个代码格式工具,支持基本主流前端的语言(js, ts, Es6, Es7,markdown等等);
会根据书写的代码,重新解析和构建显示格式(即,它清除原来的格式,按照自己认为美丽的方式重新显示代码格式)
Prettier 不会像EsLint,TsLint,StyleLint 那样告诉你,语法哪里错了,它只会告诉你代码这么写不美观
编写.prettierrc
1 2 3 4 5 6 7 8 9 10 11 { "singleQuote": true, "trailingComma": "all", "printWidth": 80, "overrides": [ { "files": ".prettierrc", "options": { "parser": "json" } } ] }
stylelint stylelint是现代化的前端项目中一个强大的代码检查工具。可以帮忙检查样式文件并在样式中强制执行约定。
stylelint 默认地能解析 如下的非标准语法,包括Sass、Less 和 SugarSS,非标准语法可以从以下文件扩展名 .sass
、.scss
、.less
和 .sss
中自动推断出来。或者您也可以自己指定语法。
此外,在使用命令行界面或 Node.js 应用程序接口时,stylelint 可以接受任何PostCSS兼容语法 。但请注意,stylelint 无法保证核心规则可以在上面列出的默认值以外的语法中正常工作
lint-staged lint-staged 是一个在git暂存文件上运行linters的工具,当然如果你觉得每次修改一个文件就给所有文件执行一次lint检查不恶心的话,这个工具对你来说就没有什么意义了,请直接关闭即可
配置
在package.json中配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 { "name" : "My project" , "version" : "0.1.0" , "scripts" : { "my-custom-script" : "linter --arg1 --arg2" }, "husky" : { "hooks" : { "pre-commit" : "lint-staged" } }, "lint-staged" : { "*.{js}" : [ "eslint --cache --fix" , "prettier --write" ], "*.css" : [ "stylelint --cache --fix" , "prettier --write" ] } }
使用prettier
自动修复javascript
、typescript
、markdown
、HTML
或CSS
的代码样式
1 2 3 { "*.{js,jsx,ts,tsx,md,html,css}": "prettier --write" }
Stylelint用于具有默认值的CSS和具有SCSS语法的SCSS
1 2 3 4 { "*.css": "stylelint", "*.scss": "stylelint --syntax=scss" }
自动修复代码
1 2 3 { "*.js": "eslint --fix" }
过滤文件原则
Linter命令处理由glob模式定义的所有暂存文件的子集
如果全局模式不包含斜杠(/
),matchBase则将启用micromatch的选项,因此无论目录如何,全局匹配文件的基本名称:
"*.js"
将匹配所有JS文件,例如/test.js
和/foo/bar/test.js
"!(*test).js"
。将匹配所有以结尾的JS文件test.js
,因此foo.js
但不匹配foo.test.js
如果全局模式确实包含斜杠(/
),则它也将与路径匹配:
"./*.js"
将匹配git repo根目录中的所有JS文件,因此/test.js
但不匹配`/foo/bar/test.js``
``”foo/*/\ .js”将匹配
/foo目录中的所有JS文件,所以
/foo/bar/test.js但不匹配
/test.js`
JSdoc JSDoc 是一个针对 JavaScript 的 API 文档生成器,类似于 Java 中的 Javadoc 或者 PHP 中的 phpDocumentor;在源代码中添加指定格式的注释,JSDoc 工具便会自动扫描你的代码并生成一个 API 文档网站(在指定目录下生成相关的网页文件)
生成 API 文档只是一方面,其更主要的贡献在于对代码注释格式进行了规范化
安装
在js文件中写入对应的函数和注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function sum (a, b ) { return a + b; } function diff (a, b ) { return a - b; }
然后就是在当前目录执行以下命令
最后就会在当前目录下生成一个名为 out
的目录(也可以另外指定),里面有包含接口文档的html页面
常用写法:
@description:也可写作 @desc
,描述当前注释对象的详细信息
@file:注释写在文件开头,用于描述当前文件的相关信息
@class 描述一个 class
类
@returns 或者写作 @return
,描述函数的返回值的信息;
@param 与 @arg
, @argument
含义相同,描述一个函数的参数信息;
@function 与 @func
, @method
含义相同,描述一个函数;
@todo 描述接下来准备做的事情;
@copyright 描述当前文件的版权相关信息
@file 注释写在文件开头,用于描述当前文件的相关信息
react的Ts写法 react、react-dom类型声明文件 使用tsx之前要安装react的声明文件,否则会报错找不到模块react
安装
1 2 npm install @types/react -s npm install @types/react-dom -s
有状态组件 有状态组件中的state和props使用ts去定义类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import * as React from 'react' interface IProps { color: string, size?: string } interface IState { count: number, } class App extends React.PureComponent<IProps, IState> { public readonly state: Readonly<IState> = { count: 1 } public render () { return ( <div>Hello world</div> ) } public componentDidMount () { } }
事件类型 常用Event事件对象类型
ClipboardEvent<T = Element> 剪贴板事件对象
DragEvent<T = element> 拖拽事件对象
ChangeEvent<T = element> Change事件对象
KeyboardEvent<T = element> 键盘事件对象
MouseEvent<T = element> 鼠标事件对象
TouchEvent<T = element> 触摸事件对象
WheelEvent<T = element> 滚轮事件对象
AnimationEvent<T = element> 动画事件对象
TransitionEvent<T = element> 过渡事件对象
1 2 3 4 5 import { MouseEvent } from 'react' interface Iprops { onClick (event: MouseEvent<HTMLDivElement>): void, }
CSS属性类型 有时候会在props或者state中使用css属性,这个时候就使用react自带的css类型
1 2 3 4 5 6 import React from 'react'; export type EdgeTextProps = { style?: React.CSSProperties; color: React.CSSProperties['color']; };
泛型组件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 //泛型ts组件 function Foo<T>(props: Props<T>){ return <div>{props.content}</div> } const App = () => { return ( <div className="App"> <Foo content={42}></Foo> <Foo<string> content={"hello"}></Foo> </div> ) } //普通ts组件 interface Props { content: string; } function Foo(props: Props) { return <div>{props.content}</div> } const App = () => { return ( <div className="App"> // Type number not assignable to type string <Foo content={42}></Foo> <Foo<string> content={"hello"}></Foo> </div> ) }
react库 recomponse loadable-components 懒加载
安装
1 npm install @loadable/component
使用
1 2 3 4 5 6 7 8 9 10 11 import loadable from '@loadable/component' const OtherComponent = loadable(() => import('./OtherComponent')) function MyComponent() { return ( <div> <OtherComponent /> </div> ) }
安装
1 npm i react-media-recorder
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import { ReactMediaRecorder } from "react-media-recorder"; const RecordView = () => ( <div> <ReactMediaRecorder video render={({ status, startRecording, stopRecording, mediaBlobUrl }) => ( <div> <p>{status}</p> <button onClick={startRecording}>Start Recording</button> <button onClick={stopRecording}>Stop Recording</button> <video src={mediaBlobUrl} controls autoPlay loop /> </div> )} /> </div> ); import { useReactMediaRecorder } from "react-media-recorder"; const RecordView = () => { const { status, startRecording, stopRecording, mediaBlobUrl, } = useReactMediaRecorder({ video: true }); return ( <div> <p>{status}</p> <button onClick={startRecording}>Start Recording</button> <button onClick={stopRecording}>Stop Recording</button> <video src={mediaBlobUrl} controls autoPlay loop /> </div> ); };
react-three-fiber 在react中使用three.js的插件
安装
1 2 3 npm install three @react-three/fiber # npm install @types/three
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 /* eslint-disable */ import * as THREE from 'three' import React, { useRef, useState } from 'react' import { Canvas, useFrame } from '@react-three/fiber' function Box(props: JSX.IntrinsicElements['mesh']) { // This reference will give us direct access to the THREE.Mesh object const ref = useRef<THREE.Mesh>(null!) // Hold state for hovered and clicked events const [hovered, hover] = useState(false) const [clicked, click] = useState(false) // Rotate mesh every frame, this is outside of React without overhead useFrame((state, delta) => (ref.current.rotation.x += 0.01)) return ( <mesh {...props} ref={ref} scale={clicked ? 1.5 : 1} onClick={(event) => click(!clicked)} onPointerOver={(event) => hover(true)} onPointerOut={(event) => hover(false)}> <boxGeometry args={[1, 1, 1]} /> <meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} /> </mesh> ) } export default function App() { return ( <Canvas> <ambientLight /> <pointLight position={[10, 10, 10]} /> <Box position={[-1.2, 0, 0]} /> <Box position={[1.2, 0, 0]} /> </Canvas> ) }
prop-types 使用第三方包 prop-types
可以对react的 props
进行类型校验
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import React from 'react' // 导入包 import PropTypes from 'prop-types' function About (props) { const { name, age } = props console.log(name, age) return ( <div> <p>{ name }</p> <p>{ age }</p> </div> ) } About.defaultProps = { name: 'ReoNa', age: 22 } // 这里通过函数组件的 propTypes 属性设置类型校验 // PropType.类型:规定传入类型 // PropType.类型.isRequired:规定必须传入 About.propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number } export default About
react-helmet React Helmet是一个HTML文档head管理工具,管理对文档头的所有修改。React Helmet采用纯HTML标记并输出纯HTML标记,非常简单,对react初学者友好
特点:
支持所有有效的head标签,title、base、meta、link、script、noscript和style
支持body、html和title的属性
支持服务端渲染
嵌套组件覆盖重复的head标签修改
同一组件中定义时将保留重复的head标签修改(比如“apple-touch-icon”)
支持跟踪DOM更改的回调
安装
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import {Helmet} from "react-helmet" class Application extends React.Component { render(){ return( <div className="application"> <Helmet> <meta charSet="utf-8"/> <title>My title</title> <link rel="canonical" href="http://mysite.com/example" /> </Helmet> <Child> <Helmet> <title>new Title</title> </Helmet> </Child> </div> ) } }
上面代码中,后面的helmet会覆盖前面的helmet
服务端渲染时,需要在ReactDOMServer.renderToString或ReadDOMServer.renderToStaticMarkup后调用Helmet.renderStatic()来获得你预渲染的head数据
1 2 ReactDOMServer.renderToString(<Handler />); const helmet = Helmet.renderStatic();
二维码 QR Code数据表示方法 : 深色模块表示二进制”1”,浅色模块表示二进制”0”。
纠错能力:
L级:约可纠错7%的数据码字;
M级:约可纠错15%的数据码字;
Q级:约可纠错25%的数据码字;
H级:约可纠错30%的数据码字;
使用qrcode.react npm包
安装
1 npm install qrcode.react
api
prop
type
default value
value
string
renderAs
string (‘canvas’ ‘svg’)
‘canvas’
size
number
128
bgColor
string (CSS color)
“#FFFFFF”
fgColor
string (CSS color)
“#000000”
level
string (‘L’ ‘M’ ‘Q’ ‘H’)
‘L’
includeMargin
boolean
false
imageSettings
object (see below)
图片设置参数imageSettings
field
type
default value
src
string
x
number
none, will center
y
number
none, will center
height
number
10% of size
width
number
10% of size
excavate
boolean
false
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <QRCode id="qrCode" value={"https://gongyi.m.jd.com/oneDetails.html?id=930"} imageSettings={{ // 中间有图片logo src: `http://img13.360buyimg.com/imagetools/jfs/t1/203384/29/6713/37826/6142ef39E5f79ed2b/47200134bf8d0571.jpg`, height: 30, width: 30, excavate: true, }} size={99} // 二维码的大小 fgColor="#000000" // 二维码的颜色 /> //转换为图片 changeCanvasToPic = () => { const canvasImg = document.getElementById('qrCode'); // 获取canvas类型的二维码 const img = new Image(); img.src = canvasImg.toDataURL('image/png'); // canvas.toDataUrl() 可以将canvas格式的文件转换成基于base64的指定格式的图片 // 注意这个api ie9以下不支持 const downLink = document.getElementById('down_link'); downLink.href = img.src; downLink.download = '二维码'; //下载图片name }; //定时刷新 //定时刷新功能是使用 setInterval 定时更新 value 值来更新二维码,跳转地址后面拼上一个radomCode, radomCode定时更新,就实现二维码的刷新了,需要及时清理定时器。
antd Ant-Design是蚂蚁金服开发的面向React和Vue的类似于bootstrap的框架,官网链接为:https://ant.design/index-cn
安装包
1 2 npm install antd --save cnpm i antd -S
在App.css文件中导入样式
1 @import '~antd/dist/antd.css' ;
按需导入包
组件
Upload
Table
antd
的 table
组件,table
的 columns
有一个属性叫做 align
,它的使用是控制当前列是居左、居中、居右的。
它的类型为AlignType,在node_modules/rc-table/lib/interface.d.ts
中可以找到
1 export declare type AlignType = 'left' | 'center' | 'right' ;
在使用时,如果对table进行二次封装,它的值
1 2 3 const columns = [{ align: 'right', }]
此时会报错,类型推论会将align推论为string,而AlignType是字面量类型,没有string
使用as进行断言就不会报错
1 2 3 4 5 6 import { AlignType } from 'rc-table/lib/interface.d.ts' ;columns: [{ align: 'right' as 'right' }]
tooltip
tooltip组件需要禁用时没有直接的disable属性,使用onchange事件进行回调
1 2 3 4 5 6 7 8 9 10 11 12 const checkTipVisible = (visible: boolean) => { VisibleCrtl.toggle(!Boolean(enableCreatePlan) ? visible : false); }; <Tooltip title={formatMessage({ id: 'CREATE_OPERATING_PLAN_ERROR', })} visible={visible} onVisibleChange={checkTipVisible} > </Tooltip>
Form
在form.item中可以使用shouldUpdate包一层,然后将Form传入item的回调函数中
这样做:
1.可以在item中取到form的其他值,从而进行表单联动
2.可以确保字段在更新时及时更新,相当于一次setState
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <Form.Item shouldUpdate> {(form) => { const branches = form.getFieldValue('branches') || []; return ( <> <Form.List name="branches"> {(fields, operation) => { return ( <BranchSortTable readonly={readonly} data={branches} fields={fields} operation={operation} /> ); }} </Form.List> </> ); }} </Form.Item>
验证
表单提交时对有rules的item要进行校验,比较繁琐的写法像这样
1 2 3 const handleSubmit = async () => { await form.validateFields(); }
如果有单独的需要提前校验/接口校验的可以用validateFirst
校验时可以用validator写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <ProFormText readonly={readOnly} label={ <Text style={{ maxWidth: 110 }} ellipsis={{ tooltip: formatMessage({ id: 'FORM_LABEL_NODE_NAME' }), }} > {formatMessage({ id: 'FORM_LABEL_NODE_NAME' })} </Text> } required validateFirst name="name" rules={[ { required: true, message: formatMessage({ id: 'FORM_LABEL_PLACEHOLDER_INPUT', }), }, { validator: async (_rule, value) => { if (!validateNameUnique(value, element.id)) { throw formatMessage({ id: 'AUTO_FLOW_NAME_UNIQ_ERROR_MESSAGE', }); } }, }, ]} fieldProps={{ placeholder: formatMessage({ id: 'AUTO_FLOW_LIMIT_CHAR_LENGTH_MESSAGE', }), }} />
获取字段的校验状态可以使用getFieldError/getFieldsError获取字段或者全部字段的验证信息
namePath
在form.item的name中使用数组,能够把不同的表单放到同一个对象中,而不是普通的key-value
1 2 3 4 5 6 7 8 9 10 11 12 <Form.Item name={['a', 'select']} options={listData as treeItemType[]} readonly={isView} > </Form.Item> <Form.Item name={['a', 'input']} options={listData as treeItemType[]} readonly={isView} > </Form.Item>
如果namePath后面跟的是index,可以自动合并成数组
namePath可以进行嵌套,输出对象数组的表单项
1 2 3 4 5 6 7 8 ['1', '2','3'].map((index)=> { <Form.Item name={['a', 'select', index]} options={listData as treeItemType[]} readonly={isView} > </Form.Item> })
自定义表单组件
表单组件不一定非要input、select,也可以自己通过form.item填充,取值的时候使用get和set就比较方便
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const handleChange = () => { labelsForm.setFieldsValue({ tree: [] }); } const FormTree: React.FC<{ value?: any }> = (value) => { return ( <Tree isDirectoryTree searchAble searchingMode="filter" treeData={value.value || []} onSelect={(selectKey, info) => { setSelectId({ key: selectKey, title: info?.node.title }); }} /> ); }; <ProForm.Item name="tree" noStyle shouldUpdate> <FormTree /> </ProForm.Item>
form之外的dom需要在form字段更新时重新render可以使用useWatch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import React from 'react'; import { Form, Input, InputNumber, Typography } from 'antd'; const Demo = () => { const [form] = Form.useForm<{ name: string; age: number }>(); const nameValue = Form.useWatch('name', form); return ( <> <Form form={form} layout="vertical" autoComplete="off"> <Form.Item name="name" label="Name (Watch to trigger rerender)"> <Input /> </Form.Item> <Form.Item name="age" label="Age (Not Watch)"> <InputNumber /> </Form.Item> </Form> <Typography> <pre>Name Value: {nameValue}</pre> </Typography> </> ); }; export default Demo;
给form item添加提示和自定义图标
1 2 3 4 5 6 <Form.Item label="Field B" tooltip={{ title: 'Tooltip with customize icon', icon: <InfoCircleOutlined /> }} > <Input placeholder="input placeholder" /> </Form.Item>
form的setFieldsValue 和 resetFields 不会触发 Form 的 onValuesChange,
Modal
如果在Modal的content中使用国际化,需要使用Modal的hooks
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 const [modal, contextHolder] = Modal.useModal(); modal.info({ title: null, icon: null, okText: formatMessage({ id: 'ACTION_CONFIRM' }), className: styles.deleteCheckModal, content: ( <div> <div className={styles.deleteCheckModalTitle}> {formatMessage({ id: 'TIPS' })} </div> <div className={styles.deleteCheckModalTips}> <InfoCircleFilled className="mr-6" /> {formatMessage({ id: 'CANOT_DELETE_TIPS' })} </div> <div className={styles.deleteCheckModalTable}> <ProTable pagination={false} scroll={{ y: 200 }} size="small" dataSource={checkResult} > <ProTable.Column title="ID" dataIndex="id" width={100} /> <ProTable.Column title={formatMessage({ id: 'PLAN_NAME' })} dataIndex="name" /> </ProTable> </div> </div> ), }); return ( <>{contextHolder}</> )
如果在modal中使用ref,在初次modal渲染时ref拿不到值,此时应该手动强制刷新一次modal子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 const [Key, setKey] = useState( `${+new Date()}`, ); // ref相关的事件改变时也要reset key,防止拿不到 const handleChange = () => { setKey(() => `${+new Date()}`); }; <Modal visible={visible} form={modalForm} title={formatMessage({ id: 'ATTACHMENT_ADD', })} modalProps={{ onCancel: () => { setVisible(false); }, }} onVisibleChange={(visible) => { if (visible) { // 当modal打开后强制渲染一次 RichTextInput 否则获取不到ref setTimeout(() => { setImageAddressInputKey(() => `${+new Date()}`); }, 0); } }}> </ModalForm>
Select
select的下拉框展开时,如果滚动页面的话会下拉框会移动位置,需要添加一个属性防止下拉框滚动
1 2 3 4 5 6 <Select getPopupContainer={(triggerNode) => triggerNode.parentNode } />
TreeSelect
triggerNode.props非公开api
pro-components ProComponents 是基于 Ant Design 而开发的模板组件,提供了更高级别的抽象支持,开箱即用。可以显著的提升制作 CRUD 页面的效率,更加专注于页面。
ProLayout 解决布局的问题,提供开箱即用的菜单和面包屑功能
ProTable表格模板组件,抽象网络请求和表格格式化
ProForm表单模板组件,预设常见布局和行为
ProCard提供卡片切分以及栅格布局能力
ProDescription定义列表模板组件,ProTable 的配套组件
ProSkeleton页面级别的骨架屏
Proform有很多ProFormFields 表单项组件。这些组件本质上是 Form.Item 和 组件的结合,我们可以帮他们当成一个 FormItem 来使用,并且支持各种 props
。每个表单项都支持 fieldProps
属性来支持设置输入组件的props
。 同时支持了 placeholder
的透传,你可以直接在组件上设置 placeholder
。
每个表单项同时也支持了 readonly
,不同的组件会有不同的只读样式,与 disable
相比 readonly
展示更加友好。生成的 dom 也更小,比如 ProFormDigit 会自动格式化小数位数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <ProFormText width="md" name="name" label="签约客户名称" tooltip="最长为 24 位" placeholder="请输入名称" /> <ProFormDateRangePicker name="contractTime" label="合同生效时间" /> <ProFormSelect width="xs" options={[ { value: 'time', label: '履行完终止', }, ]} name="unusedMode" label="合同约定失效效方式" />
chakra-UI Material-UI 推出很久 很好用
Elastic-UI rsuitejs Charts.rsuite.js
blueprint.js NextUI 看起来很好看
HeadlessUI tailwind的公司开源的UI库
geist-ui swr swr是用于数据请求的react hooks库。SWR 由 Next.js (React 框架)背后的同一团队创建
“SWR” 这个名字来自于 stale-while-revalidate
:一种由 HTTP RFC 5861 推广的 HTTP 缓存失效策略。这种策略首先从缓存中返回数据(过期的),同时发送 fetch 请求(重新验证),最后得到最新数据。
安装
对于返回 JSON 数据的普通 RESTful APIs,首先需要创建一个 fetcher
函数,这个函数只是原生 fetch
的包装
1 const fetcher = (...args ) => fetch(...args).then((res ) => res.json())
然后在组件中使用useSWR使用数据
1 2 3 4 5 6 7 8 9 10 11 import useSWR from "swr"; function Profile() { const { data, error } = useSWR("/api/user/123", fetcher) if (error) return <div>failed to load</div> if (!data) return <div>loading...</div> // 渲染数据 return <div>hello {data.name}!</div> }
echarts-for-react 安装
1 npm install --save echarts-for-react
使用
1 2 3 4 5 6 7 8 9 10 11 12 import React from 'react'; import ReactECharts from 'echarts-for-react'; // or var ReactECharts = require('echarts-for-react'); <ReactECharts option={this.getOption()} notMerge={true} lazyUpdate={true} theme={"theme_name"} onChartReady={this.onChartReadyCallback} onEvents={EventsDict} opts={} />
@monaco-editor/react 在页面内插入文本编辑器,可以提供代码高亮、错误提示等功能
1 npm install @monaco-editor/react
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import Editor, { DiffEditor, useMonaco, loader } from "@monaco-editor/react" ;function App ( ) { const editorRef = useRef(null ); useEffect(() => { if (monaco) { console .log("here is the monaco isntance:" , monaco); } }, [monaco]); function handleEditorWillMount (monaco ) { monaco.languages.typescript.javascriptDefaults.setEagerModelSync(true ); } function handleEditorDidMount (editor, monaco ) { editorRef.current = editor; } function handleEditorChange (value, event ) { console .log("here is the current model value:" , value); } function showValue ( ) { alert(editorRef.current.getValue()); } return ( <> <button onClick={showValue}>Show value</button> <Editor height="90vh" defaultLanguage="javascript" defaultValue="/ / some comment" beforeMount={handleEditorWillMount} onMount={handleEditorDidMount} onChange={handleEditorChange} / > </> ); } const rootElement = document.getElementById("root"); ReactDOM.render(<App / >, rootElement);
classnames 当react原生动态添加多个className时就会报错,这时我们就可以利用classnames库添加多个className,这也是react官方推荐使用
安装
1 npm install classnames --save
支持动态导入
1 2 3 4 5 6 7 import classnames from 'classnames' <div className=classnames({ 'class1': true, 'class2': true )> </div>
支持class动态传入变量,或者传入数组
1 2 3 4 5 6 7 8 9 10 11 import classNames from 'classnames'; render() { const classStr = classNames({ 'class1': true, 'class2': this.props.isCompleted, 'class3': !this.props.isCompleted [a]: this.props.isCompleted }); return (<div className={classStr}></div>); }
GraphQL Apollo是基于GraphQL的全栈解决方案集合,包括了apollo-client和apollo-server,从后端到前端提供了对应的lib使得开发GraphQL更加方便
1 2 3 4 apollo-boost 包含启动阿波罗客户端的所有依赖 react-apollo 视图层面的集合 graph-tag 解析查询语句 graphql 也是解析查询语句
1 2 3 4 5 6 7 8 import ApolloClient from 'apollo-boost' const client = new ApolloClient({ uri: 'http://localhost:5000/graphql' }) import { ApolloProvider,Query } from 'react-apollo' import { Mutation,MutationFunc } from 'react-apollo'
1 npm install @apollo/client graphql
使用hooks
简单好看的react form表单
安装
1 npm install react-hook-form
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import React from 'react'; import { useForm } from 'react-hook-form'; function App() { const { register, handleSubmit, formState: { errors }, } = useForm(); const onSubmit = (data) => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register('firstName')} /> {/* register an input */} <input {...register('lastName', { required: true })} /> {errors.lastName && <p>Last name is required.</p>} <input {...register('age', { pattern: /\d+/ })} /> {errors.age && <p>Please enter number for age.</p>} <input type="submit" /> </form> ); }
eventbus 安装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import {EventEmitter} from 'events' export default new EventEmitter()import emitter from './event' class Father extends React .Component { constructor (props){ super (props) } handleClick = () => { emitter.emit('info' ,'来自father的info' ) } } export default Fatherimport emitter from './event' class Son extends React .Component { constructor (props){ super (props) } }
react-flow 安装
1 npm install --save react-flow-renderer
使用
1 2 3 4 5 6 7 8 9 10 import React from 'react'; import ReactFlow from 'react-flow-renderer'; const elements = [ { id: '1', type: 'input', data: {lable: 'Node 1'},position: {x: 250, y: 50}}, { id: '2', data: {lable: <div>Node 2</div>}, position: {x: 100, y: 100}}, { id: 'el-2', source: '1', targetL '2', animated: true} ] export default ()=> <ReactFlow elements={elements}></ReactFlow>
React Flow有两个背景变体:点和线。您可以通过将其作为子级传递给ReactFlow
组件来使用它
1 2 3 4 5 6 7 8 9 10 11 import ReactFlow, { Background } from 'react-flow-renderer'; const FlowWithBackground = () => ( <ReactFlow elements={elements}> <Background variant="dots" gap={12} size={4} /> </ReactFlow> );
可以通过将mini-map插件作为子级传递给ReactFlow
组件来使用它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import ReactFlow, { MiniMap } from 'react-flow-renderer'; const FlowWithMiniMap = () => ( <ReactFlow elements={elements}> <MiniMap nodeColor={(node) => { switch (node.type) { case 'input': return 'red'; case 'default': return '#00ff00'; case 'output': return 'rgb(0,0,255)'; default: return '#eee'; } }} /> </ReactFlow> );
控制面板包含zoom-in、zoom-out、fit-view和一个锁定/解锁按钮。
1 2 3 4 5 6 7 import ReactFlow, { Controls } from 'react-flow-renderer'; const FlowWithControls = () => ( <ReactFlow elements={elements}> <Controls /> </ReactFlow> );
如果需要访问ReactFlow
组件外部的React Flow的内部状态和操作,可以用ReactFlowProvider
组件包装它
1 2 3 4 5 6 7 8 9 10 11 import ReactFlow, { ReactFlowProvider } from 'react-flow-renderer'; const FlowWithOwnProvider = () => ( <ReactFlowProvider> <ReactFlow elements={elements} onElementClick={onElementClick} onConnect={onConnect} /> </ReactFlowProvider> );
https://www.5axxw.com/wiki/content/obkffc
react-children-utilities 返回组件中的文字
1 npm install --save react-children-utilities
使用
1 2 3 4 5 6 7 import React from 'react'; import Children from 'react-children-utilities'; const MyComponent = ({ children }) => { const onlySpans = Children.filter(children, (child) => child.type === 'span'); return <div>{onlySpans}</div>; };
其他api:
https://www.npmjs.com/package/react-children-utilities
react-beautiful-dnd 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd' class DraggableTags extends Component { render() { return ( <DragDropContext onDragEnd={this.onDragEnd}> <Droppable droppableId="droppable" direction="horizontal"> {(provided, _snapshot)=> ( <Draggable> {(provided, _snapshot)=> ( style={ } )} </Draggable> )} </Droppable> </DragDropContext> ) } }
react-dnd Https://juejin.cn/post/6933036276660731912
react-intl-universal 不建议使用react-intl,而使用React-intl-universal实现
建立英文和中文语言包,可以是json或者js文件
1 2 3 4 5 6 7 8 const en_US = { 'hello' :'nihao' , 'name' : 'zhangsan' , 'age' : '30' , 'changelang' : 'qiehuanyuyan' , } export default en_US
中文包
1 2 3 4 5 6 7 8 const zh_CN = { 'hello' :'nihao' , 'name' : 'zhangsan' , 'age' : '30' , 'changelang' : 'qiehuanyuyan' , } export default zh_CN
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import intl from 'react-intl-universal' import cn from '../../assets/locals/zh-CN' import us from '../../assets' class IntlExample extends React.Component{ constructor(){ super(); this.locals = { 'zh_CN': cn, 'en_US': us } this.state = { intl: cn } } componentDidMount() { this.initLocale(); } initLocale(locale="zh_CN"){ } }
react-hot-loader React-Hot-Loader 使用了 Webpack HMR API,针对 React 框架实现了对单个 component 的热替换,并且能够保持组件的 state。 React-Hot-Loader 在编译时会在每一个 React component 外封装一层,每一个这样的封装都会注册自己的 module.hot.accept 回调,它们会监听每一个 component 的更新,在当前 component 代码更新时只替换自己的模块,而不是整个替换 root component。 同时,React-Hot-Loader 对 component 的封装还会代理 component 的 state,所以当 component 替换之后依然能够保持之前的 state。
安装
1 npm install --save-dev react-hot-loader
hot-loader 是基于 webpack-dev-server,所以还得安装 webpack-dev-server
1 npm install --save-dev webpack-dev-server
首先还是要让 webpack-dev-server 打开。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var webpack = require ('webpack' );var WebpackDevServer = require ('webpack-dev-server' );var config = require ('./webpack.config' );new WebpackDevServer(webpack(config), { publicPath: config.output.publicPath, hot: true , historyApiFallback: true }).listen(3000 , 'localhost' , function (err, result ) { if (err) { return console .log(err); } console .log('Listening at http://localhost:3000/' ) });
然后在 webpack 的配置文件里添加 react-hot-loader。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 var webpack = require ('webpack' );module .exports = { entry: [ "webpack-dev-server/client?http://0.0.0.0:3000" , "webpack/hot/only-dev-server" , "./src/app.js" , ], output: { path: __dirname, filename: "build/js/bundle.js" , publicPath: "/build" }, module : { loaders: [ { test: /\.jsx?$/ , exclude: /node_modules/ , loaders: ['react-hot' , 'babel?presets[]=react,presets[]=es2015' ] } ] }, plugins: [ new webpack.HotModuleReplacementPlugin() ]
react-hot-toast 全屏的通知组件
安装
1 npm install react-hot-toast
使用
1 2 3 4 5 6 7 8 9 10 11 12 import toast, { Toaster } from 'react-hot-toast'; const notify = () => toast('Here is your toast.'); const App = () => { return ( <div> <button onClick={notify}>Make me a toast</button> <Toaster /> </div> ); };
remotion 用react写video
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import { useCurrentFrame } from "remotion"; export const MyVideo = () => { const frame = useCurrentFrame(); return ( <div style={{ flex: 1, textAlign: "center", fontSize: "7em", }} > The current frame is {frame}. </div> ); };
配置帧数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import { useVideoConfig } from "remotion"; export const MyVideo = () => { const { fps, durationInFrames, width, height } = useVideoConfig(); return ( <div style={{ flex: 1, textAlign: "center", fontSize: "7em", }} > This {width}px x {height}px video is {durationInFrames / fps} seconds long. </div> ); };
uuid uuid是通用唯一识别码(Universally Unique Identifier)的缩写。是一种软件建构辨准,亦为开发软件基金会组织在分布式计算环境领域的一部分。其目的是让分布式系统中的所有元素具有唯一的辨识信息,而不需要通过中央控制端来做辨识信息的指定。
UUID由一组32位数的16进制数字构成。对于UUID,就算每纳秒产生一百万个UUID,要花100亿年才会将所有UUID用完。
格式
uuid32个16进制数字用连字号分成五组来显示,所以共有36个字符
UUID版本通过M表示,当前规范有5个版本,可选值为1、2、3、4、5,这5个版本使用不同的算法,利用不同的信息产生UUID,各版本有各版本的优势,具体来说:
uuid.v1():创建版本1(时间戳)UUID
uuid.v3():创建版本3(md5命名空间)UUID
uuid.v4():创建版本4(随机)UUID
uuid.v5():创建版本5(带SHA-1的命名空间)IIOD
安装
使用
1 2 3 import { v4 as uuidv4} from 'uuid' uuidv4()
可以使用uuid进行验证登陆,未登陆状态下生产uuid
1 2 3 4 5 6 7 8 9 10 11 12 13 let uuid = sessionStorage.getItem('uuid' )if (!uuid){ sessionStorage.setItem('uuid' ) } if (getToken()){ sessionStorage.removeItem('uuid' ); }else { let uuid = sessionStorage.getItem('uuid' ); if (!uuid){ sessionStorage.setItem('uuid' ,uuidv4()); } }
react-sortable-hoc 拖动排序
安装
1 npm install react-sortable-hoc --save
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import React, {Component} from 'react'; import {render} from 'react-dom'; import {SortableContainer, SortableElement} from 'react-sortable-hoc'; import arrayMove from 'array-move'; const SortableItem = SortableElement(({value}) => <li>{value}</li>); const SortableList = SortableContainer(({items}) => { return ( <ul> {items.map((value, index) => ( <SortableItem key={`item-${value}`} index={index} value={value} /> ))} </ul> ); }); class SortableComponent extends Component { state = { items: ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 'Item 6'], }; onSortEnd = ({oldIndex, newIndex}) => { this.setState(({items}) => ({ items: arrayMove(items, oldIndex, newIndex), })); }; render() { return <SortableList items={this.state.items} onSortEnd={this.onSortEnd} />; } } render(<SortableComponent />, document.getElementById('root'));
参数说明:
axis: 拖动时的轴。默认为y,当需要有多个方向时需要指定为xy
lockAxis:锁定子项目拖动的轴,可以为x或者y
useDragHandle:使用拖动图标。不是整个子项拖动
K-bar k-bar可以给react部署的站点提供一个舒服的搜索框样式
安装
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import { KBarProvider, KBarPortal, KBarPositioner, KBarAnimator, KBarSearch, useMatches, NO_GROUP } from "kbar"; function MyApp() { const actions = [ { id: "blog", name: "Blog", shortcut: ["b"], keywords: "writing words", perform: () => (window.location.pathname = "blog"), }, { id: "contact", name: "Contact", shortcut: ["c"], keywords: "email", perform: () => (window.location.pathname = "contact"), }, ] return ( <KBarProvider actions={actions}> <KBarPortal> // Renders the content outside the root node <KBarPositioner> // Centers the content <KBarAnimator> // Handles the show/hide and height animations <KBarSearch /> // Search input <RenderResults />; </KBarAnimator> </KBarPositioner> </KBarPortal> <MyApp /> </KBarProvider>; ); }
自定义搜索结果样式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import { // ... KBarResults, useMatches, NO_GROUP, } from "kbar"; function RenderResults() { const { results } = useMatches(); return ( <KBarResults items={results} onRender={({ item, active }) => typeof item === "string" ? ( <div>{item}</div> ) : ( <div style={{ background: active ? "#eee" : "transparent", }} > {item.name} </div> ) } /> ); }
nanoid react-virtualized 使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import {AutoSizer, List, CellMeasurerCache, CellMeasurer} from 'react-virtualized' // 宽度固定 const measureCache = new CellMeasurerCache({ fixedWidth: true, minHeight: 60, }) // 每一行内容 const rowRenderer = ({ index, key, parent, style }) => { const item = records[index] return ( <CellMeasurer cache={measureCache} key={key} parent={parent} rowIndex={index}> <div style={style}> content </div> </CellMeasurer> ) } <AutoSizer> {({width, height}) => ( <List width={width} height={height} deferredMeasurementCache={measureCache} rowCount={records.length} rowHeight={measureCache.rowHeight} rowRenderer={rowRenderer} className={styles.list} /> )} </AutoSizer>
无限滚动 + 可编辑表格
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 import {Table, Column} from 'react-virtualized' import {Input, Empty} from 'antd' // 防止Input组件不必要的渲染 const MemoInput = React.memo(function (props) { const {rowIndex, field, handleFieldChange, ...restProps} = props return <Input {... restProps} onChange={(e) => handleFieldChange(field, e, rowIndex)} /> }) function VirtualTable(props) { // ... // 列, 使用useCallback优化 const nameColumn = ({cellData, rowIndex, dataKey }) => { // ... return ( <div> <MemoInput placeholder="请输入姓名" value={cellData} rowIndex={rowIndex} field="姓名" handleFieldChange={handleFieldChange} /> </div> ) } // 表头 const columnHeaderRenderer = useCallback(({dataKey}) => dataKey, []) const rowGetter = useCallback(({index}) => dataSource[index], [dataSource]) const noRowsRenderer = useCallback(() => <Empty className={styles.empty} />, []) return <Table ref={tableRef} className={styles.virtualTable} headerClassName={styles.header} rowClassName={styles.row} headerHeight={TableHeaderHeight} width={TableWidth} height={TableHeight} noRowsRenderer={noRowsRenderer} rowHeight={TableRowHeight} rowGetter={rowGetter} rowCount={dataSource.length} overscanRowCount={OverscanRowCount} > <Column width={120} dataKey="姓名" headerRenderer={columnHeaderRenderer} cellRenderer={nameColumn} /> </Table> }
react-window react虚拟列表库 React Window是一个有效呈现大型列表和表格数据的组件,是React-virtualized的完全重写。
React Window专注于使软件包更小,更快,同时API(和文档)对初学者尽可能友好。
安装
固定高度列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import { FixedSizeList as List } from 'react-window'; const Row = ({ index, style }) => ( <div style={style}>Row {index}</div> ); const Example = () => ( <List height={150} itemCount={1000} itemSize={35} width={300} > {Row} </List> );
VariableSizeList (可变尺寸列表)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import { VariableSizeList } from 'react-window'; const rowHeights = new Array(1000) .fill(true) .map(() => 25 + Math.round(Math.random() * 50)); const getItemSize = index => rowHeights[index]; // 此处采用随机数作为每个列表项的高度 /** * 每个列表项的组件 * @param index:列表项的下标;style:列表项的样式(此参数必须传入列表项的组件中,否则会出现滚动到下方出现空白的情况) **/ const Row = ({ index, style }) => ( <div style={style}>Row {index}</div> ); const Example = () => ( <VariableSizeList height={150} // 列表可视区域的高度 itemCount={1000} // 列表数据长度 itemSize={getItemSize} // 设置列表项的高度 layout= "vertical" // (vertical/horizontal) 默认为vertical,此为设置列表的方向 width={300} > {Row} <VariableSizeList> );
结合react-virtualized-auto-sizer使列表自适应当前页面的宽高
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import { FixedSizeList } from "react-window"; import AutoSizer from "react-virtualized-auto-sizer"; const Example = () => ( <AutoSizer> {({ height, width }) => ( <FixedSizeList className="List" height={height} itemCount={1000} itemSize={35} width={width} > {Row} </FixedSizeList> )} </AutoSizer> );
react-sticky 让组件实现类似position-sticky的效果
安装
1 npm install react-sticky
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import React from 'react'; import { StickyContainer, Sticky } from 'react-sticky'; class App extends React.Component { render() { return ( <StickyContainer> {/* Other elements can be in between `StickyContainer` and `Sticky`, but certain styles can break the positioning logic used. */} <Sticky> {({ style, // the following are also available but unused in this example isSticky, wasSticky, distanceFromTop, distanceFromBottom, calculatedHeight }) => ( <header style={style}> {/* ... */} </header> )} </Sticky> {/* ... */} </StickyContainer> ); }, }
sticky上可以添加不同的属性
1 2 3 4 5 6 7 <StickyContainer> ... <Sticky topOffset={80} bottomOffset={80} disableCompensation> { props => (...) } </Sticky> ... </StickyContainer>
crypto-browserify 加密
js-cookie cookie插件
1 npm install js-cookie --save
引用
1 2 3 4 5 6 7 8 9 10 11 import Cookies from 'js-cookie' Cookies.set('name' ,'value' ,{expire :7 ,path :'' }); Cookies.set('name' ,{foo :'bar' }); Cookies.get('name' ); Cookies.get(); Cookies.remove('name' );
react-color react-color是一个拾色器,通过它获取颜色值
安装
使用
1 2 3 4 5 6 7 8 9 10 import { TwitterPicker } from 'react-dom' function () { render() { <TwitterPicker width="240px" /> } }
react-lazyload 安装
1 npm install --save react-lazyload
懒加载图片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import React from 'react'; import ReactDOM from 'react-dom'; import LazyLoad from 'react-lazyload'; import MyComponent from './MyComponent'; const App = () => { return ( <div className="list"> <LazyLoad height={200}> <img src="tiger.jpg" /> /* Lazy loading images is supported out of box, no extra config needed, set `height` for better experience */ </LazyLoad> <LazyLoad height={200} once > /* Once this component is loaded, LazyLoad will not care about it anymore, set this to `true` if you're concerned about improving performance */ <MyComponent /> </LazyLoad> <LazyLoad height={200} offset={100}> /* This component will be loaded when it's top edge is 100px from viewport. It's useful to make user ignorant about lazy load effect. */ <MyComponent /> </LazyLoad> <LazyLoad> <MyComponent /> </LazyLoad> </div> ); }; ReactDOM.render(<App />, document.body);
默认懒加载组件
1 2 3 4 5 6 7 8 9 10 11 12 import { lazyload } from 'react-lazyload'; @lazyload({ height: 200, once: true, offset: 100 }) class MyComponent extends React.Component { render() { return <div>this component is lazyloaded by default!</div>; } }