React-建立实时评论应用

来源:互联网 发布:php缓存技术有哪些 编辑:程序博客网 时间:2024/06/06 05:58

React入门学习

此文章仅供学习交流,本人水平有限,建议点击标题学习原文。

按照教程下载文件,运行服务器文件。开始。
所有代码编写,完善在my_copy.js文件中。在index.html中引用该文件<script type="text/babel" src="scripts/my_copy.js"></script>
打开服务,localhost:3000浏览器中运行页面。


First component

React is all about modular,composable components.
for comment box example,we’ll have the following conponents structure:
做一个实时添加评论的应用,应用组件结构如下:

  • CommentBox
    • ConmmentList
      • Comment
    • CommentForm

Let’s build the CommentBox component,which is just a simple

:

//tutorial1.jsvar 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 Syntax

你可能注意到了以上代码中的XML语法,我们有一个简单的预编译器把它翻译成了普通的JavaScript。

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

这种做法是供你选择的,但是相比原生的JavaScript我们发现JSX语法更简单一些。可以这里学习更多JSX语法。

what’s going on

翻译:我们传了一个包含一些方法的JavaScript对象给React.createClass(),这样我们能创建一个React组件。那些方法里中最重要的是render,这个方法返回一个React组件的树,而这棵树最终将呈现在HTML上。

附一个render的翻译
You can use render with an adjective that describes a particular state to say that someone or something is changed into that state. For example, if someone or something makes a thing harmless, you can say that they render it harmless.

你看到的那个div标签事实上不是真正的DOM元素节点,而是React的div组件的一个实例。最终render的时候React知道怎么把它处理成一个真正的DOM元素。React并没有直接产生HTML字符串所以不用担心XSS,XSS保护是天然的。

你不必(在render函数中)返回基本的HTML元素,你可以返回React组件树(最终将会被处理成标准的DOM树呈现)。这就是为什么说React是可组装的,也是可维护前端的一个关键。

ReactDOM.render()有两个参数,第一个是整个应用的入口组件,第二个是一个原生的DOM元素。这个函数用来,初始化整个应用的根组件,是整个应用的入口,并将这个跟组件注入到一个原生的DOM元素中,即第二个参数。

The ReactDom module exposes DOM-specific methods,while React has the core tools shared by React on different platforms(e.g.,React Native)(不懂,不会翻译,大概是说ReactDOM是专门用来操作DOM的)

ReactDOM.render()留在最后一行非常有必要,因为在render之前必须保证所需要的组件已经完成。同理,在一个组件中调用其他组件时也要保证调用的组件存在,即组件之间存在依赖关系。

撰写组件

现在在构建CommentListCommentForm组件。先给它们一个骨架,暂时用div填充。把它们添加在js文件中后面再继续完善。

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

现在,更新CommentBox组件,调用新创建的两个组件。

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

注意我们在混用原生DOM元素和自定义的React组件,事实上HTML组件被视为一般的React组件,所以你可以把DOM元素当成构建的React组件来使用。有一个区别:JSX编译器会将原生HTML元素会被重写成React.createElement(tagName)(事实上我认为程序员层面看不到区别)

The JSX compiler will automatically rewrite HTML tags to React.createElement(tagName) expressions and leave everything else alone. This is to prevent the pollution of the global namespace

使用props

接下来构建最深层的comment组件,代表着一条条评论。它依存在来自父类的数据上,而这些来自父类的数组被视为子类的一个属性(有点像遗产),用this.props来访问。我们可以通过props属性来读取来自CommentList的数据,并render出来。

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

在JSX中用花括号把JavaScript表达式圈起来(无论是属性或者是子节点),就可以文本或者React组件添加到组件树中。可以用this.props.namedAttr来访问命名属性(namAttr),用this.props.children访问所有的(嵌套)子节点。

添加属性(props)

我们将会在comment组件中添加两个属性,author和text。
现在先让我们在commentList中添加两个评论:

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

我们在父组件CommentList中编码,传递了一些数据给子组件Comment。(命名属性author和子节点text)。这样Comment就可以使用this.props.authorthis.props.children分别获取。
现在刷新页面,可以看到两条评论。

添加markdone语法

markdone是一种很简单便捷的方式来格式化你的字符串。在这篇教程中我们使用了第三方markdone库来把字符串转换为原生HTML。来,使用markdone。
<script src="https://unpkg.com/remarkable@1.7.1/dist/remarkable.min.js"></script>

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

可惜这样是不行的(咋不上天),刷新页面会看到,评论变成了这样:<p>This is <em>another</em> comment</p>,我们想要的是那些标签变成元素样子。这其实不是没有道理的,这样是为了防止XSS攻击(尤其是脚本注入)。有一个方法可以绕过去,但是框架并不建议你使用。我们改写 Comment组件

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

dangerouslySetInnerHTML,这个API是故意把插入原生HTML设置的这么困难,防止XSS。但是如果是remarkable,我们可以使用这个后门。因为remarkable会自动将HTML和不安全的链接脱掉或者是什么的(这块不太懂)

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.

现在再次刷新页面,就可以看到效果了。

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

之前我们直接把评论数据写在代码里,现在我们试一试json数据对象类型(blob of JSON data)。最终这个数据将来自服务器,不过现在我们写在代码里。

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

最终我们要在comment组件中使用这些数据。首先我们通过把数据传进CommentBox中,commentBox组件把它传进commentList中,在CommentList中遍历构造comment组件,然后render出来。下面是修改的代码。

修改CommentBox和ReactDOM.render()

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中遍历data,创建Comment组件。
在render函数中创建了commentNodes用以保存遍历数据产生的Comment树。

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

虽然页面没有什么变化,可是我们已经使用了json对象来处理数据。现在我们更进一步,来从服务器中动态获取数据。

从服务器中获取数据

首先修改ReactDOM.render(),把从data中获取数据改为从url文件中获取。给CommentBox添加url属性。

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

这个应用会在发到服务器的请求(url)返回时重新绘制(re-render)。

Reactive state

到现在为止,所有的组件会根据自己的props绘制一次(render)。props是不变的,从父类处定义的。为了实现互动(动态显示),我们引入了可变的state属性,可以通过this.state来访问,也可以通过this.setState()来修改state。state是当前组件的私有属性,当 state变化时,组件会re-render(重新绘制)自己。

render() methods are written declaratively as functions of this.props and this.state. The framework guarantees the UI is always consistent with the inputs.
(大概是说render函数跟this.props和this.state有很大关系,剩下那句就不知道怎么翻译*

当请求返回数据时,我们需要更新我们的评论数据。我们在CommentBox组件里添加一个评论数组作为它的state,这样评论数据变化的时候,我们就能做一些处理(state变化的时候会re-render)。

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

getInitialState函数在组件的生命周期里只会执行一次,用来初始化组件的state,在这里我们将state初始化为一个对象,内包含一个空的data数组。

更新state

当我们第一次在浏览器中加载所有组件时,我们向服务器发送一个get请求获取之前已经有的数据进行显示。我们使用jQuery做一个异步请求,从服务器获取数据。
更新CommentBox。

var CommentBox=React.createClass({  getInitialState:function(){    return{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>Comments</h1>        <CommentList data={this.state.data} />        <CommentForm />      </div>    );  }});

componentDidMount函数会在组件第一次加载完成后自动执行,该函数通过jQuery发送一个url请求,成功后更新状态,将返回的数据赋给state的data。后面加一个bind(this)可以理解为被绑定函数里的this都是外部(组件)。
动态更新的关键就在于this.setState({data:;data}),前面说过state变化时会发生重新绘制。而重绘时数据已经更新。正是由于这个动态性,我们只要加一个定时的获取数据,UI便可以动态显示数据。
更新CommentBox组件。

//CommentBoxvar CommentBox=React.createClass({  getInitialState:function(){    return{data:[]};  },  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)    });  },  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'));

我们将AJAX(获取数据更新state)单独封装成一个函数,在CommentBox第一个加载后执行一次,之后每两秒执行一次。现在改变comments.js文件,两秒内会在网页上看到变化。

Build CommentForm

现在来创建CommentForm组件,这个组件要求用户输入姓名和评论,提交后显示在页面上并发送一个以将新数据保存在服务器端。

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

现在网页下方已经有了两个输入框。
下面我们为这两个input元素添加onchange事件,以便掌控整个组件的各个部分的随时状态。(这段看不太懂,大概这么个意思)

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. This is not ideal as the state of the view will differ from that of the component. In React, components should always represent the state of the view and not only at the point of initialization.

//CommentFormvar 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.author}          onChange={this.handleTextChange}//这条是为了将输入反映在输入框中,state会自动更新        />        {/*<input type="text" placeholder="Your name" />*/}        {/*<input type="text" placeholder="say something" />*/}        <input type="submit" value="Post" />      </form>    );  }});

我们为CommentForm添加getInitialState函数,初始化state属性author和text为空字符串,为两个输入框添加onChange函数,当输入框值改变时,重新设置this.state(author或者text),这里需要注意的是只要输入框值改变(而不是失去焦点)就会触发onChange函数,哪怕你敲了一个字符。onchange函数被触发后state改变就会重绘,在input中将的value设置为state(author或者text)也会改变,所以实时显示输入。

Event

React添加事件命名规范为小驼峰命名。
可能有读者发现,onchange事件的this并没有明确指定是谁,似乎是绑定在input元素上的,React.createClass会自动把每个方法都绑定在它的实例上,避免显示绑定。即是{this}指的都是这个组件。

提交表单

让表单与服务器进行交互。当用户提交表单的时候,首先清楚表单,然后分析数据并重新刷新评论列表。继续修改CommentForm。

//CommentFormvar CommentForm=React.createClass({  getInitialState:function(){    return{author:'',text:''}  },  handleAuthorChange:function(e){    // alert(e.target.value);    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(!author||!text){      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}//这条是为了将输入反映在输入框中,state会自动更新          onChange={this.handleTextChange}//下面这两种写法行不行        />        <input type="submit" value="Post" />      </form>    );  }});

我们绑定了一个onsubmit函数到标签,当数据有效时我们请求到服务器。
preventDefault是为了取消表单提交的默认行为,我们并没有为表单提交做任何努力,不是吗?

回调函数属性(Callbacks as props)

当用户提交一条评论时,我们需要更新评论列表去包含这条新评论。我们将在CommentBox中更新,
因为CommentBox有评论列表组件(CommentList)。
那么我们怎么在CommentBox中获得CommetForm中的提交的数据呢。我们可以在CommentBox中定义一个回调函数,这个函数有一个comment参数,在这个回调函数中,我们发送post请求到服务器,同时更新服务器和浏览器的评论列表。然后把这个回调函数作为属性(props)传给CommentForm,当表单提交的时候调用这个函数并传入获得数据。
更新CommentForm和CommentBox如下:

//CommentBoxvar CommentBox=React.createClass({  getInitialState:function(){    return{data:[]};  },  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)    });  },  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>    );  }});//CommentFormvar CommentForm=React.createClass({  getInitialState:function(){    return{author:'',text:''}  },  handleAuthorChange:function(e){    // alert(e.target.value);    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(!author||!text){      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}//这条是为了将输入反映在输入框中,state会自动更新          onChange={this.handleTextChange}        />        <input type="submit" value="Post" />      </form>    );  }});

优化

每次提交一条评论,就会向服务器发请求更新文件,在返回更新后的文件,然后在把整个文件显示在页面上。这样可能会让你的页面看起来有点慢,事实上我们只需要先在页面上添加最新的一条评论,剩下的就是我们看不见的了。这样可以让你的页面“快起来”。优化后的CommentBox如下:

//CommentBoxvar CommentBox=React.createClass({  getInitialState:function(){    return{data:[]};//初始化组件,评论数据为空列表  },  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;//获得已有的旧的评论列表    comment.id=Date.now();//以当前的时间作为评论ID,不要在真正的项目里这么做。    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)    });  },  componentDidMount:function(){    this.loadCommentsFromServer();    setInterval(this.loadCommentsFromServer,this.props.pollInterval);  },  render:function(){//最重要的render    return(      <div className="commentBox">        <h1>Comments</h1>        <CommentList data={this.state.data} />{/*把评论数据传给list处理显示*/}        <CommentForm  onCommentSubmit={this.handleCommentSubmit} />      </div>    );  }});

至此,这个实时评论应用(支持markdone语法)教程完成。下面附上完整的my_copy.js代码。

/** * Created by liuwuhao on 2016/10/19. *///datavar data=[  {id:1,author:"Pete Hunt",text:"This is a comment"},  {id:2,author:"Jordan Walke",text:"This is *another* comment"}];//Commentvar 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>    );  }});//CommentListvar 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>    );  }});//CommentFormvar CommentForm=React.createClass({  getInitialState:function(){    return{author:'',text:''}  },  handleAuthorChange:function(e){    // alert(e.target.value);    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(!author||!text){      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}//这条是为了将输入反映在输入框中,state会自动更新          onChange={this.handleTextChange}        />        <input type="submit" value="Post" />      </form>    );  }});//CommentBoxvar CommentBox=React.createClass({  getInitialState:function(){    return{data:[]};//初始化组件,评论数据为空列表  },  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;//获得已有的旧的评论列表    comment.id=Date.now();//以当前的时间作为评论ID,不要在真正的项目里这么做。    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)    });  },  componentDidMount:function(){    this.loadCommentsFromServer();    setInterval(this.loadCommentsFromServer,this.props.pollInterval);  },  render:function(){//最重要的render    return(      <div className="commentBox">        <h1>Comments</h1>        <CommentList data={this.state.data} />{/*把评论传给list处理显示*/}        <CommentForm  onCommentSubmit={this.handleCommentSubmit} />      </div>    );  }});ReactDOM.render(  <CommentBox url="/api/comments" pollInterval={2000} />,  document.getElementById('content'));
0 0
原创粉丝点击