新搭建的个人博客,本文地址:React学习笔记2:React官方CommentBox实践
所有的操作是继续上一个学习笔记,参考的是React官方的CommentBox,不过不是100%按照其实现。
参考:https://facebook.github.io/re...

1、首先创建相关的文件

touch src/comment.js

2、修改webpack配置,一处是告诉webpack预处理的实体增加comment.js,另外一个是告诉webpack输出的时候按照文件名字编译输出,而不是将所有js文件编译到bundle.js,[name]实际上是entry数组中的key,通过修改key可以归类目录,例如'comment/index':./src/index.js,'comment/index':'./src/comment.js'会将编译后的文件放到comment目录下,通过这些配置可以更好的组织代码结构

  entry:{
    'index':'./src/index.js',
    'comment':'./src/comment.js'
  },
  output: {
     path: path.resolve(__dirname, 'build'),
     filename: '[name].js'
  },

3、修改'build/index.html'引入文件修改为comment.js,重新运行webpack-dev-server,开始修改comment.js

<script src="comment.js"></script>

4、分拆Comment组件,梳理出如下结构,在React中所有的东西都是以组件的形式存在

- CommentBox
  - CommentList
    - Comment
  - CommentForm

5、创建CommentBox组件,return()中的类html内容,在react中叫做JSX语法,其本身符合XML语法,react在编译后会转化为相应的js文件,官方介绍https://facebook.github.io/re...
之后可以去浏览器中看下效果

var CommentBox = React.createClass({
  render:function(){
    return (
      <div className='commentBox'>
          Hello world! I am a comment box.
      </div>
    )
  }
});
//渲染组件,注意修改index.html中div的id为content
ReactDOM.render(
  <CommentBox />,document.getElementById('content')
);

6、创建CommentList、CommentForm组件

var CommentList = React.createClass({
  render:function(){
    return (
      <div className='commentList'>
        Hello, I am a comment list!
      </div>
    )
  }
});

var CommentForm = React.createClass({
  render:function(){
    return (
      <div className='commentForm'>
        hi, I am a comment form.
      </div>
    )
  }
});

7、修改CommentBox代码,引入CommentList、CommentForm组件。下面代码中混合了HTML和组件代码,JSX编译器会自动将HTML代码用React.createElement(tagName)去转换。浏览器看下效果。

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

8、创建Comment组件,里面有{this.props.author}和{this.props.children}两个变量,称之为组件的属性。修改CommentList组件,可以看到我们传递了author属性。children是React预置属性,指向组件内嵌的内容。返回浏览器查看修改变化。

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

    )
  }
})
//修改CommentList组件,让其载入Comment组件
var CommentList = React.createClass({
  render:function(){
    return (
      <div className='commentList'>
        <Comment author='stone'>就是瞎比比</Comment>
        <Comment author='mpanda'>我不听瞎比比</Comment>
      </div>
    )
  }
});

9、添加Markdown支持

//安装依赖包
npm install marked --save
//引入marked包
var marked = require('marked')
//修改Comment组件,利用marked组件解析评论内容,转为富文本格式。使用toString()是为了明确传送给marked的为字符串格式。浏览器看效果效果
var Comment = React.createClass({
  render:function(){
    return (
      <div className='comment'>
        <h2 className='commentAuthor'>
          {this.props.author}
        </h2>
        {marked(this.props.children.toString())}
      </div>

    )
  }
})
//显示效果
Comments
stone
<p>就是瞎比比</p>
mpanda
<p>我不听瞎比比</p>
hi, I am a comment form.

10、解析后的文本直接被显示在页面上,并没有被浏览器解析,这是react为了防止被XSS攻击而作的保护措施。React提供了一个并不友好的特殊API保证能够实现在浏览器显示原始HTML

var Comment = React.createClass({
  rawMarkup:function(){
    var rawMarkup = marked(this.props.children.toString(),{sanitize:true})
    return {__html:rawMarkup}
  },
  render:function(){
    return (
      <div className='comment'>
        <h2 className='commentAuthor'>
          {this.props.author}
        </h2>
        <span dangerouslySetInnerHTML={this.rawMarkup()} />
      </div>

    )
  }
})

JSX中dangerouslySetInnerHTML属性必须在接收到一个对象参数,且对象参数中明确使用__html作为key时,才会将其内容作为原始HTML插入页面中,而且不建议直接在<div dangerouslySetInnerHTML={{__html:marked(this.props.children.toString(),{sanitize:true})}} />直接这样完成书写,目的就是明确提醒开发者,这里是有风险的,您是绝对的信任这段插入的内容。再次查看浏览器效果。It works!
11、评论数据显然应该来自服务器,不过在动态获取之前,我们先模拟一些数据

var data = [
  {"id":1,"author":"stone","text":"换一个位置瞎比比"},
  {"id":2,"author":"mpanda","text":"不喜欢你瞎比比"},
]
//传递数据到CommentBox
ReactDOM.render(
  <CommentBox data={data} />,document.getElementById('content')
);
//直接传递数据到CommentList
var CommentBox = React.createClass({
  render:function(){
    return (
      <div className='commentBox'>
        <h1>Comments</h1>
        <CommentList data={this.props.data} />
        <CommentForm />
      </div>
    )
  }
});
//在CommentList重新完成组件的组装,刷新浏览器看效果
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>
    )
  }
});

