什么是React

React 是 Facebook 开发团队在 2013 年构建的 JavaScript 库,用于使用户界面更加模块化(或可重用)且更易于维护。根据 React 的网站,它用于"构建管理自身状态的封装组件,然后组合它们以生成复杂的 UIs"。

我要在这篇文章中使用很多 Facebook 的例子, 因为他们写 React 摆在首位。

还记得 Facebook 从喜欢转向React吗?现在,您可以用一颗心、笑脸或喜欢的帖子来回应,而不是仅仅喜欢帖子。如果这些React主要是在HTML中做出,那么改变所有这些喜欢的React并确保它们有效将是一项巨大的工作。

这就是 React 的用武之所——我们在第一天就对开发人员印象深刻,而不是实现对开发人员印象深刻的"关注点分离",而是在 React 中采用不同的体系结构,它基于组件结构增加模块化,而不是分离不同的编程语言。

今天,我们将将 CSS 分开,但如果您需要,甚至可以使该组件特定于该组件。

React与香草 Javascript

当我们谈论"香草"JavaScript时,我们通常谈论的是编写不使用其他库(如JQuery、React、Angular或Vue)的JavaScript代码。如果你想阅读更多关于这些和什么是框架,我有一个帖子所有关于Web框架。

开始之前几个快速笔记

  • 若要使本教程更加简洁一点,某些代码示例在它们之前或之后具有,这意味着省略了某些代码。...
  • 我在某些地方使用 Git diffs 来显示将更改的代码行,因此,如果您复制和粘贴,则需要删除行的开头。+
  • 我有完整的 CodePens 与每个部分的完成版本 - 所以您可以使用这些追赶。
  • 更高级的概念,不是必要的教程是块报价,这些大多只是事实,我认为是有趣的。

建立

如果要创建生产 React 应用程序,您将需要使用生成工具(如 Webpack)来捆绑代码,因为 React 会使用一些在浏览器中默认不起作用的模式。创建React应用程序对于这些目的非常有帮助,因为它会为你执行大部分配置。

现在,由于我们希望快速启动和运行,以便编写实际的 React 代码,我们将使用 React CDN,它仅用于开发目的。我们还将使用 Babel CDN,以便可以使用一些非标准的 JavaScript 功能(我们稍后将讨论更多)。

我还做了一个代用模板,你可以使用!

在一个完整的 React 项目中,我将我的组件拆分成不同的文件,但出于学习目的,我们将现在将 JavaScript 合并到一个文件中。

组件

对于本教程,我们将建立一个Facebook状态小部件,因为Facebook写了React摆在首位。

想想小部件在 Facebook 上显示多少个位置——你可以喜欢状态、链接帖子、视频帖子或图片。甚至一页!每次 Facebook 调整类似功能时, 他们都不想在所有这些地方这样做。所以,这就是组件的用向。网页的所有可重用部分都抽象为一个组件,可以一遍又一次地使用,我们只需在一个地方更改代码就可以更新它。like

让我们看看 Facebook 状态的图片,并分解其中的不同组件。

img

状态本身将是一个组件 - 在 Facebook 时间线中有很多状态,因此我们当然希望能够重用状态组件。

在该组件中,_我们将在父组件中_具有子组件或组件。这些组件也是可重用的,因此我们可以让像按钮组件一样是组件和组件的子组件。PhotoStatus``````LinkStatus

也许我们的子组件看起来像这样:

img

我们甚至可以在子组件内有子组件!因此,喜欢、评论和共享的组可能是它自己的组件,组件用于喜欢其中的评论和共享!ActionBar

img

有许多方法可以分解这些组件和子组件,具体取决于您将在应用程序中重用该功能的地点。

开始

我想开始本教程与React"你好世界" - 这是传统毕竟!然后,我们将转到稍微复杂的状态示例。

在我们的 HTML 文件中,让我们只添加一个元素 – 一个包含 ID 的元素。按照惯例,您通常会看到 div 的 ID"root",因为它将成为我们 React 应用程序的根。div

 <div id="root"></div>

