在 React hook 篇章时我们认识了一些避免 re-render 的 hook,像是 useMemo、useCallback,还有 HOC React.memo。
而在这个篇章中会介绍几个除了 hook 以外,让 React 效能更好或是避免效能变差的技巧。
React 专案常常都会搭配着 webpack 使用,而透过 webpack 打包 React 专案後产生一个 bundle.js (或者可以自己修改预设名字)的档案,里面包含了许多的 js 程序码。
当一个专案的规模越庞大时,打包出来的 bundle.js 档案也会越大,载入网站的时间也会变得更久,但实际上 bundle.js 里面某些的程序码会在一开始载入就用上吗?并不见得。所以我们可以靠 Code-Splitting 的其中一个方式: 以 Dynamic Import 的方式在适当的时机才载入需要的程序码。
要做 Dynamic Import 有好几种方式,以下一一介绍:
在 React 官网 有个段落提到可以使用 import() 进行 Dynamic Import。
范例:
import('/modules/my-module.js')
.then((module) => {
// Do something with the module.
});
这两个是 React 提供的 API,可惜 React 官网 提到 SSR 时不能使用它们。
React.lazy 接受一个 callback function 当作参数,当首次渲染元件时才会 import 该元件的 bundle。
使用范例:
const OtherComponent = React.lazy(() => import('./OtherComponent'));
接着 React.lazy 回传的 lazy 元件要放在 Suspense 元件内部,并且 Suspense 的 props fallback 可以放置一个元件,当 Suspense 元件内部的元件渲染完成前,就先渲染 fallback 内的元件。
import React, { lazy, Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
<AnotherComponent />
</Suspense>
</div>
);
}
另外,React.lazy & React.Suspense 也可以搭配 React Router 使用。
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
</Switch>
</Suspense>
</Router>
);
除了上面的两个方式外,我们也可以使用 React Loadable 套件 这个套件,如果要做 SSR 就可以使用它来做。
react-loadable 接收了一个物件参数,loader 属性是一个函式,会 Dynamic Import 需要的元件,当 Dynamic Import 元件还没渲染完成前,就先渲染 loading 属性的元件。
import Loadable from 'react-loadable';
const Loading = () => {
return <div>Loading......</div>
};
const LoadableComponent = Loadable({
loader: () => import('./my-component'),
loading: Loading,
});
export default class App extends React.Component {
render() {
return <LoadableComponent/>;
}
}
如果要隐藏和显示元件的话可以透过 CSS 的 opacity 去控制,而不是透过 return 不同的元件去做隐藏和显示。
// 如果 state view 频繁变更,元件也会频繁的 mount & unmount
function Component(props) {
const [view, setView] = useState('view1');
return view === 'view1' ? <SomeComponent /> : <AnotherComponent />
}
// 改善
const visibleStyles = { opacity: 1 };
const hiddenStyles = { opacity: 0 };
function Component(props) {
const [view, setView] = useState('view1');
return (
<React.Fragment>
<SomeComponent style={view === 'view1' ? visibleStyles : hiddenStyles}>
<AnotherComponent style={view !== 'view1' ? visibleStyles : hiddenStyles}>
</React.Fragment>
)
}
因为在每次元件 re-render 时,匿名函式都要被重新分配记忆体位置,行内样式也是一样的原理。
import React from 'react';
// bad
// 父元件为 class component
class Page extends React.Component {
render() {
return <Button onClick={() => {
this.setState({ isClicked: true })
}} />
}
}
// 父元件为 function component
const Page = () => {
return <Button onClick={() => {
setIsClicked(true)
}} />
}
// good
// 父元件为 class component
class Page extends React.Component {
handleClick = () => {
this.setState({ isClicked: true })
}
render() {
return <Button onClick={this.handleClick} />
}
}
// 父元件为 function component
const Page = () => {
const handleClick = React.useCallback(() => {
setIsClicked(true)
}, [])
return <Button onClick={handleClick} />
}
将 React element 用 useMemo 优化,不然每次 re-render 都会建立新的 element。
// bad
const Page = () => {
return <Button content={<p>Click me!</p>} />
}
// good
const Page = () => {
const content = React.useMemo(() => <p>Click me!</p>, [])
return <Button content={content} />
}
这点还蛮好理解的,若列表里面的项目有改变,react 会透过 key 的值去检查哪些项目要做重新渲染。
如果用 map 的 index 当 key,列表的第一个项目被移除,所有列表项目(可以想成阵列里面的元素)都往前递补,原本的第二个项目变成新的第一个项目,项目内容当然会改变导致整个列表重新渲染。
6 tips for better React performance
5 Tips to Improve the Performance of Your React Apps
<<: 【24】如果把 Dropout 放在 CNN 之後会发生什麽事
>>: Unity与Photon的新手相遇旅途 | Day23-Photon房间场景
昨天介绍的全手动安装是对系统掌控的一种极端,而今天要介绍的,是对系统洁癖的另一种极端。假如你受够了要...
“工慾善其事必先利其器”:利器阶段(把技术放入自己的工具箱): Step1:协助公司(中小型公司)维...
我们在前面 Day09 , 有简单讨论到 function,这篇就会来看一些更深入 functio...
-一般问题解决过程 如果配置由其他工程师仔细检查,则对配置更改强制执行两人控制可能会避免该事件。这...
有了基础的注册系统後,建立资料库来连接系统,用於储存用户、使用者的帐号与密码、等级等,区分版本功能。...