我一直在阅读一堆 React 代码,但我看到了一些我不明白的东西:
handleChange = field => e => {
e.preventDefault();
/// Do something here
}
这是一个curried function
首先,用两个参数检查这个函数……
const add = (x, y) => x + y
add(2, 3) //=> 5
这里又是咖喱形式……
const add = x => y => x + y
这是没有箭头函数的相同代码……
const add = function (x) {
return function (y) {
return x + y
}
}
专注于 return
它可能有助于以另一种方式对其进行可视化。我们知道箭头函数是这样工作的——让我们特别注意返回值。
const f = someParam => returnValue
所以我们的 add
函数返回一个 function——我们可以使用括号来增加清晰度。 粗体文本是我们函数 add
的返回值
const add = x => (y => x + y)
换句话说,某个数字的 add
返回一个函数
add(2) // returns (y => 2 + y)
调用柯里化函数
因此,为了使用我们的 curried 函数,我们必须稍微不同地调用它……
add(2)(3) // returns 5
这是因为第一个(外部)函数调用返回了第二个(内部)函数。只有在我们调用第二个函数之后,我们才能真正得到结果。如果我们将调用分开在两条线上,这一点会更加明显……
const add2 = add(2) // returns function(y) { return 2 + y }
add2(3) // returns 5
将我们的新理解应用于您的代码
相关:“绑定、部分应用和柯里化有什么区别?”
好的,现在我们了解了它是如何工作的,让我们看看你的代码
handleChange = field => e => {
e.preventDefault()
/// Do something here
}
我们将首先在不使用箭头函数的情况下表示它……
handleChange = function(field) {
return function(e) {
e.preventDefault()
// Do something here
// return ...
};
};
然而,由于箭头函数在词法上绑定 this
,它实际上看起来更像这样......
handleChange = function(field) {
return function(e) {
e.preventDefault()
// Do something here
// return ...
}.bind(this)
}.bind(this)
也许现在我们可以更清楚地看到它在做什么。 handleChange
函数正在为指定的 field
创建一个函数。这是一种方便的 React 技术,因为您需要在每个输入上设置自己的侦听器才能更新应用程序状态。通过使用 handleChange
函数,我们可以消除所有会导致为每个字段设置 change
侦听器的重复代码。凉爽的!
1 这里我不必对 this
进行词法绑定,因为原始的 add
函数不使用任何上下文,因此在这种情况下保留它并不重要。
更多的箭头
如有必要,可以对两个以上的箭头函数进行排序 -
const three = a => b => c =>
a + b + c
const four = a => b => c => d =>
a + b + c + d
three (1) (2) (3) // 6
four (1) (2) (3) (4) // 10
柯里化函数能够带来令人惊讶的事情。下面我们看到 $
被定义为一个带有两个参数的柯里化函数,但在调用站点,它看起来好像我们可以提供任意数量的参数。柯里化是 arity 的抽象 -
const $ = x => k => $ (k (x)) const add = x => y => x + y const mult = x => y => x * y $ (1) // 1 (add ( 2)) // + 2 = 3 (mult (6)) // * 6 = 18 (console.log) // 18 $ (7) // 7 (add (1)) // + 1 = 8 (mult (8)) // * 8 = 64 (mult (2)) // * 2 = 128 (mult (2)) // * 2 = 256 (console.log) // 256
部分应用
部分应用是一个相关的概念。它允许我们部分应用函数,类似于柯里化,除了函数不必以柯里化形式定义 -
const partial = (f, ...a) => (...b) =>
f (...a, ...b)
const add3 = (x, y, z) =>
x + y + z
partial (add3) (1, 2, 3) // 6
partial (add3, 1) (2, 3) // 6
partial (add3, 1, 2) (3) // 6
partial (add3, 1, 2, 3) () // 6
partial (add3, 1, 1, 1, 1) (1, 1, 1, 1, 1) // 3
这是您可以在自己的浏览器中玩的 partial
的工作演示 -
const partial = (f, ...a) => (...b) => f (...a, ...b) const preventDefault = (f, event) => ( event .preventDefault () , f (event) ) const logKeypress = event => console .log (event.which) document .querySelector ('input[name=foo]') .addEventListener ('keydown', partial (preventDefault, logKeypress))
简要地
它是一个返回另一个以简短方式编写的函数的函数。
const handleChange = field => e => {
e.preventDefault()
// Do something here
}
// is equal to
function handleChange(field) {
return function(e) {
e.preventDefault()
// Do something here
}
}
为什么?
您是否曾经遇到过需要创建可以自定义的功能的情况?或者你可能有一个带有固定参数的回调函数,但是你需要在避免全局变量的同时发送额外的变量?如果您回答是,那么这就是如何做到这一点的方法。
例如,我们有一个带有 onClick
回调的按钮。我们想将 id
传递给函数,但是,onClick
只接受一个参数 event
,所以我们不能这样做:
const handleClick = (event, id) {
event.preventDefault()
// Dispatch some delete action by passing record id
}
这是行不通的!
作为解决方案,我们编写了一个函数,该函数返回另一个函数,其变量范围为 id
,而不使用任何全局变量:
const handleClick = id => event {
event.preventDefault()
// Dispatch some delete action by passing record id
}
const Confirm = props => (
<div>
<h1>Are you sure to delete?</h1>
<button onClick={handleClick(props.id)}>
Delete
</button>
</div
)
功能组成
多个箭头函数也称为“curried 函数”,它们用于函数组合。
// It is just an example, unfortunately, redux does not export dispatch function
import {dispatch, compose} from 'redux'
const pickSelectedUser = props => {
const {selectedName, users} = props
const foundUser = users.find(user => user.name === selectedName)
return foundUser.id
}
const deleteUser = userId => event => {
event.preventDefault()
dispatch({
type: `DELETE_USER`,
userId,
})
}
// The compose function creates a new function that accepts a parameter.
// The parameter will be passed throw the functions from down to top.
// Each function will change the value and pass it to the next function
// By changing value it was not meant a mutation
const handleClick = compose(
deleteUser,
pickSelectedUser,
)
const Confirm = props => (
<div>
<h1>Are you sure to delete?</h1>
<button onClick={handleClick(props)}>
Delete
</button>
</div
)
const handleClick = (ev, id) => {ev.preventDefault(); //do somth with id}
并执行 onClick="(ev) => handleClick(ev, id);"
<--- 这是 方式 更具可读性。在您的版本中,event
甚至发生了什么事情并不明显
handleClick(ev, id)
在某些时候更明显,但它不是可组合的。检查此代码段:gist.github.com/sultan99/13ef56b4089789a8d115869ee2c5ec47,您会发现 curried 函数非常适合函数组合,这是函数式编程中非常重要的一部分。
一般提示:如果您对任何新的 JavaScript 语法及其编译方式感到困惑,您可以检查 Babel。例如,在 Babel 中复制代码并选择 ES 2015 预设将给出如下输出
handleChange = function handleChange(field) {
return function (e) {
e.preventDefault();
// Do something here
};
};
https://i.stack.imgur.com/vWrgH.png
了解 available syntaxes of arrow functions 将使您了解他们在“链接”时引入的行为,就像您提供的示例中一样。
当一个箭头函数被编写时没有大括号,有或没有多个参数,构成函数体的表达式被隐式返回。在您的示例中,该表达式是另一个箭头函数。
No arrow funcs Implicitly return `e=>{…}` Explicitly return `e=>{…}`
---------------------------------------------------------------------------------
function (field) { | field => e => { | field => {
return function (e) { | | return e => {
e.preventDefault() | e.preventDefault() | e.preventDefault()
} | | }
} | } | }
使用箭头语法编写匿名函数的另一个优点是它们在词法上绑定到定义它们的范围。从 'Arrow functions' on MDN:
与函数表达式相比,箭头函数表达式的语法更短,并且在词法上绑定了 this 值。箭头函数始终是匿名的。
考虑到它取自 reactjs 应用程序,这在您的示例中尤为相关。正如@naomik 所指出的,在 React 中,您经常使用 this
访问 component's member functions。例如:
Unbound Explicitly bound Implicitly bound
------------------------------------------------------------------------------
function (field) { | function (field) { | field => e => {
return function (e) { | return function (e) { |
this.setState(...) | this.setState(...) | this.setState(...)
} | }.bind(this) |
} | }.bind(this) | }
可以这样想,每次看到箭头时,都将其替换为 function
。function parameters
在箭头之前定义。
因此在您的示例中:
field => // function(field){}
e => { e.preventDefault(); } // function(e){e.preventDefault();}
然后一起:
function (field) {
return function (e) {
e.preventDefault();
};
}
// Basic syntax:
(param1, param2, paramN) => { statements }
(param1, param2, paramN) => expression
// equivalent to: => { return expression; }
// Parentheses are optional when there's only one argument:
singleParam => { statements }
singleParam => expression
this
。
它可能并不完全相关,但由于提到的问题反应用例(我一直碰到这个 SO 线程):双箭头函数有一个重要方面,这里没有明确提到。只有“第一个”箭头(函数)被命名(因此在运行时“可区分”),任何后面的箭头都是匿名的,从 React 的角度来看,每次渲染都算作“新”对象。
因此双箭头函数将导致任何 PureComponent 一直重新渲染。
例子
您有一个带有更改处理程序的父组件:
handleChange = task => event => { ... operations which uses both task and event... };
并使用如下渲染:
{ tasks.map(task => <MyTask handleChange={this.handleChange(task)}/> }
然后将 handleChange 用于输入或单击。这一切都有效,看起来非常好。但这意味着任何会导致父级重新渲染的更改(例如完全不相关的状态更改)也会重新渲染您的所有 MyTask,即使它们是 PureComponents。
这可以通过多种方式得到缓解,例如传递“最外”箭头和您将使用的对象或编写自定义 shouldUpdate 函数或返回基础知识,例如编写命名函数(并手动绑定 this...)
您问题中的示例是使用 arrow function
并且第一个参数有 implicit return
的 curried function
示例。
箭头函数在词法上绑定 this 即它们没有自己的 this
参数,但从封闭范围中获取 this
值
与上述代码等效的是
const handleChange = (field) {
return function(e) {
e.preventDefault();
/// Do something here
}.bind(this);
}.bind(this);
关于您的示例要注意的另一件事是将 handleChange
定义为 const 或函数。可能您将它用作类方法的一部分,并且它使用 class fields syntax
所以不是直接绑定外部函数,而是在类构造函数中绑定它
class Something{
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(field) {
return function(e) {
e.preventDefault();
// do something
}
}
}
示例中要注意的另一件事是隐式返回和显式返回之间的区别。
const abc = (field) => field * 2;
上面是一个隐式返回的例子,即。它将 value 字段作为参数并返回结果 field*2
明确指定要返回的函数
对于显式返回,您将显式告诉方法返回值
const abc = () => { return field*2; }
关于箭头函数需要注意的另一件事是它们没有自己的 arguments
但也从父范围继承。
例如,如果您只定义一个箭头函数,例如
const handleChange = () => {
console.log(arguments) // would give an error on running since arguments in undefined
}
作为替代箭头函数提供您可以使用的其余参数
const handleChange = (...args) => {
console.log(args);
}
$
用于演示该概念,但您可以随意命名。巧合但完全不相关的是,$
已在 jQuery 等流行库中使用,其中$
是整个函数库的全局入口点。我认为它也被用于其他人。您将看到的另一个是_
,它在 underscore 和 lodash 等库中很流行。没有一个符号比另一个符号更有意义; you 为 your 程序指定含义。它只是有效的 JavaScript :D$
。如果您询问的是实现本身,$
是一个接收值x
并返回一个新函数k => ...
的函数。查看返回函数的主体,我们看到k (x)
,因此我们知道k
也必须是一个函数,并且无论k (x)
的结果是什么,都会返回到$ (...)
,我们知道它会返回另一个 { 4},然后继续...如果您仍然遇到困难,请告诉我。abc(1,2,3)
不如abc(1)(2)(3)
理想。很难推理代码的逻辑,很难阅读函数 abc,也很难阅读函数调用。在您只需要知道 abc 做什么之前,现在您不确定 abc 返回的未命名函数是做什么的,并且两次。abc
。我要说的一件事是,柯里化允许在程序时间轴上的各个调用站点提供不同的参数。这在所有参数未在同一时间/地点准备好的情况下很有用。学习替代编程风格的好处是巨大的。如果您好奇为什么函数式语言通常使用这些技术,您将不得不开始学习亲自看看!