教程-Tutorial

我们将建造一个简单但是实用的评论框,你可以把它用在博客或者作为一个基础版的实时评论组件,比如淘宝、微博。

我们将提供:

  • 所有评论的查看
  • 一个提供评论功能的表单
  • 将你定制的后端与之关联

它还有一些优雅的特性

  • 积极的评论:提交之后的评论会马上出现在评论列表,因此它看起来很快。
  • 动态更新:其它用户的评论将会实时出现。
  • Markdown 支持:用户可用MarkDown语法给自己的评论排版。

想跳过所有步骤仅仅查看代码?

都在 GitHub 上

启动一个服务器

为了开始我们的教程,我们需要一个运行中的服务器。它仅仅提供一个用来获取和提交数据的 API 端点服务。为了做起来更加简单,我们用简单的脚本语言来搭建服务。你可以查看源码或者下载压缩文件来包含一切我们需要的环境并开始我们的教程。

开始搭建

我们致力于让本教程变得相对简单,在我们上文提到的服务器的包中,包含着一个 HTML 文件,我们将用它来开始工作。在你喜欢的编辑器中打开 public/index.html,它看起来应该像下面这样:

<!-- index.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>React Tutorial</title>
    <script src="https://npmcdn.com/react@15.3.1/dist/react.js"></script>
    <script src="https://npmcdn.com/react-dom@15.3.1/dist/react-dom.js"></script>
    <script src="https://npmcdn.com/babel-core@5.8.38/browser.min.js"></script>
    <script src="https://npmcdn.com/jquery@3.1.0/dist/jquery.min.js"></script>
    <script src="https://npmcdn.com/remarkable@1.6.2/dist/remarkable.min.js"></script>
  </head>
  <body>
    <div id="content"></div>
    <script type="text/babel" src="scripts/example.js"></script>
    <script type="text/babel">
      // To get started with this tutorial running your own code, simply remove
      // the script tag loading scripts/example.js and start writing code here.
    </script>
  </body>
</html>

在接下来的教程中,我们将在 script 标签中编写JavaScript代码,我们没有任何先进的动态加载功能因此你只需在保存后刷新你的浏览器就可以看到你的更新。通过在浏览器中打开http://localhost:3000来跟进你的进度(在启动服务器之后)。当你第一次加载这个文件并且没有做任何改动时,你将会看到我们要打造功能的最终样子。当你准备好接下来的教程了,只需要删除<script>标签。

备注:
我们包含进了JQuery库因为我们想要简化将来用到的AJAX代码,但是它并不是强制地为React工作。

你的第一个组件

React 是模块化的,由组件构成的。拿我们的评论框举例,我们将会有如下的几个组件:

- CommentBox
  - CommentList
    - Comment
  - CommentForm

让我们建造一个CommentBox组件,它仅仅是一个简单的<div>

// tutorial1.js
var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        Hello, world! I am a CommentBox.
      </div>
    );
  }
});
ReactDOM.render(
  <CommentBox />,
  document.getElementById('content')
);

注意到原生的 HTML 元素采用小驼峰命名法,同时定制的 React 类名采用大驼峰命名法。

JSX 语法

你第一个注意到的会是 XML 语法联合在你的JavaScript 中。我们有一个前置编译器来将这种语法糖转化为朴素的JS语法:

// tutorial1-raw.js
var CommentBox = React.createClass({displayName: 'CommentBox',
  render: function() {
    return (
      React.createElement('div', {className: "commentBox"},
        "Hello, world! I am a CommentBox."
      )
    );
  }
});
ReactDOM.render(
  React.createElement(CommentBox, null),
  document.getElementById('content')
);

语法的选择是可选的但我们发现JSX语法比朴素的JS语法更加好用。在JSX 语法文章中阅读更多。

接下来做什么

我们通过在JS类中的一些方法如 React.createClass()来创建一个 React 组件。最重要的方法是调用 render(),该方法返回一个组件树,该树用来渲染成HTML。
<div>标签并不是真实的 DOM 节点,他们是实例化的 React div组件。你可以把它们想象成React用来处理数据的标记。React 是安全的,我们不生成 HTML 字符串,所以默认有 XSS 跨站脚本攻击的防护。
你不用返回一个基本的 HTML。你可以返回一个自己或他人的组件树。这就是React的组成:前端可维护的信条。
ReactDOM.render() 实例化根组件,启动框架,把标记映射成新的DOM 元素。
重要的是ReactDOM.render放在脚本的最底部,ReactDOM.render应该确保等所有组件被初始化完毕再调用。

拼装组件

让我们创建CommentListCommentForm的骨骼,同样采用简单的<div>,添加这两个组件到你的文件中,保持CommentBox的定义和ReactDOM.render的调用:

// tutorial2.js
var CommentList = React.createClass({
  render: function() {
    return (
      <div className="commentList">
        Hello, world! I am a CommentList.
      </div>
    );
  }
});

var CommentForm = React.createClass({
  render: function() {
    return (
      <div className="commentForm">
        Hello, world! I am a CommentForm.
      </div>
    );
  }
});

接下来,更新CommentBox来使用新的组件:

// tutorial3.js
var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList />
        <CommentForm />
      </div>
    );
  }
});

注意我们是怎么把HTML 标签和我们自定义的组件混用的。HTML 组件是正规的 React 组件,就像你自己定义的一样,有一点不同的是。JSX编译器会自动地把HTML标签替换成React.createElement(tagName)表达式并且把它们单独放置。这样可以避免全局命名空间内的垃圾数据。

使用属性

让我们创建 Comment组件,它依靠父组件传递给他的数据。从父组件传递过来的数据通过属性的方式传递给子组件。这些属性通过this.props访问。通过属性,我们可以读取通过CommentList传递给Comment的数据,并实施一些标记:

// tutorial4.js
var Comment = React.createClass({
  render: function() {
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        {this.props.children}
      </div>
    );
  }
});

花括号中的JS表达式被嵌套在JSX中,你可以把文本或者React组件放入树中,我们通过命名的属性访问传递过来的数据,并且访问元素中的数据。

组件的属性

既然我们定义了Comment属性,我们希望传递一些属性给它。这可以允许我们对代码进行复用。现在,让我们给CommentList中加一些评论。

// tutorial5.js
var CommentList = React.createClass({
  render: function() {
    return (
      <div className="commentList">
        <Comment author="Pete Hunt">This is one comment</Comment>
        <Comment author="Jordan Walke">This is *another* comment</Comment>
      </div>
    );
  }
});

注意,我们通过CommentList组件传递一些数据给了Comment组件。比如,我们传递了 Pete HuntThis is one commentComment。如上文那样,Comment组件通过this.props.authorthis.props.children来访问这些属性

添加Markdown 支持

Markdown是格式化你的文本的一种简单的方法。比如,用幸好环绕文本会让它加重。
在本教程中,我们使用第三发的库remarkable来实现。在初始的页面中,我们已经包含了这个库,所以我们可以直接使用它。

  // tutorial6.js
var Comment = React.createClass({
  render: function() {
    var md = new Remarkable();
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        {md.render(this.props.children.toString())}
      </div>
    );
  }
});

我们在这里做的事情就是调用 remarkable 库,我们需要把 this.props.children 的React的文字换行格式转化成未加工的字符串以使 remarkable 很好的工作。
问题出现了,我们的加工过的字符串成了这样子<p>This is <em>another</em> comment</p>,我们希望这些标签编程HTML。
这是因为React为了防止XSS 攻击所做的工作。有一种方法摆脱这种防护,但是框架会警告你最好不要这么做。

// tutorial7.js
var Comment = React.createClass({
  rawMarkup: function() {
    var md = new Remarkable();
    var rawMarkup = md.render(this.props.children.toString());
    return { __html: rawMarkup };
  },

  render: function() {
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        <span dangerouslySetInnerHTML={this.rawMarkup()} />
      </div>
    );
  }
});

这是一个特殊的API 故意使插入一个未加工的HTML变得困难,但是为了使用 remarkable 我们可以利用这个后门。

联系数据模型

目前为止,我们直接在源代码中插入了评论。现在,让我们用json格式的数据来填充评论列表。事实上这些数据将来自服务器,但是现在我们先把它写入源码中。

// tutorial8.js
var data = [
  {id: 1, author: "Pete Hunt", text: "This is one comment"},
  {id: 2, author: "Jordan Walke", text: "This is *another* comment"}
];

我们需要以一种模块化的方式将这个数据传入到 CommentList。修改 CommentBox 和 ReactDOM.render() 方法,以便于通过 props 传入数据到 CommentList:

// tutorial9.js
var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.props.data} />
        <CommentForm />
      </div>
    );
  }
});

ReactDOM.render(
  <CommentBox data={data} />,
  document.getElementById('content')
);

既然现在数据在 CommentList 中可用了,让我们动态地渲染评论:

// tutorial10.js
var CommentList = React.createClass({
  render: function() {
    var commentNodes = this.props.data.map(function(comment) {
      return (
        <Comment author={comment.author} key={comment.id}>
          {comment.text}
        </Comment>
      );
    });
    return (
      <div className="commentList">
        {commentNodes}
      </div>
    );
  }
});

就是这样!

从服务器获取数据

让我们用一些来自服务器的动态数据替换硬编码的数据。我们将移除数据的prop,用获取数据的URL来替换它:

// tutorial11.js
ReactDOM.render(
  <CommentBox url="/api/comments" />,
  document.getElementById('content')
);

这个组件不同于和前面的组件,因为它必须重新渲染自己。该组件将不会有任何数据,直到请求从服务器返回,此时该组件或许需要渲染一些新的评论。

注意: 此代码在这个阶段不会工作。

Reactive state

迄今为止,基于它自己的props,每个组件都渲染了自己一次。props 是不可变的:它们从父级传来并被父级“拥有”。为了实现交互,我们给组件引进了可变的 state。this.state 是组件私有的,可以通过调用 this.setState() 改变它。每当state更新,组件就重新渲染自己。

render() 方法被声明为一个带有 this.props 和 this.state 的函数。框架保证了 UI 总是与输入一致。

当服务器获取数据时,我们将会改变我们已有的评论数据。让我们给 CommentBox 组件添加一组评论数据作为它的状态:

// tutorial12.js
var CommentBox = React.createClass({
  getInitialState: function() {
    return {data: []};
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm />
      </div>
    );
  }
});

getInitialState() 在生命周期里只执行一次,并设置组件的初始状态。

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

推荐阅读更多精彩内容

  • 按照惯例,先给ReactJS背书 React是一个Facebook开发的UI库,于2013年5月开源,并迅速的从最...
    艾伦先生阅读 3,175评论 1 12
  • 关于JSX 考虑这样一段代码:const element = Hello, world! ;这段代码既不是字符串...
    带三本书阅读 348评论 0 1
  • 深入JSX date:20170412笔记原文其实JSX是React.createElement(componen...
    gaoer1938阅读 7,981评论 2 35
  • 突然记起来高三时候几乎每天晚上都要去吃校门口对面的麻辣烫,永远不会忘记第一次点的时候点了18块,这别的女生最多...
    写文的诗琪阅读 200评论 0 0
  • 学校老师有要求,假期里要看86版《西游记》。让儿子在网络电视中几个客户端中寻找,找不到这部,只能在台脑中看...
    李梓荣妈妈阅读 271评论 0 0