文档章节

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

LeoG0816
 LeoG0816
发布于 2015/02/21 23:17
字数 2242
阅读 11962
收藏 49

本文是在阅读学习了官方的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组件就大功告成了,实例链接

© 著作权归作者所有

上一篇: CSS Reset
下一篇: React JSX语法说明
LeoG0816
粉丝 13
博文 7
码字总数 8927
作品 0
浦东
程序员
私信 提问
加载中

评论(9)

y
yuzhewo

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

请问下楼主,在这个实例中,最后 React.render(); 会给CommentBox 一个请求数据的url ,但是CommentForm提交数据的url 好像也是这一个哦,有些不太理解啊,提交数据和提交数据的ajax 里面用的都是this.props.url
后端接口会根据请求协议做不同判断,读取数据时用的是get,发布评论时用的是post,后端会做不同的处理动作。
排骨捞青菜
排骨捞青菜
请问下楼主,在这个实例中,最后 React.render(); 会给CommentBox 一个请求数据的url ,但是CommentForm提交数据的url 好像也是这一个哦,有些不太理解啊,提交数据和提交数据的ajax 里面用的都是this.props.url
12叔
12叔
应该主要是组件化的 概念 其他好像没啥
t
trancelover

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

js里写html简直就是反人类,我用jq写比这个能省不少事,最多加点事件绑定,兼容性还好。不考虑兼容angular完爆react三条街,不服来辩?
萝卜白菜各有所爱,facebook 的技术会比你弱?楼主秉着分享精神,写出他的理解和实现。比你在评论里面简单粗暴的开喷要强太多吧?
LeoG0816
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只是多一种选择。欢迎继续交流。
且行且珍惜吧
且行且珍惜吧
js里写html简直就是反人类,我用jq写比这个能省不少事,最多加点事件绑定,兼容性还好。不考虑兼容angular完爆react三条街,不服来辩?
colinapks
colinapks
这玩意,我暂时还没有发现它的神奇地方
LeoG0816
LeoG0816 博主

引用来自“CodingKu”的评论

能跨域获取评论数据么
React仅专注于视图层,取数据的话,跨域问题是肯定要处理的,可以在服务器端做个代理
木川瓦兹
木川瓦兹
能跨域获取评论数据么
开发微信小程序的必备技能图谱

今天被微信小程序彻底刷屏了,哎呀,JS开发者坐等涨工资吧。 小程序是一种不需要下载安装即可使用的应用,它实现了应用“触手可及”的梦想,用户扫一扫或者搜一下即可打开应用。也体现了“用...

Yomut
2016/09/23
142
0
从0开发简书项目(1)-react初探

写在开头: 感觉社会变化很快,我记得从我第一次学习 的时候,我都一直希望赶紧学习后端,因为觉得后端工资高,一直忽视前端,到现在才猛然发现,前端要解决的问题太多,前端越来越有竞争力,...

程序员小哥哥
2018/09/01
0
0
揭开redux,react-redux的神秘面纱

16年开始使用react-redux,迄今也已两年多。这时候再来阅读和读懂redux/react-redux源码,虽已没有当初的新鲜感,但依然觉得略有收获。把要点简单写下来,一方面供感兴趣的读者参考,另一方面...

苏溪云
2018/12/18
0
0
React Native第2天——底层原理了解

我(web+android开发经验)学习React Native过程中接触的知识点和学习的线路图。 React Native第1天——环境配置及知识体系(http://my.oschina.net/addcn/blog/647290) 掌握环境配置及运行h...

addcn
2016/03/28
2.3K
0
如果你也刚入门React,来一起学习吧

本文提要 本文主要写一些CRA脚手架的安装,React的语法,组件分类和组件传值等;如果您是已经在React上有丰富经验的开发者,欢迎指出文中有问题和可以改进的地方,对此我将表示感谢! 这是r...

zhangyuxiang1226
2018/09/05
0
0

没有更多内容

加载失败,请刷新页面

加载更多

哪些情况下适合使用云服务器?

我们一直在说云服务器价格适中,具备弹性扩展机制,适合部署中小规模的网站或应用。那么云服务器到底适用于哪些情况呢?如果您需要经常原始计算能力,那么使用独立服务器就能满足需求,因为他...

云漫网络Ruan
今天
10
0
Java 中的 String 有没有长度限制

转载: https://juejin.im/post/5d53653f5188257315539f9a String是Java中很重要的一个数据类型,除了基本数据类型以外,String是被使用的最广泛的了,但是,关于String,其实还是有很多东西...

低至一折起
今天
23
0
OpenStack 简介和几种安装方式总结

OpenStack :是一个由NASA和Rackspace合作研发并发起的,以Apache许可证授权的自由软件和开放源代码项目。项目目标是提供实施简单、可大规模扩展、丰富、标准统一的云计算管理平台。OpenSta...

小海bug
昨天
11
0
DDD(五)

1、引言 之前学习了解了DDD中实体这一概念,那么接下来需要了解的就是值对象、唯一标识。值对象,值就是数字1、2、3,字符串“1”,“2”,“3”,值时对象的特征,对象是一个事物的具体描述...

MrYuZixian
昨天
9
0
解决Mac下VSCode打开zsh乱码

1.乱码问题 iTerm2终端使用Zsh,并且配置Zsh主题,该主题主题需要安装字体来支持箭头效果,在iTerm2中设置这个字体,但是VSCode里这个箭头还是显示乱码。 iTerm2展示如下: VSCode展示如下: 2...

HelloDeveloper
昨天
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部