React.js:初学者指南

这篇文章的作者(我们正在翻译的译本)认为,不幸的是,大多数现有的React指南并未充分重视有价值的实用开发技术。 这些指南并不总是使参与其中的人员了解使用React的“正确方法”是什么。

图片

本教程的目标是使初学者了解HTML,JavaScript和CSS,它将涵盖React的基础知识以及使用该库的程序员可能遇到的最常见错误。

为什么Web开发人员会选择React?


在我们开始做生意之前,先说几句话,为什么React可以被认为是开发Web界面的工具中最好的替代品。 有很多UI框架。 为什么选择React? 为了回答这个问题,让我们比较两个最受欢迎的界面开发工具-React和Angular。 应该注意的是,这种比较中可以包括越来越受欢迎的Vue.js框架,但是我们将自己限制在React和Angular。

▍以声明方式描述接口


React开发在于描述需要在页面上显示的内容(而不是针对浏览器的操作说明进行编译)。 除其他外,这意味着大大减少了样板代码量。

另一方面,Angular具有生成组件模板代码的命令行工具。 这似乎与您对现代界面开发工具的期望有所不同吗? 实际上,我们正在谈论的事实是,Angular中有太多的模板代码,为了生成它,甚至已经创建了一个特殊的工具。

在React中,开始开发时,他们只是开始编写代码。 没有需要以某种方式生成的样板组件代码。 当然,在开发之前需要进行一些准备,但是当涉及到组件时,它们可以被描述为纯函数。

▍清晰的语法


角度代码使用ng-modelngIfngFor 。 此代码看起来很麻烦。 另一方面,React使用JSX语法(被视为常规HTML),也就是说,为了开始React开发,您不需要从根本上学习新知识。 看起来是这样的:

 const Greetings = ({ firstName }) => (  <div>Hi, {firstName}</div> ); 

▍正确的学习曲线


学习曲线是选择UI框架时要考虑的重要因素。 在这方面,应该注意的是,React中的抽象少于Angular中的抽象。 如果您知道JavaScript,那么您大概一天就能学会如何编写React应用程序。 当然,为了学习正确的方法,这需要一些时间,但是您可以非常非常快速地开始工作。

如果您分析Angular,事实证明,如果您决定掌握此框架,则必须学习一种新语言(Angular使用TypeScript),以及学习如何使用Angular命令行工具并习惯于使用指令。

the数据绑定机制的特点


Angular具有双向数据绑定系统。 例如,这表现为以下事实:元素形式的变化导致应用程序状态的自动更新。 这使调试变得复杂,并且是该框架的一个很大的缺点。 使用这种方法,如果出现问题,程序员将无法确切知道是什么导致了应用程序更改状态。

另一方面,React使用单向数据绑定。 这是该库的一大优点,因为它可以表示为程序员始终清楚地知道是什么导致了应用程序状态的改变。 这种数据绑定方法极大地简化了应用程序的调试。

▍实用的开发方法


我相信React的强项之一就是该库不会强迫开发人员使用类。 在Angular中,所有组件都必须实现为类。 这导致过多的代码复杂性,而没有提供任何优势。

在React中,所有用户界面组件都可以表示为一组纯函数。 使用纯函数形成UI可以比作一口清新的空气。

既然我们已经研究了React流行的原因,那么当选择用于开发用户界面的工具时,很可能会使您倾向于这个特定的库,让我们继续实践。

React应用开发实践


▍Node.js


Node.js是一个支持JavaScript代码执行的服务器平台,其功能对于我们进行React开发非常有用。 如果您尚未安装此平台,那么现在该修复它了

project项目准备


在这里,我们将使用Facebook的create-react-app包来创建React应用程序的核心。 这可能是设置工作环境的最流行方法,它使您可以开始开发。 多亏了create-react-app程序员可以使用许多必要的工具,从而无需自己选择它们。

create-react-app安装create-react-app ,请使用以下命令:

 npm i -g create-react-app 

然后,要创建应用程序模板,请运行以下命令:

 create-react-app react-intro 

初步准备已完成。 要启动该应用程序,请运行以下命令:

 cd react-intro npm start 

在这里,我们转到项目文件夹并启动开发服务器,通过该服务器,您可以通过以下浏览器打开新的React应用程序:http:// localhost:3000 /

▍项目结构


让我们弄清楚React应用程序是如何工作的。 为此,请打开使用IDE创建的项目(我建议使用Visual Studio Code )。

