为什么 JSX 道具不应该使用箭头函数或绑定?
Why shouldn't JSX props use arrow functions or bind?
我正在 运行 与我的 React 应用程序进行 lint,但我收到此错误:
error JSX props should not use arrow functions react/jsx-no-bind
这就是我 运行 箭头函数(在 onClick
内)的位置:
{this.state.photos.map(tile => (
<span key={tile.img}>
<Checkbox
defaultChecked={tile.checked}
onCheck={() => this.selectPicture(tile)}
style={{position: 'absolute', zIndex: 99, padding: 5, backgroundColor: 'rgba(255, 255, 255, 0.72)'}}
/>
<GridTile
title={tile.title}
subtitle={<span>by <b>{tile.author}</b></span>}
actionIcon={<IconButton onClick={() => this.handleDelete(tile)}><Delete color="white"/></IconButton>}
>
<img onClick={() => this.handleOpen(tile.img)} src={tile.img} style={{cursor: 'pointer'}}/>
</GridTile>
</span>
))}
这是应该避免的不良做法吗?最好的方法是什么?
为什么你不应该在 JSX 属性中使用内联箭头函数
在 JSX 中使用箭头函数或绑定是一种会损害性能的不良做法,因为在每次渲染时都会重新创建该函数。
每当创建一个函数时,都会对前一个函数进行垃圾回收。重新渲染许多元素可能会导致动画卡顿。
使用内联箭头函数将导致 PureComponent
s,并且在 shouldComponentUpdate
方法中使用 shallowCompare
的组件无论如何都会重新渲染。由于每次都重新创建箭头函数 prop,浅比较会将其识别为对 prop 的更改,并且组件将重新渲染。
正如您在以下 2 个示例中看到的那样 - 当我们使用内联箭头函数时,<Button>
组件每次都会重新呈现(控制台显示 'render 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 - PureComponent with 内联处理程序
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>
在不内联箭头函数的情况下将方法绑定到 this
在构造函数中手动绑定方法:
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>
);
}
}
正在使用 proposal-class-fields with an arrow function. As this is a stage 3 proposal, you'll need to add the Stage 3 preset or the Class properties transform 将方法绑定到您的 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>
);
}
}
具有内部回调的函数组件
当我们在函数组件中创建内部函数(例如事件处理程序)时,每次渲染组件时都会重新创建该函数。如果该函数作为道具(或通过上下文)传递给子组件(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()
hook 包装回调,并将依赖项设置为空数组。
注意: 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>
这是因为如果在 JSX 属性 中使用,箭头函数显然会在每个渲染器上创建一个新的函数实例。这可能会对垃圾收集器造成巨大压力,并且还会阻碍浏览器优化任何 "hot paths",因为函数将被丢弃而不是重用。
您可以在 https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
查看完整的解释和更多信息
为避免创建具有相同参数的新函数,您可以记住函数绑定结果,这里有一个名为 memobind
的简单实用程序可以执行此操作:https://github.com/supnate/memobind
像这样使用内联函数非常好。 linting 规则已过时。
这个规则来自于箭头函数不那么普遍并且人们使用 .bind(this) 的时代,它曾经很慢。性能问题已在 Chrome 49.
中修复
请注意不要将内联函数作为 props 传递给子组件。
React Router 的作者 Ryan Florence 对此写了一篇很棒的文章:
https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578
您可以使用 react-cached-handler 库使用箭头函数,无需担心重新渲染性能:
Note : Internally it caches your arrow functions by the specified key,
no need to be worried about re-rendering!
render() {
return (
<div>
{this.props.photos.map((photo) => (
<Photo
key={photo.url}
onClick={this.handler(photo.url, (url) => {
console.log(url);
})}
/>
))}
</div>
);
}
其他功能:
- 命名处理程序
- 通过箭头函数处理事件
- 访问密钥、自定义参数和原始事件
- 组件渲染性能
- 处理程序的自定义上下文
为什么 JSX 属性不应该使用箭头函数或绑定?
主要是因为内联函数会破坏优化组件的记忆:
Traditionally, performance concerns around inline functions in React have been related to how passing new callbacks on each render breaks shouldComponentUpdate
optimizations in child components. (docs)
额外函数创建成本较少:
Performance issues with Function.prototype.bind
got fixed here and arrow functions are either a native thing or are transpiled by babel to plain functions; in both cases we can assume it’s not slow. (React Training)
I believe people claiming function creation is expensive have always been misinformed (React team never said this). (Tweet)
react/jsx-no-bind
规则何时有用?
您想确保记忆组件按预期工作:
React.memo
(对于函数组件)
PureComponent
或自定义 shouldComponentUpdate
(对于 class 组件)
通过遵守此规则,传递了稳定的函数对象引用。因此,当先前的道具没有改变时,上述组件可以通过防止重新渲染来优化性能。
如何解决ESLint错误?
类:将处理程序定义为方法,或 class property 用于 this
绑定。
挂钩:使用 useCallback
.
中间地带
在很多情况下,内联函数使用起来非常方便,对性能的要求也绝对没问题。不幸的是,这条规则不能仅限于记忆组件类型。如果您仍然想全面使用它,您可以例如disable it 对于简单 DOM 节点:
rules: {
"react/jsx-no-bind": [ "error", { "ignoreDOMComponents": true } ],
}
const Comp = () => <span onClick={() => console.log("Hello!")} />; // no warning
我正在 运行 与我的 React 应用程序进行 lint,但我收到此错误:
error JSX props should not use arrow functions react/jsx-no-bind
这就是我 运行 箭头函数(在 onClick
内)的位置:
{this.state.photos.map(tile => (
<span key={tile.img}>
<Checkbox
defaultChecked={tile.checked}
onCheck={() => this.selectPicture(tile)}
style={{position: 'absolute', zIndex: 99, padding: 5, backgroundColor: 'rgba(255, 255, 255, 0.72)'}}
/>
<GridTile
title={tile.title}
subtitle={<span>by <b>{tile.author}</b></span>}
actionIcon={<IconButton onClick={() => this.handleDelete(tile)}><Delete color="white"/></IconButton>}
>
<img onClick={() => this.handleOpen(tile.img)} src={tile.img} style={{cursor: 'pointer'}}/>
</GridTile>
</span>
))}
这是应该避免的不良做法吗?最好的方法是什么?
为什么你不应该在 JSX 属性中使用内联箭头函数
在 JSX 中使用箭头函数或绑定是一种会损害性能的不良做法,因为在每次渲染时都会重新创建该函数。
每当创建一个函数时,都会对前一个函数进行垃圾回收。重新渲染许多元素可能会导致动画卡顿。
使用内联箭头函数将导致
PureComponent
s,并且在shouldComponentUpdate
方法中使用shallowCompare
的组件无论如何都会重新渲染。由于每次都重新创建箭头函数 prop,浅比较会将其识别为对 prop 的更改,并且组件将重新渲染。
正如您在以下 2 个示例中看到的那样 - 当我们使用内联箭头函数时,<Button>
组件每次都会重新呈现(控制台显示 'render 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 - PureComponent with 内联处理程序
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>
在不内联箭头函数的情况下将方法绑定到 this
在构造函数中手动绑定方法:
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> ); } }
正在使用 proposal-class-fields with an arrow function. As this is a stage 3 proposal, you'll need to add the Stage 3 preset or the Class properties transform 将方法绑定到您的 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> ); } }
具有内部回调的函数组件
当我们在函数组件中创建内部函数(例如事件处理程序)时,每次渲染组件时都会重新创建该函数。如果该函数作为道具(或通过上下文)传递给子组件(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()
hook 包装回调,并将依赖项设置为空数组。
注意: 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>
这是因为如果在 JSX 属性 中使用,箭头函数显然会在每个渲染器上创建一个新的函数实例。这可能会对垃圾收集器造成巨大压力,并且还会阻碍浏览器优化任何 "hot paths",因为函数将被丢弃而不是重用。
您可以在 https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
查看完整的解释和更多信息为避免创建具有相同参数的新函数,您可以记住函数绑定结果,这里有一个名为 memobind
的简单实用程序可以执行此操作:https://github.com/supnate/memobind
像这样使用内联函数非常好。 linting 规则已过时。
这个规则来自于箭头函数不那么普遍并且人们使用 .bind(this) 的时代,它曾经很慢。性能问题已在 Chrome 49.
中修复请注意不要将内联函数作为 props 传递给子组件。
React Router 的作者 Ryan Florence 对此写了一篇很棒的文章:
https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578
您可以使用 react-cached-handler 库使用箭头函数,无需担心重新渲染性能:
Note : Internally it caches your arrow functions by the specified key, no need to be worried about re-rendering!
render() {
return (
<div>
{this.props.photos.map((photo) => (
<Photo
key={photo.url}
onClick={this.handler(photo.url, (url) => {
console.log(url);
})}
/>
))}
</div>
);
}
其他功能:
- 命名处理程序
- 通过箭头函数处理事件
- 访问密钥、自定义参数和原始事件
- 组件渲染性能
- 处理程序的自定义上下文
为什么 JSX 属性不应该使用箭头函数或绑定?
主要是因为内联函数会破坏优化组件的记忆:
Traditionally, performance concerns around inline functions in React have been related to how passing new callbacks on each render breaks
shouldComponentUpdate
optimizations in child components. (docs)
额外函数创建成本较少:
Performance issues with
Function.prototype.bind
got fixed here and arrow functions are either a native thing or are transpiled by babel to plain functions; in both cases we can assume it’s not slow. (React Training)I believe people claiming function creation is expensive have always been misinformed (React team never said this). (Tweet)
react/jsx-no-bind
规则何时有用?
您想确保记忆组件按预期工作:
React.memo
(对于函数组件)PureComponent
或自定义shouldComponentUpdate
(对于 class 组件)
通过遵守此规则,传递了稳定的函数对象引用。因此,当先前的道具没有改变时,上述组件可以通过防止重新渲染来优化性能。
如何解决ESLint错误?
类:将处理程序定义为方法,或 class property 用于 this
绑定。
挂钩:使用 useCallback
.
中间地带
在很多情况下,内联函数使用起来非常方便,对性能的要求也绝对没问题。不幸的是,这条规则不能仅限于记忆组件类型。如果您仍然想全面使用它,您可以例如disable it 对于简单 DOM 节点:
rules: {
"react/jsx-no-bind": [ "error", { "ignoreDOMComponents": true } ],
}
const Comp = () => <span onClick={() => console.log("Hello!")} />; // no warning