如果要在 CodePen 模板中编写代码,可以直接在节中编写此 JavaScript。如果改为在计算机上编写此内容,则必须添加具有 类型的 脚本标记,因此:js``````text/jsx

 <script type="text/jsx"></script>

现在,让我们来了解一下我们的React代码!

class HelloWorld extends React.Component {
  render() {
    // Tells React what HTML code to render
    return <h1>Hello World</h1>
  }
}
// Tells React to attach the HelloWorld component to the 'root' HTML div
ReactDOM.render(<HelloWorld />, document.getElementById("root"))

发生的只是"你好世界"显示为页面上的H1!

让我们来了解一下这里发生了什么。

首先,我们使用从类继承的 ES6 类。这是一个模式,我们将用于我们的大多数React组件。React.Component

接下来,我们类中有一个方法,它是一种称为 的特殊方法。React 会寻找方法以决定在页面上呈现什么内容。这个名字有道理。无论从该方法返回什么,都将由该组件呈现。render``````render``````render

在这种情况下,我们将返回一个包含"Hello World"文本的 H1 ,这正是 HTML 文件中通常的内容。

最后,我们有:

 ReactDOM.render(<HelloWorld />, document.getElementById("root"))

我们使用 ReactDOM 功能将React组件附加到 DOM。

React 利用一种叫做虚拟 DOM 的东西,它是 DOM 的虚拟表示形式,您通常会在 Vanilla JavaScript 或 JQuery 中与之交互。这会将这个虚拟 DOM 呈现到实际的 DOM 中。 在场景后面, React 在界面上需要更改内容时, 会执行大量的工作来有效地编辑和重新呈现 DOM。reactDOM.render

我们的组件,看起来像一个HTML标签!此语法是_JSX 的一部分_,JSX 是 JavaScript 的扩展。您不能本机在浏览器中使用它。还记得我们如何使用巴别作为 JavaScript 吗?Babel 将转换(或转换)我们的JSX到常规JavaScript,以便浏览器能够理解它。<HelloWorld />

JSX 实际上是 React 中的可选选择,但在大多数情况下,您会看到它使用。

然后,我们使用 JavaScript 的内置功能来获取我们在 HTML 中创建的根元素。document.getElementById

总之,在此语句中,我们将我们的组件附加到我们在 HTML 文件中创建的组件。ReactDOM.render``````HelloWorld``````div

初学者代码

好了, 现在我们已经做了一个 “你好世界”, 我们可以开始我们的 Facebook 组件。

首先,我想让你玩这个演示。我们将在本教程的其余部分中对此进行处理。也请随意查看代码,但不要担心不理解它。这就是本教程的其余部分!

让我们从小部件的 HTML"硬编码"开始:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
  <div class="col-6 offset-3">
    <div class="card">
      <div class="card-block">
        <div class="row">
          <div class="col-2">
            <img src="https://zen-of-programming.com/react-intro/selfiesquare.jpg" class="profile-pic">
          </div>
          <div class="col-10 profile-row">
            <div class="row">
              <a href="#">The Zen of Programming</a>
            </div>
            <div class="row">
              <small class="post-time">10 mins</small>
            </div>
          </div>
        </div>
        <p>Hello World!</p>
        <div>
          <span class="fa-stack fa-sm">
            <i class="fa fa-circle fa-stack-2x blue-icon"></i>
            <i class="fa fa-thumbs-up fa-stack-1x fa-inverse"></i>
          </span>
        </div>
        <div>
          <hr class="remove-margin">
          <div>
            <button type="button" class="btn no-outline btn-secondary">
              <i class="fa fa-thumbs-o-up fa-4 align-middle" aria-hidden="true"></i>
              &nbsp;
              <span class="align-middle">Like</span>
            </button>
          </div>
        </div>
      </div>
      <div class="card-footer text-muted">
        <textarea class="form-control" placeholder="Write a comment..."></textarea>
        <small>120 Remaining</small>
      </div>
    </div>
  </div>

对于一些添加的 CSS,如下所示:

img

