1、组件设计基本原则
单一职责。
也就是:
① 降低组件的复杂度,减少代码量,提升可读性。
② 降低与其他组件的耦合度(低耦合),从而降低变更导致的对功能的影响。
③ 提高可复用性,功能单一(高内聚),有明确的边界,不访问其他组件的内部细节,接口最小化,单向数据流...。
2、TIPs
性能:无状态函数组件 > 有状态函数组件 > class组件;
减少props传递;
抽取过多的条件控制流;
不过度优化;
3、分类
【展示组件】-【本质:视图】
通用的组件:开发时,要以“第三方组件库”的标准考虑其设计,不和任何项目的业务耦合。
项目通用的组件:可多个组件共享,可能与业务有较低耦合程度。
组件特有的组件库:与业务深度耦合,不能与其他组件共享。
【容器组件】-【本质:逻辑】
侧重业务处理,通常为高阶组件。
数据来源:直接请求接口或从外部传入。
然后展示完整的视图。
【有状态组件&无状态组件】
无状态组件完全由外部props控制状态。如果想要优化它的props映射,提升性能,可以使用React.memo避免无用渲染。
React.memo类似于React.PureComponent,两者的区别为:React.memo是用于函数组件的,React.PureComponent是用于类组件的。
React.memo的用法为:
const Component = ()=> { return ( <div>嘎嘎嘎</div> ) } const MemodFuncComponent = React.memo(Component)
React.PureComponent 的用法为:
import React from 'react'; class Test extends React.PureComponent { constructor(props) { super(props); this.state = { count: 0 } } componentWillUpdate(nextProps, nextState) { console.log('componentWillUpdate') } componentDidUpdate(prevProps, prevState) { console.log('componentDidUpdate') } render() { return ( <div> { this.state.count } <button onClick = { () => this.setState({ count: 1 }) }>点击</button> </div > ); } } export default Test;
React在进行组件更新时,如果发现这个组件是一个PureComponent,它会将组件现在的state和props和其下一个state和props进行浅比较,如果它们的值没有变化,就不会进行更新。
类似于写了一个shouldComponentUpdate生命周期,判断
nextProps和
nextState是否有变化:
import React from 'react'; class Test extends React.Component { constructor(props) { super(props); this.state = { count: 0 } } componentWillUpdate(nextProps, nextState) { console.log('componentWillUpdate') } componentDidUpdate(prevProps, prevState) { console.log('componentDidUpdate') } shouldComponentUpdate(nextProps, nextState) { // 如果没变化,就不渲染,否则重新渲染 if (this.state.count === nextState.count) { return false } return true } render() { return ( <div> { this.state.count } <button onClick = { () => this.setState({ count: 1 }) }>点我</button> </div> ); } } export default Test;
【纯组件&非纯组件】
纯组件其实类似于纯函数的概念。
纯组件就是[单向],给定输入,就永远返回相同的输出,过程没有副作用与外部的状态依赖(props,state,context没有变化,则输出没有变化),也就代表着这个组件不需要重新渲染,从而提高性能收益。
而很多个组件组成的比较复杂的组件树,则需要外部去维护它的状态,然后通过注入依赖,展示相对应的视图,这样可以维持组件树的纯净性。比较典型的解决方案是Redux,它把组件树看做一个状态树,分离逻辑与视图,再配合异步处理,就可以实现单向的数据流。类似的还有Cycle.js,有空学习。(#^.^#) (但还是附个链接: http://cyclejs.cn/#-%E7%BB%84%E4%BB%B6%E5%8C%96)
【布局组件&内容组件】
内容组件通常被约束在布局组件的占位中。抽离开来可以避免互相影响,提高可维护性。
布局组件:Grid, Layout, HorizontalSplit……
内容组件:Button, Label, Input……
【表单组件】
沿用通用的属性,保证兼容,减少代码重复,使用方便。
组件受控。实际开发中非受控组件的场景非常少, 自定义组件应只提供完全受控表单组件,避免组件自身维护缓存状态。
4、目录
大牛推荐的多页面目录:
src/ components/ # 共享组件 containers/ Admin/ # 后台管理页面 components/ # 后台特定的组件库 LoginPage/ index.tsx ... App/ components/ # App特定的组件库 LoginPage/ # App页面 index.tsx stores.ts # redux stores AnotherApp/ # 另外一个App页面 hooks/ ... app.tsx # 应用入口 anotherApp.tsx # 应用入口 admin.tsx # 后台入口
。。。。。。
还有一些没用过的目录划分方式,有空学习。(#^.^#)
5、模块
每个目录都是一个模块,都应该有一个index的唯一出口文件来统一管理模块的导出(限定模块的可见性)
(除了utils文件夹,它只是一块模块命名空间,因为它目录下是互不相关或不同类型的文件)
相对路径不超过两级(即../
和./
)
或者将相对路径转为绝对路径, 例如webpack
中可以配置resolve.alias
属性来实现(直接copy一下大佬的代码):
... resolve: { ... alias: { // 可以直接使用~访问相对于src目录的模块 // 如 ~/components/Button '~': context, }, }
然后就可以:import { hide } from '~/utils/dom';
6、组件拆分
①拆分为render方法,比如:
// 生成数据列表工具栏 const renderToolBar = () => { const toolBarList: ReactNode[] = [ <Btn key="showMetrics" type="primary" onClick={showMetrics}> 自定义指标 </Btn>, rowKey ==='key' && <DownloadEfforts columnsData={columnsData} totalList={totalList} tableInfo={tableInfo} searchParams={searchParams} key="downloadExcel" dataType="effect"/>, rowKey ==='materialId' && <DownloadEfforts columnsData={columnsData} totalList={totalList} tableInfo={tableInfo} searchParams={searchParams} key="downloadExcel" dataType="materialRank"/>, (rowKey === 'adId' || rowKey === 'creativeId') && <DownloadEfforts columnsData={columnsData} tableInfo={tableInfo} searchParams={searchParams} key="downloadExcel" dataType="relation"/>, ]; return toolBarList; }; return ( <BasisTable ... operateToolBar={() => renderToolBar()} /> )
但是这个方法只是在return处简化了代码,其实并没有真的实现组件的拆分。
当代码行数超过300这个阈值,就要进行组件进一步拆分。
②拆分为组件
7、组件文档化
https://storybook.js.org/
推荐用storybook.js进行文档化,用过,还是挺好上手的,就不赘述了。