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

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom