React-Tutorial学习笔记

1.My first component


We will have the following component structure:

-CommentBox
  -CommentList
    -Comment
  -CommentForm
//index.html
<div id="content"></div>
//tutorial1.js
var CommentBox = React.createClass({
  render:function(){
    return (
      <div className="commentBox">
        Hello , World ! 
      </div>
    );
  }
});
ReactDOM.render(
  <CommentBox />,
  document.getElementById('content')
);
//JSX syntax
// 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')
);

(1)Note that native HTMLelement names start with a lowercase letter , while customReactclass names begin with an uppercase letter.
译:HTML 标签以小写字母开头,而 React 组件 以大写字母开头。
(2)We pass some methods in a JavaScript object to React.creatClass()to create a new React component . The most important of these methods is called render which returns a tree of React components that will eventually render to HTML.
译:我们在JS 对象中传递一些方法到React.createClass()来创建一个新的React 组件。其中最重要的方法是 render ,他返回一个 React 组件树,最终将渲染到HTML
(3)The<div> tags are not actual DOM nodes; they are instantiations of React div components.You can think of these as markers or pieces of data that React knows how to handle.
(4)You do not have to return basic HTML. You can return a tree of components that you (or someone else) built. This is what makes React composable(可组合): a key tenet of maintainable frontends.(可维护前端的宗旨)
(5)ReactDOM.render() instantiates(实例化) the root component, starts the framework(框架), and injects( 注入、添加 ) the markup into a raw DOM element, provided as the second argument(参数).
(6)It is important that ReactDOM.render remain at the bottom of the script for this tutorial. ReactDOM.render should only be called after the composite components(复合组件) have been defined.

2.Composing components


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

Notice how we're mixing HTML tags and components we've built. HTML componentsare regular React components, just like the ones you define, with one difference. The JSX compiler will automatically rewrite HTML tags to React.createElement(tagName) expressions and leave everything else alone. ( JSX 编译器会自动将HTML标签重写为React.createElement(tagname)表达式,并留下其余的一切 )This is to prevent the pollution of the global namespace.(防止全局命名空间的污染)

3.Using props


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

(1)Data passed in from a parent component is available as a 'property' (属性)on the child component
These 'properties' are accessed through this.props.
(2)By surrounding a JavaScript expression in braces(大括号) inside JSX (as either an attribute or child), you can drop text or React components into the tree. We access named attributes passed to the componentas keys on this.props and any nested elements as this.props.children.(我们通过this.props上的键 或 任何像this.props.children的嵌套元素 访问传递给组件的命名属性)

4.Component Properties(组件属性)


// 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>
    );
  }
});

Note that we have passed some data from the parent CommentListcomponent to the child Commentcomponents.

5.Adding Markdown

// 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>
    );
  }
});

This is a special API that intentionally makes it difficult to insert raw HTML, but for remarkable we'll take advantage of this backdoor.
Remember: by using this feature you're relying on remarkable to be secure. In this case, remarkable automatically strips HTML markup and insecure links from the output.

6.Hook up the data model(挂钩数据模型)


So far we've been inserting the comments directly in the source code. Instead, let's render a blob of JSON data into the comment list. Eventually this will come from the server, but for now, write it in your source:

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

We need to get this data into CommentList in a modular way(模块化的方式). Modify(修改) CommentBox and theReactDOM.render() call to pass this data into theCommentList via(通过) props:

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

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

Now that the data is available in the CommentList, let's render the comments dynamically:

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>
    );
  }
});

7.Fetching from the server


Let's replace the hard-coded data with some dynamic data from the server. We will remove the data prop and replace it with a URL to fetch:

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

This component is different from the prior components because it will have to re-render itself.The component won't have any data until the request from the server comes back, at which point the component may need to render some new comments.
Note: the code will not be working at this step.

8.Reactive state


So far, based on its props, each component has rendered itself once.props are immutable(不可变): they are passed from the parent and are "owned" by the parent(props 从parent而来,归 parent 所有)

To implement (执行)interactions(互动), we introduce mutable(可变的) state to the component. this.state is private to the component and can be changed by calling this.setState(). When the state updates, the component re-renders itself.

render() methods are written declaratively as functions of this.props and this.state. The framework guarantees the UI is always consistent with the inputs.(框架保证UI总是与输入保持一致)

When the server fetches data(服务器获取数据), we will be changing the comment data we have. Let's add an array of comment data to theCommentBoxcomponent as its state:

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

getInitialState() executes exactly once during the lifecycle of the component and sets up the initial state of the component.(getInitialState()在组件的生命周期中执行一次,并且设置组件的初始状态)

9.Updating state


When the component is first created, we want to GET some JSON from the server and update the state to reflect the latest data. We're going to use jQuery to make an asynchronous request (异步请求)to the server we started earlier(之前启动的服务器) to fetch the data we need. The data is already included in the server you started (based on the comments.json file), so once it's fetched, this.state.data will look something like this:

