我一直在阅读一些React代码,发现有些地方我不理解:
handleChange = field => e => {
e.preventDefault();
/// Do something here
}
我一直在阅读一些React代码,发现有些地方我不理解:
handleChange = field => e => {
e.preventDefault();
/// Do something here
}
这是一个柯里化函数 (Currying 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 => <b>returnValue</b>
add
函数返回一个函数 - 为了更清晰,我们可以使用括号。 粗体文本是我们函数add
的返回值。const add = x => (y => x + y)
add
操作会返回一个函数。add(2) // returns (y => 2 + y)
调用柯里化函数
为了使用我们的柯里化函数,我们需要以稍微不同的方式来调用它...
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
科里化函数能够做出令人惊讶的事情。在下面的例子中,我们将$
定义为一个有两个参数的科里化函数,但在调用时,看起来我们可以提供任意数量的参数。科里化是元数的抽象 -
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))
<input name="foo" placeholder="type here to see ascii codes" size="50">
$
的使用来更好地理解它。如果你正在询问实现本身,那么$
是一个函数,接收一个值 x
并返回一个新的函数 k => ...
。查看返回的函数体,我们看到 k (x)
,所以我们知道 k
也必须是一个函数,并且 k(x)
的任何结果都会被放回 $ (...)
,我们知道这将返回另一个 k => ...
,如此循环下去......如果你还有困惑,请告诉我。 - Mulanabc(1,2,3)
不如 abc(1)(2)(3)
理想。这让代码逻辑更难以理解,很难读取函数abc,也更难读取函数调用。以前你只需要知道abc函数做什么,现在你不确定未命名的函数abc返回的内容是什么,而且还有两次。 - Muhammad Umer简述
它是一种以简短方式编写并返回另一个函数的函数。
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
,因此无法将id
与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
)
函数组合
多个箭头函数也被称为“柯里化函数”,用于函数组合。
import {compose} from 'redux'
import {store} from './store.js'
const pickSelectedUser = props => {
const {selectedName, users} = props
const foundUser = users.find(user => user.name === selectedName)
return foundUser.id
}
const deleteUser = userId => event => {
event.preventDefault()
store.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
的任何信息。 - ToskanhandleClick(ev, id)
在某些时候更明显,但它不可组合。查看此片段:https://gist.github.com/sultan99/13ef56b4089789a8d115869ee2c5ec47,您会发现柯里化函数非常适合函数组合,这是函数式编程的一个非常重要的部分。 - sultan一个常见的技巧: 如果您对任何新的JavaScript语法及其编译方式感到困惑,可以查看Babel。例如,将您的代码复制到Babel并选择ES 2015预设,将会得到以下输出:
handleChange = function handleChange(field) {
return function (e) {
e.preventDefault();
// Do something here
};
};
当箭头函数不带块括号、带有或不带有多个参数时,构成函数体的表达式会被隐式返回。在您的示例中,该表达式是另一个箭头函数。
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()
} | | }
} | } | }
箭头语法写匿名函数的另一个优点是,它们在定义时被词法绑定到作用域中。来自于 MDN 的“箭头函数”:
这在你的例子中尤其相关,因为它来自于一个React.js 应用程序。正如 @naomik 指出的那样,在 React 中,通常使用 this
访问组件的成员函数,例如:
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
。 - Mulan虽然问题提到了React使用案例(我一直遇到这个SO线程),它可能与完全相关的内容不同,但是有一个重要的双箭头函数方面在这里没有明确提到。仅仅“第一个”箭头函数被命名(因此可以由运行时进行“区分”),任何后续的箭头函数都是匿名的,并且从React的角度来看,在每次渲染中都会计算为“新”的对象。
因此,双箭头函数将导致任何PureComponent一直重新渲染。
示例
您有一个带有更改处理程序的父组件如下:
handleChange = task => event => { ... operations which uses both task and event... };
并使用以下类似的渲染方式:
{
tasks.map(task => <MyTask handleChange={this.handleChange(task)}/>
}
然后在输入或点击上使用handleChange。这一切都很好,看起来也很不错。但是,这意味着任何会导致父组件重新渲染的更改(例如完全不相关的状态更改)也会重新渲染所有的MyTask,尽管它们是PureComponent。
可以通过多种方式来减轻这种情况,例如传递最外层箭头和您将用它提供的对象,编写自定义shouldUpdate函数或回到基础知识,例如编写命名函数(并手动绑定this...)
const handleChange = (field) {
return function(e) {
e.preventDefault();
/// Do something here
}.bind(this);
}.bind(this);
handleChange
定义为一个常量或函数。可能你正在将它作为类方法的一部分使用,并且它使用了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;
field*2
,明确指定函数返回值。const abc = () => { return field*2; }
需要注意的是,箭头函数没有自己的arguments
,而是从父作用域继承。
例如,如果您只定义了一个箭头函数,如下所示:
Original Answer翻译成"最初的回答"
const handleChange = () => {
console.log(arguments) // would give an error on running since arguments in undefined
}
const handleChange = (...args) => {
console.log(args);
}
在JavaScript中,多个箭头函数表示函数的链接,其中一个函数的输出作为下一个函数的输入,依此类推。例如:
const add = (x) => (y) => x + y;
const multiply = (x) => (y) => x * y;
const result = add(2)(3); // 5
const finalResult = multiply(result)(4); // 20
add
和multiply
是箭头函数,它们接受一个参数并返回另一个箭头函数。第一个箭头函数接受一个数字x
并返回另一个箭头函数,该函数接受另一个数字y
并返回x
和y
的总和。第二个箭头函数接受一个数字x
并返回另一个箭头函数,该函数接受另一个数字y
并返回x
和y
的乘积。result
是add(2)(3)
的输出,即5
,而finalResult
是multiply(result)(4)
的输出,即20
。