为什么在函数式React组件中,箭头语法比函数声明更受欢迎?

64

我经常看到使用箭头函数语法定义的函数式React组件的示例:

const foo = () => (...);

export default foo;

与更传统的函数声明语法不同:

export default function foo() {
  return ...;
}

前者是否比后者更优的原因是什么?


55
看起来酷多了。 - Dan Mandel
2
需要引用出处 :) 我认为这是基于个人偏好的观点,我们可能只是在谈论开发者的个人喜好。 - Kos
23
这是一种不良实践,因为炒作而变得流行起来。React社区开始这样做,然后所有人突然都跟进了。尽管有几个原因应该被认为是反模式,普通的函数声明应该更受欢迎。 - dfsq
请查看此文章:https://www.sitepoint.com/es6-arrow-functions-new-fat-concise-syntax-javascript/ - Dom
6
值得一提的是,官方React文档使用传统的function - rodrigocfd
@DanMandel 不,它看起来不酷,很烦人。 - basickarl
5个回答

28

实际上它们之间没有区别,我在CodeSandBox上创建了一个小项目并创建了两个简单组件,其中一个是使用箭头函数创建的Arrow组件:

import React from 'react';

const MyArrowComponent = () => (
  <main>
    <h2>Arrow</h2>
  </main>
);

export default MyArrowComponent;

另一种方法是使用函数声明创建 Declaration 组件:

import React from "react";

function MyFunctionComponent() {
    return (
        <main>
            <h2>Declaration</h2>
        </main>
    );
}

export default MyFunctionComponent;