//comments.json
[
   {"id" : "1" , "author" : "Pete Hunt" , "text" : "This is one comment" },
   {"id" : "2" , "author" : "Angela Ma" , "text" : "This is my comment" }
]
var  CommentBox  = React.createClass({
  getInitialState:function(){
    return{[data:data]};
  },
  
  componentDidMount:function(){
    $.ajax({
      url : this.props.url,
      dataType :'json',
      cache:false,
      success:function(data){
        this.setState({data:data});
      }.bind(this),
      error:function(xhr,status,err){
        console.error(this.props.url,status,err.toString());
      }.bind(this)
    });
  },

  render:function(){
    return(
      <div  className="commentBox">
        <h1>Comment</h1>
        <CommentList  data={this.state.data} />
        <CommentForm />
      </div>
    );
  }
  
});

Here, componentDidMount is a method called automatically by React after a component is rendered for the first time. (componentDidMount是组件第一次渲染后,由React自动调用的方法)
The key to dynamic updates is the call to this.setState().(动态更新的关键是 this.setState()的调用。) We replace the old array of comments with the new one from the server and the UI automatically updates itself.( 我们用服务器中的新数组替换旧数组的内容,UI自动更新自己。)
Because of this reactivity, it is only a minor change to add live updates.(由于这种反应性,添加实时更新只是很小的改变。)

var CommentBox=React.createClass({
          loadCommentsFromServer:function(){        //!!!!!
            $.ajax({
              url:this.props.url,
              dataType:'json',
              cache:false,
              success:function(data){
                this.setState({data:data});
              }.bind(this),
              error:function(xhr,status,err){
                console.error(this.props.url,status,err.toString());
              }.bind(this)
            });
          },
          getInitialState:function(){
            return {data:[]};
          },
          componentDidMount:function(){   // !!!!
            this.loadCommentsFromServer();
            setInterval(this.loadCommentsFromServer,this.props.pollInterval);
          },
          render:function(){
            return(
              <div className="commentBox">
                <h1>Comments</h1>
                <CommentList data={this.state.data}/>
                <CommentForm />
              </div>
            );
          }
        });

ReactDOM.render(
  <CommentBox url="/api/comments" pollInterval={2000} />,     //!!!
  document.getElementById('content')
);

All we have done is move the AJAX call to a separate method and call it when the component is first loaded and every 2 seconds after that.(我们所做的是把AJAX调用封装成单独的方法,并在组件第一次加载时调用它)

10.Adding new comments


Now it's time to build the form. Our CommentForm component should ask the user for their name and comment text and send a request to the server to save the comment.(向服务器发送请求来保存这些内容)

// tutorial15.js
var CommentForm = React.createClass({
  render: function() {
    return (
      <form className="commentForm">
        <input type="text" placeholder="Your name" />
        <input type="text" placeholder="Say something..." />
        <input type="submit" value="Post" />
      </form>
    );
  }
});

Controlled components(受控组件)

With the traditional DOM, input elements are rendered and the browser manages the state (its rendered value). As a result, the state of the actual DOM will differ from that of the component.(在传统的DOM中,input元素被渲染,浏览器管理状态(渲染值),因此,实际的DOM状态(输入值)与组件的状态(初始值)不同)。

