关于组件化

组件开发原则

遵循一定的原则,可以设计出简单易用、易于扩展的组件;

文件组织

每个(业务)组件使用的资源:(css)less、js、jsx、图片、字体等都放入一个文件夹下。

单一权责 组件的功能界限

掌握好组件拆分粒度,每一个组件要处理一个核心问题。提高重用性、灵活度,不要为了拆分而拆分,重点是满足业务需求,而不是炫技。

  1. 组件只关心自己的业务逻辑,一些交互,通过触发回调(props.onClickprops.onChange等)的方式,交给调用者来处理;

弹框操作封装

较少字段的表单,最好可以封装到modal中,让用户在一个页面中完成添加、修改操作

  • 如果弹框中的内容通用性不大,直接封装到modal中,如果后期有通用性需求,再拆分,一般情况下,modal中的内容不会有通用性,基本上都是以modal形式存在;
  • 如果弹框中内容有公用情况,可以分别定义两个组件,一个是基础组件,一个是基础组件+弹框,分多个个文件定义;
  • antd 中的modal有正常的声明周期,也就是willMount,didMount会在页面加载时触发一次,以后每次modal被重新打开都不触发,多个操作公用一个modal就会出现数据互相干扰;import {Modal} from 'zk-tookit/antd',zk-tookit中的modal,对antd 的 modal进行了扩展,添加了 beforeOpen方法,每次打开都会触发,可以做modal的初始化工作;也避免了modal没有被打开,modal的willMout,didMount也触发,造成的一些操作浪费;

组件常用数据命名

组件只负责显示数据,至于数据由哪儿来的,外部通过props传入,一般情况下,很少在组件内发请求获取数据,但是也要视情况而定.数据名称有统一约定,可以降低沟通成本。

  1. 核心数据:
    1. 列表:dataSource;
    2. 对象:data;
    3. 基础数据:value;
  2. 加载:loading;
  3. 是否可见:visible;
  4. 是否禁用:disabled;
  5. 提交回调:onSubmit;
  6. 确定回调:onOk;
  7. 取消回调:onCancel;
  8. 事件回调:onXxx(onChage,onClick等等);

props类型和默认值

要申明props类型并注释属性的作用,通过props类型声明,即可知晓组件有哪些props;合理设置(不是全部设置)组件的默认props,可以有效的简化外部传入props的判断工作、以简化组件的调用传参;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, {Component} from 'react';
import PropTypes from 'prop-types';

class A extends Component{
static propTypes{
loading: PropTypes.bool, // 页面加载状态
};

static defaultProps{
loading: false,
};

state = {};

render(){
...
}
}

基础业务数据组件

基础业务数据,经常以radioselectcheckbox等形式被其他业务组件调用,提供一些组件:XxxSelect、XxxCheckbox、XxxRadio、XxxTransfer等,方便其他组件调用;

设计原则:

  1. 组件内部发送ajax请求,获取数据,其他组件只管调用即可,不必关心数据;
  2. 外部提供数据,通过dataSource属性传入,一般不会用到,如果有的组件用到再扩展即可;
  3. onLoad(dataSource, res)回调,将ajax请求返回的数据、res暴露给其他组件,其他组件有可能会使用到数据;
  4. dataFilter(dataSource, res) => dataSource回调,其他组件用来对ajax返回的数据进行过滤;
  5. showDisabled属性,默认false:是否显示禁用项,有些基础业务数据,有禁用启用操作,通过此字段表明是否显示禁用项;禁用项不可选择,label上显示禁用图标;
  6. 修改数据时,禁用项回显问题,如果修改的数据就是禁用的,不处理会显示value,而不是label;
  7. showAll属性,默认false:显示所有数据,不分禁用与启用,都是正常显示;

第三方组件再次封装

有时候,第三方组件不能满足业务需求,需要进行一次包装,包装后只是功能的增强,不要修改第三方组件原有的功能;

  1. 单独包装一个第三方组件,要将props也传递给第三方组件,比如zk-tookit/antd中的Modal组件,只是添加了beforeOpen回调,并将所有的props也传递给antd的modal,保持antd的modal原有功能;
  2. 组合封装多个第三方组件,所有第三方组件相应的声明对应的props属性,比如ListPage封装,用到了Table,listPage组件提供tableProps属性,所有表格相关的属性,都通过tableProps传递;form的getFieldDecorator所需参数,都通过decorator传递等等;

基础数据组件

比如:星期、男女、是否等硬编码数据

可以定义一个文件夹,分别定义数据以及各个组件,比如:weeks.js、WeekCheckbox.jsx、WeekRadio.jsx、WeekSelect.jsx;具体有什么组件基于需求而定,可以用到一个扩展一个。

其他

  1. 一些key-value相关的数据,一般定义为:[{value: '1', label: '星期一'}, ...],以valuelabel为关键字。
  2. 组件扩展,小版本内要注意向前兼容,参数要保证只增不减,同时用FIXME标记好哪些是不好的设计,等到大版本更新时,进行重构;
  3. 合理区分展示组件、容器组件;

props处理

抽取部分封装时用到的props属性,其他的原封不动赋值给html标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
render(){
const {
type, shape, size, className, htmlType, ...others,
} = this.props;
// 使用type 等一些属性,干些事情
...
return (
<button
{...others}
type={htmlType || 'button'}
...
>
...
</button>
);
}

componentWillReceiveProps周期函数

获取新旧props进行对比,将props转化成内部state等操作;

判断chilren是什么组件

A.jsx

1
2
3
4
5
6
7
export default class A extends React.Component{
static __IS_A = true; // 添加一个静态变量,作为标记
...
render(){
...
}
}

B.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
export default class B extends React.Component{
render(){
const {children} = this.props;
let hasA = false;
React.Children.map(children, item => {
// 从type中获取静态变量,作为判断依据
if(item && item.type && item.type.__IS_A) hasA = true;
});
return (
<div>{children}</div>
);
}
}

C.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
import A from 'path/to/A';
import B from 'path/to/B';
export default class C extends React.Component{
render(){
return (
<B>
<A/>
<span>...</span>
...
</B>
);
}
}

一些实用小工具

  • classNames className处理工具
  • omit 浅拷贝,删除指定属性

propTypes 的使用

通用组件要声明propTypes,复杂结构(对象,数组内部包含对象)要通过PropTypes.shape明确声明内部结构

  • 通过propTypes可以清晰看出组件需要哪些属性,没个属性都是什么类型、结构;
  • 有些IDE可以基于propTypes给出提示;
  • 方便调用;