React 性能优化与坑
一、性能优化
减少重复渲染
shouldComponentUpdate
jsx
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.num === this.props.num) {
return false
}
return true
}shouldComponentUpdate(nextProps, nextState) {
if (nextProps.num === this.props.num) {
return false
}
return true
}PureComponent
jsx
class App extends React.PureComponent {}
// 自动对比 props 和 state(浅比较)class App extends React.PureComponent {}
// 自动对比 props 和 state(浅比较)React.memo(函数组件)
jsx
const App = React.memo((props) => {
return <div>{props.name}</div>
})const App = React.memo((props) => {
return <div>{props.name}</div>
})性能优化总结
| 组件类型 | 优化方式 |
|---|---|
| 只有 props | PureComponent / memo |
| 只有 state | Component(state 变了直接更新) |
| 同时有 state 和 props | shouldComponentUpdate + componentWillReceiveProps |
长列表优化
使用 react-window 虚拟列表。
错误边界
jsx
export default class ErrorBoundary extends React.Component {
state = { hasError: false, error: null }
componentDidCatch(error, errorInfo) {
this.setState({ hasError: true, error })
}
render() {
if (this.state.hasError) {
return this.props.fallback || <div>出错了</div>
}
return this.props.children
}
}
// 使用
<ErrorBoundary fallback={<p>出错了</p>}>
<MyComponent />
</ErrorBoundary>export default class ErrorBoundary extends React.Component {
state = { hasError: false, error: null }
componentDidCatch(error, errorInfo) {
this.setState({ hasError: true, error })
}
render() {
if (this.state.hasError) {
return this.props.fallback || <div>出错了</div>
}
return this.props.children
}
}
// 使用
<ErrorBoundary fallback={<p>出错了</p>}>
<MyComponent />
</ErrorBoundary>严格模式
jsx
<React.StrictMode>
<App />
</React.StrictMode><React.StrictMode>
<App />
</React.StrictMode>检测内容:
- 不安全生命周期
- 旧式字符串 ref
- 废弃的 findDOMNode
- 意外副作用
- 遗留 context API
Fragment
<></> 等价于 <React.Fragment></React.Fragment><></> 等价于 <React.Fragment></React.Fragment>路由按需加载
bash
npm i react-loadablenpm i react-loadablejsx
import Loadable from 'react-loadable'
const SelectComponent = Loadable({
loader: () => import('@/components/Select'),
loading: Loading
})import Loadable from 'react-loadable'
const SelectComponent = Loadable({
loader: () => import('@/components/Select'),
loading: Loading
})首屏加载优化
- 骨架屏:
react-content-loader - 服务端渲染:ReactDOMServer
二、常见坑点
state 初始化不要使用 null
jsx
// 不要
state = { list: null }
// 使用空数组或空字符串
state = { list: [] }// 不要
state = { list: null }
// 使用空数组或空字符串
state = { list: [] }请求数据时判空
jsx
render() {
if (!this.state.list) return null
// 或
if (!arr.length) return null
}render() {
if (!this.state.list) return null
// 或
if (!arr.length) return null
}点击事件需要两次的问题
原因:第一次点击时数据尚未更新。可使用 setTimeout 解决:
jsx
setTimeout(() => {
this.setState({ data: newData })
}, 0)setTimeout(() => {
this.setState({ data: newData })
}, 0)iOS 输入框导致页面上移
jsx
componentDidMount() {
let timer
const inputBlur = (e) => {
if (e.target.tagName.toLowerCase() === 'input') {
timer = setTimeout(() => window.scrollTo(0, 0), 4)
}
}
const inputFocus = (e) => {
if (e.target.tagName.toLowerCase() === 'input') {
clearTimeout(timer)
}
}
parentEl.addEventListener('focusout', inputBlur)
parentEl.addEventListener('focusin', inputFocus)
}componentDidMount() {
let timer
const inputBlur = (e) => {
if (e.target.tagName.toLowerCase() === 'input') {
timer = setTimeout(() => window.scrollTo(0, 0), 4)
}
}
const inputFocus = (e) => {
if (e.target.tagName.toLowerCase() === 'input') {
clearTimeout(timer)
}
}
parentEl.addEventListener('focusout', inputBlur)
parentEl.addEventListener('focusin', inputFocus)
}内存泄露
报错:Can't perform a React state update on an unmounted component
jsx
class Test extends React.Component {
_isMounted = false
componentDidMount() {
this._isMounted = true
this._isMounted && this.setState({ ... })
}
componentWillUnmount() {
this._isMounted = false
}
}class Test extends React.Component {
_isMounted = false
componentDidMount() {
this._isMounted = true
this._isMounted && this.setState({ ... })
}
componentWillUnmount() {
this._isMounted = false
}
}常见场景:定时器、网络请求、事件监听 — 在组件销毁前都应处理。
链接出现 callback is not defined
jsx
window.callback = function() {}window.callback = function() {}"不能用作 JSX 组件" 错误
json
// package.json
"resolutions": {
"@types/react": "17.0.44"
}// package.json
"resolutions": {
"@types/react": "17.0.44"
}bash
pnpm add @types/react@17.0.44pnpm add @types/react@17.0.44
Cao Note