In React, components should always represent the state of the view and not only at the point of initialization.(在``React`中,组件始终表示视图的状态,而不仅仅是初始值)

Hence, we will be using this.state to save the user's input as it is entered. We define an initial state with two properties author and text and set them to be empty strings.
In our<input> elements, we set the value prop to reflect the state of the component and attach onChange handlers to them.
These <input> elements with a value set are called controlled components.(具有value值的Input元素,称为受控组件)

// tutorial16.js
var CommentForm = React.createClass({
  getInitialState: function() {
    return {author: '', text: ''};
  },
  handleAuthorChange: function(e) {
    this.setState({author: e.target.value});
  },
  handleTextChange: function(e) {
    this.setState({text: e.target.value});
  },
  render: function() {
    return (
      <form className="commentForm">
        <input
          type="text"
          placeholder="Your name"
          value={this.state.author}
          onChange={this.handleAuthorChange}
        />
        <input
          type="text"
          placeholder="Say something..."
          value={this.state.text}
          onChange={this.handleTextChange}
        />
        <input type="submit" value="Post" />
      </form>
    );
  }
});

Events
React attaches event handlers to components using a camelCase naming convention.(驼峰命名法)
React.createClass(...) automatically binds each method to its component instance, obviating the need for explicit binding.(React.createClass(...)自动绑定每个方法到它的组件实例,避免了显式绑定的需要 。)

Submitting the form

To start, let's listen for the form's submit event and clear it.

// tutorial17.js
var CommentForm = React.createClass({
  getInitialState: function() {
    return {author: '', text: ''};
  },
  handleAuthorChange: function(e) {
    this.setState({author: e.target.value});
  },
  handleTextChange: function(e) {
    this.setState({text: e.target.value});
  },
  handleSubmit: function(e) {      //!!!
    e.preventDefault();
    var author = this.state.author.trim();
    var text = this.state.text.trim();
    if (!text || !author) {
      return;
    }
    // TODO: send request to the server
    this.setState({author: '', text: ''});
  },
  render: function() {
    return (
      <form className="commentForm" onSubmit={this.handleSubmit}>   //!!!
        <input
          type="text"
          placeholder="Your name"
          value={this.state.author}
          onChange={this.handleAuthorChange}
        />
        <input
          type="text"
          placeholder="Say something..."
          value={this.state.text}
          onChange={this.handleTextChange}
        />
        <input type="submit" value="Post" />
      </form>
    );
  }
});

Call preventDefault() on the event to prevent the browser's default action of submitting the form.(阻止浏览器提交表单的默认行为)

Callbacks as props
When a user submits a comment, we will need to refresh the list of comments to include the new one. It makes sense to do all of this logic in CommentBox since CommentBox owns the state that represents the list of comments.(在CommentBox中执行这个逻辑是有意义的,因为CommentBox持有评论列表的状态)

We need to pass data from the child component back up to its parent.
We do this in our parent's render method by passing a new callback (handleCommentSubmit) into the child(我们通过一个新的回调handleCommentSubmit传递给子组件), binding(绑定) it to the child's onCommentSubmit event.
Whenever the event is triggered, the callback will be invoked:(当事件被触发,回调将被调用)

// tutorial18.js
var CommentBox = React.createClass({
  loadCommentsFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      cache: false,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  handleCommentSubmit: function(comment) {    //!!!
    // TODO: submit to the server and refresh the list
  },
  getInitialState: function() {
    return {data: []};
  },
  componentDidMount: function() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);  
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm onCommentSubmit={this.handleCommentSubmit} />    //!!!
      </div>
    );
  }
});

Now that CommentBox has made the callback available to CommentForm via the onCommentSubmit prop, the CommentForm can call the callback when the user submits the form:

// tutorial19.js
var CommentForm = React.createClass({
  getInitialState: function() {
    return {author: '', text: ''};
  },
  handleAuthorChange: function(e) {
    this.setState({author: e.target.value});
  },
  handleTextChange: function(e) {
    this.setState({text: e.target.value});
  },
  handleSubmit: function(e) {
    e.preventDefault();
    var author = this.state.author.trim();
    var text = this.state.text.trim();
    if (!text || !author) {
      return;
    }
    this.props.onCommentSubmit({author: author, text: text});   //!!!
    this.setState({author: '', text: ''});
  },
  render: function() {
    return (
      <form className="commentForm" onSubmit={this.handleSubmit}>
        <input
          type="text"
          placeholder="Your name"
          value={this.state.author}
          onChange={this.handleAuthorChange}
        />
        <input
          type="text"
          placeholder="Say something..."
          value={this.state.text}
          onChange={this.handleTextChange}
        />
        <input type="submit" value="Post" />
      </form>
    );
  }
});

Now that the callbacks are in place, all we have to do is submit to the server and refresh the list:

var CommentBox = React.createClass({
  loadCommentsFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      cache: false,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  handleCommentSubmit: function(comment) {    //!!!
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      type: 'POST',
      data: comment,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {data: []};
  },
  componentDidMount: function() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm onCommentSubmit={this.handleCommentSubmit} />
      </div>
    );
  }
});

11.Optimization: optimistic updates(优化)

Our application is now feature complete but it feels slow to have to wait for the request to complete before your comment appears in the list. (功能齐全,但是评论出现在列表之前需要等待请求完成,所以感觉很慢)
We can optimistically add this comment to the list to make the app feel faster.(我们可以乐观地添加这个评论到列表,使应用程序感觉更快。)

// tutorial21.js
var CommentBox = React.createClass({
  loadCommentsFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      cache: false,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  handleCommentSubmit: function(comment) {
    var comments = this.state.data;    //!!!
    // Optimistically set an id on the new comment. It will be replaced by an
    // id generated by the server. In a production application you would likely
    // not use Date.now() for this and would have a more robust system in place.
    comment.id = Date.now();   //!给新的comment设置id,它将被服务器生成的id-Date.now()替换,希望之后不要使用Date.now()
    var newComments = comments.concat([comment]);   //!!!
    this.setState({data: newComments});   //!!!
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      type: 'POST',
      data: comment,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        this.setState({data: comments});    //!!!
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {data: []};
  },
  componentDidMount: function() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm onCommentSubmit={this.handleCommentSubmit} />
      </div>
    );
  }
});

Congrats!
We have just built a comment box in a few simple steps.

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

推荐阅读更多精彩内容