Skip to content

Commit

Permalink
feat: base impl for BiciTable, add custom filters(search, select, ran…
Browse files Browse the repository at this point in the history
…ge & date)
  • Loading branch information
campcc committed Sep 30, 2020
1 parent 02cf6f6 commit 86a4cd0
Show file tree
Hide file tree
Showing 19 changed files with 850 additions and 12 deletions.
3 changes: 2 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.umi
.umi
webpack.config.js
7 changes: 6 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
{
"extends": "eslint-config-bicitech"
"extends": "eslint-config-bicitech",
"rules": {
"react/forbid-prop-types": 0,
"react/prop-types": 0,
"no-console": 0
}
}
144 changes: 144 additions & 0 deletions docs/components/BiciTable.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,147 @@ group:
title: 组件
order: 5
---

# BiciTable

抽象至博智云创大量数据中后台产品的表格,用于展示行列数据。

## 何时使用

- 当有大量结构化的数据需要展现时;
- 当需要对数据进行排序、搜索、分页、自定义操作等复杂行为时。

## 代码演示

### 基础表格

```jsx
import React from 'react';
import { Tag, Space } from 'antd';
import { BiciTable } from '../../dist/bici-design.js';

const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
filterType: 'search',
onFilter: val => console.log(val),
render: text => <a>{text}</a>,
},
{
title: 'Select',
dataIndex: 'select',
filterType: 'select',
multiple: false,
selectData: [
{ id: 1, name: 'Bici' },
{ id: 2, name: 'FED' },
{ id: 3, name: 'Table', dataStatus: -1 },
{ id: 4, name: 'BiciTable', dataStatus: 2 },
],
onFilter: (val, extra) => console.log(val, extra),
key: 'select',
},
{
title: 'Multiple',
dataIndex: 'multiple',
filterType: 'select',
multiple: true,
selectData: [
{ id: 1, name: 'Bici' },
{ id: 2, name: 'FED' },
{ id: 3, name: 'Table', dataStatus: -1 },
{ id: 4, name: 'BiciTable', dataStatus: 2 },
],
onFilter: (val, extra) => console.log(val, extra),
key: 'multiple',
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
filterType: 'range',
onFilter: val => console.log(val),
},
{
title: 'Date',
dataIndex: 'date',
key: 'date',
filterType: 'date',
isRange: true,
picker: 'date',
format: 'YYYY-MM-DD HH:mm:ss',
showTime: { format: 'HH:mm:ss' },
onFilter: val => console.log(val),
},
{
title: 'Tags',
key: 'tags',
dataIndex: 'tags',
render: tags => (
<>
{tags.map(tag => {
let color = tag.length > 5 ? 'geekblue' : 'green';
if (tag === 'loser') {
color = 'volcano';
}
return (
<Tag color={color} key={tag}>
{tag.toUpperCase()}
</Tag>
);
})}
</>
),
},
{
title: 'Action',
key: 'action',
render: (text, record) => (
<Space size="middle">
<a>Invite {record.name}</a>
</Space>
),
},
];

const data = [
{
key: '1',
name: 'John Brown',
select: 'Bici',
multiple: 'FED',
address: 'New York No. 1 Lake Park',
tags: ['nice', 'developer'],
date: '2020-05-20',
},
{
key: '2',
name: 'Jim Green',
select: 'Bici',
multiple: 'FED',
address: 'London No. 1 Lake Park',
tags: ['loser'],
date: '2020-05-20',
},
{
key: '3',
name: 'Joe Black',
select: 'Bici',
multiple: 'FED',
address: 'Sidney No. 1 Lake Park',
tags: ['cool', 'teacher'],
date: '2020-05-20',
},
];

export default () => (
<BiciTable
size="small"
columns={columns}
dataSource={data}
pagination={{ current: 1, pageSize: 10 }}
/>
);
```
57 changes: 57 additions & 0 deletions docs/hooks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Hooks

## Class base 迁移

### 1. 执行初始化操作,模拟生命周期

- componentDidMount:useEffect(() => {}, []),第二个参数传空数组,useEffect 作为函数内的同步代码,至少会执行一次,没有依赖项时,不会触发二次执行
- componentDidMount & componentDidUpdate:useEffect(() => {}),不传第二个参数

### 2. 做一些清理操作

useEffect 第一个参数的返回函数,React 会在每次执行新的 Effect 之前,执行该函数

### 3. useState 与 Class 版本的区别

Class 版本会做 state 合并,我们在 setState 的时候只需要增量 set,Hooks 中的修改 state 需要传入完整的修改后的 state,因为会覆盖之前的 state。

### 4. 减少不必要的渲染

Class Component 开发时,我们可以通过 shouldComponentUpdate 优化渲染,Hooks 中如何减少不必要的渲染 ?

- React.memo

这不是一个 Hook,你可以把它理解为 Class base 里的 PureComponent,会做 props 的浅比较

- useMemo

这是一个 Hook,我们通过官方例子直观了解下。

```js
function Parent({ a, b }) {
// Only re-rendered if `a` changes:
const child1 = useMemo(() => <Child1 a={a} />, [a]);
// Only re-rendered if `b` changes:
const child2 = useMemo(() => <Child2 b={b} />, [b]);
return (
<>
{child1}
{child2}
</>
);
}
```

