以一个项目中实际的例子,介绍如何使用React插槽和PureComponent优化你的React代码
使用过Vue小伙伴肯定对slot插槽这个概念并不陌生,其实,react也提供了类似Vue的插槽特性,也就是render props,这篇文章将结合一个实际的例子,来介绍如何使用react的“插槽”优化我们的代码,并结合PureComponent进行性能优化。
1. 案例介绍
这是一个很典型的前端页面,主要功能是用户录入一些信息,之后进行提交,主要功能和模块如下:
- 页面有四个主要模块:页面的名字,右上角的提交按钮,两个展示不同类型信息的卡片
- 卡片1包括卡片名称,一个输入框,几个单选框,几个复选框,一个用于选择的按钮,例如点击按钮,会出现一个选择器,用户从中选择一些信息
- 卡片2包括卡片名称,一段文本,一个文本输入。这里的文本和卡片1的单选框有联动关系,文本会根据单选框的不同选择内容,展示不同的文字
- 点击页面右上角的按钮,会将两个卡片中填写的信息进行提交
下面就集中实现方案进行介绍,并评估优劣。
2. 基础版:一把梭
基础版的实现方案很简单,就是把所有的ui和逻辑写到一个文件中,下面使用类组件实现
class Page extends React.Component {
state = { ... }
// input处理函数
onInputChange = () => { ... }
// 单选框处理函数
onRadioChange = () => { ... }
// 复选框处理函数
onCheckoutboxChange = () => { ... }
// 按钮点击处理函数
onButtonClick = () => { ... }
// 文本输入处理函数
onTextAreaChange = () => { ... }
// 提交数据
submit = () => { ... }
// 渲染头部
renderNavigator = () => { ... }
// 渲染卡片1
renderCard1 = () => { ... }
// 渲染卡片2
renderCard2 = () => { ... }
// 主渲染函数
render = () => {
return (
<>
{this.renderNavigator()}
{this.renderCard1()}
{this.renderCard2()}
</>
)
}
}
这种写法的缺点很明显,首先,所有的代码写到同一个文件中,文件太大,如果页面比较复杂, 代码行数很可能会破1000行,代码可读性差,不易扩展,相信维护过这种页面同学肯定十分痛苦。第二个原因,这个代码的性能很差。例如用户改变了文本框的内容,会执行setState,由于所有的ui都写在了一个组件内,所以整个页面都会重新渲染,但是实际发生改变的只有输入框,这就导致了不必要的渲染,性能不好。
3.升级版1.0
简单的从UI来看,这个页面可以分为三个大部分,头部的导航部分,卡片1和卡片2,因此可以把这三部分抽成三个组件,Header,Card1和Card2,下面看代码
class Header extends React.PureComponent {
constructor(props) {
super(props)
}
render() {
return (
<div>
<span>{this.props.title}</span>
<button onClick=(this.props.onSubmit)>{this.props.btnText}</button>
</div>
)
}
}
class Card1 extends React.PureComponent {
constructor(props) {
super(props)
}
renderTitle = () => { ... }
renderInput = () => { ... }
renderRadios = () => { ... }
renderCheckboxs = () => { ... }
renderButton = () => { ... }
render() {
return (
<div>
{this.renderTitle()}
{this.renderInput()}
{this.renderRadios()}
{this.renderCheckboxs()}
{this.renderButton()}
</div>
)
}
}
class Card2 extends React.PureComponent {
constructor(props) {
super(props)
}
renderTitle = () => { ... }
renderText = () => { ... }
renderTextArea = () => { ... }
render() {
return (
<div>
{this.renderTitle()}
{this.renderText()}
{this.renderTextArea()}
</div>
)
}
}
class Page extends React.Component {
constructor(props) {
super(props)
this.state = { ... }
}
// ... event handlers
render() {
return (
<div>
<Header />
<Card1 />
<Card2 />
</div>
)
}
}
这种方式通过UI将页面分为了三个组件,并且每个组件使用PureComponent,这样,如果Card1组件更新,Card2和Header组件的props没有变化,就不会更新在一定程度上优化了性能。但是,这种方式依然存在问题:
- 优化了性能,但没有完全优化。组件的拆分力度较大,组件更新时依然会有不必要的渲染。例如用户修改了input的内容,Card1组件会重新渲染,但是单选和多选框是不需要更新的。
- 对项目的后续维护人员来说,组件是一个黑箱。如果只看Page组件的话,是无法获知每个子组件的渲染内容的,并且每个组件的props会很多。由于点击提交按钮时要提交所有的信息,因此,state必须写在Page组件中,不能写在子组件里面。因此,每个组件都要接受很多的props,包括用于数据展示的state和事件处理函数。例如Card1组件里面包含了input,radio,checkout。因此需要给这个组件传递input的state和handler,radio的state和handler,checkbox的state和handler。在不了解Card1组件代码的前提下,这些props会给维护人员造成困惑。
- 可能会有同学将Card1和Card2组件,根据UI继续拆分,例如
class Card1 extends React.PureComponent {
constructor(props) {
super(props)
}
render() {
return (
<div>
<Input />
<Radios />
<Checkboxs />
<Button />
</div>
)
}
}
这种写法相对于上面的写法来说,进一步缩小了组件的粒度,如果Input等子组用PureComponent实现,可以进一步减少非必要的render。但是前面提到的问题2并没有得到解决,此外,这种写法加深了组件树的层级,导致props需要多传一层,反而加重了问题2。有些同学可能想到使用Context,但是使用Context很容易导致整个组件树的更新,性能不好。
4.升级版2.0
难道就没有一种方法,既能优化性能,又能让组件层级一目了然的方法吗?答案就是render props。render props允许用户传递一个方法或者组件作为props,并在组件中渲染该组件。
个人感觉React并不是不支持插槽,jsx支持用户传递一个返回组件的函数,或者直接传递一个组件,用户可以自由地决定这个prop如何使用。Vue只是将这种比较特殊的prop进行了一种封装,独立出一个功能,叫做插槽。两者在基础使用上本有什么本质的区别。
就像Vue将prop,事件处理函数做了单独的封装,并使用不同的语法,React使用的jsx可以传递任意类型的数据,用户自行决定这个prop是什么。
React提供了一种比较特殊的render props,就是children,在代码中可以直接使用this.props.children
来调用,下面就是用children继续对代码进行优化。
class Page extends React.Component {
constructor(props) {
super(props)
this.state = { ... }
}
// ... event handlers
render() {
return (
<div>
<Header>
<Title />
<Button />
</Header>
<Card>
<Input />
<Radios />
<Checkboxs />
<Button />
</Card>
<Card>
<Text />
<TextArea />
</Card>
</div>
)
}
}
以Card组件为例
class Card extends React.PureComponent {
constructor(props) {
super(props)
}
render() {
<div>
{this.props.children}
</di>
}
}
使用children,我们将所有子组件的渲染都写在了Page.js中,这样,就无须一层层的透传props,而且组件层级清晰,每个组件只接受本组件需要的props,一目了然。并且,每个子组件使用PureComponent实现,从而减少不必要的渲染。这种方法还可以更大程度的进行组件复用,例如Card组件就进行了复用。此外,这种方式无需考虑兄弟组件通信的问题,因为所有的state都提升到了公共的父组件当中。所有的子组件都是无状态组件,数据流清晰。
5.总结
灵活使用React的“插槽”机制,对组件进行细粒度划分,能够明确组件的层级,结合PureComponent,可以减少组件不必要的渲染。
转载自:https://juejin.cn/post/7180640225117339685