react设计模式与最佳实战——整理代码

本章假设你已经有了JSX经验,你希望提高自己的技能去高效地使用它。
要想使用JSX,没有任何问题或不被期望的行为,理解它底层的工作原理和它成为构建UI一个有用工具的原因是重要的。
我们的目标是编写干净且可维护的JSX代码,因此,我们必须知道它的出处,如何转换为JavaScript,以及它的特性。
在第一部分,我们会有一点回顾,请耐心等待,因为它对于掌握基础以应用最佳实践是至关重要的。
在本章,我们将讲解以下内容:

  • JSX的概念以及使用它的理由
  • Babel的概念,如何使用它编写现代化的JavaScript代码
  • JSX 的特性,HTML和JSX的区别
  • 以优雅和可维护的方式编写JSX的最佳实践
  • linting,特别是ESLint如何使我们的JavaScript代码跨应用程序和团队也保持一致
  • 函数式编程的基础知识,以及为什么遵循功能范式会让我们写出更好的React组件

2.1 JSX

在前一章中,我们了解了React如何改变关注点分离的概念,将界限移入组件。
我们已经学习了React 如何用组件返回的元素在屏幕上显示UI。
让我们现在看一下如何在组件内声明元素。
React 提供两种方式来定义元素。第一个是函数式声明,第二种是使用JSX,一种可选的XML式语法。下面是React官网的示例:


image.png

最初,JSX是人们无法接近React的主要原因之一。因为在首页的例子可以看见HTML和JavaScript是混在一起写的。这对于大部分人来说第一眼看上去是奇怪的。
一旦我们习惯了它,我们就会意识到它非常方便,正因为它类似于HTML,对于已经在web上创建过UI的人来说,它看起来非常熟悉。
开始和关闭标签使其更容易表示嵌套树元素——这些东西本来是不可读的并且很难用纯JavaScript来维护。

2.1.2 Babel

为了在我们的代码中使用JSX(和ES2015的一些特性),我们必须安装Babel。
首先,重要的是要清楚地理解它能解决的问题以及为什么需要在程序中添加一个步骤。原因是我们想要使用语言的特性,这在浏览器,即我们的目标环境,还没有添加。对于开发人员,这些先进的特性使我们的代码更简洁,但是浏览器不能理解和
执行它。
解决方案是用JSX和ES2015中编写脚本,当我们准备发布时,我们将源代码编译成ES5,这是在主流浏览器中实现的标准规范。

Babel是一个流行的JavaScript编译器在React社区中被广泛采用。

Babel可以将ES2015代码编译成ES5 JavaScript,并将JSX编译成JavaScript
函数。该过程称为转换,因为它将源代码编译为新的资源而不是可执行文件。
使用它很简单;安装:

npm install --global babel-cli

如果你不想在全局上安装它(开发人员通常倾向于避免这种情况),您可以通过一个npm脚本将Babel局部安装到一个项目中,但是本章为了达到这个目的,使用一个全局实例。
安装完成后,运行以下命令可以编译任何JavaScript文件:

babel source.js -o output.js

Babel之所以如此强大,原因之一在于它是高度可配置的。Babel只是一个将源文件转换成输出文件的工具,但是要应用一些转换,我们需要配置。
幸运的是,有一些非常有用的配置集,我们可以很容易地安装和使用:

npm install --global babel-preset-es2015
babel-preset-react

安装完成后,在根目录下创建了一个名为.babelrc的配置文件,并将以下几行放入其中,告诉Babel使用这些预设值:

{
 "presets": [
 "es2015",
 "react"
 ]
}

现在,我们可以在源文件中编写ES2015和JSX,并在浏览器中执行输出文件。

2.1.3 Hello World

现在我们的环境已经设置为支持JSX,我们可以深入研究最基本的例子:生成div元素。
这是使用React的createElement函数创建div的方法:

React.createElement('div)

这是在JSX中创建div元素:

<div />

它看起来类似于常规HTML。
最大的区别是我们在.js文件中编写标记,但重要的是请注意,JSX只是语法糖,它会被转换为JavaScript在浏览器中执行。
实际上,当我们运行Babel时,我们的<div />被转换成React.createElement('div'),这是我们在编写模板时应始终牢记的事情。

2.1.4 DOM元素和React组件

使用JSX,我们可以创建HTML元素和React组件;唯一的区别是首字母是否大写。
例如,为了渲染按钮,在HTML中我们使用<button/>,在组件中我们使用<Button/>渲染。
第一个按钮被转换成以下内容:

React.createElement('button')

第二种则被转换为以下内容:

React.createElement(Button)

这里的不同之处在于,在第一次调用中,我们将DOM元素的类型传递为一个字符串,在第二个调用中我们传递的是组件本身,也就是在工作的范围内存在。
正如您可能已经注意到的,JSX支持自闭标签,这非常适合保持代码简洁,不要求我们重复不必要的标记。

2.1.5 Props

当DOM元素或者React组件有props,JSX是十分便利的。事实上,使用XML给元素设置属性更加容易:

<img src="https://facebook.github.io/react/img/logo.svg"
alt="React.js" />

JavaScript 中等效代码如下:

React.createElement("img", {
 src: "https://facebook.github.io/react/img/logo.svg",
 alt: "React.js"
});

这很难读懂,即使只有几个属性,不经过一点推理很难读懂。

2.1.6 Children

ISX允许你定义子元素来描述元素树,并组成复合元素。
一个基本的例子是一个包含文本的链接,如下所示:

<a href="https://facebook.github.io/react/">Click me!</a>

这将被转换成下列代码:

React.createElement(
 "a",
 { href: "https://facebook.github.io/react/" },
 "Click me!"
);

我们的链接可以包含在一个div中,以满足一些布局需求,以及JSX代码片段。实现如下:

<div>
 <a href="https://facebook.github.io/react/">Click me!</a>
</div>

JavaScript 中等效代码如下:

React.createElement(
 "div",
 null,
 React.createElement(
 "a",
 { href: "https://facebook.github.io/react/" },
 "Click me!"
 )
);

现在应该很清楚,类似于xml的JSX语法如何使所有东西都更易于阅读和维护,但是了解JSX的JavaScript并行性对于控制元素的创建总是很重要的。。
好的方面是我们不局限于将元素作为元素的子元素,而且我们可以使用lavaScript表达式,如函数或变量。
要做到这一点,我们只需要把表达式括在大括号中:

<div>
 Hello, {variable}.
 I'm a {function()}.
</div>

这同样适用于非字符串属性:

<a href={this.makeHref()}>Click me!</a>

2.1.7 与HTML的不同

到目前为止,我们已经研究了JSX和HTML之间的相似之处。现在让我们看看它们之间的细微差别以及它们存在的原因。

Attributes

我们必须始终牢记JSX不是一种标准语言,它被转换成lavaScript。因此,有些属性无法使用。
例如,我们必须使用className,而不是class,我们必须使用htmlFor,而不是for:

<label className="awesome-label" htmlFor="name" />

原因是这个类和for是JavaScript中的保留字。

Style

一个非常重要的区别是样式属性的工作方式。我们将在第7章中详细介绍如何使用它,使组件看起来更漂亮。但现在我们将关注它的工作方式。
style属性不像HTML并行程序那样接受CSS字符串,但它希望是一个JS对象,其中样式名是驼峰式的:

<div style={{ backgroundColor: 'red' }} />

Root

值得一提的HTML的一个重要区别是,由于JSX元素被转换为JavaScript函数,并且您不能在JavaScript中返回两个函数,因此当您在同一级别上有多个元素时,您必须将它们包装为一个父元素。
让我们看一个简单的例子:

<div />
<div />

这给了我们以下错误:

Adjacent JSX elements must be wrapped in an enclosing tag

另一方面,以下工作:

<div>
 <div />
 <div />
</div>

为了使JSX工作,不得不添加不必要的div标签是非常烦人的。但是React开发人员正在努力寻找解决方案(在撰写本文时):
https://github.com/reactjs/core-notes/blob/master/2016-07/july-07.md

Spaces

有一件事在一开始可能会有点棘手,而且它涉及到这样一个事实,即我们应该始终记住JSX不是HTML,即使它具有类似xml的语法。
实际上,JSX在某种程度上以不同于HTML的方式处理文本和元素之间的空格。这是反直觉的。
思考以下代码片段:

<div>
 <span>foo</span>
 bar
 <span>baz</span>
</div>

在解析HTML的浏览器中,这段代码会给你foo bar baz,这正是我们所期望的。
相反,在ISX中,相同的代码将呈现为foobarbaz,这是因为三个.嵌套的行被转换为div元素的子元素,而不考虑空格。为了得到相同的输出,一个常见的解决方案是在元素之间显式地放置一个空格:

<div>
 <span>foo</span>
 {' '}
 bar
 {' '}
 <span>baz</span>
</div>

你可能已经注意到的,我们使用了一个包装在JavaScript表达式中的空字符串来强制编译器应用元素之间的空格。

Boolean attributes

在开始介绍真正的ISX中定义布尔属性的方法之前,还有几件事值得一提。如果您设置了一个不带值的属性,ISX假设它
关于你的生活方式,在真正开始介绍JSX中定义布尔属性的方法之前还有几件事值得一提。如果您设置了一个没有值的属性,JSX默认值为true。例如,HTML disabled属性。
这意味着如果我们想要将一个属性设置为false,我们必须显式地声明它为false:

<button disabled />
React.createElement("button", { disabled: true });

下面是另一个例子:

<button disabled={false} />
React.createElement("button", { disabled: false });

这在一开始可能会令人困惑,因为我们可能认为省略属性意味着false,但事实并非如此。在React中,我们应该始终明确避免混淆。

2.1.8 Spread attributes

一个重要的特性是 spread attributes属性操作符,它来自于ECMAScript提案的Rest/spread属性(https://github.com/sebmarkbage/ecmascript-re-spread),当我们想将一个JavaScript对象的所有属性传递给一个元素时,它都非常方便。
使bug更少的一个常见实践不是通过引用将整个JavaScript对象传递给子对象,而是使用它们的基本值,这可以很容易地进行验证,从而使组件更加健壮且不会出错。
让我们看一下它是如何工作的:

const foo = { id: 'bar' }
return <div {...foo} />

前面的代码被转换为以下代码:

var foo = { id: 'bar' };
return React.createElement('div', foo);

2.1.9 JavaScript templating

最后,我们假设移动模板的优点之一。在组件内部,而不是使用外部模板库,我们可以使用lavaScript的全部功能,所以让我们开始看看这意味着什么。
spread属性就是一个例子,另一个常见的例子是JavaScript表达式可以作为属性值使用,方法是将它们括在花括号中:

<button disabled={errors.length} />

2.1.10 常见模式

既然我们已经知道ISX是如何工作的,并且能够掌握它,我们就可以看看如何在其中使用它了。遵循一些有用的约定和技术的正确方法。

Multi-line

让我们从一个非常简单的开始。如前所述,我们应该更喜欢JSX而不是React的createElement的主要原因之一是它类似于xml的语法,也因为平衡的闭合标签十分适合代表节点树。
一个例子如下;每当我们有嵌套的元素,我们应该总是走多行:

<div>
 <Header />
 <div>
 <Main content={...} />
 </div>
</div>

这比下列更可取:

<div><Header /><div><Main content={...} /></div></div>

如果子元素不是元素(如文本或变量),则例外。在这种情况下,保持在同一行是有意义的,并避免给标记添加噪声,如下所示:

<div>
 <Alert>{message}</Alert>
 <Button>Close</Button>
</div>

当你用多行来写你的元素时,一定要记得把它们用圆括号括起来。实际上,JSX总是被函数所替代,而在新的一行上编写的函数会由于自动分号插入而得到意想不到的结果。例如,假设您正在从render方法返回ISX,这是创建UI在React的方法。
下面的示例工作得很好,因为div与返回的内容在同一行:

return <div />

但是,下列情况是不对的:

return
 <div />

原因是:

return;
React.createElement("div", null);

这就是为什么你必须用括号括起来:

return (
 <div />
)

多属性

当一个元素具有多个属性时,就会出现编写JSX的问题。一种解决方案是将所有属性写在同一行上,但是这将导致非常长的行,这是我们的代码中不希望看到的(关于如何执行编码风格的指南,请参阅下面一节)。
一种常见的解决方案是在新行中写入每个属性,使用一层缩进,然后将结束括号与开始标记对齐:

<button
 foo="bar"
 veryLongPropertyName="baz"
 onSomething={this.handleSomething}
/>

Conditionals

当我们开始使用条件语句时,事情变得更加有趣,例如,如果我们想只在特定条件匹配时render某些组件。这一事实中,我们可以使用JavaScript条件是一大亮点,但也有许多不同的方式来表达条件JSX。为了代码可读性和可维护性,重要的是要理解每一个的好处和问题。
假设我们想要显示一个注销按钮,仅当当前用户在应用程序中被注销时。
一个简单的代码片段如下:

let button
if (isLoggedIn) {
 button = <LogoutButton />
}
return <div>{button}</div>

这是可行的,但是可读性不是很好,特别是当有多个组件和多个条件时。
在JSX中,我们可以使用内联条件:

<div>
 {isLoggedIn && <LoginButton />}
</div>

这可以工作,因为如果条件为false,就不会呈现任何内容,但是如果条件为true,就会调用LoginButton的createElement函数,并返回元素以组成结果树。
如果条件有选择,(经典的if…else语句),我们想,例如,要显示用户是否登录的注销按钮和登录按钮,我们可以使用JavaScript的if…else其他,如下所示:

let button
if (isLoggedIn) {
 button = <LogoutButton />
} else {
 button = <LoginButton />
}
return <div>{button}</div>

或者,更好的是,我们可以使用三元条件,使代码更紧凑:

<div>
 {isLoggedIn ? <LogoutButton /> : <LoginButton />}
</div>

您可以找到流行的存储库中使用的三元条件,例如Redux realworld示例 https://github.com/reactjs/redux/blob/master/examples/real-world/src/components/List.js#L25,其中如果组件正在获取数据,三元用于显示加载标签,或者根据isFetching变量的值在按钮中加载更多内容:

<button [...]>
 {isFetching ? 'Loading...' : 'Load More'}
</button>

现在让我们看看当事情变得更复杂时的最佳解决方案,例如,我们必须检查多个变量以确定是否渲染组件:

<div>
 {dataIsReady && (isAdmin || userHasPermissions) &&
 <SecretData />
 }
</div>

在这种情况下,显然使用内联条件是一种很好的解决方案,但是可读性有很大的影响。我们可以在组件内部创建一个函数,并在JSX中使用它来验证条件:

canShowSecretData() {
 const { dataIsReady, isAdmin, userHasPermissions } = this.props
 return dataIsReady && (isAdmin || userHasPermissions)
}
<div>
 {this.canShowSecretData() && <SecretData />}
</div>

如您所见,此更改使代码更具可读性,条件更显式。如果您在6个月后查看这段代码,您仍然会发现仅仅通过读取函数的名称就可以清楚地理解它。
如果你不喜欢使用函数,那么可以使用对象的getters,这些getters构成代码。。更优雅。
例如,我们不是声明一个函数,而是定义一个getter:

get canShowSecretData() {
 const { dataIsReady, isAdmin, userHasPermissions } = this.props
 return dataIsReady && (isAdmin || userHasPermissions)
}
<div>
 {this.canShowSecretData && <SecretData />}
</div>

这同样适用于计算属性。假设有两个属性货币和值。你不需要在渲染方法中创建价格字符串,你可以创建一个类函数:

getPrice() {
 return `${this.props.currency}${this.props.value}`
}
<div>{this.getPrice()}</div>

这更好,因为它是隔离的,您可以很容易地测试它,以防它包含逻辑。或者,你可以更进一步,就像我们刚才看到的,使用getter:

get price() {
 return `${this.props.currency}${this.props.value}`
}
<div>{this.price}</div>

回到条件语句,还有其他需要使用外部依赖的解决方案。一个好的实践是尽可能地避免外部依赖,以使您的包更小,但是在这种特殊情况下,这样做可能是值得的,因为改进模板的易用性是一个很大的胜利。
第一个解决方案是render-if,我们可以使用以下方法安装它:

npm install --save render-if

我们可以很容易地在我们的项目中使用它,如下所示:

const { dataIsReady, isAdmin, userHasPermissions } = this.props
const canShowSecretData = renderIf(
 dataIsReady && (isAdmin || userHasPermissions)
)
<div>
 {canShowSecretData(<SecretData />)}
</div>

我们将条件包装在renderIf函数中。
返回的实用程序函数可以用作函数,当条件为真时,该函数接收要显示的JSX标记。
我们应该牢记的一个目标是,永远不要在我们的组件中加入太多逻辑。其中一些可能需要一些,但是我们应该尽量使它们尽可能简单,这样我们就可以很容易地发现和修复错误。
我们至少应该尽量保持renderIf方法的干净,为了做到这一点,我们可以使用另一个实用库,称为react-only-if,它允许我们通过使用高阶组件设置条件函数来编写组件,就好像条件总是为真一样。
我们将在第4章(Compose All the Things)详细讨论高阶组件,但现在您只需要知道它是接收组件并通过添加一些属性或修改其行为返回增强组件的函数。
使用库,我们只需要安装如下:

npm install --save react-only-if

一旦安装完毕,我们就可以在应用程序中使用它:

const SecretDataOnlyIf = onlyIf(
 ({ dataIsReady, isAdmin, userHasPermissions }) => {
 return dataIsReady && (isAdmin || userHasPermissions)
 }
)(SecretData)
<div>
 <SecretDataOnlyIf
 dataIsReady={...}
 isAdmin={...}
 userHasPermissions={...}
 />
</div>

正如您在这里看到的,组件本身中根本没有逻辑。
我们将条件作为onlyIf函数的第一个参数传递,当条件匹配时,组件将被渲染。
用于验证条件的函数接收组件的props、state和context。
这样,我们就避免用条件句污染我们的组件,以便更容易理解和理解。

循环

UI开发中一个非常常见的操作是显示项目列表。当涉及到显示列表,使用JavaScript作为模板语言是一个很好的主意。
如果我们编写一个函数来返回JSX模板中的数组,那么数组的每个元素都会被编译成一个元素。
正如我们之前看到的,我们可以在花括号中使用任何lavaScript表达式,在给定对象数组的情况下,最常用方法是使用map生成元素数组。
让我们看一个真实的例子。假设您有一个用户列表,每个用户都有一个name属性。
要创建一个无序列表来显示用户,您可以执行以下操作:

<ul> {users.map(user =><li>{user.name}</li>)}</u

这段代码非常简单,同时又非常强大,HTML和JavaScript的力量汇聚在一起。

控件语句

条件和循环是UI模板中非常常见的操作,使用JavaScript三目运算符或map函数执行它们时,您可能会感到错误。JSX的构建方式是这样的:它只抽象元素的创建,而把逻辑部分留给了真正的lavaScript,这很好,只是有时代码变得不那么清晰。
通常,我们的目标是从组件中删除所有逻辑,特别是从render方法中删除逻辑。但有时我们必须根据应用的state显示和隐藏元素,通常需要循环遍历集合和数组。
如果您觉得将JSX用于这种操作将使代码更易于阅读,那么可以使用Babel插件:jsx-control-statements.
这遵循与JSX相同的原理,并且它不向语言添加任何真正的功能;只是语法糖被编译成JavaScript。
让我们看看它是如何工作的。
首先,我们必须安装它:

npm install --save jsx-control-statements

一旦安装完毕,我们必须将其添加到.babelrc文件中的babel插件列表中:

"plugins": ["jsx-control-statements"]

从现在开始,我们可以使用插件提供的语法,Babel将把它与常用的ISX语法一起传递出去。
使用插件编写的条件语句如下:

<If condition={this.canShowSecretData}>
 <SecretData />
</If>

这被转换成三元表达式如下:

{canShowSecretData ? <SecretData /> : null}

If组件很好,但是如果由于某种原因您在render方法中有嵌套的条件,那么它很容易变得混乱和难以理解。下面是Choose组件派上用场的地方:

<Choose>
 <When condition={...}>
 <span>if</span>
 </When>
 <When condition={...}>
 <span>else if</span>
 </When>
 <Otherwise>
 <span>else</span>
 </Otherwise>
</Choose>

请注意,前面的代码被转换为多个三目运算符。
最后,还有一个组件(请记住,我们讨论的不是全部组件,而是语法糖)来管理循环,这也非常方便:

<ul>
 <For each="user" of={this.props.users}>
 <li>{user.name}</li>
 </For>
</ul>

前面的代码被转换为map函数——没有魔法。
如果你习惯使用linters,你可能会想为什么linters没有抱怨该代码。事实上,变量user在转换之前并不存在,也不会被包装成函数。为了避免这些连接错误,还需要安装另一个插件:eslint�plugin-jsx-control-statements。
如果你不明白前面的句子,不要担心;我们将在下一节讨论linting。

Sub-rendering

值得强调的是,我们总是希望保持组件非常小,渲染方法非常干净和简单。
然而,这并不是一个简单的目标,特别是当您迭代地创建应用程序时,在第一次迭代中,您不确定如何将组件划分为更小的组件。
那么,当render方法变得太大而无法维护时,我们应该做什么呢?一种解决方案是将它分解成更小的函数,这样我们就可以保持所有逻辑不变。
让我们看一个例子:

renderUserMenu() {
 // JSX for user menu
}
renderAdminMenu() {
 // JSX for admin menu
}
render() {
 return (
 <div>
 <h1>Welcome back!</h1>
 {this.userExists && this.renderUserMenu()}
 {this.userIsAdmin && this.renderAdminMenu()}
 </div>
 )
}

这并不总是被认为是最佳实践,因为将组件分成更小的组件似乎更明显。然而,有时它只有助于保持呈现方法的清晰。例如,在Redux realworld示例中,使用子呈现方法来渲染 load more按钮。
现在我们是JSX的高级用户,现在是时候继续前进了,看看如何在我们的代码中遵循样式指南以使其一致。

2. ESLint

我们总是尽可能地编写最好的代码,但有时会发生错误,并且花费几个小时解决拼写错误的bug是非常令人沮丧的。幸运的是,有一些工具可以帮助我们在输入代码时检查代码的正确性。
这些工具不能告诉我们我们的代码是否会做它应该做的事情,但是它们可以帮助我们避免语法错误。
如果你写过C语言,那么您已经习惯在IDE中得到这种警告。
Douglas Crockford几年前使用JSLint(最初于2002年发布),使linting在JavaScript中流行起来。然后我们有了JSHint,最后,当今react世界标准是ESLint。
ESLint是2013年发布的一个开源项目,由于其高度可配置性和可扩展性而广受欢迎。
在JavaScript生态系统中,库和技术的变化非常迅速。有一个可以很容易地扩展插件,和规则,在需要时启用和禁用的工具,这一点至关重要。
最重要的是,现在我们使用的是像Babel这样的编译器和不是JavaScript标准版本的一部分实践特性,因此我们需要能够告诉linter在源文件中遵循哪些规则。
linter不仅帮助我们减少错误,或者至少更快地发现这些错误,而且它还强制执行一些常见的编码风格指南,这非常重要,特别是在有许多开发人员的大型团队中,每个开发人员都有自己喜欢的编码风格。
在使用不同风格编写的不同文件甚至各种函数的代码库中,很难读取代码。

安装

首先,我们如下安装ESLint:

npm install --global eslint

一旦安装了可执行文件,我们可以使用以下命令运行它:

eslint source.js

输出将告诉我们文件中是否有错误。
当我们第一次安装并运行它时,我们没有看到任何错误,因为它是完全可配置的,并且没有任何默认规则。

配置项

让我们开始配置。
可以使用位于项目根文件夹中的.eslintrc文件配置ESLint。
要添加一些规则,我们使用规则键。
例如,让我们创建一个.eslintrc并禁用分号:

{
 "rules": {
       "semi": [2, "never"]
 }
}

这个配置文件需要解释一下:“semi”是规则名称,[2,"never"]是值。当你第一次看到它的时候,并不是很直观。
”ESLint规则有三个级别,决定问题的严重程度:

  • off(或0):禁用。
  • warn(或1):警告。
  • error(或2):抛出错误。
    我们使用这个值2,是因为每当我们的代码不遵守规则时,我们都希望ESLint抛出一个错误。
    第二个参数告诉ESLint我们不希望使用分号(对立的值是 always)。
    ESLint和它的插件都有很好的文档记录,对于任何一个规则,您都可以找到关于规则的描述,以及它何时通过以及何时失败的一些说明。
    现在,创建一个包含以下内容的文件:.
var foo = 'bar';

(注意,我们在这里使用var是因为ESLint不知道我们想在ES2015中编写代码。)
如果我们运行eslint index-js,我们会得到以下结果:

Extra semicolon (semi)

这是好的;我们建立了linter,它帮助我们遵循我们的第一条规则。
我们可以手动启用和禁用每个规则,或者通过将以下代码放入我们的.eslintrc,我们可以一次性启用推荐的配置:

{
 "extends": "eslint:recommended"
}

extends键意味着我们是从ESLint扩展建议的规则。但是,我们总是可以使用规则键在.eslintrc中手动重写单个规则,就像我们之前所做的那样。
一旦启用了推荐的规则并再次运行ESLint,就不应该接收。分号的错误(这不是建议配置的一部分),但是我们应该看到linter提示foo变量已经声明且从未使用过。
不使用vars规则对于保持代码整洁非常有用。
正如我们从一开始就说过的,我们想要编写ES2015代码,但是如下改变代码,返回错误:

const foo = 'bar'

错误:

Parsing error: The keyword 'const' is reserved

所以要启用ES2015,我们需要添加一个配置选项:

"parserOptions": {
 "ecmaVersion": 6,
}

一旦我们这样做了,我们将再次得到未使用的错误,这是好的。
最后,为了启用ISX,我们使用以下方法:

"parserOptions": {
 "ecmaVersion": 6,
 "ecmaFeatures": {
 "jsx": true
 }
},

在这一点上,如果您以前编写过任何React应用程序,而且从未使用过linter,那么学习规则并习惯它的一个很好的练习就是对源代码运行ESLint并修复所有问题。。我们通过不同的方式,使ESLint帮助我们写更好的代码。其中一个是我们到目前为止所做的:从命令行运行它并获得错误列表。
这是可行的,但是一直手动运行它不是很方便。最好在编辑器中添加linting过程,以便在输入时立即得到反馈。要做到这一点,有针对SublimeText、Atom和其他最流行的编辑器的ESLint插件。
在实际应用中,手动运行ESLint或在编辑器中实时获取反馈,即使非常有用,也不够,因为我们可能会遗漏一些警告或错误,或者干脆忽略它们。
为了避免在存储库中有未连接的代码,我们可以做的是在一个库中添加ESLint。我们过程的重点。例如,我们可以在测试时运行linting,如果代码没有通过linting规则,那么整个测试步骤都会失败。
另一个解决方案是在打开pull请求之前添加linting,这样我们就有机会在同事开始审查代码之前清理代码。

React 插件

正如前面提到的,ESLint流行的主要原因之一是它可以通过插件进行扩展,对我们来说最重要的原因是ESLint -plugin-react。
ESLint可以在不使用任何插件的情况下解析JSX(仅通过启用标记),但我们还想做更多。例如,我们可能希望执行我们在上一节中看到的最佳实践之一,并使我们的模板在开发人员和团队之间保持一致。
要使用插件,我们首先必须安装它:

npm install --global eslint-plugin-react

安装完成后,我们指示ESLint使用它,向配置文件中添加以下行:

"plugins": [
 "react"
]

如您所见,它非常简单,不需要任何复杂的配置或设置。就像ESLint一样,没有任何规则,它不会做任何事情,但是我们可以启用建议的配置来激活一组基本规则。
.为此,我们更新了.eslintrc文件中的extend键,如下所示:

"extends": [
 "eslint:recommended",
 "plugin:react/recommended"
],

现在,如果我们写错了,例如,我们试图在一个React组件中使用同一个prop两次,我们就会得到一个错误:

No duplicate props allowed (react/jsx-no-duplicate-prop

在我们的项目中有很多可用的规则。让我们浏览其中的一些,看看它们如何帮助我们遵循最佳实践。
正如前一章所讨论的,遵循元素树结构,缩进JSX是很有帮助的。提高可读性。
当缩进与代码库和组件不一致时,就会出现问题。
因此,这里有一个例子说明了ESLint是如何帮助团队中的每个人在不需要记住它的情况下遵循一个风格指南的。
注意,在这种情况下,有错误的缩进不是实际的错误,代码是可运行的;这只是一个一致性的问题。
首先,我们必须激活以下规则:

"rules": {
 "react/jsx-indent": [2, 2]
}

第一个2表示我们希望ESLint在代码中不遵守规则时引发错误。第二个2表示我们希望每个JSX元素都缩进2个空格。ESLint不会为您做任何决定,所以由您决定要启用的规则。您甚至可以选择使用0作为第二个参数强制执行不缩进。
这样写:

<div>
<div />
</div>

ESLint 提示如下:

Expected indentation of 2 space characters but found 0
 (react/jsx-indent)

当我们在新行上写入属性时,类似的规则也适用于缩进属性的方式。
正如我们在前一节中看到的,当属性太多或太长时,最好将它们写在新行上。
如果属性与元素名称之间有两个空格缩进,为了强制格式化,我们可以启用以下规则:

"react/jsx-indent-props": [2, 2]

从现在开始,如果我们不使用两个空格缩进属性,ESLint将失败。
问题是,我们什么时候认为一行太长?多少属性太多了?
每个开发人员对此都会有不同的看法。ESLint帮助我们保持与jsx-max-props-per-line规则的一致性,以便以相同的方式编写每个组件。
ESLint的React插件不仅为我们提供了一些规则来编写更好的JSX,还提供了一些规则来编写更好的React组件,
例如,我们可以让一个规则使prop类型按字母顺序排序,当我们使用prop还没有声明,会抛出错误。规则迫使我们更喜欢无状态组件多于类(我们将会看到在第3章中详细的区别,创建真正可重用的组件),等等。

Airbnb configuration

我们已经看到了ESLint如何使用静态分析帮助我们发现错误,以及它如何迫使我们在整个代码库中遵循一致的风格。
我们还看到了它的灵活性,以及如何通过配置和插件来扩展它。
我们知道我们已经推荐了激活基本规则集,避免手工操作的配置,,这可能是一项乏味的任务。
让我们更进一步。
ESLint extends属性非常强大,您可以使用第三方配置作为起点,然后在其上添加特定的规则。
React公司最常用的配置之一无疑是Airbnb那种。Airbnb的开发人员创建了一组遵循React最佳做法的规则,你可以很容易地在代码库中使用它们,这样你就不必手动决定启用哪些规则。
为了使用它,你必须首先安装一些依赖:

npm install --global eslint-config-airbnbeslint@^2.9.0 eslint-plugin-jsx-a11y@^1.2.0 eslint-plugin-import@^1.7.0 eslint-plugin-react@^5.0.1

然后将以下配置添加到您的eslintrc:

{
 "extends": "airbnb"
}

尝试对您的React源文件再次运行ESLint,您将看到您的代码是否遵循了Airpnb规则,以及是否喜欢这些规则。
这是最简单也是最常见的开始linting的方法。

3.函数式编程的基础知识

除了在编写JSX时遵循最佳实践之外,我们还使用linter来强制一致性和及早发现错误,我们还可以做一件事来清理我们的代码:遵循函数式编程(FP)风格。
如第一章所述,关于React基础知识,React具有声明式编程,使我们的代码更具可读性。
函数式编程是一种声明性范式,在这种范式中,可以避免副作用,并且认为数据是不可变的,以使代码更易于维护和推理。
不要认为下面的部分是函数式编程的详尽指南;这只是一个介绍,让我们从React常用的一些概念入手,你应该知道这些概念。

First-class objects

在JavaScript中,函数是一级对象,这意味着它们可以分配给变量,并作为参数传递给其他函数。
这允许我们引入高阶函数(HoF)的概念。高阶函数是将函数作为参数(可选的其他参数)并返回函数的函数。返回的函数通常通过一些特殊的行为来增强。
让我们来看一个简单的例子,其中有一个用于添加两个数字的函数,它使用一个函数进行了增强,该函数首先记录所有参数,然后执行add(参数):.

const add = (x, y) => x + y
const log = func => (...args) => {
 console.log(...args)
 return func(...args)
}
const logAdd = log(add)

理解这个概念是非常重要的,因为在React中,一个常见的模式是使用高阶组件(HoC),将组件视为功能,并用常见的行为增强它们。我们将在第四章看到。

纯函数

FP的一个重要方面是编写纯函数。您将经常在React生态系统中遇到这个概念,特别是如果您查看Redux之类的库的话。
什么是纯函数?
一个函数在没有副作用的情况下是纯函数,这意味着函数不会改变任何与函数本身无关的东西。
例如,一个改变应用程序状态的函数,或者修改在上作用域中定义的变量,或者一个触及外部实体(如DOM)的函数,被认为是不纯的。
非纯函数更难调试,而且大多数情况下,多次调用这些函数并期望得到相同的结果是不可能的。
例如,下面的函数是纯函数:

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

它可以运行多次,总是得到相同的结果,因为任何地方都不会存储任何东西,也不会修改任何东西。
以下函数不是纯函数:

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

运行add(1)两次,我们得到两个不同的结果。第一次得到1,第二次得到2,即使我们用相同的参数调用相同的函数。我们得到这种行为的原因是在每次执行之后全局状态都会被修改。

Immutability

我们已经看到了如何编写不会改变state的纯函数,但是如果我们需要改变变量的值呢?在纯函数中,用新值创建一个新变量并返回它,来代替改变变量的值。这种处理数据的方式称为不变性。
不可变值是不能被改变的值。
让我们看一个例子:

const add3 = arr => arr.push(3)
const myArr = [1, 2]
add3(myArr) // [1, 2, 3]
add3(myArr) // [1, 2, 3, 3]

前面的函数不遵循不变性,因为它改变了给定参数的值。同样,如果我们调用同一个函数两次,得到的结果也会不同。
我们可以使用concat修改前面的函数使其不可变,concat返回一个数组,而不修改给定的数组:
在我们运行函数两次之后,nyArr仍然有它的原始值。

局部套用

纯函数中一个常见的技术是局部套用。局部套用是指将一个有多个参数的函数转换为一个每次有一个参数的函数,然后返回另一个函数的过程。让我们看一个例子来阐明这个概念。让我们从以前见过的add函数开始,并将其转换为curried函数。
让我们从以前见过的add函数开始,并将其转换为局部套用函数。
之前编写:

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

我们这样定义函数:

const add = x => y => x + y

我们是这样用的:

const add1 = add(1)
add1(2) // 3
add1(3) // 4

这是编写函数的一种非常方便的方式,因为第一个值是在第一个参数应用程序之后存储的,因此我们可以多次重用第二个函数。

组合

最后,纯函数中一个可以应用于react的重要概念是组合。可以将函数(和组件)组合起来,生成具有更高级特性和属性的新函数。
考虑以下函数:

const add = (x, y) => x + y
const square = x => x * x

这些函数可以组合在一起,创建一个新函数,将两个数字相加,然后使结果加倍:

const addAndSquare = (x, y) => square(add(x, y))
//先执行add

按照这种范式,我们最终得到了可以组合在一起的小的、简单的、可测试的纯函数。

纯函数和用户接口

最后一步是学习如何使用纯函数构建UI,这是我们使用React的目的。
我们可以把UI看作是一个调用了应用程序state的函数,如下所示:

UI = f(state)

我们期望这个函数是全等的,这样它在应用程序的相同状态下返回相同的UI。
使用React,我们可以考虑函数创建UI组件,在下一章我们将会看到。
组件可以组成最终的UI,这是纯函数的一个属性。
在我们用React和纯函数的原则构建UI的方式上有很多相似之处,我们越了解它,代码就会越好。

总结

在本章中,我们学习了很多关于JSX如何工作以及如何在组件中正确使用它的知识。我们从语法的基础知识开始,创建了一个坚实的知识库,使我们能够掌握JSX及其特性。
在第二部分中,我们研究了ESLint及其插件如何帮助我们更快地发现问题,并在我们的代码库中执行一致的风格指南。
最后,我们学习了函数式编程的基础知识,以理解编写React应用程序时要使用的重要概念。
现在我们的代码是干净的,我们准备开始深入了解React并学习如何来编写真正可重用的组件。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容

  • 本笔记基于React官方文档,当前React版本号为15.4.0。 1. 安装 1.1 尝试 开始之前可以先去co...
    Awey阅读 7,619评论 14 128
  • 作为一个合格的开发者,不要只满足于编写了可以运行的代码。而要了解代码背后的工作原理;不要只满足于自己的程序...
    六个周阅读 8,406评论 1 33
  • HTML模版 之后出现的React代码嵌套入模版中。 1. Hello world 这段代码将一个一级标题插入到指...
    ryanho84阅读 6,204评论 0 9
  • 来源:旅游学院 作者:粟泽兰 你们,还好吗? 窗外阳光和从前我们在学校一样,那么温暖和煦。最...
    旅游学院16旅游管理1班阅读 187评论 0 0
  • 无论愿还是不愿,2017就这么来了,伴着雾霾,新的一年开始喽。 这两天,大家都忙着总结2016,展...
    凉皮姐阅读 170评论 0 0