I. Introduction
For react high-end components. Everyone basically only knows the definition and usage. In actual development, it may not be used much. Team down a job, rewrite some of Antd Table
rendering as the main function of the module, we found some problems here, Antd Table
when rendering, even if there is no change Table
in props
, just change the package Table
components, Table
the components will be re-rendered.
2. The problem arises
import {
Table} from 'antd';
class TestRoot extends Component {
change() {
this.setState({
self_state: 2
});
}
render() {
const columns = [
{
title: '姓名',
dataIndex: 'name',
key: 'name',
render: (text, record, index) => {
console.log('rerendered !!!')
return <div>{
text}</div>;
}
},
{
title: '年龄',
dataIndex: 'age',
key: 'age'
},
{
title: '住址',
dataIndex: 'address',
key: 'address'
}
];
return (
<div>
<h1>{
this.state.self_state}</h1>
<Table columns={
columns} dataSource={
this.state.data_arr} />
</div>
);
}
}
When the TestRoot
state of my component changes, I will find that the console keeps outputting rerendered !!!
such prompts. Prove that Table
the re-rendering operation is done inside the form.
Three, analysis & solution
For Antd
UI libraries like this, the problem of repeated rendering is an optimization problem. Generally speaking, the library does not overdo these optimizations. What we have to do is to filter non-essential state rendering by controlling the Table
component shouldComponentUpdate
. At this time, we thought of using high-level component encapsulation, using reverse inheritance, and injecting it into the Table
component's rendering logic:
import {
Table } from 'antd';
const withPure = (Comp) => {
return class PureTable extends Comp {
shouldComponentUpdate(nextProps, nextState) {
//--在这里控制
return true;
}
render() {
return super.render();
}
};
};
export default withPure(Table)
How to use, replace the original direct use Table
with use PureTable
.
return (
<div>
<h1>{
this.state.self_state}</h1>
<PureTable columns={
columns} dataSource={
this.state.data_arr} />
</div>
);
Please note that this is the second way to implement high-end components. It adopts the form of inheriting the source component, so that we can control the rendering process by overwriting some methods of the source component, which is a deep attack injection.
1. The first level of shallow comparison through shallowCompare
Regarding shallowCompare
the usage, you can Baidu by yourself, which is a simple comparison. The changes in the first layer of state and props can be judged. But it is impossible to judge the deep reference object.
npm i react-addons-shallow-compare --save
import {
Table } from 'antd';
let shallowCompare = require('react-addons-shallow-compare');
const withPure = (Comp) => {
return class PureTable extends Comp {
shouldComponentUpdate(nextProps, nextState) {
return !shallowCompare(this, nextProps, nextState);
}
render() {
return super.render();
}
};
};
export default withPure(Table)
2. In-depth comparison through lodash _.isEqual
After testing the above code, it is found that after changing self_state, the table does not render again. But it is obvious that when my dataSource changes, shallowCompare will not judge it and prohibits the rendering of the table. For example, the following example of directly modifying dataSource:
change3() {
const {
data_arr } = this.state;
data_arr[0].name = '胡二狗';
this.setState({
data_arr
});
}
There are two branches here. The first one is to judge the deep props and state of the required Table based on the business situation, such as the dataSource attribute, or directly use the deep comparison to judge the state and props. The second method is obviously less efficient, but it can ensure that the rendering of the table will not be affected.
import {
Table } from 'antd';
let shallowCompare = require('react-addons-shallow-compare');
const withPure = (Comp) => {
return class PureTable extends Comp {
shouldComponentUpdate(nextProps, nextState) {
const is_sample = shallowCompare(this, nextProps, nextState);
if(!is_sample) {
return true;}
const state_sample = _.isEqual(nextState, this.state);
if(!state_sample) {
return true;}
const props_sample = _.isEqual(nextProps, this.props);
if(!props_sample) {
return true;}
return false;
}
render() {
return super.render();
}
};
};
export default withPure(Table)
Up to this point, in theory, when I only change the state of the Root component, the rendering of the table will not be triggered. However, when lodash performs a deep comparison, there is still an inconsistent attribute judgment. After deep debugging, it is found that the render function in the columns attribute passed by the two props is different. A problem with the writing of the table was found here.
const columns = [
{
title: '姓名',
dataIndex: 'name',
key: 'name',
render: (text, record, index) => {
console.log('rerendered !!!')
return <div>{
text}</div>;
}
},
{
title: '年龄',
dataIndex: 'age',
key: 'age'
},
{
title: '住址',
dataIndex: 'address',
key: 'address'
}
];
return (
<div>
<h1>{
this.state.self_state}</h1>
<Table columns={
columns} dataSource={
this.state.data_arr} />
</div>
);
Everyone pay attention to the render
function in the name column . This way of writing will cause a new render
function reference to be created every time, which leads to inconsistent judgments when lodash compares deeply and causes the table to re-render.
Define the render method in the class and modify the definition as follows:
//--省略
render_column_name = (text, record, index) => {
console.log('column rendered!');
return <TestColumnChild name={
text} ></TestColumnChild>;
}
render() {
const columns = [
{
title: '姓名',
dataIndex: 'name',
key: 'name',
render: this.render_column_name
},
{
title: '年龄',
dataIndex: 'age',
key: 'age'
},
{
title: '住址',
dataIndex: 'address',
key: 'address'
}
];
)
After testing, everything is normal. Setting the state of Root itself will no longer cause the table to re-render.
Four, sublimation
In fact, we found that we just implemented one ourselves through the wrapper pureComponent
. This wrapper can actually wrap any component, not just Table, if it follows the general writing of the above-mentioned depth comparison. In fact, this is the essence of high-end components. Here we introduce a library, recompose
which is lodash in react. Here is just an introduction and a recompose realization of my above writing. The following chapters will share more details on recompose.
import {
compose, pure } from 'recompose';
import {
Table} from 'antd';
const composeHoc = compose(pure);
export default composeHoc(Table);
Is not it simple?
The implementation here is to package components through the recompose native pure wrapper. It's just that pure here is a shallow comparison. Recompose provides a large number of short and beautiful wrappers, which can be packaged layer by layer through compose to enhance our components.