工作总结-React开发业务的一些总结

前言

emmm在这里工作也很久了,去年9月份到现在,虽然还是实习身份,但做的东西,其实跟正式员工也差不了多少。

自从工作以后,要学的东西太多了,又要准备毕设论文之类的,说到底还是懒吧,很少做总结。

今天拿出我半年没摸过的windows,重新开始学习,做总结。活到老学到老吧。

至于这里为什么是工作总结(一),可能只有一篇,也可能有多篇,想到什么写什么吧。毕竟还是开心最重要。

本文主要是从开发层面讲下工作中学到的一些关于React的操作,不涉及原理。工作过程中写的业务代码主要还是从模仿导师的写法开始写,后面写着写着就会开始想为什么要怎么写。下面就从React系统优化、组件封装、模块划分这几点来做下总结。

系统优化

仅从开发层业务层面上来讲,这里React的系统优化我只讲PureComponent API以及shouldComponentUpdate生命周期函数。组件内state以及props变化,都会引起组件重新渲染。上述两种方法都是通过对比state以及props的变化来决定是否阻断组件重新渲染。

前置条件

要使PureComponent和shouldComponentUpdate生效的一个条件,就是render函数中不能出现箭头函数以及bind函数。也就是说往子组件传入属性(props)的时候,避免使用箭头函数以及bind函数。

原因在于箭头函数在每次render的时候都会重新分配,props每次传的都是一个新函数,因此会影响到PureComponent以及shouldComponentUpdate的实现。因此最佳写法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class DemoComponent extends Component {
constructor(props){
super(props)
this.handleEvent = this.handleEvent.bind(this)
}
//bind方法
handleEvent(){

}
//箭头函数方法
handleEvent = ()=>{}
render(){
return(
<Button onClick={this.handleEvent}></Button>
)
}
}

shouldComponentUpdate

1
2
3
4
shouldComponentUpdate(nextProps,nextState){
//返回值为true,重新渲染,否则不重新渲染
return bool
}

所以控制组件是否重新渲染,只要把组件内的状态都加到这个函数中进行判断即可。

但是真正想用好这个生命周期钩子,我觉得还有一个很重要的问题要解决,就是对一个大的组件进行拆分,合理控制组件的粒度,实现局部刷新

之所以提到这个,是因为最近我在写React-Native的时候遇到了一个问题(react也同理):一个页面如下图(此处切换名称这个功能之所以做成modal的形式,是因为react-native中android和ios的select组件行为不一样)。

1558887920(1).jpg

最初的代码大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
shouldComponentUpdate(nextProps,nextState){
return nextState.modalVisible !== this.state.modalVisible ||
nextState.dataList !== this.state.dataList
}
render(){
const {modalVisible,dataList} = this.state
return(
<View>
<SelectMoal visible={modalVisible}></SelectMoal>
{
dataList.map(item=>(<Text>{item.name}</Text>))
}
</View>
)
}

这样写其实也没什么问题,但是在android上会出现一个情况,就是点击弹出Modal的时候,会导致dataList出现闪烁的情况,这样子会影响用户体验。出现这个问题的原因,是由于visible变化了,导致整个组件重新渲染。

理想的情况下应该是弹出Modal的时候,dataList不会重新渲染。解决方案是将dataList抽离成一个组件,在内部SCU判断dataList属性是否变化,从而达到阻断渲染的目的。更改后代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//Continer Component
shouleComponentUpdate(){...}
render(){
const {modalVisible,dataList} = this.state
return(
<View>
<SelectModal visible={modalVisible}></SelectModal>
<DataList data={dataList}></DataList>
</View>
)
}

// DataList Component
shouleComponentUpdate(nextProps){ return nextProps.data !== this.props.data }
render(){
const {data} = this.props
return(
<View>
{data.map(item=><Text>{item.name}</Text>)}
</View>
)
}

因此控制好组件的粒度,才能更好的发挥SCU的作用,减少页面刷新次数,从而优化用户体验

PureComponent

PureComponent是React15.3出的一个新特性,功能上跟SCU差不多,不过它进行的只是浅层的比较,在对象的对比上很容易出问题,因为修改了对象,但是对象分配的地址并没有改变,所以检测时候认为这个对象并没有被修改,从而阻断了重新渲染。

模块划分