下面是一个包含完整入门代码的 Codepen。

为了本教程,我们将创建四个组件:一个组件将成为父组件,一个包含喜欢逻辑的组件,以及包含用于键入注释的逻辑的组件。当您切换"喜欢"按钮时,组件还将有一个子级,该子项将显示或隐藏。Status``````Like``````Comment``````Like``````LikeIcon

组件架构

让我们继续将我们编写的 HTML 代码划分到这些组件中。

我们将从组件的外壳开始,我们将呈现它,以确保它的工作!

  class Status extends React.Component {
  render() {
    return (

      <div className="col-6 offset-3">
        <div className="card">
          <div className="card-block">
            <div className="row">
              <div className="col-10 profile-row">
                <div className="row">
                  <a href="#">The Zen of Programming</a>
                </div>
                <div class="row">
                  <small className="post-time">10 mins</small>
                </div>
              </div>
            </div>
          </div>
          <p>Hello world!</p>
          <div className="card-footer text-muted" />
        </div>
      </div>
   )
  }
}

ReactDOM.render(<Status />, document.getElementById("root"))

关于上述一个有趣的注意,是我们必须将"类"属性更改为"类名"。类在 JavaScript 中已经意味着一些东西 — — 它用于 es6 类!某些属性在 JSX 中的命名方式与在 HTML 中命名的方式不同。

我们还可以删除 HTML 的内容,只留下一个带 ID 根的元素 —— 父级"内容"div 只是为了样式化。


 <body>
 <div class="content">
   <div id="root"></div>
 </div>
</body>

下面是"状态"组件中的 HTML。请注意,一些原始 HTML 尚未存在 - 它将进入我们的子组件。

让我们创建第二个组件,然后将它包括在组件中。Status

 class Comment extends React.Component {
 render() {
   return (
     <div>
       <textarea className="form-control" placeholder="Write a comment..." />
       <small>140 Remaining</small>
     </div>
​    )
 }
}

以下是我们评论的组件。它只是有我们的输入, 和文本与多少字符, 我们剩余。请注意,两者都包装在 中 ,这是因为 React 要求我们将组件的所有内容包装在一个 HTML 标记中 – 如果我们没有父级,我们将返回 一个和标记。textarea``````div``````div``````textarea``````small

因此,现在我们需要将这个组件包含在我们的组件中,因为它将是我们的子组件。我们可以使用我们用来呈现状态组件的相同 JSX 语法来做到这一点。Status

 class Status extends React.Component {
 render() {
   return (
     <div className="col-6 offset-3">
       <div className="card">
         <div className="card-block">
           <div className="row">
             <div className="col-10 profile-row">
               <div className="row">
                 <a href="#">The Zen of Programming</a>
               </div>
               <div className="row">
                 <small className="post-time">10 mins</small>
               </div>
             </div>
           </div>
         </div>
         <div className="card-footer text-muted">
+           <Comment />
         </div>
       </div>
     </div>
   )
 }
         }

好了, 现在我们只需要做同样的喜欢!

 class LikeIcon extends React.Component {
 render() {
   return (
     <div>
       <span className="fa-stack fa-sm">
         <i className="fa fa-circle fa-stack-2x blue-icon" />
         <i className="fa fa-thumbs-up fa-stack-1x fa-inverse" />
       </span>
     </div>
   )
 }
}

class Like extends React.Component {
 render() {
   return (
     <div>
       {/* Include the LikeIcon subcomponent within the Like component*/}
       <LikeIcon />
       <hr />
       <div>
         <button type="button">
           <i
             className="fa fa-thumbs-o-up fa-4 align-middle"
             aria-hidden="true"
           />
           &nbsp;
           <span className="align-middle">Like</span>
         </button>
       </div>
     </div>
   )
 }
}

然后,我们需要包括在我们的原始组件!Status

 class Status extends React.Component {
 render() {
   return (
     <div className="col-6 offset-3">
       <div className="card">
         <div className="card-block">
           <div className="row">
             <div className="col-10 profile-row">
               <div className="row">
                 <a href="#">The Zen of Programming</a>
               </div>
               <div className="row">
                 <small className="post-time">10 mins</small>
               </div>
             </div>
           </div>
+           <Like />
         </div>
         <div className="card-footer text-muted">
           <Comment />
         </div>
       </div>
     </div>
   )
 }
         }

