我读了一堆React代码,我看到了这样的东西,我不理解:

handleChange = field => e => {
  e.preventDefault();
  /// Do something here
}

可以这样想,每次你看到一个箭头,你就用函数替换它。函数参数在箭头之前定义。 在你的例子中:

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

理解箭头函数的可用语法将使你理解它们在“链接”时引入的行为,就像你提供的例子一样。

当一个箭头函数不带大括号(带或不带多个形参)时,将隐式返回构成函数体的表达式。在您的示例中,该表达式是另一个箭头函数。

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上的“箭头函数”:

与函数表达式相比,箭头函数表达式具有更短的语法,并且在词法上绑定this值。箭头函数总是匿名的。

考虑到它来自一个reactjs应用程序,这在您的示例中尤其相关。正如@naomik所指出的,在React中,你经常使用这个来访问组件的成员函数。例如:

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)             |  }

这是一个咖喱函数

首先,用两个参数检查这个函数……

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
  }
}

关注回报

用另一种方式来想象也许会有帮助。我们知道箭头函数是这样工作的——让我们特别注意返回值。

const f = someParam => returnValue

所以我们的add函数返回一个函数-我们可以用括号来增加清晰度。黑体文本是函数add的返回值

const add = x => (y => x + y)

换句话说,某个数的加法返回一个函数

add(2) // returns (y => 2 + y)

调用curry函数

所以为了使用我们的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 ...
  };
};

然而,因为箭头函数在词法上绑定了它,它实际上看起来更像这样…

handleChange = function(field) {
  return function(e) {
    e.preventDefault()
    // Do something here
    // return ...
  }.bind(this)
}.bind(this)

也许现在我们能更清楚地看到它在做什么。handleChange函数为指定的字段创建一个函数。这是一个方便的React技术,因为您需要在每个输入上设置自己的侦听器,以便更新应用程序的状态。通过使用handleChange函数,我们可以消除所有导致为每个字段设置更改侦听器的重复代码。太酷了!

1这里我不需要对它进行词法绑定,因为原来的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

咖喱函数能够产生令人惊讶的结果。下面我们看到$被定义为一个带有两个形参的curry函数,然而在调用位置,似乎我们可以提供任意数量的参数。curry是对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

这里有一个部分的工作演示,你可以玩在自己的浏览器-

Const partial = (f,…a) =>(…b) => f(…a, b……) const preventDefault = (f, event) => (event .preventDefault () , f(事件) ) const logKeypress = event => Console .log (event.which) 文档 .querySelector(“输入[name = foo]”) .addEventListener ('keydown', partial (preventDefault, logKeypress)) <input name="foo" placeholder="type here to see ASCII codes" size="50">


一般提示:如果你对任何新的JavaScript语法以及它的编译方式感到困惑,你可以查看Babel。例如,在Babel中复制您的代码并选择ES 2015预设将会给出如下输出

handleChange = function handleChange(field) {
  return function (e) {
    e.preventDefault();
    // Do something here
  };
};


你问题中的例子是一个curry函数,它使用箭头函数,并对第一个参数有一个隐式返回。

箭头函数在词法上绑定this,即它们没有自己的this参数,而是从封闭范围中获取this值

与上述代码等价的代码是

const handleChange = (field) {
  return function(e) {
     e.preventDefault();
     /// Do something here
  }.bind(this);
}.bind(this);

关于您的示例还有一点需要注意,即将handleChange定义为const或函数。可能你正在使用它作为一个类方法的一部分,它使用一个类字段语法

因此,与其直接绑定外部函数,不如在类构造函数中绑定它

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字段作为参数,并返回显式指定要返回函数的结果字段*2

对于显式返回,您将显式地告诉方法返回值

const abc = () => { return field*2; }

关于箭头函数需要注意的另一件事是,它们没有自己的参数,而是从父函数的作用域继承了参数。

例如,如果你定义一个箭头函数

const handleChange = () => {
   console.log(arguments) // would give an error on running since arguments in undefined
}

作为可选的箭头函数,函数提供了您可以使用的其他参数

const handleChange = (...args) => {
   console.log(args);
}

短暂的

它是一个返回另一个简单函数的函数。

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
)

这可能不是完全相关的,但由于提到的react用例的问题(我一直碰到这个SO线程):这里没有明确提到双箭头函数的一个重要方面。 只有“第一个”箭头(函数)被命名(因此可以被运行时“区分”),任何后面的箭头都是匿名的,从React的角度来看,在每次渲染时都算作一个“新”对象。

因此,双箭头函数将导致任何PureComponent一直重新渲染。

例子

你有一个带有更改处理程序的父组件:

handleChange = task => event => { ... operations which uses both task and event... };

并且像这样渲染:

{ tasks.map(task => <MyTask handleChange={this.handleChange(task)}/> }

然后在输入或单击时使用handleChange。这些都很好用,看起来很不错。但是这意味着任何会导致父对象重新渲染的变化(比如一个完全不相关的状态变化)也会重新渲染你的所有MyTask,即使它们是PureComponents。

这可以通过多种方式缓解,例如传递'outmost'箭头和你将提供给它的对象,或者编写一个自定义shouldUpdate函数,或者回到基础,例如编写命名函数(并手动绑定This…)