在 React 中正确使用箭头函数
Posted
技术标签:
【中文标题】在 React 中正确使用箭头函数【英文标题】:Correct use of arrow functions in React 【发布时间】:2018-07-19 20:30:30 【问题描述】:我正在使用带有 Babel 和 Webpack 的 ReactJS,并使用 ES6 以及 proposed class fields 来实现箭头功能。我知道箭头函数通过not recreating the functions each render 使事情变得更高效,类似于构造函数中的绑定工作方式。但是,我不能 100% 确定我是否正确使用它们。以下是我在三个不同文件中的代码的简化部分。
我的代码:
Main.js
prevItem = () =>
console.log("Div is clicked")
render()
return (
<SecondClass prevItem=this.prevItem />
)
SecondClass.js
<ThirdClass type="prev" onClick=()=>this.props.prevItem() />
ThirdClass.js
<div onClick=()=>this.props.onClick()>Previous</div>
问题:
我上面的代码是否正确使用了箭头函数?我注意到对于 SecondClass.js,我也可以使用:
<ThirdClass type="prev" onClick=this.props.prevItem />
由于我在原始函数定义中使用了 ES6 箭头函数,因此一种方法或另一种方法有区别吗?或者我应该一直使用箭头语法直到我的最后一个 div?
【问题讨论】:
developer.mozilla.org/en-US/docs/Web/javascript/Reference/… 这里有更多细节..onClick=this.props.prevItem
这里因为prevItem
中没有引用this
,所以你可以使用它。但是,如果您添加范围级别的代码,它将中断。由于您从一个对象分配一个函数并调用它,它失去了它的上下文。
仅供参考,类字段不是 ES6 的一部分
How to use arrow functions (public class fields) as class methods?的可能重复
【参考方案1】:
我知道箭头函数可以让事情变得更有效率 每次引用函数时重新创建函数
This is not true。
箭头函数以词法方式处理this
上下文,其中“普通”函数处理dynamically。如果您需要更多信息,我写了关于 the this key word in depth 的文章。
在您的两个内联箭头函数示例中,您将在每个 render
上创建一个新函数实例。
这将在每次渲染时创建并传递一个新实例
onClick=() =>
在第三个示例中,您只有一个实例。 这只会传递对已经存在的实例的引用
onClick=this.myHandler
至于箭头函数作为类字段的好处(有一个小的缺点,我会在答案的底部发布),如果您有一个需要访问当前的普通函数处理程序
class
通过this
的实例:
myHandler()
// this.setState(...)
您需要将 bind
显式显示为 class
。
最常见的方法是在constructor
中执行此操作,因为它只运行一次:
constructor(props)
super(props);
this.myHandler = this.myHandler.bind(this);
如果您使用箭头函数作为处理程序,则不需要将bind
传递给class
,因为如上所述,箭头函数使用this
的词法上下文:
myHandler = () =>
// this.setState(...)
通过这两种方法,您将像这样使用处理程序:
<div onClick=this.myHandler></div>
采用这种方式的主要原因:
<div onClick=() => this.myHandler(someParameter)></div>
如果你想将参数传递给被传递的原生event
旁边的处理程序,这意味着你想向上传递一个参数。
如前所述,这将在每次渲染时创建一个新的函数实例。 (对此有更好的方法,请继续阅读)。
此类用例的运行示例:
class App extends React.Component
constructor(props)
super(props);
this.state =
items: [ name: 'item 1', active: false , name: 'item 2', active: true ],
toggleITem = (itemName) =>
this.setState(prev =>
const nextState = prev.items.map(item =>
if (item.name !== itemName) return item;
return
...item,
active: !item.active
);
return items: nextState ;
);
render()
const items = this.state;
return (
<div>
items.map(item =>
const style = color: item.active ? 'green' : 'red' ;
return (
<div
onClick=() => this.toggleITem(item.name)
style=style
>
item.name
</div>
))
</div>
);
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
更好的方法是创建组件组合。
您可以创建一个包装相关标记的子组件,将拥有它自己的处理程序,并将 data
和 handler
作为来自父级的道具。
然后子组件将调用它从父组件获得的处理程序并将data
作为参数传递。
带有子组件的运行示例:
class Item extends React.Component
onClick = () =>
const onClick, name = this.props;
onClick(name);
render()
const name, active = this.props;
const style = color: active ? 'green' : 'red' ;
return (<div style=style onClick=this.onClick>name</div>)
class App extends React.Component
constructor(props)
super(props);
this.state =
items: [ name: 'item 1', active: false , name: 'item 2', active: true ],
toggleITem = (itemName) =>
this.setState(prev =>
const nextState = prev.items.map(item =>
if (item.name !== itemName) return item;
return
...item,
active: !item.active
);
return items: nextState ;
);
render()
const items = this.state;
return (
<div>
items.map(item =>
return <Item ...item onClick=this.toggleITem />
)
</div>
);
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
类字段向下:
正如我所提到的,类字段有一个小的缺点。
类方法和类字段的区别在于类字段附加到class
(构造函数)的instance
。
其中类方法和对象附加到原型。
因此,如果您有大量此类的实例,您可能会受到性能影响。
鉴于此代码块:
class MyClass
myMethod()
myOtherMethod = () =>
babel 会将其转换为:
var _createClass = function()
function defineProperties(target, props)
for (var i = 0; i < props.length; i++)
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
return function(Constructor, protoProps, staticProps)
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
;
();
function _classCallCheck(instance, Constructor)
if (!(instance instanceof Constructor))
throw new TypeError("Cannot call a class as a function");
var MyClass = function()
function MyClass()
_classCallCheck(this, MyClass);
this.myOtherMethod = function() ;
_createClass(MyClass, [
key: "myMethod",
value: function myMethod()
]);
return MyClass;
();
【讨论】:
【参考方案2】:使用JavaScript
curring函数声明,可以和其他答案不同,注意以下代码:
clickHandler = someData => e => this.setState(
stateKey: someData
);
现在JSX
,你可以写:
<div onClick=this.clickHandler('someData') />
带有someData
的clickHandler
返回一个带有e
参数的函数,但它不在clickHandler
函数内部使用。所以效果很好。
为了写得更完整,如下所示:
clickHandler = someData => () => this.setState(
stateKey: someData
);
不用e
,那我为什么要写呢。
【讨论】:
【参考方案3】:我知道箭头函数可以让事情变得更有效率 重新创建每个呈现的函数类似于在 构造函数工作。
这不是真的。这取决于您使用箭头功能的确切位置。如果在 render 方法中使用了Arrow function
,那么它们会创建一个新实例everytime
,render 的调用方式与bind
的工作方式相同。考虑这个例子
<div onClick=()=>this.onClick()>Previous</div>
这里每次调用 render 时都会创建一个匿名函数,调用该函数时会调用 this.onClick
。
但是考虑下面的情况
onClick = () =>
console.log("Div is clicked")
在上述情况下,箭头函数不会每次都重新创建函数,而是在类实例化时将上下文绑定到 React 组件An arrow function does not have its own this; the this value of the enclosing execution context is used.
。这类似于binding works is constructor
。这是proposed class fields for arrow functions
的一部分,不是 ES6 功能,
要理解你想问什么,你必须知道一个函数从它被调用的地方获取它的上下文。检查this question
以获得更多了解。
在你的例子中,你使用了Arrow function
来定义prevItem
,因此它获得了封闭的 React 组件的上下文。
prevItem = () =>
console.log("Div is clicked")
render()
return (
<SecondClass prevItem=this.prevItem />
)
现在在它的子组件中,即使您使用任何自定义上下文调用 prevItem
,using bind or arrow function
、prevItem
在父组件(即 Main.js
)中执行时也会获得其封闭的 React 组件的上下文。而且由于您只想执行 prevItem 函数并且不想将任何数据从孩子传递给它,因此编写
<ThirdClass type="prev" onClick=()=>this.props.prevItem() />
和
<div onClick=()=>this.props.onClick()>Previous</div>
根本没用,只会增加性能影响,因为每次都会在 SecondClass
和 ThirdClass
中创建新函数。您根本不需要将这些函数定义为箭头函数,只需编写
<ThirdClass type="prev" onClick=this.props.prevItem />
和
<div onClick=this.props.onClick>Previous</div>
因为它已经绑定在父级中。
现在即使你必须从 ThirdClass 和 SecondClass 向这些函数传递一些额外的数据,你也不应该直接使用 Arrow function
或 bind in render
。在How to Avoid binding in Render method
上查看这个答案
【讨论】:
使用箭头函数本身还不错。在render
中使用箭头函数非常好,即使它们被重新创建。在大多数应用程序中,性能差异不会很明显。
@DivyanshuMaithani 上面的代码是否也适用于功能组件或仅适用于类组件,当使用功能组件 SecondClass.js 时,我们是否需要像下面一样再次绑定它 this
的用法。【参考方案4】:
所以你的第一种方法
<ThirdClass type="prev" onClick=()=>this.props.prevItem() />
在此您可以将 ThirdClass 中可用的任何参数传递给 prevItem 函数。这是用参数调用父函数的好方法。像这样
<ThirdClass type="prev" onClick=()=>this.props.prevItem(firstArgument, secondArgument) />
你的第二种方法是
<ThirdClass type="prev" onClick=this.props.prevItem />
这种方法不允许您传递任何 ThirdClass 特定参数。
这两种方法都是对的,只是,这取决于你的使用 案子。两种使用 es6 箭头函数的方法都在上面提到的 各自的场景
【讨论】:
【参考方案5】:在原始函数定义中使用箭头可以让您不绑定构造函数中的函数。
如果你没有使用箭头...
prevItem()
console.log("Div is clicked")
然后你必须创建一个构造函数并在那里绑定它......
class MyComponent extends Component
constructor(props)
super(props)
this.prevItem = this.prevItem.bind(this)
prevItem() ...
刚开始时使用箭头会更容易,因为它可以正常工作,而且您不必了解构造函数是什么,也不必深入研究 javascript 中 this
的复杂性。
然而,在性能方面最好在构造函数中绑定。构造函数方法中的绑定将创建函数的单个实例并重复使用它,即使渲染方法被多次调用。
【讨论】:
用这种方法,有没有办法在调用prevItem时添加参数。例如:onpress=this.prevItem
但我想打电话给this.prevItem(variable)
以上是关于在 React 中正确使用箭头函数的主要内容,如果未能解决你的问题,请参考以下文章
使用 React 时,在构造函数中使用粗箭头函数还是绑定函数更可取?
[react] 在React中什么时候使用箭头函数更方便呢?
为啥我能够使用 this.state 而无需绑定或使用箭头函数 React