然后我运行了 yarn build 命令,得到了以下的捆绑包:
(window.webpackJsonp = window.webpackJsonp || []).push([[0], {
  14: function (e, n, t) {
    "use strict";
    t.r(n);
    var a = t(0), r = t.n(a), l = t(2),
        c = t.n(l), u = t(3), i = t(4), o = t(6), m = t(5), E = t(7);
    var p = function () {
      return r.a.createElement("main", null, r.a.createElement("h2", null, "Declaration"))
    }, s = function () {
      return r.a.createElement("main", null, r.a.createElement("h2", null, "Arrow"))
    }, d = function (e) {
      function n() {
            return (
              Object(u.a)(this, n),
              Object(o.a)(this, Object(m.a)(n).apply(this, arguments))
      }
      return Object(E.a)(n, e), Object(i.a)(n, [{
        key: "render", value: function () {
          return r.a.createElement(
            'div',
            null,
            r.a.createElement('div', null, 'Hi'),
            r.a.createElement(p, null),
            r.a.createElement(s, null)
          );
        }
      }]), n
    }(r.a.Component);
    c.a.render(r.a.createElement(d, null), document.getElementById("root"))
  }, 8: function (e, n, t) {
    e.exports = t(14)
  }
}, [[8, 1, 2]]]);

请注意ArrowDeclaration组件的定义:

var p = function () {
  return r.a.createElement("main", null, r.a.createElement("h2", null, "Declaration"))
}, s = function () {
  return r.a.createElement("main", null, r.a.createElement("h2", null, "Arrow"))
}

这两者的定义方式是相同的,因此它们之间没有区别,完全取决于开发人员对代码可读性和清晰性的态度,根据我们团队使用的ESLint 5.x,我们选择使用箭头函数来定义功能组件。


直到你遇到一些问题,花了整整一天的时间试图找到错误,才意识到你必须使用普通函数而不是箭头函数。它们并不相同,箭头函数会破坏JSX流程。 - ErayZaxy

25

我认为这是一个带有主观色彩的选择。至少有几个原因让我(个人而言)认为在纯函数组件中使用箭头函数是一种相当糟糕的实践。以下是这些原因:

  1. 语法滥用。我们定义函数组件时不需要将其上下文预先绑定到特定范围。该上下文(this)在模块命名空间中将始终为undefined。箭头函数的使用在这里由纯美学理由,如简洁性所决定。但是,箭头函数作为语言特性首先存在的目的并不是“酷炫”和简洁。

  2. 错误堆栈跟踪。在箭头函数中抛出的异常将不太具描述性,因为箭头函数本质上是匿名的。这可能不是巨大的问题,因为React项目很可能配置了适当的源映射支持,但如果使用命名函数,则堆栈跟踪会更清晰。 如评论中所述,这实际上不是函数组件的问题,因为名称基本上将是变量的名称。

  3. 日志记录不太方便。考虑这种非常典型的纯函数组件样式:

const Header = ({ name, branding }) => (
  <header>
    ...
  </header>
)

在上面的函数中,无法快速插入debugger语句或console.log。您将不得不将其暂时转换为以下内容

const Header = function ({ name, branding }) { 
  console.log(name)
  return (
    <header>
      ...
    </header>
  )
}

对于较大的纯函数组件来说,这可能会非常烦人。

    1. 尽管如此,这是许多团队非常流行的选择,也是ESLint默认首选,所以如果您没有看到问题,那么它可能没问题。


      1
      @dfsq 3 也不是很适用。对于多行函数来说使用临时变量确实更方便,但你总可以在一行箭头函数中加入 console.log(),eval('debugger')。请注意,React函数组件已经为此提供了 (...)。此外,这不是关于常规函数与箭头函数的区别,而是关于隐式返回与显式返回。但调试的重要之处在于,在常规函数中可以使用 arguments(但箭头函数在ES5目标中也会有它)。 - Estus Flask
      1
      3 不是一个真正的问题 - 你可以只需配置 eslint来要求显式返回和花括号。const Header = ({ name, branding }) => { console.log(name); return (
      ...
      ); }
      - wmp224
      @wmp224,那你打算如何记录name以进行检查? - dfsq
      1
      @wmp224 哦,我明白了,我们谈论的是不同的事情。当然可以。我说的是短返回符号。 - dfsq
      2
      @IvanKleshnin 组件定义不需要简洁。如果有什么,应该是清晰而不是简洁。谁会编写1-2行的功能组件(根本不需要),然后因为隐式返回而感到满意?人们这样做是因为Facebook这样做,而且ESLint默认这样做。尝试阅读一个ES6模块,其中所有内容都被定义为const...区别是有价值的。更不用说方法签名实际上需要更多的按键,并且由于=和=>而本质上更繁忙。箭头符号是一种特性,应该有意识地使用。 - GHOST-34
      显示剩余8条评论

      11

      函数声明和箭头函数在本质上是不同的,但在您的问题范围内,这基本上是一种代码风格偏好。就个人而言,我更喜欢函数声明,因为我觉得它更容易看出代码行的含义。

      如果您将使用箭头函数或函数声明,请尝试从上下文中考虑哪种方式更有意义,从而使代码更干净,更易于阅读。清晰易读的代码不仅关乎编写的代码量,还关乎代码表达的内容。

      例如,我倾向于在回调时使用箭头函数,如:[].map(() => {})


      你可以认为,因为ES6实现了许多箭头函数.map/.filter/.reduce,编写命名函数会引入不一致性。这是一个虽然薄弱但仍有道理的论点。 - Walter Monecke

      7

      其他答案中未提及的几点:

      • 使用箭头函数组件时,在Chrome/Firefox中使用React开发工具时,这些组件将出现为匿名,使调试变得更加困难。这些匿名组件也在性能火焰树等开发工具中普遍存在。函数式组件在dev tools中显示其名称。
      • 标准函数声明可以在单行上定义。您不需要在文件中稍后定义export default。当您想添加/删除default关键字时,这也会使其更容易。
      export default async function MyComponent() {
        ...
      }
      

      3
      使用箭头函数不仅因为语法简洁,能够通过使用箭头函数写更少的代码,而且因为:
      1. 作用域安全:一旦始终使用箭头函数,将保证所有函数使用相同的thisObject作为根对象。如果将单个标准函数回调与大量箭头函数混合使用,则有可能会破坏作用域。

      2. 紧凑性:箭头函数易于阅读和编写。

      3. 清晰性:当几乎所有函数都是箭头函数时,任何常规函数立即显露出来以定义作用域。开发人员可以始终查找下一个更高的函数语句,以查看此对象。

      欲了解更多细节,请参阅以下问题

      在ECMAScript 6中应该何时使用箭头函数?


      15
      没有绑定在对象上的函数中,this绑定是无关紧要的。 - Jimmy Breck-McKye
      23
      我不同意"更易阅读"的说法。我们从左到右,从上到下阅读,使用function可以轻松识别声明是函数还是变量,而无需视觉扫描向右。我非常讨厌阅读文件时,从左到右排列的const,因为需要扫描右侧以确定它是函数还是变量。使用function关键字将使得代码在从上到下扫描时更容易,而无需从左到右扫描。 - Charles Chen
      @CharlesChen 我曾经参与过使用箭头函数或命名函数的大型项目。对我来说,箭头函数更容易识别(而不是阅读)。我认为这纯粹是一种观点,每个人都会喜欢其中之一。干杯! - Walter Monecke
      4
      “紧凑性”似乎不是正确的术语。export const Component = () => {…};实际上比export function Component() {…}更长。 - Bergi
      3
      1. 你所说的“安全性”只对方法有用(从对象调用的函数)。功能组件不依赖于对象,“this”关键字属于模块本身的范围,将未定义或指向窗口或文档对象(我不确定React和Babel如何定义它)。
      2. 我认为使用函数单词从左到右阅读实际上更容易阅读。箭头函数增加了一层语法复杂性。尽管如此,我同意箭头函数要酷得多。
      - babaliaris
      显示剩余2条评论

      网页内容由stack overflow 提供, 点击上面的
      可以查看英文原文,
      原文链接