Javascript 为什么 JSX 道具不应该使用箭头函数或绑定?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/36677733/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why shouldn't JSX props use arrow functions or bind?
提问by KadoBOT
I'm running lint with my React app, and I receive this error:
我正在使用 React 应用程序运行 lint,但收到此错误:
error JSX props should not use arrow functions react/jsx-no-bind
And this is where I'm running the arrow function (inside onClick
):
这就是我运行箭头函数的地方(内部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>
))}
Is this a bad practice that should be avoided? And what's the best way to do it?
这是应该避免的不良做法吗?最好的方法是什么?
回答by Ori Drori
Why you shouldn't use inline arrow functions in JSX props
为什么不应该在 JSX props 中使用内联箭头函数
Using arrow functions or binding in JSX is a bad practice that hurts performance, because the function is recreated on each render.
在 JSX 中使用箭头函数或绑定是一种不好的做法,它会损害性能,因为在每次渲染时都会重新创建该函数。
Whenever a function is created, the previous function is garbage collected. Rerendering many elements might create jank in animations.
Using an inline arrow function will cause
PureComponent
s, and components that useshallowCompare
in theshouldComponentUpdate
method to rerender anyway. Since the arrow function prop is recreated each time, the shallow compare will identify it as a change to a prop, and the component will rerender.
无论何时创建一个函数,前一个函数都会被垃圾回收。重新渲染许多元素可能会导致动画卡顿。
使用在线箭头功能将导致
PureComponent
S,和组件的使用shallowCompare
在shouldComponentUpdate
方法无论如何重新呈现。由于每次都会重新创建箭头函数 prop,因此浅比较会将其识别为对 prop 的更改,并且组件将重新渲染。
As you can see in the following 2 examples - when we use inline arrow function, the <Button>
component is rerendered each time (the console shows the 'render button' text).
正如您在以下 2 个示例中所见 - 当我们使用内联箭头函数时,<Button>
每次都会重新渲染组件(控制台显示“渲染按钮”文本)。
Example 1 - PureComponent withoutinline handler
示例 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>
Example 2 - PureComponent withinline handler
示例 2 -带有内联处理程序的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
}
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>
Binding methods to this
without inlining arrow functions
将方法绑定到this
不内联箭头函数
Binding the method manually in the constructor:
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> ); } }
Binding a method using the proposal-class-fieldswith an arrow function. As this is a stage 3 proposal, you'll need to add the Stage 3 presetor the Class properties transformto your babel configuration.
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> ); } }
在构造函数中手动绑定方法:
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绑定方法。由于这是第 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> ); } }
Function Components with inner callbacks
具有内部回调的函数组件
When we create an inner function (event handler for example) inside a function component, the function will be recreated every time the component is rendered. If the function is passed as props (or via context) to a child component (Button
in this case), that child will re-render as well.
当我们在函数组件中创建内部函数(例如事件处理程序)时,每次渲染组件时都会重新创建该函数。如果函数作为道具(或通过上下文)传递给子组件(Button
在这种情况下),该子组件也将重新渲染。
Example 1 - Function Component with an inner callback:
示例 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>
To solve this problem, we can wrap the callback with the useCallback()
hook, and set the dependencies to an empty array.
为了解决这个问题,我们可以用useCallback()
hook包装回调,并将依赖项设置为一个空数组。
Note:the useState
generated function accepts an updater function, that provides the current state. In this way, we don't need to set the current state a dependency of useCallback
.
注意:在useState
生成函数接受更新器功能,其提供当前状态。这样,我们不需要将当前状态设置为 的依赖项useCallback
。
Example 2 - Function Component with an inner callback wrapped with 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>
回答by Karl-Johan Sj?gren
This is because an arrow function apparently will create a new instance of the function on each render if used in a JSX property. This might create a huge strain on the garbage collector and will also hinder the browser from optimizing any "hot paths" since functions will be thrown away instead of reused.
这是因为如果在 JSX 属性中使用,箭头函数显然会在每个渲染上创建该函数的新实例。这可能会给垃圾收集器带来巨大压力,也会阻碍浏览器优化任何“热路径”,因为函数将被丢弃而不是重用。
You can see the whole explanation and some more info at https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
您可以在https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md 上查看整个解释和更多信息
回答by supNate
To avoid creating new functions with the same arguments, you could memoize the function bind result, here is a simple utility named memobind
to do it: https://github.com/supnate/memobind
为了避免创建具有相同参数的新函数,您可以记住函数绑定结果,这里有一个简单的实用程序来命名memobind
:https: //github.com/supnate/memobind
回答by sbaechler
Using inline functions like this is perfectly fine. The linting rule is outdated.
使用这样的内联函数非常好。linting 规则已经过时。
This rule is from a time when arrow functions were not as common and people used .bind(this), which used to be slow. The performance issue has been fixed in Chrome 49.
这个规则是在箭头函数不那么常见并且人们使用 .bind(this) 的时候出现的,它曾经很慢。性能问题已在 Chrome 49 中修复。
Do pay attention that you do not pass inline functions as props to a child component.
请注意不要将内联函数作为道具传递给子组件。
Ryan Florence, the author of React Router, has written a great piece about this:
React Router 的作者 Ryan Florence 对此写了一篇很棒的文章:
https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578
https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578
回答by Ghominejad
You can use arrow functions using react-cached-handlerlibrary, no need to be worried about re-rendering performance :
您可以使用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>
}
Other features:
其他特性:
- Named handlers
- Handle events by arrow functions
- Access to the key, custom arguments and the original event
- Component rendering performace
- Custom context for handlers
- 命名处理程序
- 通过箭头函数处理事件
- 访问键、自定义参数和原始事件
- 组件渲染性能
- 处理程序的自定义上下文
回答by ford04
Why shouldn't JSX props use arrow functions or bind?
为什么 JSX 道具不应该使用箭头函数或绑定?
Mostly, because inline functions can break memoization of optimized components:
大多数情况下,因为内联函数会破坏优化组件的记忆:
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)
传统上,React 中内联函数的性能问题与在每个渲染上传递新回调如何破坏
shouldComponentUpdate
子组件中的优化有关。(文档)
It is less about additional function creation cost:
它不是额外的函数创建成本:
Performance issues with
Function.prototype.bind
got fixed hereand 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)
性能问题
Function.prototype.bind
在这里得到修复,箭头函数要么是原生的,要么被 babel 转译为普通函数;在这两种情况下,我们都可以假设它并不慢。(反应训练)我相信声称函数创建成本高昂的人总是被误导(React 团队从未说过这一点)。(推文)
When is the react/jsx-no-bind
rule useful?
react/jsx-no-bind
规则什么时候有用?
You want to ensure, that memoized components work as intended:
您要确保记忆化的组件按预期工作:
React.memo
(for function components)PureComponent
or customshouldComponentUpdate
(for class components)
React.memo
(对于功能组件)PureComponent
或自定义shouldComponentUpdate
(用于类组件)
By obeying to this rule, stable function object references are passed. So above components can optimize performance by preventing re-renders, when previous props have not changed.
通过遵守此规则,传递稳定的函数对象引用。所以当之前的 props 没有改变时,上面的组件可以通过防止重新渲染来优化性能。
How to solve the ESLint error?
如何解决 ESLint 错误?
Classes: Define the handler as method, or class propertyfor this
binding.
Hooks: Use useCallback
.
类:将处理程序定义为方法或用于绑定的类属性this
。
钩子:使用useCallback
.
Middleground
中间地带
In many cases, inline functions are very convenient to use and absolutely fine in terms of performance requirements. Unfortunately, this rule cannot be limited to only memoized component types. If you still want to use it across-the-board, you could e.g. disable itfor simple DOM nodes:
在很多情况下,内联函数使用起来非常方便,而且在性能要求方面绝对没问题。不幸的是,这条规则不能仅限于记忆化的组件类型。如果您仍然想全面使用它,您可以例如为简单的 DOM 节点禁用它:
rules: {
"react/jsx-no-bind": [ "error", { ignoreDOMComponents: true } ],
}
const Comp = () => <span onClick={() => console.log("Hello!")} />; // no warning