目前SPA单页应用的优点是用户体验好、快,内容的改变不需要重新加载整个页面,能大大减少服务器压力。真因为这样子,项目打包成静态资源文件时,会生成几个很大的文件,而这样带来的后果是首屏加载过慢,出现白屏现象。

为了解决这个问题,出现了按需加载这种技术。原理很简单,就是按组件进行代码切割,切割的粒度完全由开发者决定。切割的方式目前由两种,一种是React16.3之后的版本更新的lazy函数,一种是结合react-loadable这个依赖包进行代码切割处理。前端每切换一次路由,就加载相应路由的模块代码,能加快首屏相应速度,因为首屏只需加载首屏模块的代码。

模块的划分,一般是一个功能划分为一个模块,模块的组织方式为容器组件与展示组件结合。容器组件负责数据的获取与更新,负责整个模块的数据流处理,展示组件仅进行数据的展示与触发数据更新。

举个例子,之前毕业设计做的一个管理系统的一个模块,容器代码组件代码如下,ProjectForm、ProjectTable、ProjectModal均为展示组件,ProjectForm负责数据查询UI显示,ProjectTable负责数据列表显示,ProjectModal负责数据增、改显示,数据的增删查改操作在展示组件中触发后,均在容器组件中进行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<Fragment >
<ProjectForm
onDidMount={this.onFormDidMount}
onFormSearch={this.onFormSearch}
showModal={this.showModal}
/>
<ProjectTable
data={projects}
onDidMount={this.onTableDidMount}
onSearch={this.onTableSearch}
showModal={this.showModal}
onDelete={this.deleteProject}
downloadProjectFile={this.downloadProjectFile}
resetPage={this.state.resetTablePage}
/>
<ProjectModal
modalType={modalType}
visible={visible}
onClose={this.closeModal}
modifyRecord={modifyRecord}
addProject={this.addProject}
updateProject={this.updateProject}
/>
</Fragment>

模块效果图如下,当路由跳转到这个模块下是,那么只加载这个容器组件的相关代码:

image.png

通过容器组件与展示组件的原则来编写模块的代码,还有个有点就是代码可读性强,数据流清晰,便于维护以及方便项目进行扩展,扩展新功能只需要再编写个路由以及相对于的容器组件即可。

组件封装

组件封装的目的是降低开发成本,加快开发效率,减少冗余代码,提高代码可读性。

组件封装的原则是职责单一,输入输出明确,可配置、可扩展。一个组件最终实现的效果应该是类似于一个纯函数,多次传入相同的值,组件输出的效果应该是一样的。

就拿文本输入框这个组件来说,不同的场景,不同的地方有不同的定制需求。例如,有的地方的Input输入框,是Number输入框;有的地方的Input输入框,则是TextArea输入框;有的情况下Input是一个普通的文本输入框,但有时候对输入的文本我们需要做一些正则限制,例如这个文本输入框智能输入email、url,还有就是文本长度的限制等等。Antd提供了很多配置的选项,但如果全部的表单都用Antd的原始组件写的话,那会出现很多冗余的代码,而且代码可读性会降低。

基于上面的考虑,我对Antd的Input组件进行了二次封装,使之在项目中具有可重用性,并且使用方便,极大提高开发效率。

进行组件封装之前先理清这个组件需要做什么任务。

(1)首先他是一个Input输入组件,需要满足的业务有Number,TextArea,Text。

(2)如果是Number输入框,那么Number的限制范围需要限制(max、min);如果是TextArea与Text,那么文本的长度需要做限制(max、min);如果是普通的Text,那么有几种常见的输入场景可以通过正则进行校验,例如email,url,密码等等,这里的扩展性较强。

(3)对于每个Input组件都有一些通用的属性,例如每个Input都需要一个唯一的fieldId来方便输入内容的获取,一个disabled来控制是否只读,需要一个label来控制显示这个Input的名称,一个required字段来控制是否必填等等。

按照上面的考虑,便可以进行组件设计了,设计导图如下图所示:

image.png

在调用这个Input组件的方法如下,使用方式非常简洁,不仅提高了代码的复用率,还提高了代码的可读性,提高了开发效率。

1
2
3
4
5
6
7
8
9
10
11
 <FormInput
placeholder="公告标题"
formRelated={{
form,
fieldId : fieldIds.title,
label : "公告标题",
required : true,
formItemLayout,
initialValue : modifyData ? modifyData.title : undefined
}}
/>

总结

有点乱,基本都是我毕业论文的东西。emmm先这样子。