??React 作為一個用于構(gòu)建用戶界面的 JAVASCRIPT 庫,具有高效、靈活的特性。react本身性能就很高,并且內(nèi)置了很多用于提高性能的Api,供我們使用。本文主要介紹在不借助任何Api的前提下,如何去優(yōu)化性能。下面以常見的B端列表頁面為例,并使用react-devtools-highlight-updates+gif動畫來記錄頁面的渲染范圍,來介紹如何通過狀態(tài)內(nèi)聚提高性能。話不多說,直接上代碼:
const UserInfoList: React.FC = () => {
const [form] = Form.useForm();
const [dataSource, setDataSource] = useState([]);
const [cityList, setCityList] = useState([]);
useEffect(() => {
fetchDataSource();
}, []);
const fetCityList = (open) => {
if (open) {
// ... 偽代碼
setCityList(res.data);
}
};
const fetchDataSource = () => {
// ...偽代碼
setDataSource(res.data.list);
};
return (
<div className="page-list">
<Form form={form} onFinish={fetchDataSource}>
<Row>
<Col span={6}>
<Item label="姓名" name="name"><Input/></Item>
</Col>
<Col span={6}>
<Item label="年齡" name="age"> <Input/></Item>
</Col>
<Col span={6}>
<Item label="城市" name="cityId">
<Select
options={cityList}
onDropdownVisibleChange={fetCityList}
/>
</Item>
</Col>
<Col span={6}>
<Button type="primary" htmlType="submit">搜索</Button>
</Col>
</Row>
</Form>
<div className="table-box">
<Table
columns={columns}
dataSource={dataSource}
pagination={false}
/>
</div>
</div>
);
};
export default UserInfoList;
??作為一個簡單的列表頁面,上面的代碼看起來沒啥問題,但是我們通過圖一可以看出,當(dāng)獲取城市數(shù)據(jù)并更新state時,整個頁面都重新render了,這在中大型應(yīng)用中很容易造成性能問題。

??我們對UserInfoList組件進(jìn)行優(yōu)化一下,把篩選區(qū)域提成一個單獨的組件,并且把cityList狀態(tài)從UserInfoList移動到FilterPanel中,代碼如下:
import FilterPanel from '../FilterPanel';
const UserInfoList: React.FC = () => {
const fetchDataSource = () => {};
return (
<div className="page-list">
<FilterPanel fetchDataSource={fetchDataSource} />
<div className="table-box">
<Table
columns={columns}
dataSource={dataSource}
pagination={false}
/>
</div>
</div>
);
};
export default UserInfoList;
const FilterPanel: React.FC = ({fetchDataSource}) => {
const [form] = Form.useForm();
const [cityList, setCityList] = useState([]);
const fetCityList = (open) => {
if (open) {
// ... 偽代碼
setCityList(res.data);
}
};
return (
<Form form={form} onFinish={fetchDataSource}>
<Row>
{/**此處省略一萬個字...*/}
<Col span={6}>
<Item label="城市" name="cityId">
<Select
options={cityList}
onDropdownVisibleChange={fetCityList}
/>
</Item>
</Col>
</Row>
</Form>
);
};
export default FilterPanel;
??現(xiàn)在我們再來看下圖二記錄的頁面render范圍,已經(jīng)從整個頁面,縮小到FilterPanel組件所在的篩選區(qū)域了,性能提升還是很明顯的。此時有同學(xué)可能會說,render范圍還是有點大,能不能繼續(xù)優(yōu)化呢?

??答案是肯定的,我們先來觀察一下FiltrPanel組件,cityList只有下拉組件用到了,F(xiàn)ilterPanel內(nèi)的其他代碼并沒有用到,那我們把cityList狀態(tài)繼續(xù)內(nèi)聚不就可以進(jìn)一步縮小render范圍嗎!下面我們對Select組件進(jìn)行封裝,使其變成一個相對通用的下拉組件,代碼如下:
import CusSelect from '../CusSelect'
const FilterPanel: React.FC = ({fetchDataSource}) => {
const [form] = Form.useForm();
return (
<Form form={form} onFinish={fetchDataSource}>
<Row>
<Col span={6}>
<Item label="城市" name="cityId">
<CusSelect fetchOptions={getCityList}/>
</Item>
</Col>
</Row>
</Form>
);
};
export default FilterPanel;
import React, { useState, useEffect } from 'react';
import { Select } from 'antd';
const CusSelect: React.FC = ({ fetchOptions }) => {
const [options, setOptions] = useState([]);
// 正常是在useEffect內(nèi)調(diào)用接口
// 我們在onDropdownVisibleChange函數(shù)內(nèi)部調(diào)用接口只是為了方便測試
// useEffect(() => {
// fetchOptions().then(res => {
// setOptions(res.data);
// });
// }, []);
const getOptions = (open) => {
if (open) {
fetchOptions().then(res => {
setOptions(res.data);
});
}
};
return (
<Select options={options} onDropdownVisibleChange={getOptions}/>
);
};
export default CusSelect;
??現(xiàn)在我們再看一下圖三記錄的頁面render范圍,已經(jīng)縮小到下拉組件所在區(qū)域了。此時我們再對比一下圖一,性能提升效果一目了然。

??總結(jié):本文通過B端常見的列表例子,介紹了如何使用狀態(tài)內(nèi)聚,來提高性能。如果組件內(nèi)狀態(tài)很多,通過狀態(tài)內(nèi)聚,能夠做到幾倍甚至幾十倍的性能提升。并且我們在做狀態(tài)內(nèi)聚的同時,還可以把原本復(fù)雜的組件,按照功能進(jìn)行拆分,這樣不僅可以提高組件的可維護(hù)性,也更加符合單一職責(zé)原則。當(dāng)然,狀態(tài)內(nèi)聚也會有一定的局限性,例如父組件或者兄弟組件依賴子組件狀態(tài),這個時候就不太適合把狀態(tài)聚合到子組件中。