酷, 现在我们已经React化了我们原来的 Html, 但它仍然什么都不做!我们开始修复它吧!

总之,本节中的代码将看起来像此CodePen!

州和道

我们有两个不同的用户交互,我们要实现:

  • 我们希望只有在按下"喜欢"按钮时才显示"喜欢"图标
  • 我们希望剩余字符数作为人减少

让我们开始研究这些吧!

道具

假设我们希望我们的注释框允许不同位置的字母数量不同。例如,在状态上,我们希望允许用户编写 200 个字母长的响应。但是,在图片上,我们只希望他们能够编写 100 个字符的响应。

React 允许我们从组件和组件传递道具(属性的短),以指定我们想要在响应中允许的字母数,而不是具有两个不同的注释组件。PictureStatus``````Status

道具的语法如下所示:

 <Comment maxLetters={20} />
<Comment text='hello world' />
<Comment show={false} />

var test = 'hello world'
<Comment text={test} />

道具看起来像 HTML 属性!如果通过道具传递字符串,则不需要括号,但任何其他数据类型或变量都需要在括号内。

然后,在我们的组件中,我们可以使用我们的道具:

console.log(this.props.maxLetters)

它们捆绑在实例的属性中,以便可以使用 访问它们。props``````this.props.myPropName

因此,让我们更改硬编码的 140 个字符,以便于在组件外部更改。

首先,我们将更改状态组件中实例化注释组件的地点(请注意省略了一些代码!

 class Status extends React.Component {
       ...
         <div className="card-footer text-muted">
+            <Comment maxLetters={280} />
         </div>
       </div>
     </div>
   )
 }
         }

然后,我们将更改注释组件中的硬编码 140 个字符的限制。

 class Comment extends React.Component {
 ...
       <div>
       <textarea className="form-control" placeholder="Write a comment..." />
+       <small>{this.props.maxLetters} Remaining</small>
     </div>
 ...
     }

状态

我们从组件传递到组件的道具永远不会_在子_组件内更改 —— 它们可以在父组件内更改,但在子组件内可以更改。但是 –很多时候,我们将具有在组件生命周期内想要更改的属性。例如,我们希望记录用户在文本区域中键入的字符数,并且我们希望跟踪状态是否"喜欢"。我们将存储那些要在其状态的组件中更改_的属性_。

你会注意到 React 中的很多不变性 —— 它受到功能范式的影响很大,因此也不鼓励副作用。

我们希望每当我们创建组件的新实例时都会创建此状态,因此我们将使用 ES6 类构造函数来创建它。如果你想快速刷新ES6类MDN是一个伟大的资源。

