react.js复用ui
先决条件:对HTML,JavaScript和CSS有所了解。
对React有更好的介绍吗?
不幸的是,那里的大多数React教程都没有考虑最佳实践,并且并不总是教会您“正确”的React方法。
在本教程中,我将介绍React的基础知识以及您可能会遇到的最常见的不良做法。
本教程将很长,因此请确保自己喝点咖啡!
为什么选择React?
在开始之前,我们先停下来看看为什么React确实是最佳选择。
陈述式
在React中,您描述要渲染的内容(而不是告诉浏览器该怎么做)。 这也意味着样板的数量大大减少了。
在React中,您只需开始编码,它就没有要生成的组件样板。 涉及到一些设置,但是对于组件,您可以将它们表示为纯函数。
语法清晰
React中的JSX就像HTML一样,没有特殊的语法需要学习:
学习曲线
选择UI框架时,学习曲线非常重要。 React的抽象最少。 如果您了解JavaScript,那么您可能可以在一天内开始编写React代码。 是的,选择最佳做法需要时间,但是您将能够非常快速地开始。
功能性
在我看来,React的最大优势在于您甚至没有被迫使用类。 类过于复杂,没有提供任何好处。
在React中,所有UI都可以表示为一组纯函数,并且使用纯函数来呈现UI就像呼吸新鲜空气一样。
让我们开始编码!
现在,我已经希望您说服您使用React,让我们来编写一些代码!
Node.js
Node.js是一个JavaScript运行时环境,使我们能够编译出色的React代码!
首先,让我们确保已安装Node.js。 如果没有,您可以从这里下载: https : //nodejs.org/en/download
创建React应用
我们将使用来自Facebook的create-react-app来搭建我们的应用程序。 这是设置环境并开始编码的最流行方法。 它带有许多必需的内置工具,可帮助我们消除许多决策。
要全局安装create-react-app:
npm i -g create-react-app
然后脚手架您的第一个项目运行:
create-react-app react-intro
就这样! 现在,启动应用程序:
cd react-intro
npm start
这将启动开发服务器,并允许您通过在浏览器中访问http:// localhost:3000 /来打开新的闪亮的React应用程序。
引擎盖下
现在,让我们来看看事物是如何运作的。 使用您选择的IDE(我建议使用Visual Studio Code )打开新创建的项目。
index.html
在项目内,转到public/index.html
文件。 这是您将看到的:
我们感兴趣的部分是<div id="root"></div>
。 这是我们的React应用程序将要运行的地方。 整个root div将简单地替换为我们React应用程序的内容。 其他所有内容将保持不变。
index.js
现在打开src/index.js
。 这是引导整个React应用程序的文件。 顺便说一下,我们所有的React源代码都将进入src
目录。
神奇的行是:
ReactDOM.render(<App />, document.getElementById('root'));
此行是告诉React使用我们的App组件的方法(我们将稍作讨论),并将其放置在index.html
文件中上面定义的root
div中。
现在让我们关注<App />
部分。 这看起来很像HTML,不是吗? 这被称为JSX,是React用来实现其魔力的一种特殊JavaScript语法。 请注意,它以大写字母A
开头-是<App />
,而不是<app />
。 这是React使用的约定,它使它可以消除常规HTML标签和我们创建的React组件之间的歧义。 如果您不以大写字母开头组件,那么React将无法渲染您的组件。
每当使用JSX时,我们总是必须通过在.js
文件中添加以下行来导入React:
import React from 'react';
App.js
现在,我们准备看一下我们的第一个组件。 让我们打开src/app.js
:
为了创建一个React组件,我们必须创建一个从React.Component
继承的类。 这正是class App extends Component
线class App extends Component
功能。 所有React组件都应实现一个render
方法-您可能已经猜到了,所有渲染都在此方法内进行。 render
方法必须返回要渲染的标记。
一个小的旁注:className
属性等效于HTML中的class
属性,用于分配CSS类以进行样式设置。class
是JavaScript中的保留关键字,不能用于属性名称。
让我们回顾一下:
- 该组件名为App(大写A)
- 它扩展了
React.Component
类 - 它必须实现
render
方法,该方法返回标记。
坏习惯#1-随处可见的类组件
在React中有两种创建组件的方法-类组件和功能组件。 您可能已经注意到,上面的示例使用了一个类组件。 而且,不幸的是,大多数初学者的React教程都鼓励使用Class Components。
类组件有什么问题? 它们很难测试,往往会变得很大,易于分散关注点,将逻辑与表示耦合在一起(这使得调试和测试更加困难)。 通常,您将通过使用Class Components来射击自己。 尤其是如果您是初学者,我建议您完全远离他们。
好的,类组件不好,我明白了。 但是还有哪些选择? 功能组件。 如果组件仅具有render
方法,那么它是重构为功能组件的理想选择。 让我们看看如何改善由create-react-app创建的App组件:
看看我们在这里做了什么? 我们删除了该类,并用function App() {...}
替换了render
方法。 而且,如果我们使用ES6箭头功能,它将看起来更好:
我们已经将类组件转换为一个函数,该函数返回要呈现的标记。
仔细考虑一下……一个返回标记的函数,没有不必要的样板代码,只有纯标记! 不漂亮吗
该功能组件的读取效果更好,信噪比更高。
在本文中,我们将坚持使用类组件,因为它们涉及的抽象较少,并且更易于演示核心React概念。 一旦您熟悉了React基础知识,我强烈建议您阅读我更深入的文章— 通过Recompose掌握React功能组件 。
道具介绍
道具是React的核心概念。 到底是什么道具? 考虑一下传递给函数的参数。 道具就是这样-将参数传递给组件。
在这里,我们创建了一个Greetings
组件,并使用它从App
组件中向John Smith致意。 这将导致以下标记:
<div>
<div>Hey you! John Smith!</div>
</div>
{props.name}
的大括号表示JavaScript代码。 Greetings
组件传递了firstName
和lastName
作为参数,我们只需通过访问props
对象来检索它们。
请注意,组件传递了一个props
对象,而不是firstName
和lastName
两个值。
我们可以通过使用ES6对象解构语法来进一步简化代码:
请注意, (props)
被替换为({ firstName, lastName })
。 这意味着我们只对props
对象的这两个属性感兴趣。 这样一来,我们就可以直接访问firstName
和lastName
值,而不必显式指定props.firstName
。
如果我们一直使用类组件怎么办?
我不了解您,但是对我来说,这看起来更肿胀了! 我们总是必须显式使用this.props
。
单一责任原则
单一责任原则是要遵循的最重要的编程原则。 它指出模块应该做一件事,并且应该做好。 单单不遵循这一原则,就可能将任何代码库变成无法维护的噩梦。
我们如何违反这一原则? 最常见的方法是将不相关的内容放置在同一文件中。
在本教程中,我将多次参考“单一责任原则”。
初学者通常将多个组件放在同一文件中。 在这里,我们将Greetings和App组件放置在同一文件中。 这是一个坏习惯,因为这违反了“单一责任原则”。
即使最小的组件(如上面的Greetings组件)也应放在单独的文件中。
让我们将Greetings组件放入其自己的文件中:
然后在App
组件中使用它:
import Greetings from "./Greetings";
const App = () => (
...
);
确保文件名与组件名称匹配。 App
组件应放置在App.js
, Greetings
组件应放置在Greetings.js
,依此类推。
介绍状态
状态是React的另一个核心概念。 这是您想要保留数据的地方-可能会发生变化。 是否存储键入到表单元素中的值? 使用状态。 在游戏中跟踪得分? 使用状态。
让我们构建一个简单的表单,以用户的名字作为输入。 请注意,我故意使用类组件来演示该概念。 在我的其他文章《 用Recompose掌握React功能组件》中,我演示了将类组件重构为功能组件的方法 。
好的,用户可以在表格中输入他的电子邮件,这太好了! 如果您一直在注意,那么您会发现,无论如何,问候语中都会使用约翰这个名字。 如果不是我们所有的用户名都是John,该怎么办? 我们将自己置于非常不舒服的境地。
我们如何使用输入到输入中的值? 在React中,我们不应该直接查询DOM。 这是输入处理程序和状态进入的地方。
状态基本上是一个普通JavaScript对象,它作为属性存储在SimpleForm
类组件中。 在这里,我们为类添加值firstName
。
现在,我们的firstName
输入具有onChange
事件处理程序。 每次用户在输入中键入键时都会触发。 我们类中的属性this.onFirstNameChange
处理onChange事件。
让我们看一下onFirstNameChange
属性:
this.setState(...)
这就是我们更新组件状态的方式。 我们不应该直接通过setState
方法直接更新组件状态。 为了更新firstName
状态值的值,我们只需将具有更新后值的对象传递给setState
方法:
{ firstName: event.target.value }
在这种情况下, event.target.value
是在表单输入中键入的值。 在这种情况下,这就是用户名。
附带说明:我们尚未将onFirstNameChange
定义为方法。 将其定义为类的箭头函数属性,而不是方法,这一点非常重要。 如果我们它定义为一个方法来代替,那么this
将被绑定到表单输入调用该方法,而不是类如我们所预期的。 这个小细节经常使初学者绊倒。 这是避免在JavaScript中使用类的另一个原因。
表格验证
现在,让我们使用正则表达式实现简单的表单验证-确保名字至少三个字符,并且仅包含字母。
我们将为onBlur
事件添加另一个事件处理程序-每当用户离开输入时它将触发。 我们还将在状态中添加另一个属性firstNameError
。 然后,我们将在输入下方显示验证错误(如果存在错误)。
州
首先,我们向状态添加了firstNameError
属性:
state = {
...
firstNameError: "",
};
验证功能
验证本身在上面的validateName
箭头函数中进行。 它只是针对正则表达式测试输入名称:
validateName = name => {
const regex = /[A-Za-z]{3,}/;
return !regex.test(name)
? "The name must contain at least three letters..."
: "";
}
如果验证失败,我们将返回验证错误。 如果验证成功,那么我们将返回一个空字符串(表示没有错误)。 我们在这里使用JavaScript三元表达式来使代码更简洁。
onBlur事件处理程序
让我们看一下onBlur
事件处理程序(在用户离开输入时触发):
onFirstNameBlur = () => {
const { firstName } = this.state;
const firstNameError = this.validateName( firstName );
return this.setState({ firstNameError });
};
在这里,我们通过使用ES6对象解构语法从状态中提取firstName
。 第一行等效于:
const firstName = this.state.firstName;
然后,我们使用firstName
运行上面定义的验证函数,然后使用返回的错误设置firstNameError
状态属性。 如果验证失败,则将设置firstNameError
。 如果成功,则将其设置为空字符串。
render
方法
现在让我们看一下render()
方法:
render() {
const { firstNameError, firstName} = this.state;
...
}
在这里,我们再次使用ES6对象解构从状态中提取值。
<input
...
onBlur={this.onFirstNameBlur}
/>
此行将onFirstNameBlur
函数分配为onBlur
事件的事件处理程序。
{firstNameError && <div>{firstNameError}</div>}
在这里,我们使用JavaScript的短路评估功能。 仅当值本身为true时,才会呈现包含firstNameError
的div。
造型
如果您一直在关注,那么您可能已经注意到我们的表格不是特别漂亮……让我们通过添加一些内联样式来更改它!
只需在style
属性中传递样式即可添加React中的style
。
我承认我不是设计师,但我的程序员艺术现在看起来好多了。 这是带有验证错误的表格:
坏习惯#3-组件中的样式
在这里,我们遇到了另一种不好的做法,不幸的是这种做法太普遍了—将样式放置在组件的render
方法中。 为什么这样不好? 这违反了单一责任原则。 它也使我们的组件杂乱无章,极大地损害了可读性。
有哪些补救办法? 创建一个包含所有样式的特殊style
对象。 将styles
放在单独的文件中被认为是一个好习惯:
然后在我们的SimpleForm
组件中使用它:
这看起来更干净!
总结:将样式放在单独的文件中。
添加更多表单字段
让我们通过添加一个字段来输入姓氏来使表单更有趣:
这里没有太大变化-我们只复制了firstName
输入的代码,也复制了其事件处理程序。
我只是说重复吗? 重复代码在软件开发中是一个很大的难题,应不惜一切代价避免重复代码。
坏习惯4:不要拆分组件。
这种不好的做法再次回到违反单一责任原则的状态。 好的书面代码应该读起来就像一首诗,我敢打赌我们组件的render方法读起来不像一首诗。 让我们改变一下。
输入几乎是相同的,并且都需要某种验证。 让我们对组件应用一些重构,然后创建一个可重用的TextField
组件:
我只是从render
方法中提取了一个组件,将其转换为功能组件,然后将其更改为props:
很好,现在读起来好多了! 我们甚至可以更进一步,并为名字和姓氏创建专用的TextField
组件:
在这里,我们只是返回一个预制组件以显示名字。 ({...rest})
是新的Object Rest语法-这意味着作为prop传递的所有内容都将保存到rest
对象中。 然后将道具传递到TextField
我们使用了Object Spread语法{...rest}
。 这将获取rest
对象,传播其属性,然后将其向下传递到TextField
组件。
换句话说:我们接受传递给FirstNameField
任何内容,并将其TextField
传递给TextField
。
同样, LastNameField
:
这是我们的表格现在的样子:
好多了!
为什么类组件仍然如此糟糕?
- 类组件很难测试(与功能组件不同)。
- 关注点分离差:如果我们很懒惰,那么我们将把所有东西都放到一个类中,随着时间的流逝,它可能会变成1000行怪兽(我已经看到过多次发生)。
- 类组件倾向于将逻辑和表示形式放在一个单独的类中。 这对于分离关注点再次不利。
- 类组件不是纯组件,很难推理。 另一方面,功能组件是纯组件-它们将始终导致为相同的输入props渲染相同的标记。
- 功能组件强制执行良好的设计。 您不得不考虑设计。
- 无需使用
this
关键字,它一直是造成混淆的主要原因。
源代码
随附的源代码可以在GitHub上找到
下一步是什么?
本教程的时间比我预期的要长。 还有很多要讨论的内容,尤其是与代码组织有关的内容以及其他一些最佳实践。 让我知道您是否有兴趣听取更多评论。
如果您真的想精通React,我强烈建议您阅读另一篇文章: 使用Recompose精通React Functional Components 。
请确保关注我以获取更多有关React和JavaScript的文章!
react.js复用ui