12、从组件封装来说我们已经封装了一个很不错的组件,只需要传递相关json数据到CommentBox即可。不过所有的数据都是在组件创建的时候,利用不可变量参数props一次性传递给组件。state同样为组件的私有变量,可以通过this.setState()来设置变量的值,每次设置变量的值,组件都会重新渲染一遍自己。利用state修改我们的程序,让其动态渲染页面。

//为了方便进行ajax请求,引入jquery,当然完全可以不引入
var $ = require("jquery")
var CommentBox = React.createClass({
  //getInitialState函数在组件的整个生命周期只会执行一次,我们在里面初始化数据
  getInitialState:function(){
    return {data:[]}
  },
  //componentDidMount函数同样是有React自动调用,时间是在组件第一渲染完毕后。当然因为data在初始化的时候数据为空,实际上这时候渲染的组件没有内容。
  componentDidMount:function(){
    $.ajax({
      url:this.props.url,
      //因我本地server是php,且跨域,所以我们使用jsonp解决跨域问题,具体jsonp实现,请自行google
      dataType:"jsonp",
      cache:false,
      jsonp:'callback',
      jsonpCallback:'getComment',
      success:function(data){
        //获取到数据后,通过setState设置数据,组件会自动再次渲染
        this.setState({"data":data})
      }.bind(this),
      error:function(xhr,status,err){
        console.log(this.props.url,status,err.toString())
      }.bind(this)
    })
  },
  render:function(){
    return (
      <div className='commentBox'>
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm />
      </div>
    )
  }
});
//将URL地址传递个组件
ReactDOM.render(
  <CommentBox url="http://***.local.com/" />,document.getElementById('content')
);

13、我们在浏览器看效果的时候因为数据请求都是在毫秒级别完成不方便看到重新渲染的效果,我们引入一个定时器。刷新浏览器,哈哈,2s后自动载入了评论数据。

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

14、官方教程中评论提交分两种完成。第一种完成了提交完成后,需要刷新,再次从服务器获取评论数据,第二种提交评论后直接把提交的数据附加到评论后面,利用setState重新渲染页面。显然第二种体验更好,直接实现第二种。
先做分析,在CommentForm组件中,如果完成数据的提交,那么需要重新设置CommentList中的数据,但是CommentList的数据又是CommentBox传递过去的,那么提交数据的操作不如直接在CommentBox中完成,然后利用setState重新设置CommentList的数据,CommentList完成自动刷新。

//之前获取评论接口利用的是jsonp,但是提交评论必须post方法,所以jsonp无法完成,但是有不能通过跨域操作。webpack支持proxy(代理)模式,可以把一部分接口直接转发到后端,修改webpack配置,请自行替换后端服务。
    devServer:{
        contentBase:'./build',
        proxy:{
            "/api/*":{
                target:"http://***.local.com:80",
                host:"***.local.com",
                secure: false,
            },
            bypass: function(req, res, proxyOptions) {
                if (req.headers.accept.indexOf('html') !== -1) {
                    console.log('Skipping proxy for browser request.');
                    return '/index.html';
                }
            },
        }
    }
//注意传递的url,会自动转发到http://***.local.com:80/api/comment
ReactDOM.render(
  <CommentBox url="http://localhost:8080/api/comment" />,document.getElementById('content')
);
var CommentBox = React.createClass({
  //增加评论提交方法,后台服务  
  handleSubmitComment:function(data){
    $.ajax({
      //请注意,后台接口我把评论和获取评论放到了一起,只是提交方式不一样,一个是get,一个是post
      url:this.props.url,
      type:"POST",
      data:data,
      dataType:"json",
      cache:false,
      success:function(data){
        //测试接口直接返回了我提交的内容,所以可以直接附加数据,让CommentList自动刷新
        this.setState({data:this.state.data.concat(data)});
      }.bind(this),
      error:function(xhr,status,err){
        console.log(this.props.url,status,err.toString())
      }.bind(this)
    })
  },
  render:function(){
    return (
      <div className='commentBox'>
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        //将评论提交接口传递个CommentForm组件
        <CommentForm onSubmitComment={this.handleSubmitComment} />
      </div>
    )
  }
});

var CommentForm = React.createClass({
  getInitialState:function() {
    return {author:"",text:""}
  },
  //完成数据的绑定,通过setState也能保证跟此数据相关的UI完成重新的渲染
  handleAuthorChange:function(event){
    this.setState({author:event.target.value})
  },
  handleTextChange:function(event){
    this.setState({text:event.target.value})
  },
  handleSubmit:function(event){
    //组织表单默认的submit提交
    event.preventDefault();
    var author = this.state.author.trim()
    var text = this.state.text.trim()
    if(!text||!author) {
      return;
    }
    //调用CommentBox上的评论提交方法
    this.props.onSubmitComment({author:author,text:text});
    this.setState({author:"",text:""})
  },

  render:function(){
    return (
      <form className='commentForm' onSubmit={this.handleSubmit}>
        <input type='text' onChange={this.handleAuthorChange}
           placeholder='怎么称呼您呢?爷' value={this.state.author} />
         <input type='text' onChange={this.handleTextChange}
           placeholder='爷有什么赐教?' value={this.state.text} />
         <input type='submit' value='提交' />
      </form>
    )
  }
});

到此整个示例联系完成!之后要完成用es6语法重构该项目。


MStone
334 声望33 粉丝

全栈攻城狮,PHP++GO+前端,长期工作于创业公司!