State 将是具有我们想要包含的任何键值对的对象。在这种情况下,我们需要一个字符计数的用户键入的字符数。我们现在将设置为零。

 class Comment extends React.Component {
 constructor () {
   super()
   this.state = {
     characterCount: 0
   }
 }
 ...

现在,让我们从道具中减去它,所以我们总是知道我们还剩多少个字符!maxLetters

 <small>{this.props.maxLetters - this.state.characterCount} Remaining</small>

如果增加 ,则剩余的显示字符将减少。characterCount

但是 - 键入时没有任何React。我们永远不会改变 的值。我们需要向 添加一个事件处理程序,以便我们更改用户类型时。characterCount``````textarea``````characterCount

事件处理程序

过去编写 JavaScript 时,您可能已编写事件处理程序以与用户输入进行交互。我们将在 React 中做同样的事情,语法只是会有点不同。

我们将向 中添加一个处理程序。在其中,我们将对事件处理方法进行引用,该方法将在 每次用户在 中输入时运行。onChange``````textarea``````textarea

  <textarea className="form-control" placeholder="Write a comment..." onChange={this.handleChange}/>

现在我们需要创建一个方法:handleChange

 class Comment extends React.Component {
 constructor () {
   super()
   this.state = {
     characterCount: 0
   }
 }

 handleChange (event) {
   console.log(event.target.value)
 }
...

现在,我们只是-ing - 这将工作的方式与它在无ReactJavaScript中的工作方式相同(不过,如果你深入一点,事件对象就有点不同了)。如果你看看那个控制台, 我们正在打印出我们在文本框中键入的内容!console.log``````event.target.value

现在我们需要更新处于状态的属性。在 React 中_,我们从不直接_修改状态 ,因此我们不能做类似的事情: 。相反,我们需要使用该方法。characterCount``````this.state.characterCount = event.target.value.length``````this.setState

  handleChange (event) {
  this.setState({
    characterCount: event.target.value.length
  })
}

但!您会收到一个错误 – “未捕获的 TypeRror: this.setState 不是函数”。此错误告诉我们,需要在事件处理程序中保留 es6 类的上下文。我们可以通过绑定到构造函数中的方法来做到这一点。如果你想阅读更多关于这个,这里有一篇好文章this

 class Comment extends React.Component {
 constructor () {
   super()    
   this.handleChange = this.handleChange.bind(this)
...

好!我们快到了!我们只需要添加切换显示的能力。like

我们需要向组件添加一个构造函数。在该构造函数中,我们需要实例化组件的状态。组件生命周期内将发生的变化是状态是否被喜欢。Like

 class Like extends React.Component {
 constructor() {
   super()

   this.state = {
     liked: false
   }
 }
 ...

现在我们需要添加一个事件处理程序来更改状态是否被喜欢。

 class Like extends React.Component {
 constructor() {
   super()

   this.state = {
     liked: false
   }
   
   this.toggleLike = this.toggleLike.bind(this)
 }

 toggleLike () {
   this.setState(previousState => ({
     liked: !previousState.liked
   }))
 }
...

这里的区别在于,接收参数的回调函数– .正如您可能从参数的名称中猜到的,这是调用之前的状态值。 是异步的,所以我们不能依赖于在它里面使用。this.setState``````previousState``````this.setState``````setState``````this.state.liked

现在,我们需要:

a) 每当用户单击"喜欢"按钮时调用事件处理程序: b) 仅在为 true 时显示 Likeiconliked

  render() {
  return (
    <div>
      {/* Use boolean logic to only render the LikeIcon if liked is true */}
+       {this.state.liked && <LikeIcon />}
      <hr />
      <div>
+          <button type="button" className="btn no-outline btn-secondary" onClick={this.toggleLike}>
          <i
            className="fa fa-thumbs-o-up fa-4 align-middle"
            aria-hidden="true"
          />
          &nbsp;
          <span className="align-middle">Like</span>
        </button>
      </div>
    </div>
  )
}

棒!现在,我们所有的功能都到位了。

奖励:功能组件

如果你觉得你已经在你的头上,请随时跳过这部分,但我想使一个快速重构这个项目。如果我们创建没有与其关联的状态的组件(我们称之为无状态组件),我们可以将组件转换为函数而不是 ES6 类。

在这种情况下,我们可以看起来像这样:LikeIcon

 const LikeIcon = () => {
 return (
   <div>
     <span className="fa-stack fa-sm">
       <i className="fa fa-circle fa-stack-2x blue-icon" />
       <i className="fa fa-thumbs-up fa-stack-1x fa-inverse" />
     </span>
   </div>
 )
}

我们只是返回组件的 UI,而不是使用 方法。render

下面是实现此重构的 CodePen。

备忘单

我喜欢备忘单, 所以我做了一个从这篇文章的内容!

img

您也可以在这里下载它作为PDF!

步骤

回顾一下,我们讨论了组件体系结构、基本 React 语法和 JSX、状态和道具、事件处理程序和功能组件。

如果您想查看本教程中的所有 CodePens,下面是个集合!

如果您想尝试从本教程中扩展代码,我建议将赞更改为React或创建一个照片组件,以重用我们制作的某些组件!