为什么不应该在 JSX 道具中使用内联箭头函数
在 JSX 中使用箭头函数或绑定是一种不良做法,会损害性能,因为该函数是在每次渲染上重新创建的。
每当创建函数时,都会对前一个函数进行垃圾回收。重新渲染许多元素可能会在动画中产生卡顿。
使用内联箭头函数将导致 s 和在该方法中使用的组件重新呈现。由于每次都会重新创建箭头函数 prop,因此浅层比较会将其标识为对 prop 的更改,并且组件将重新呈现。PureComponent
shallowCompare
shouldComponentUpdate
正如您在以下2个示例中看到的那样 - 当我们使用内联箭头函数时,每次都会重新呈现组件(控制台显示“渲染按钮”文本)。<Button>
示例 1 - 不带内联处理程序的 PureComponent
class Button extends React.PureComponent {
render() {
const { onClick } = this.props;
console.log('render button');
return (
<button onClick={ onClick }>Click</button>
);
}
}
class Parent extends React.Component {
state = {
counter: 0
}
onClick = () => this.setState((prevState) => ({
counter: prevState.counter + 1
}));
render() {
const { counter } = this.state;
return (
<div>
<Button onClick={ this.onClick } />
<div>{ counter }</div>
</div>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
示例 2 - 具有内联处理程序的纯组件
class Button extends React.PureComponent {
render() {
const { onClick } = this.props;
console.log('render button');
return (
<button onClick={ onClick }>Click</button>
);
}
}
class Parent extends React.Component {
state = {
counter: 0
}
render() {
const { counter } = this.state;
return (
<div>
<Button onClick={ () => this.setState((prevState) => ({
counter: prevState.counter + 1
})) } />
<div>{ counter }</div>
</div>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
将方法绑定到此
函数而不内联箭头函数
-
在构造函数中手动绑定方法:
class Button extends React.Component {
constructor(props, context) {
super(props, context);
this.cb = this.cb.bind(this);
}
cb() {
}
render() {
return (
<button onClick={ this.cb }>Click</button>
);
}
}
-
使用箭头函数绑定使用建议类字段的方法。由于这是第 3 阶段的建议,因此您需要将阶段 3 预设或类属性转换添加到 babel 配置中。
class Button extends React.Component {
cb = () => { // the class property is initialized with an arrow function that binds this to the class
}
render() {
return (
<button onClick={ this.cb }>Click</button>
);
}
}
具有内部回调的函数组件
当我们在函数组件内创建一个内部函数(例如事件处理程序)时,每次渲染组件时都会重新创建该函数。如果函数作为 props(或通过上下文)传递给子组件(在本例中),则该子组件也将重新呈现。Button
示例 1 - 具有内部回调的函数组件:
const { memo, useState } = React;
const Button = memo(({ onClick }) => console.log('render button') || (
<button onClick={onClick}>Click</button>
));
const Parent = () => {
const [counter, setCounter] = useState(0);
const increment = () => setCounter(counter => counter + 1); // the function is recreated all the time
return (
<div>
<Button onClick={increment} />
<div>{counter}</div>
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
为了解决这个问题,我们可以用 useCallback()
钩子包装回调,并将依赖项设置为空数组。
注意:生成的函数接受提供当前状态的更新程序函数。这样,我们就不需要将当前状态设置为 的依赖项。useState
useCallback
示例 2 - 具有使用 useCallback 包装的内部回调的函数组件:
const { memo, useState, useCallback } = React;
const Button = memo(({ onClick }) => console.log('render button') || (
<button onClick={onClick}>Click</button>
));
const Parent = () => {
const [counter, setCounter] = useState(0);
const increment = useCallback(() => setCounter(counter => counter + 1), []);
return (
<div>
<Button onClick={increment} />
<div>{counter}</div>
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>