利用React写一个评论区组件(React初探)

原创
2015/02/21 23:17
阅读数 2.6W

本文是在阅读学习了官方的React Tutorial之后的整理,实例链接

####开始使用React

首先从官方获取React.js的最新版本(v0.12.2),或者下载官方的Starter Kit,并在我们的html中引入它们:

<head>
    <meta charset="UTF-8">
    <title>React Test Page</title>
    <script src="../build/react.js"></script>
    <script src="../build/JSXTransformer.js"></script>
</head>

####JSX语法

我们可以在React组件的代码中发现xml标签似乎直接写进了javascript里:

React.render(
    <CommentBox />,
    document.getElementById('content')
);

这种写法被称作JSX,是React的一个可选功能,将xml标签直接写在javascript中看上去比调用javascript方法要更加直观些。要正常使用这个功能,需要在你的页面中引入JSXTransformer.js文件,或者使用npm安装react-tools,将包含JSX语法的源文件编译成常规的javascript文件,比较推荐的是后者,因为使用后者让页面可以直接使用编译后的javascript文件而不需要在加载页面时进行JSX编译。

JSX中的类HTML标签并不是真正的HTML元素,也不是一段HTML字符串,而是实例化了的React组件,关于JSX语法的更多内容,可以看这篇文章

####创建组件

React可以为我们创建模块化、可组合的组件,对于我们需要做的评论区,我们的组件结构如下:

- CommentBox
    - CommentList
        -Comment
    - CommentForm

通过React.createClass()可以一个React元素,我们可以像这样定义我们的CommentBox,并通过React.render()方法可以让我们在指定的容器中将React元素渲染为一个DOM组件:

<body>
    <div id="content"></div>
    <script type="text/jsx">
        var CommentBox = React.createClass({
            render: function() {
                return (
                    <div className="contentBox">
                        <h1>Comments</h1>
                        <CommentList />
                        <CommentForm />
                    </div>
                );
            }
        });
 
        React.render(
            <CommentBox />,
            document.getElementById('content')
        );
    </script>
</body>

从这个例子也可以看出一个组件可以包含子组件,组件之间是可以组合的(Composing),并呈现一个树形结构,也可以说render方法中的的CommentBox代表的是组件树的根元素。那么接下来我们来创建CommentList和CommentForm这两个子组件。

首先是CommentList组件,这个组件是用来呈现评论列表的,根据开始我们设计的组件结构树,这个组件应该是包含许多Comment子组件的,那么,假设我们已经获取到评论数据了:

var comments = [
    {author: "Pete Hunt", text: "This is one comment"},
    {author: "Jordan Walke", text: "This is *another* comment"}
];

我们需要把数据传递给CommentList组件才能让它去呈现,那么如何传递呢?我们可以通过this.props来访问组件标签上的属性,比如我们在CommentBox组件的代码中做如下修改:

<CommentList data=comments  />

于是在CommentList组件中,我们可以通过访问this.props.data来获取到我们的评论数据。

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

接下来写Comment组件,这个组件用于呈现单个评论,我们希望它可以支持markdown语法,于是我们引入showdown这个库,在HTML中引入它之后,我们可以调用它让我们的评论支持Markdown语法。在这里我们需要this.props.children这个属性,它返回了该组件标签里的所有子元素。

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

我们看一下现在的效果: 效果

我们发现经过解析后html标签被直接呈现了上去,因为React默认是有XSS保护的,所有对呈现的内容进行了转义,但在现在的场景中,我们并不需要它的转义(如果取消React默认的XSS保护,那么就需要仰仗于我们引入的库具有XSS保护或者我们手动处理),这时我们可以这样:

var converter = new Showdown.converter();
var Comment = React.createClass({
    render: function() {
 
        // 通过this.props.children访问元素的子元素
        var rawHtml = converter.makeHtml(this.props.children.toString());
        return (
            // 通过this.props访问元素的属性
            // 不转义,直接插入纯HTML
            <div className="comment">
                <h2 className="commentAuthor">{this.props.author}</h2>
                <span dangerouslySetInnerHTML={{__html: rawHtml}} />
            </div>
        );
    }
});

好了,接下来我们的CommentList算是完成了,我们需要加上CommentForm组件让我们可以提交评论:

var CommentForm = React.createClass({
    handleSubmit: function(e) {
 
        e.preventDefault();

        var author = this.refs.author.getDOMNode().value.trim();
        var text = this.refs.text.getDOMNode().value.trim();
 
        if(!text || !author) return;
 
        // TODO 修改commentList
 
        // 获取原生DOM元素
        this.refs.author.getDOMNode().value = '';
        this.refs.text.getDOMNode().value = '';
    },
    render: function() {
        return (
            // 为元素添加submit事件处理程序
            // 用ref为子组件命名,并可以在this.refs中引用
            <form className="commentForm" onSubmit={this.handleSubmit}>
                <input type="text" placeholder="Your name" ref="author"/>
                <input type="text" placeholder="Say something..." ref="text"/>
                <input type="submit" value="Post"/>
            </form>
        );
    }
});

从以上的代码中我们可以发现,我们可以为我们的组件添加事件处理程序,比如在这里我们需要利用form的submit事件,于是直接在标签上添加onSubmit的属性即可。需要注意的是,事件属性需要满足驼峰命名规则,也就是说如果是要添加click事件,那就要添加onClick,以此类推。还有一点就是我们需要获取两个文本框中的内容,这里使用的方法是在input标签上添加ref属性,这样就可以认为这个input是它的一个子组件,然后就可以通过访问this.refs来访问到这个子组件了,通过调用getDOMNode方法可以获取原生的DOM对象进行相应的操作。

我们发现到现在为止,我们的页面是静态的,但我们希望可以在成功提交了评论后可以立刻在评论列表中看到自己的评论,并可以每隔一段时间获取最新的评论,也就是说我们希望我们的CommentBox可以动态地改变状态。

首先我们先让CommentBox组件可以通过AJAX请求(在这里我用setTimeout来模拟获取数据的延迟),从服务器端获取评论数据同时更新CommentList。React组件有一个私有的this.state属性用于保存组件可变状态的数据,但一开始我们需要的是一个初始的状态,初始状态可以通过设置组件的getInitialState方法,它的返回值即为状态初始值。这个时候我们不是从标签的属性上直接获取数据了,需要通过访问this.state来获取(这个state属性如果直接用javascript访问会返回undefined,但可以在JSX中可以像this.state.data这样使用):

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

接下来我们需要获取评论数据,我们可以在组件的componentDidMount方法中实现,这个方法会在组件呈现在页面上之后会被立刻调用一次,我们就在这个方法中获取到数据后更新下组件的状态,要更新组件的状态需要调用组件的this.setState方法,于是我们就这样写:

var CommentBox = React.createClass({
    // 在组件的生命周期中仅执行一次,用于设置初始状态
    getInitialState: function() {
        return {data: []};
    },
    loadCommentsFromServer : function() {
 
        var self = this;
        setTimeout(function() {
            // 动态更新state
            self.setState({data: comments});
        }, 2000);
    },
    // 当组件render完成后自动被调用
    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>
        );
    }
});

现在我们已经可以更新评论列表里的数据了,那么同样的我们在CommentForm中成功提交的评论也要可以在CommentList中呈现出来,在这里需要注意的是我们现在设置的初始状态是CommentBox这个组件的,修改状态也是修改的CommentBox的状态,那么如果要在CommentForm中改变CommentBox的状态,就需要在CommentBox组件中通过标签属性的方式传递一个方法给子组件CommentForm,让CommentForm组件中的handleSubmit可以调用这个方法(也就是上面TODO的位置),于是我们的代码就是这样的:

var CommentBox = React.createClass({
    // 在组件的生命周期中仅执行一次,用于设置初始状态
    getInitialState: function() {
        return {data: []};
    },
    onCommentSubmit: function(comment) {
        // 模拟提交数据
        comments.push(comment);
 
        var self = this;
        setTimeout(function() {
            // 动态更新state
            self.setState({data: comments});
        }, 500);
    },
    loadCommentsFromServer : function() {
 
        var self = this;
        setTimeout(function() {
            // 动态更新state
            self.setState({data: data});
 
        }, 2000);
    },
    // 当组件render完成后自动被调用
    componentDidMount: function() {
 
        this.loadCommentsFromServer();
        setInterval(this.loadCommentsFromServer, this.props.pollInterval);
    },
    render: function() {
        return (
            // 并非是真正的DOM元素,是React的div组件,默认具有XSS保护
            <div className="commentBox">
                <h1>Comments</h1>
                <CommentList data={this.state.data} />
                <CommentForm onCommentSubmit={this.onCommentSubmit} />
            </div>
        );
    }
});

