前言
我写代码一直有个毛病就是代码耦合性太高,可能是性子太急,总觉得前期设计组件结构会花费太多时间(其实是我能力不足),还有不自信自己最后设计出来的公共组件会提高效率,害怕得不偿失。但是今天遇到了很明显的需要抽离出一个公共组件的情况……我记录下来自己的设计思路,可能不是很准确,欢迎路人善意的指教。
内聚性和耦合性
偶然间刷到知乎的一个关于架构设计的文章,引用其中对我来说印象深刻的观点。详情指路: 【架构设计】如何让你的应用做到高内聚、低耦合?
内聚
关于代码的内聚性通常有7种描述
功能内聚 | 顺序内聚 | 通信内聚 | 过程内聚 | 时间内聚 | 逻辑内聚 | 偶然内聚 |
---|
如果一个页面没有任何公共组件那么就是一个偶然内聚的情况,所有功能集中在一个模块中,这也是我以前写过的,不过在工作中很快就被纠正了,因为实际工作中大多是很多模块有着相同功能,这个时候每个页面都要写一遍的话太费时间了。下面我写的例子我认为可以归为功能内聚。
耦合
非直接耦合 | 数据耦合 | 标记耦合 | 控制耦合 | 外部耦合 | 公共耦合 | 内容耦合 |
---|
技术框架
示例用的是Taro React框架以及TaroUI。
感觉TaroUI很灵活,很多实现可以自己去定制化开发。
需求描述
1、列表搜索功能
2、列表下拉加载,触底分页请求列表数据
上面的需求涉及到了3个不同页面,并且这3个页面的列表接口不是同一个。
设计思路分析
本来是想直接用Taro React的useReachBottom
这个事件监听滚动列表是否触底的,奈何这个事件并没有监听到,并且usePageScroll
这个事件也没有监听到,去github的issue板块查也没有确切的结果,只能用ScrollView滚动视图来替代了,这也是Taro的一个容器组件,只要设置容器高度就可以通过onScrollToLower
监听到是否滚动到底部。
首先是父组件引用我写的公共组件CommonScrollView
很明显给子组件传了3个值:
1、service是每个页面的列表请求接口,类型是(params: any)Promise<any>
2、showSearch,boolean
是否存在搜索栏(这个暂时没有不存在的情况,这个目前意义不大)
3、renderList,({ searchVal, dataList }: { searchVal: string, dataList: any[] }) => React.ReactNode
这个方法会接收公共组件请求列表接口返回的列表数据,不同页面拿到数据可以在自己的页面写渲染方法,还接收一个search Value,是因为搜索空的空白展示图和普通空白展示图不一样,用来判断展示不同图片的条件。
三个页面的请求参数都是相同的,搜索值(value
)、第x页(pageNo
)、每页x条(pageSize
)
所以我干脆把请求参数也放到公共组件里了,不然每个页面还要维护一套pageNo和pageSize的状态,实际公共组件负责更改pageNo(触底+1)
<CommonScrollView
service={getProspectusPage}
showSearch
>
{renderList}
</CommonScrollView>
renderList:
const renderList = ({searchVal, dataList}: { searchVal: string, dataList: IProspectus[] }) => {
if (!dataList.length) {
if (searchVal) {
return (
<View className={styles.noData}>
</View>
)
}
return (
<View className={styles.noData}>
</View>
)
}
return dataList.map((item: IProspectus) => (
<View >
</View>
))
}
也就是说我把滚动触底触发分页查询请求,以及搜索触发接口请求和页面初始化时的列表接口请求都放到公共组件CommonScroll
里面了,页面只负责拿这个子组件传过来的列表数据进行渲染即可。
而且当我写好一个页面之后,剩下两个页面花了1分钟就解决了!不得不感慨一下前期的组件设计真的很重要!
下面是我公共组件的代码结构
interface IProps {
children: ({ searchVal, dataList }: { searchVal: string, dataList: any[] }) => React.ReactNode,
service: (params: any) => Promise<any>,
showSearch: boolean;
}
const CommonScrollView = (props: IProps) => {
const { children, service, showSearch = false } = props
const [searchVal, setSearchVal] = useState<string>('');
const [currentList, setCurrentList] = useState<any>([]);
const [loading, setLoading] = useState<boolean>(true);
const [pageNo, setPageNo] = useState<number>(1);
const [dataReachBottom, setDataReachBottom] = useState<boolean>(false);
useDidShow(() => {
fetchData('', 1)
})
const fetchData = (val = '', searchPageNo?: number | undefined) => {
setLoading(true);
const currentPageNo = searchPageNo || pageNo;
service?.({
param: val,
pageNo: currentPageNo,
pageSize: 10
}).then((res: any) => {
setLoading(false);
let _currentList = [...currentList, ...res.data.list];
if(searchPageNo === 1) {
_currentList = res.data.list;
}
setCurrentList(_currentList);
if (_currentList.length === res.data.total) {
setDataReachBottom(true);
} else {
setPageNo(currentPageNo + 1)
}
});
}
const onScrollToLower = () => {
if (!dataReachBottom) {
fetchData(searchVal)
return;
}
};
const handleChange = (val: string) => {
setSearchVal(val);
}
const handleSearch = () => {
setPageNo(1)
fetchData(searchVal, 1)
}
const handleClear = () => {
setSearchVal('');
fetchData('', 1)
}
return (
<Fragment>
{showSearch && <AtSearchBar
value={searchVal}
onChange={handleChange}
onActionClick={handleSearch}
onClear={handleClear}
/>}
<ScrollView
scrollY
className='scroll-view'
scrollWithAnimation
onScrollToLower={onScrollToLower}
style={{ height: '80vh' }}
>
{children({ searchVal, dataList: currentList })}
{loading && (
<View style={{ position: 'relative', height: '5%' }}>
<AtActivityIndicator content='加载中...' mode='center'></AtActivityIndicator>
</View>
)}
{(dataReachBottom && !searchVal) && (
<AtDivider
content='没有更多了'
fontColor='#676874'
lineColor='#ECECEC'
/>
)}
</ScrollView>
</Fragment>
);
}
export default CommonScrollView;
这样看来,如果这些代码在每个页面都写一次,真的很臃肿,用我前同事的话来讲就是:“很僵硬”。