Index.html文件


在项目文件夹中,打开位于public/index.html的文件。 这是您将通过此操作看到的内容。


Index.html文件

在这里,我们对<div id="root">行特别感兴趣。 这是我们的React应用程序所在的位置。 所有此元素将被应用程序代码替换,其他所有内容将保持不变。

Index.js文件


现在打开src/index.js 。 该文件是部署React应用程序的文件。 并且,顺便说一句,应用程序的源代码将放置在src目录中。


Index.js文件

这是负责将我们称为“ React应用程序”的内容输出到页面的代码行:

 ReactDOM.render(<App />, document.getElementById('root')); 

此行告诉React,我们需要使用App组件(我们将很快讨论)并将其放置在root div ,该div在我们刚刚检查的index.html文件中定义。

现在,我们将处理<App />构造。 它与HTML代码非常相似,但是它是JSX代码的示例,它是React使用的一种特殊的JavaScript语法。 请注意,此构造以大写字母A开头,它完全是<App /> ,而不是<app /> 。 这是因为React中使用了实体命名约定。 这种方法使系统可以区分常规HTML标签和React组件。 如果组件名称不大写,React将无法在页面上显示它们。

如果您打算在某个.js文件中使用JSX,则需要使用以下命令在其中导入React:

 import React from 'react'; 

App.js文件


现在我们准备看一下第一个组件的代码。 为此,请打开src/App.js


App.js文件

为了创建一个React组件,您必须首先创建一个作为React.Component的后代的React.Component 。 这正是class App extends Component线class App extends Component解决的问题。 所有React组件都必须包含render方法的实现,从中可以猜出它的名称,可以渲染该组件并生成其可视表示的描述。 此方法应返回HTML标记以输出到页面。

请注意, className属性与HTML中的class属性等效。 它用于将CSS类分配给元素以对其进行样式设置。 JavaScript关键字class是保留的,不能用作属性名称。

让我们重复一下我们刚刚发现的有关组件的内容:

  1. 他们的名字以大写字母( App A )开头。
  2. 它们扩展了React.Component类。
  3. 他们必须实现一个返回标记的render方法。

现在让我们讨论开发React应用程序时应避免的事情。

▍建议1:没有必要在所有地方使用组件类


React中的组件可以使用两种方法来创建。 第一种是使用组件类(Class Component),第二种是使用功能组件(Functional Component)。 您可能已经注意到,以上示例使用了类。 不幸的是,大多数React初学者教程都建议使用它们。

使用类机制描述组件有什么问题? 事实是,此类组件很难测试,而且往往会过度增长。 这些组件会遇到质量低下的职责分离,混合逻辑和可视化表示的问题(这会使应用程序的调试和测试变得复杂)。 通常,使用组件类会导致这样的事实,即,从形象上讲,程序员“会自以为是”。 因此,特别是对于新手程序员,我建议他们根本不要使用组件类。

因此,我们发现使用类来描述组件不是一个好主意。 我们有什么选择? 这个问题的答案是功能组件。 如果使用该类创建的组件仅具有render方法,则它是将其处理为功能组件的绝佳选择。 有了这个想法,让我们考虑如何改善由create-react-app工具create-react-appApp组件:

 function App() { return (   <div className="App">     ...   </div> ); } export default App; 

看看我们在这里做了什么? 即,我们删除了该类,并用表单function App() {...}的构造替换了render方法。 如果在这里使用ES6箭头函数语法,我们的代码将看起来更好:

 const App = () => ( <div className="App">   ... </div> ); export default App; 

我们将类转换为一个函数,该函数返回需要在页面上显示的标记。

考虑一下。 返回标记的函数没有样板代码。 这几乎是纯标记。 那不是完美的吗?

功能组件的代码易于阅读,使用它们,对标准设计的注意力也大大减少。

这里应该注意,尽管我们只是说功能组件比组件类更可取,但是在本文中,我们将主要使用类,因为对于初学者来说,组件类的代码更加清晰,但是它依赖于更少的抽象,并且演示关键的React概念更加容易。 但是,当您足够熟悉React应用程序的开发时,强烈建议在开发实际项目时考虑到上述内容。 为了更好地理解功能组件,请看一下该材料

properties了解物业