用法有点类似于 useEffect,第二个参数是一个依赖数组,只有依赖项发生变化时,才会触发 re-render 重新渲染。

### 5. 为什么不能用 condition 包裹 useHook 语句

与 Hooks 实现原理有关,React Hooks 并不是通过 Proxy 或者 getters 实现的,而是通过数组实现的,每次 useState 都会改变下标,如果 useState 被包裹在 condition 中,那每次执行的下标就可能对不上,导致 useState 导出的 setter 更新错数据。

### 6. 异步如何访问过去或未来的 state ?

使用 useRef 缓存 state,useRef 的作用相当于让你在 Class 组件的 this 上添加属性。

### 7. state 初始值优化

初始值很复杂时,推荐使用 useState 函数形式,useState 允许传入一个函数,React 只会在首次渲染时调用这个函数。
8 changes: 8 additions & 0 deletions jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,17 @@
]
},
"dependencies": {
"@ant-design/colors": "^4.0.5",
"@ant-design/icons": "^4.2.2",
"antd": "^4.6.2",
"antd-dayjs-webpack-plugin": "^1.0.1",
"babel-plugin-import": "^1.13.0",
"dayjs": "^1.8.34",
"lodash": "^4.17.20",
"prop-types": "^15.7.2",
"react": "^16.13.1",
"react-dom": "^16.13.1"
"react-dom": "^16.13.1",
"uuid": "^8.3.0"
},
"devDependencies": {
"@babel/core": "^7.11.5",
Expand Down
24 changes: 24 additions & 0 deletions src/assets/css/property.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,27 @@
overflow: hidden;
text-overflow: ellipsis;
}

.width100 {
width: 100%;
}

.mr4 {
margin-right: 4px;
}
.mr8 {
margin-right: 8px;
}
.mr12 {
margin-right: 12px;
}

.ml4 {
margin-left: 4px;
}
.ml8 {
margin-left: 8px;
}
.ml12 {
margin-left: 12px;
}
29 changes: 29 additions & 0 deletions src/components/BiciDatePicker/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* @File: BiciDatePiker
*/
import React from 'react';
import PropTypes from 'prop-types';
import { DatePicker } from 'antd';
import _ from 'lodash';
import 'dayjs/locale/zh-cn';
import locale from 'antd/es/date-picker/locale/zh_CN';

const { RangePicker } = DatePicker;

function BiciDatePicker(props) {
const { type } = props;
const omitProps = _.omit(props, ['type']);
const Picker = type === 'range' ? RangePicker : DatePicker;

return <Picker locale={locale} {...omitProps} />;
}

BiciDatePicker.defaultProps = {
type: 'date',
};

BiciDatePicker.propTypes = {
type: PropTypes.oneOf(['date', 'range']),
};

export default BiciDatePicker;
12 changes: 12 additions & 0 deletions src/components/BiciTable/BiciTable.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.filterDropdown {
display: flex;
align-items: center;
padding: 8px;
border-radius: 6px;
background: #fff;
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2);
}

.filterDropdownSelect {
width: 100%;
}
72 changes: 72 additions & 0 deletions src/components/BiciTable/FilterDropdown/DropdownDate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* @File: 日期筛选,高度可配,支持传入所有的 Ant Design DatePicker props
*/
import React from 'react';
import PropTypes from 'prop-types';
import { v4 as uuidv4 } from 'uuid';
import _ from 'lodash';
import BiciDatePicker from '@/components/BiciDatePicker';
import styles from '@/components/BiciTable/BiciTable.module.css';

const controlProps = [
'title',
'isRange',
'clearFilters',
'confirm',
'dataIndex',
'filterType',
'filters',
'prefixCls',
'onFilter',
'selectedKeys',
'setSelectedKeys',
'updateFilters',
'value',
'visible',
];

function DropdownDate(props) {
const { size, isRange, value, showTime, updateFilters, onFilter } = props;
const type = isRange ? 'range' : 'date';
const controlValue = isRange ? value : value[0];
// 强制挂载 Picker 浮层,防止因滑动导致的定位问题
const containerId = uuidv4();
const omitProps = _.omit(props, controlProps);

const handleOk = (e, extra) => {
// 范围值交互优化,需要同时选择起始值后确定回调才会生效
const [start, end] = isRange ? e : [];
const isOk = isRange ? start && end : !!e;
if (isOk) updateFilters({ visible: false, value: isRange ? e : [e] });
if (isOk && onFilter) onFilter(e, extra);
};

return (
<div id={containerId} className={styles.filterDropdown}>
<BiciDatePicker
{...omitProps}
type={type}
size={size}
allowClear={false}
value={controlValue}
getPopupContainer={() => document.getElementById(containerId)}
onChange={(e, extra) => {
updateFilters({ value: isRange ? e : [e], visible: !!showTime });
if (!showTime && onFilter) onFilter(e, extra);
}}
onOk={handleOk}
/>
</div>
);
}

DropdownDate.defaultProps = {
isRange: true,
format: 'YYYY-MM-DD',
};

DropdownDate.propTypes = {
isRange: PropTypes.bool,
};

export default DropdownDate;
Loading

0 comments on commit 86a4cd0

Please sign in to comment.