前端框架,快速开发页面,函数式编程,与后端api快速搭建
react的代码规范库
yarn add eslint eslint-plugin-react
如果是typescript项目按照ts相关插件
yarn add @typescript-eslint/eslint-plugin @typescript-eslint/parser
使用yarn eslint --lint向导来完成配置,或者手动创建eslintrc。json填入如下配置
{
"extends": ["eslint:recommended","plugin:react/recommended"],
"parser": "@typescript-eslint/parser",
"plugins": ["react","@typescript-eslint"],
"rules": {
"react/self-closing-comp": ["error"] //组件无内容时自闭合
}
}
在vscode中配置
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
]
https://github.com/tsconfig/bases
Prettier是
编写.prettierrc
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 80,
"overrides": [
{
"files": ".prettierrc",
"options": { "parser": "json" }
}
]
}
stylelint是现代化的前端项目中一个强大的代码检查工具。可以帮忙检查样式文件并在样式中强制执行约定。
stylelint 默认地能解析如下的非标准语法,包括Sass、Less 和 SugarSS,非标准语法可以从以下文件扩展名 .sass
、.scss
、.less
和 .sss
中自动推断出来。或者您也可以自己指定语法。
此外,在使用命令行界面或 Node.js 应用程序接口时,stylelint 可以接受任何PostCSS兼容语法。但请注意,stylelint 无法保证核心规则可以在上面列出的默认值以外的语法中正常工作
除了使用设定默认的 standard
规则外,我们还可以在 .stylelintrc.js
内添加自己喜欢的规则
安装stylelint-order
npm install stylelint-order --save-dev
在 .stylelintrc.js
设置代码如下
module.exports = {
"plugins": [
"stylelint-order"
],
"rules": {
"order/properties-order": [
"width",
"height"
]
}
}
lint-staged 是一个在git暂存文件上运行linters的工具,当然如果你觉得每次修改一个文件就给所有文件执行一次lint检查不恶心的话,这个工具对你来说就没有什么意义了,请直接关闭即可
npx mrm lint-staged
配置
在package.json中配置
{
"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
的代码样式
{
"*.{js,jsx,ts,tsx,md,html,css}": "prettier --write"
}
Stylelint用于具有默认值的CSS和具有SCSS语法的SCSS
{
"*.css": "stylelint",
"*.scss": "stylelint --syntax=scss"
}
自动修复代码
{
"*.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`使用tsx之前要安装react的声明文件,否则会报错找不到模块react
安装
npm install @types/react -s
npm install @types/react-dom -s
有状态组件中的state和props使用ts去定义类型
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> 过渡事件对象
import { MouseEvent } from 'react'
interface Iprops {
onClick (event: MouseEvent<HTMLDivElement>): void,
}
有时候会在props或者state中使用css属性,这个时候就使用react自带的css类型
import React from 'react';
export type EdgeTextProps = {
style?: React.CSSProperties;
color: React.CSSProperties['color'];
};
//泛型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>
)
}
懒加载
安装
npm install @loadable/component
使用
import loadable from '@loadable/component'
const OtherComponent = loadable(() => import('./OtherComponent'))
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
)
}
安装
npm i react-media-recorder
使用
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.js的插件
安装
npm install three @react-three/fiber
## 如果使用ts还要安装ts包
npm install @types/three
使用
/* 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
可以对react的 props
进行类型校验
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是一个HTML文档head管理工具,管理对文档头的所有修改。React Helmet采用纯HTML标记并输出纯HTML标记,非常简单,对react初学者友好
特点:
支持所有有效的head标签,title、base、meta、link、script、noscript和style
支持body、html和title的属性
支持服务端渲染
嵌套组件覆盖重复的head标签修改
同一组件中定义时将保留重复的head标签修改(比如“apple-touch-icon”)
支持跟踪DOM更改的回调
安装
npm i react-helmet
使用
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数据
ReactDOMServer.renderToString(<Handler />);
const helmet = Helmet.renderStatic();
QR Code数据表示方法 : 深色模块表示二进制"1",浅色模块表示二进制"0"。
纠错能力:
使用qrcode.react npm包
安装
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 |
示例代码
<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定时更新,就实现二维码的刷新了,需要及时清理定时器。
Ant-Design是蚂蚁金服开发的面向React和Vue的类似于bootstrap的框架,官网链接为:https://ant.design/index-cn
安装包
npm install antd --save
cnpm i antd -S
在App.css文件中导入样式
@import '~antd/dist/antd.css';
按需导入包
import { } from 'antd';
组件
Upload
Table
antd
的 table
组件,table
的 columns
有一个属性叫做 align
,它的使用是控制当前列是居左、居中、居右的。
它的类型为AlignType,在node_modules/rc-table/lib/interface.d.ts
中可以找到
export declare type AlignType = 'left' | 'center' | 'right';
在使用时,如果对table进行二次封装,它的值
const columns = [{
align: 'right',
}]
此时会报错,类型推论会将align推论为string,而AlignType是字面量类型,没有string
使用as进行断言就不会报错
import { AlignType } from 'rc-table/lib/interface.d.ts';
columns: [{
align: 'right' as 'right'
// 或者 align: 'right' as AlignType
}]
table的filteredValue属性会使表格的filter字段受控 如果没设置好会导致table所有列的fliter失效,所以尽量不要用这个属性
tooltip
tooltip组件需要禁用时没有直接的disable属性,使用onchange事件进行回调
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
<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要进行校验,比较繁琐的写法像这样
const handleSubmit = async () => {
await form.validateFields();
}
如果有单独的需要提前校验/接口校验的可以用validateFirst
校验时可以用validator写
<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
<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'].map((index)=> {
<Form.Item
name={['a', 'select', index]}
options={listData as treeItemType[]}
readonly={isView}
>
</Form.Item>
})
自定义表单组件
表单组件不一定非要input、select,也可以自己通过form.item填充,取值的时候使用get和set就比较方便
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
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添加提示和自定义图标
<Form.Item
label="Field B"
tooltip={{ title: 'Tooltip with customize icon', icon: <InfoCircleOutlined /> }}
>
<Input placeholder="input placeholder" />
</Form.Item>
form的setFieldsValue 和 resetFields 不会触发 Form 的 onValuesChange,
form的初始值initialValue在初始化的时候有初始值,此后不受控,无法更改。因此如果需要根据接口或者state更改初始化值,则需要在拿到正确初始值之前不渲染form,或者在state/接口更新之后使用setFieldValue更改该form的值。推荐第一种方式
const app = () => {
if(state) {
return <div />
}
return <Form.Item initialValue='1'> <input /> </Form.Item>
}
Modal
如果在Modal的content中使用国际化,需要使用Modal的hooks
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={data}
>
<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子组件
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>
原因:
ant design的modal在渲染时如果同步渲染会堵塞react的事件,因此ant design的modal使用异步渲染,初次渲染是ref就绑定不到
// components/modal/confirm.tsx
function render({ okText, cancelText, prefixCls: customizePrefixCls, ...props }: any) {
/**
* https://github.com/ant-design/ant-design/issues/23623
*
* Sync render blocks React event. Let's make this async.
*/
setTimeout(() => {
const runtimeLocale = getConfirmLocale();
const { getPrefixCls, getIconPrefixCls } = globalConfig();
// because Modal.config set rootPrefixCls, which is different from other components
const rootPrefixCls = getPrefixCls(undefined, getRootPrefixCls());
const prefixCls = customizePrefixCls || `${rootPrefixCls}-modal`;
const iconPrefixCls = getIconPrefixCls();
reactRender(
<ConfirmDialog
{...props}
prefixCls={prefixCls}
rootPrefixCls={rootPrefixCls}
iconPrefixCls={iconPrefixCls}
okText={okText || (props.okCancel ? runtimeLocale.okText : runtimeLocale.justOkText)}
cancelText={cancelText || runtimeLocale.cancelText}
/>,
container,
);
});
}
可以基于modal的命令式包装弹框hooks,实现命令式的自定义弹框组件
export const useCustomModal = () => {
const { formatMessage } = useIntl();
const [modal, contextHolder] = Modal.useModal();
const showModal = (props: showModalProps) => {
const { tip, okText, onOk, className } = props;
modal.info({
title: null,
icon: null,
okText: okText ?? formatMessage('ACTION_CONFIRM'),
className: className,
content: (
<div className={styles.quotePlanModal}>
{data}
</div>
),
onOk: () => {
onOk?.();
},
});
}
return [showModal, contextHolder];
}
Select
select的下拉框展开时,如果滚动页面的话会下拉框会移动位置,需要添加一个属性防止下拉框滚动
<Select
getPopupContainer={(triggerNode) =>triggerNode.parentNode}
/>
TreeSelect
triggerNode.props非公开api
antd的form使用这个验证库
使用
import Schema from 'async-validator';
const descriptor = {
name: {
type: 'string',
required: true,
validator: (rule, value) => value === 'muji',
},
age: {
type: 'number',
asyncValidator: (rule, value) => {
return new Promise((resolve, reject) => {
if (value < 18) {
reject('too young'); // reject with error message
} else {
resolve();
}
});
},
},
};
const validator = new Schema(descriptor);
validator.validate({ name: 'muji' }, (errors, fields) => {
if (errors) {
// validation failed, errors is an array of all errors
// fields is an object keyed by field name with an array of
// errors per field
return handleErrors(errors, fields);
}
// validation passed
});
// PROMISE USAGE
validator.validate({ name: 'muji', age: 16 }).then(() => {
// validation passed or without error message
}).catch(({ errors, fields }) => {
return handleErrors(errors, fields);
});
滚动窗口组件
yarn add scroll-into-view-if-needed
使用
import scrollIntoView from 'scroll-into-view-if-needed'
const node = document.getElementById('hero')
scrollIntoView(node, {
// Your scroll actions will always be an array, even if there is nothing to scroll
behavior: actions =>
// list is sorted from innermost (closest parent to your target) to outermost (often the document.body or viewport)
actions.forEach(({ el, top, left }) => {
// implement the scroll anyway you want
el.scrollTop = top
el.scrollLeft = left
// If you need the relative scroll coordinates, for things like window.scrollBy style logic or whatever, just do the math
const offsetTop = el.scrollTop - top
const offsetLeft = el.scrollLeft - left
}),
// all the other options (scrollMode, block, inline) still work, so you don't need to reimplement them (unless you really really want to)
})
首先安装react-resizable
npm install react-resizable --save
使用
import React, { PureComponent } from 'react';
import { Table } from 'antd';
import { Resizable } from 'react-resizable';
const ResizeableTitle = (props: { [x: string]: any; onResize: any; width: any; }) => {
const { onResize, width, ...restProps } = props;
if (!width) {
return <th {...restProps} />
}
return (
<Resizable width={width} height={0} onResize={onResize} draggableOpts={{ enableUserSelectHack: false }}>
<th {...restProps} />
</ Resizable >
)
}
class TableResize extends PureComponent<any, any> {
constructor(props: any) {
super(props);
}
state = {
columns: this.props.columns
};
components = {
header: {
cell: ResizeableTitle,
},
};
handleResize = (index: number) => (e: any, { size }: any) => {
this.setState(({ columns }: any) => {
const nextColumns = [...columns];
nextColumns[index] = {
...nextColumns[index],
width: size.width,
};
return { columns: nextColumns };
});
};
render() {
const columns = this.state.columns.map((col: any, index: number) => ({
...col,
onHeaderCell: (column: { width: any; }) => ({
width: column.width,
onResize: this.handleResize(index),
}),
}));
return <Table
components={this.components}
columns={columns}
rowKey="id"
border // 不要这个属性也有拖拽表示
pagination={false}
rowSelection={this.props.rowSelection} // 选中行的事件,不需要可删除
dataSource={this.props.dataSource}
onChange={(pagination: any, filters: any) => this.props.handleTableChange(pagination, filters)} /> //表格筛选功能触发的事件,不需要可删除
}
}
export default TableResize
使用组件
import TableReszie from '@components/TableResize'
<TableReszie
dataSource={dataSource}
columns={columns}
loading={loading}
handleTableChange={handleTableChange}
/>
或者换用组件:https://ali-react-table.js.org/docs/pipeline/features/column-resize/
在用antd的select组件时, mode:multiple时, 选中的tag可以拖拽排序
npm i simple-react-draggable
使用
import Draggable from 'simple-react-draggable';
const list = [{
id: 1,
name: '测试1'
},{
id: 2,
name: '测试2'
},{
id: 3,
name: '测试3'
},{
id: 4,
name: '测试4'
},{
id: 5,
name: '测试6'
},{
id: 7,
name: '测试7'
}];
<Draggable list={list}/>
相关issue:https://github.com/react-component/tooltip/issues/18
workaround方式
<Tooltip title="Tooltip Title">
<span style={{ cursor: disabled ? 'not-allowed' : 'pointer' }}>
<button disabled={disabled} style={{ pointerEvents: 'none' }}>
button text
</button>
</span>
</Tooltip>
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 会自动格式化小数位数。
<ProFormText
width="md"
name="name"
label="签约客户名称"
tooltip="最长为 24 位"
placeholder="请输入名称"
/>
<ProFormDateRangePicker name="contractTime" label="合同生效时间" />
<ProFormSelect
width="xs"
options={[
{
value: 'time',
label: '履行完终止',
},
]}
name="unusedMode"
label="合同约定失效效方式"
/>
React Query 通常被描述为 React 缺少的数据获取(data-fetching)库,但是从更广泛的角度来看,它使 React 程序中的获取,缓存,同步和更新服务器状态变得轻而易举
尽管大多数传统状态管理库非常适合用于处理客户端状态,但是它们并不适合处理异步或服务器状态。 这是因为服务器状态完全不同。对于初学者,服务器状态
一旦你掌握了应用中服务器状态的本质,你会遇到更多的挑战,例如:
如果您没有被这个列表压垮,那么这一定意味着您可能已经解决了所有的服务器状态问题,值得获奖。 然而,如果你和大多数人一样,你要么还没有解决所有这些挑战,要么还没有解决大部分挑战,我们只是触及了表面!
React Query 无疑是管理服务器状态的最佳库之一。它非常好用,开箱即用,无需配置,并且可以随着应用的增长而根据自己的喜好进行定制。
React Query 使您可以击败并征服棘手的服务器状态挑战和障碍,并在开始控制您的应用数据之前对其进行控制。
从更专业的角度来说,React Query 可能会:
安装
$ npm i react-query
# or
$ yarn add react-query
使用
import {
useQuery,
useMutation,
useQueryClient,
QueryClient,
QueryClientProvider,
} from 'react-query'
import { getTodos, postTodo } from '../my-api'
// 创建一个 client
const queryClient = new QueryClient()
function App() {
return (
// 提供 client 至 App
<QueryClientProvider client={queryClient}>
<Todos />
</QueryClientProvider>
)
}
function Todos() {
// 访问 client
const queryClient = useQueryClient()
// 查询
const query = useQuery('todos', getTodos)
// 修改
const mutation = useMutation(postTodo, {
onSuccess: () => {
// 错误处理和刷新
queryClient.invalidateQueries('todos')
},
})
return (
<div>
<ul>
{query.data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
<button
onClick={() => {
mutation.mutate({
id: Date.now(),
title: 'Do Laundry',
})
}}
>
Add Todo
</button>
</div>
)
}
render(<App />, document.getElementById('root'))
React Query 自带了专用的开发工具。它们有助于可视化 React Query 的所有内部工作原理
devtools 包被拆分为react-query/devtools
包。不需要安装任何额外的东西
import { ReactQueryDevtools } from 'react-query/devtools'
function App() {
return (
<QueryClientProvider client={queryClient}>
{/* The rest of your application */}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
}
请求库,支持vue、react
安装
npm install alova --save
使用
import { createAlova, useRequest } from 'alova';
import GlobalFetch from 'alova/GlobalFetch';
import ReactHook from 'alova/react';
// 1. 创建alova实例
const alovaInstance = createAlova({
// ReactHook用于创建ref状态,包括请求状态loading、响应数据data、请求错误对象error等
statesHook: ReactHook,
// 请求适配器,推荐使用fetch请求适配器
requestAdapter: GlobalFetch(),
// 全局的响应拦截器
responded: response => response.json()
});
const App = () => {
// 2. 使用alova实例创建method并传给useRequest即可发送请求
const { loading, data, error } = useRequest(
alovaInstance.Get('https://jsonplaceholder.typicode.com/todos/1')
);
if (loading) {
return <div>Loading...</div>;
} else if (error) {
return <div>{error.message}</div>;
}
return (
<span>responseData: {JSON.stringify(data)}</span>
);
};
export default App;
H5DS (HTML5 design software)可以理解成一款做H5的在线工具,H5就是在手机上滑动的页面,像易企秀,百度H5,wps秀堂,Maka一样的在线工具。
import 'h5ds/editor/style.css';
import React, { Component } from 'react';
import H5dsEditor from 'h5ds/editor';
class Editor extends Component {
constructor(props) {
super(props);
this.state = {
data: null
};
}
/**
* 保存APP
*/
saveApp = async data => {
console.log('saveApp ->', data);
};
/**
* 发布 app
*/
publishApp = async data => {
console.log('publishApp ->', data);
};
componentDidMount() {
// 模拟异步加载数,设置 defaultData 会默认加载一个初始化数据
setTimeout(() => {
this.setState({ data: 'defaultData' });
}, 100);
}
/**
* 使用编辑器部分
*/
render() {
const { data } = this.state;
return (
<H5dsEditor
plugins={[]} // 第三方插件包
data={data}
options={{
publishApp: this.publishApp,
saveApp: this.saveApp, // 保存应用
appId: 'test_app_id' // 当前appId
}}
/>
);
}
}
export default Editor;