属性(props)是React的核心概念之一。 什么是“属性”? 为了理解这一点,请记住传递给函数的参数。 本质上,属性是传递给组件的参数。 考虑以下代码:

 const Greetings = (props) => <div>Hey you! {props.firstName} {props.lastName}!</div>; const App = () => ( <div>   <Greetings firstName="John" lastName="Smith" /> </div> ); 

在这里,我们创建了Greetings组件,并用它来问候来自App组件的一个叫John Smith的人。 所有这些代码将导致以下HTML标记:

 <div>  <div>Hey you! John Smith!</div> </div> 

{props.name}类的表达式中的花括号用于突出显示JavaScript代码。 Greetings组件以参数的形式传递firstNamelastName属性。 我们使用它们来处理props对象的属性。

请注意,将一个props对象props ,而不是两个表示firstNamelastName属性的值。

通过利用ES6的重组对象功能,可以简化代码:

 const Greetings = ({ firstName, lastName }) => <div>Hey you! {firstName} {lastName}!</div>; 

如您所见,此处的构造(props)已由({ firstName, lastName })代替。 通过这种替换,我们告诉系统我们只对props对象的两个属性感兴趣。 进而,这使我们可以直接访问firstNamelastName的值,而无需显式指定对象属性(例如props.firstName

如果要解决同一问题,而不是使用功能组件,我们将使用基于类的组件怎么办? 在这种情况下, Greetings组件代码将如下所示:

 class Greetings extends React.Component { render() {   return (     <div>Hey you! {this.props.firstName} {this.props.lastName}!</div>   ); } } 

我不知道这段代码会给您带来什么感觉,但是在我看来,辅助机制实在不胜枚举。 特别是在这里,要访问属性,必须使用this.props形式的this.props

▍唯一责任原则


单一责任原则(SRP)是要遵守的最重要的编程原则之一。 他告诉我们,该模块只能解决一个问题,并且应该以高质量的方式来解决。 如果您仅不遵循此原则来开发项目,则此类项目的代码可能会变成无法支持的噩梦设计。

如何违反独占责任原则? 通常,将彼此不相关的机制放在同一文件中时会发生这种情况。 在本材料中,我们将经常引用此原理。

初学者通常将许多组件放在一个文件中。 例如,我们在同一文件中具有GreetingsApp组件的代码。 实际上,不应这样做,因为这违反了SRP。

甚至很小的组件(例如, Greetings组件)也需要放在单独的文件中。

Greetings组件代码放在单独的文件中:

 import React from "react"; const Greetings = ({ firstName, lastName }) => (   <div>       Hey you! {firstName} {lastName}!   </div> ); export default Greetings; 

然后在App组件中使用此组件:

 import Greetings from "./Greetings"; const App = () => ( ... ); 

请注意,文件名必须与组件名称匹配。 也就是说, App组件的代码应放置在App.js文件中, Greetings组件的代码应放置在Greetings文件中,依此类推。

▍熟悉申请状态


状态是React中的另一个核心概念。 在这里存储应用程序数据-即可以更改的内容。 是否需要保存在表单字段中输入的内容? 使用状态。 是否需要保存玩家在浏览器游戏中获得的积分? 为此,您必须使用应用程序的状态。

让我们创建一个简单的表单,用户可以使用它输入他的名字。 请注意,这里我故意使用一个类来描述组件,因为这样可以更轻松地演示所讨论的概念。 您可以在此处阅读有关如何将使用类创建的组件转换为功能组件的信息

 import React from "react"; class SimpleForm extends React.Component { render() {   return (     <div>       <input type="text" name="firstName" />       <Greetings firstName="John" />     </div>   ); } } const App = () => ( <div>   <SimpleForm /> </div> ); 

用户可以在表单字段中输入内容,这很好。 但是,如果您仔细阅读此代码,您可能会注意到在欢迎页面的输出中始终将John用作用户名。 如果不是我们所有的用户都这样被称呼呢? 如果是这样,那么我们将自己摆在一个不太舒服的位置。

如何使用在字段中输入的值? React并不直接依赖DOM元素。 事件处理程序和应用程序状态将帮助我们解决此问题。

 class SimpleForm extends React.Component { state = {   firstName: "", }; onFirstNameChange = event =>   this.setState({     firstName: event.target.value   }); render() {   return (     <div>       <input type="text" name="firstName" onChange={this.onFirstNameChange} />       <Greetings firstName={this.state.firstName} />     </div>   ); } } 

您可以将状态想象为一个简单的JavaScript对象,它以属性的形式存储在SimpleForm组件的类中。 在此对象中,我们添加firstName属性。

在这里,我们为firstName字段配备了事件处理程序。 每当用户在字段中输入至少一个字符时,它就会开始。 在该类中, onChange属性负责处理onChange onFirstNameChange ,在该onFirstNameChange中执行以下命令:

 this.setState(...) 

这是组件状态更新的地方。 组件状态不会直接更新。 这只能使用setState方法完成。 为了向firstName属性写入新值,我们只需将包含需要写入状态的对象传递给此方法:

 { firstName: event.target.value } 

在这种情况下, event.target.value是用户在表单字段中输入的内容,即他的姓名。

请注意,我们没有将onFirstNameChange声明为方法。 以包含箭头功能的类属性的形式而不是方法的形式声明此类内容非常重要。 如果将类似的函数声明为方法,则它将绑定到调用此方法的form元素,而不是我们期望的类。 对于初学者来说,这件小事常常令人困惑。 这是建议使用功能组件而不是组件类的原因之一。

▍检查表格上输入的数据


我们实现了一个简单的系统,用于使用正则表达式检查输入到表单中的数据。 让我们决定一个名称必须至少包含三个字符,并且只能包含字母。

onBlur事件处理程序添加到组件,当用户离开输入字段时调用该事件处理程序。 将另一个属性添加到应用程序状态– firstNameError 。 如果输入名称时发生错误,我们将在该字段下显示有关此消息。

让我们分析一下这段代码。

 class SimpleForm extends React.Component { state = {   firstName: "",   firstNameError: "", }; validateName = name => {   const regex = /[A-Za-z]{3,}/;   return !regex.test(name)     ? "The name must contain at least three letters. Numbers and special characters are not allowed."     : ""; }; onFirstNameBlur = () => {   const { firstName } = this.state;   const firstNameError = this.validateName( firstName );   return this.setState({ firstNameError }); }; onFirstNameChange = event =>   this.setState({     firstName: event.target.value   }); render() {   const { firstNameError, firstName } = this.state;   return (     <div>       <div>         <label>           First name:           <input             type="text"             name="firstName"             onChange={this.onFirstNameChange}             onBlur={this.onFirstNameBlur}           />           {firstNameError && <div>{firstNameError}</div>}         </label>       </div>       <Greetings         firstName={firstName}       />     </div>   ); } } 

申请状态


开发输入验证系统后,我们首先为状态添加了一个新属性: 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事件onBlur ,该事件onBlur在用户离开输入字段时调用:

 onFirstNameBlur = () => { const { firstName } = this.state; const firstNameError = this.validateName( firstName ); return this.setState({ firstNameError }); }; 

在这里,我们利用ES6破坏对象的能力从状态中提取firstName属性。 此代码的第一行与此等效:

 const firstName = this.state.firstName; 

然后,我们调用上述数据验证函数,将其传递给firstName ,并将该函数返回的内容写入state属性firstNameError 。 如果检查失败,将向该属性发送错误消息。 如果成功,将在其中写入一个空行。

渲染方法


考虑组件render()方法:

 render() {  const { firstNameError, firstName} = this.state;  ... } 

.

 <input ... onBlur={this.onFirstNameBlur} /> 

onFirstNameBlur onBlur .

 {firstNameError && <div>{firstNameError}</div>} 

JavaScript. div , , , firstNameError true .


, , , . , :

 render() { const { firstNameError, firstName } = this.state; return (   <div       style={{         margin: 50,         padding: 10,         width: 300,         border: "1px solid black",         backgroundColor: "black",         color: "white"       }}     >     <div style={{marginBottom: 10}}>       <label>         First name:         <input           style={{backgroundColor: '#EFEFFF', marginLeft: 10}}           type="text"           name="firstName"           onChange={this.onFirstNameChange}           onBlur={this.onFirstNameBlur}         />         {firstNameError && <div style={{color: 'red', margin: 5}}>{firstNameError}</div>}       </label>     </div>     <Greetings       firstName={firstName}     />   </div> ); } 

React style .

, , , , , . , .




▍ №2:


, , React-. , , — render , , . ? , . , , .

? , . style , . . , style.js :

 // style.js: const style = {   form: {       margin: 50,       padding: 10,       width: 300,       border: "1px solid black",       backgroundColor: "black",       color: "white"   },   inputGroup: {       marginBottom: 10   },   input: {       backgroundColor: "#EFEFFF",       marginLeft: 10   },   error: {       color: "red",       margin: 5   } }; export default style; 

, , SimpleComponent :

 import style from './style'; class SimpleForm extends React.Component {   ...       render() {       const { firstNameError, firstName } = this.state;         return (         <div style={style.form}>           <div style={style.inputGroup}>             <label>               First name:               <input                 style={style.input}                 type="text"                 name="firstName"                 onChange={this.onFirstNameChange}                 onBlur={this.onFirstNameBlur}               />               {firstNameError && (                 <div style={style.error}>{firstNameError}</div>               )}             </label>           </div>             <Greetings firstName={firstName} />         </div>       );     } } export default SimpleForm; 

, . .


, :

 class SimpleForm extends React.Component { state = {   ...   lastName: "",   lastNameError: "" }; validateName = ...; onFirstNameBlur = ...; onFirstNameChange = ...; onLastNameBlur = () => {   const { lastName } = this.state;   const lastNameError = this.validateName(lastName);   return this.setState({ lastNameError }); }; onLastNameChange = event =>   this.setState({     lastName: event.target.value   }); render() {   const { firstNameError, firstName, lastName, lastNameError } = this.state;   return (     <div style={style.form}>       <div style={style.inputGroup}>         <label>           First name:           <input             style={style.input}             type="text"             name="firstName"             onChange={this.onFirstNameChange}             onBlur={this.onFirstNameBlur}           />           {firstNameError && <div style={style.error}>{firstNameError}</div>}         </label>       </div>       <div style={style.inputGroup}>         <label>           Last name:           <input             style={style.input}             type="text"             name="lastName"             onChange={this.onLastNameChange}             onBlur={this.onLastNameBlur}           />           {lastNameError && <div style={style.error}>{lastNameError}</div>}         </label>       </div>       <Greetings firstName={firstName} lastName={lastName} />     </div>   ); } } export default SimpleForm; 

— , firstName .

«»? — , .

▍ №3:


, , , , , . , , render . .

, , , . TextField , .

 import React from 'react' import style from "./style"; const TextField = ({name, onChange, onBlur, error, label}) => ( <div style={style.inputGroup}>   <label>     {label}     <input       style={style.input}       type="text"       name={name}       onChange={onChange}       onBlur={onBlur}     />     {error && <div style={style.error}>{error}</div>}   </label> </div> ); export default TextField; 

, , render . , , .

SimpleForm :

 ... import TextField from './TextField'; class SimpleForm extends React.Component { ... render() {   const { firstNameError, firstName, lastName, lastNameError } = this.state;   return (     <div style={style.form}>       <TextField name="firstName"                  label="First name:"                  onChange={this.onFirstNameChange}                  onBlur={this.onFirstNameBlur}                  error={firstNameError} />       <TextField name="lastName"                  label="Last name:"                  onChange={this.onLastNameChange}                  onBlur={this.onLastNameBlur}                  error={lastNameError} />       <Greetings firstName={firstName} lastName={lastName} />     </div>   ); } } 

, . , TextField . FirstNameField :

 import React from 'react'; import TextField from './TextField'; const FirstNameField = ({...rest}) => ( <TextField name="firstName"             label="First name:"             {...rest} /> ); export default FirstNameField; 

, . ({...rest}) ( rest, ). , , , rest . , , TextField , « », spread (, {...rest} , ). rest , , TextField .

, : , FirstNameField TextField .

LastNameField :
:

 ... import FirstNameField from './FirstNameField'; import LastNameField from './LastNameField'; class SimpleForm extends React.Component { ... render() {   const { firstNameError, firstName, lastName, lastNameError } = this.state;   return (     <div style={style.form}>       <FirstNameField onChange={this.onFirstNameChange}                       onBlur={this.onFirstNameBlur}                       error={firstNameError} />       <LastNameField onChange={this.onLastNameChange}                       onBlur={this.onLastNameBlur}                       error={lastNameError} />       <Greetings firstName={firstName} lastName={lastName} />     </div>   ); } } 

.


, , , :

  • , , , .
  • . , , , , 1000 ( ).
  • , . , , .
  • , , , . . , , . , , , .
  • , , , , .
  • this , .

总结


, React- . , , , , . , , , React, , , , UI-.

→ , ,

亲爱的读者们! , , React-, .

Source: https://habr.com/ru/post/zh-CN428077/


All Articles