var CommentForm = React.createClass({
    handleSubmit: function(e) {
 
        e.preventDefault();
        // e.returnValue = false;
        var author = this.refs.author.getDOMNode().value.trim();
        var text = this.refs.text.getDOMNode().value.trim();
 
        if(!text || !author) return;
 
        this.props.onCommentSubmit({author: author, text: text});
 
        // 获取原生DOM元素
        this.refs.author.getDOMNode().value = '';
        this.refs.text.getDOMNode().value = '';
    },
    render: function() {
        return (
            // 为元素添加submit事件处理程序
            // 用ref为子组件命名,并可以在this.refs中引用
            <form className="commentForm" onSubmit={this.handleSubmit}>
                <input type="text" placeholder="Your name" ref="author"/>
                <input type="text" placeholder="Say something..." ref="text"/>
                <input type="submit" value="Post"/>
            </form>
        );
    }
});

到此为止,我们的CommentBox组件就大功告成了,实例链接

展开阅读全文
打赏
4
49 收藏
分享
加载中

引用来自“排骨捞青菜”的评论

请问下楼主,在这个实例中,最后 React.render(); 会给CommentBox 一个请求数据的url ,但是CommentForm提交数据的url 好像也是这一个哦,有些不太理解啊,提交数据和提交数据的ajax 里面用的都是this.props.url
后端接口会根据请求协议做不同判断,读取数据时用的是get,发布评论时用的是post,后端会做不同的处理动作。
2015/11/24 15:32
回复
举报
请问下楼主,在这个实例中,最后 React.render(); 会给CommentBox 一个请求数据的url ,但是CommentForm提交数据的url 好像也是这一个哦,有些不太理解啊,提交数据和提交数据的ajax 里面用的都是this.props.url
2015/09/21 00:13
回复
举报
应该主要是组件化的 概念 其他好像没啥
2015/09/09 18:05
回复
举报

引用来自“且行且珍惜吧”的评论

js里写html简直就是反人类,我用jq写比这个能省不少事,最多加点事件绑定,兼容性还好。不考虑兼容angular完爆react三条街,不服来辩?
萝卜白菜各有所爱,facebook 的技术会比你弱?楼主秉着分享精神,写出他的理解和实现。比你在评论里面简单粗暴的开喷要强太多吧?
2015/08/05 16:52
回复
举报
LeoG0816博主

引用来自“simon_wang”的评论

js里写html简直就是反人类,我用jq写比这个能省不少事,最多加点事件绑定,兼容性还好。不考虑兼容angular完爆react三条街,不服来辩?
你好,对于你的评论: 1. JSX是可选的,如果不能接受可以直接用JS的React.createElement方法 2. 用JQ写这个组件当然没有问题,这个例子仅是作为React入门介绍用,没有说React写就一定多好 3. angular也是我比较偏爱的前端框架,用它做过SPA,重构了两次,唯一觉得反感的地方是angular有自己的上下文,对其他库的兼容性不好,比如之前写的jq组件,要在angular里用就得重新封装directive,于是就只能用类似于angular ui或是restangular这样专为angular写的库了,新项目还好,老项目的话往angular迁成本很大,撇开这点的话,angular确实是功能强大的框架,不过直接用angular和react做比较我觉得是不公平的,angular对构建整个应用提供了完整的体系,但react是个仅专注于视图层,利用虚拟DOM元素,最大限度减少因数据的改变而带来的DOM更新,而应用的其它部分还是需要与其它库配合。 最后,这篇文章仅作为React的入门介绍,本人React的经验的不是很足,就目前来说React只是多一种选择。欢迎继续交流。
2015/02/23 11:04
回复
举报
js里写html简直就是反人类,我用jq写比这个能省不少事,最多加点事件绑定,兼容性还好。不考虑兼容angular完爆react三条街,不服来辩?
2015/02/23 02:36
回复
举报
这玩意,我暂时还没有发现它的神奇地方
2015/02/22 13:55
回复
举报
LeoG0816博主

引用来自“CodingKu”的评论

能跨域获取评论数据么
React仅专注于视图层,取数据的话,跨域问题是肯定要处理的,可以在服务器端做个代理
2015/02/22 12:46
回复
举报
能跨域获取评论数据么
2015/02/22 07:00
回复
举报
更多评论
打赏
9 评论
49 收藏
4
分享
返回顶部
顶部