我们开辟一个新产物的时刻,通常会先笼统出一些公用的组件,然后经由过程这些组件来拼装成页面。不知道人人有无发明,这类开辟体式格局带来的题目是一个团队内经常会有如许的场景:
A 已开辟了一个 XX 表格模块,B 要开辟一个相似的 YY 表格模块,然后 B 通常是去把 A 的代码 copy 一下,修正一些东西;或许不巧 B 不知道 A 已开辟 XX 表格,然后 B 又得一行行的写一些相似的代码。
形成这类题目的缘由简朴的说就是:组件笼统的粒度太单一。接下来我们会经由过程两个例子来报告题目及我们怎样处理如许的题目的。
一个简朴的组件 - Switch
起首我们看一个简朴的 Switch
组件,假如一个产物中有经常运用的两种切换功用:
假如运用之前封装的基本组件组件 Switch
来完成,我们须要以下挪用:
<Switch
className="switch"
activeIndex={this.state.activeIndex}
onChange={::this.handleSwitchChange}
>
<SwitchItem>趋向</SwitchItem>
<SwitchItem>列表</SwitchItem>
</Switch>
这类组件笼统体式格局(完成省略)优点就是通用性强,但带来一些题目:
每一个人都须要保护选项的展现称号和递次之间的关联
挪用代码较长,有冗余
因而,我们对这类组件举行了重构,愿望让每一个组件运用越发简朴,只须要关联详细的状况即可。详细的做法就是开辟一个 Generator —— generateSwitch
来天生经常运用的切换组件:
export const generateSwitch = (name, options) => {
const propTypes = {
className: PropTypes.string,
activeKey: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
onChange: PropTypes.func.isRequired,
};
const Switch = (props) => {
...
return (
<span className={classes}>
{
options.map((entry, index) => (
...
))
}
</span>
);
};
Switch.propTypes = propTypes;
Switch.displayName = name;
return Switch;
};
export const ASwitch = generateSwitch('ABSwitch', [
{ name: 'AA', key: 'a' },
{ name: 'BB', key: 'b' },
]);
export const BSwitch = generateSwitch('CDSwitch', [
{ name: 'CC', key: 'c' },
{ name: 'DD', key: 'd' },
]);
这类做法就能够处理上面说的题目:
比罕见的切换组件运用越发方便,挪用代码一行就够了,而且能够起到一致参数的作用;
对外暴露天生函数
generateSwitch
也能保证通用性。
更庞杂的例子 - 营业模块
下面以一个表格营业为例,罕见的表格模块以下:
在开辟这个模块的时刻,虽然每一个小区块我们都抽取了响应的组件,如 Selector
, Table
, Switch
, Pagination
等。然则把这些组件串起来也有许多逻辑,假如每一个相似的模块都反复写,任何一个小的逻辑发生变化,都能够须要修正一切的模块完成。所以这时刻我们想做的事变就是:这个模块自身也是一个组件,我们只须要经由过程一些设置天生差别的模块,而不是反复的 copy 代码,然后修正一些差别的处所。
在这里遇到的一个题目是,我们全部体系是运用 Redux 来治理数据的,全部项目的 Store
构造以下:
每一个营业模块会去 connect
响应的数据以及 actions
,每一个模块都有响应的 reducer
。而且每一个卡片的 action
也须要做到全局唯一,所以我们给模块的 UI Component 以及 reducer 分别开辟了响应的 Generator。
起首来看 UI Component 的 Generator:
function generateAbcModule({pageName, moduleName}) {
const ACTION_PREFIX = `${pageName}/${moduleName}`;
const LOAD = ACTION_PREFIX + 'LOAD';
...
function load(url, params, id) {
return (dispatch, getState) => {
const state = getState();
...
return dispatch({
type: LOAD,
....
});
};
}
@connect((state, props) => {
const moduleState = state[pageName][moduleName];
return {
...moduleState,
};
}, {
load,
})
class AbcModule extends Component {
...
}
return AbcModule;
}
经由过程代码发明,我们把 actionCreators
与 UI 放在了一同,然后经由过程 pageName
和 moduleName
来唯一地标识一个模块,拼装这两个参数作为 action
的前缀,从而到达每一个模块的 action
是全局唯一的。
接下来我们是 reducer
的 Generator:
function generateAbcModuleReducer({pageName, moduleName, defaultIndexes}) {
const ACTION_PREFIX = `${pageName}/${moduleName}/`;
const LOAD = ACTION_PREFIX + 'LOAD';
const initialState = {
indexes: defaultIndexes,
...
};
return function AbcModuleReducer(state = initialState, action) {
switch (action.type) {
case LOAD:
return {
...state,
isLoading: true,
...
};
...
}
};
相似的,reducer Generator 也是经由过程 pageName
和 moduleName
来唯一地标识一个模块。固然每一个模块能够会有差别的 initialState
,这个也能够经由过程 generateAbcModuleReducer 的入参来设置。
总结
上面这类运用 Generator 来封装营业模块的要领,能够在肯定程度上削减反复代码,加速开辟速率,不过假如营业生长的很快,有能够会致使营业模块组件 props 众多 的题目。
以上面的排行卡片为例,可变的东西就异常多,响应的就须要许多的 props 来设置,所以我们也须要依据详细的营业来把握是不是要举行笼统。