《React官方文档》之教程Tutorial

    xiaoxiao2024-03-30  126

    教程Tutorial

     我们建立一个简单但实际的评论框,Disqus, LiveFyre或Facebook可以提供实时评论,评论框可以放在一个博客中。

    我们提供:

    可以看到所有评论的视图提交评论的表单通过Hooks可以自定义后端

    它有如下特点:

    优化的评论: 评论将会在保存到服务器上之前就出现在列表中,这样看上去非常快。 实时更新: 其他用户的评论会实时的被放到评论界面。 Markdown格式: 用户可以使用Markdown来格式化他们的文本。

    想要跳过这些只看源码?

    都在GitHub上

    运行一个服务器

    首先,我们需要一个运行的服务器。它将作为API端口来获取并保存数据。为了简便,我们用脚本语言创建一个服务端。你可以看源文件 或者 下载zip文件 。

    服务器使用一个JSON文件作为数据库。 在实际的产品中你不会这样用,但是这样可以简单的模拟出当你使用一个API是你可能做的事情。一旦你启动服务器, 它将会支持API端口并且为我们需要的静态页面提供服务。

    开始

    本教程中我们尽量简化。 上文提到的包中包含一个HTML文件。 在你最喜欢的编辑器中打开public/index.html,可以看到:

    <!-- index.html --> <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>React Tutorial</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.5/marked.min.js"></script> </head> <body> <div id="content"></div> <script type="text/babel" src="scripts/example.js"></script> <script type="text/babel"> // To get started with this tutorial running your own code, simply remove // the script tag loading scripts/example.js and start writing code here. </script> </body> </html>

    本教程余下的部份,我们将通过JavaScript语言完成<script>标签中的内容。我们没有任何高级的livereload(自动重新加载),因此你需要在保存修改刷新浏览器。启动服务后在浏览器中打开http://localhost:3000。当你未做任何修改加载在这个页面时,你将看到我们想要构建的成品。删掉前缀为<script>的标签就可以开始了。

    注意:

    我们在这里用到了jQuery因为我们想要简化ajax调用,但这在React中不是必要的。

    你的第一个组件

    React就是将各种组件模块化组合到一起。我们的评论框例子中需要以下组件结构:

    - 评论框CommentBox - 评论列表CommentList - 每条评论Comment - 可提交的评论表单CommentForm

    首先,构建评论框组件,这就是个简单的<div>:

    // tutorial1.js var CommentBox = React.createClass({ render: function() { return ( <div className="commentBox"> Hello, world! I am a CommentBox. </div> ); } }); ReactDOM.render( <CommentBox />, document.getElementById('content') );

    注意纯TML元素名都是以小写字母开头,然而React类名通常以大写字母开头。

    JSX语法

    首先要注意到的是在你的 JavaScript中有XML化语法。我们有一个简单的预编译将语法糖转化为普通的JavaScript:

    // tutorial1-raw.js 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') );

    这种用法不是必需的,JSX确实比普通的JavaScript简单。更多内容可参考JSX 语法。

    下一步

    我们将一些JavaScript对象的方法传入React.createClass()来创造一个新的React组件。这个方法中最重要的是render,它将返回一个React组件树并最终渲染HTML。

    <div>并不是真正的DOM节点,他们是React的div组件实例。你可以把这些当作React知道如何处理的数据或者标识。React是安全的。我们并不产生HTML字符串,所以XSS保护是默认的。

    你可以不返回基本的HTML,可以返回你或者他人建立的一个组件树。这使得React是可组合的(可维护前端的一个关键原则)。

    ReactDOM.render() 实例化根节点组件,开始框架,将标识注入到原生的DOM中,作为第二个参数。

    通过在不同平台上共享的React核心工具,ReactDOM方法显示出特定的DOM方法 (e.g., React Native)。

    需要注意的是在本教程中ReactDOM.render要在js文件的底部。ReactDOM.render只有在符合组件被定义后才可以被调用。

    复合组件

    下面我们构建一个评论列表和评论表单的框架,用到的同样是简单的<div>。将这两个组件添加到你的文件中,保持评论框CommentBox定义的存在以及ReactDOM.render的调用:

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

    接下来,评论框使用这两个新组件:

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

    注意我们是怎样将HTML标签和我们构造的组件结合到一起的。HTML组件就是常规的React组件,和你自己定义的就只有首字母大小写的区别。 JSX编译器可以自动的将HTML标签重写到React.createElement(标签名)表达式中而不管其他的,这将避免全局命名空间被污染。

    使用属性

    我们创建Comment组件,它将依赖于它的父组件传递给它的数据。 对于子组件来说,从父组件传递来的数据可以像一个属性一样被获取。这些属性通过this.props得到。使用属性我们可以读取从CommentList传递给Comment的数据,并且渲染标识:

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

    通过将JavaScript表达式包含在JSX中(作为属性或者子节点),你可以将文本或者React组件放在树中。我们通过传给组件的this.props 及其他像this.props.children的键来获取值。我们获取传递给组件的属性传递给组件,比如this.props的键或者其他被大括号包起来的比如this.props.children。

    组件属性

    既然我们定义了Comment 组件,我们就希望利用它来传递作者名和评论内容。这将使得我们对每一段评论重用同样的代码。现在让我们为我们的CommentList添加一些评论:

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

    注意到我们已经从父组件CommentList 向子组件Comment传递数据。例如:我们将名字Pete Hunt (通过author属性) 和评论内容This is one comment (通过类XML的子节点)传递给第一个Comment。如上所述,Comment组件将通过this.props.author和this.props.children获取属性。

    添加Markdown

    Markdown是一个简单的方式来格式化你的文本。例如星号围绕的文本将会被强调。

    在本教程中我们使用一个第三方库marked,它将把Markdown文本转化为纯HTML.。我们为页面引用这个库然后直接使用,把文本转化为Markdown并输出:

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

    我们现在所做的就是调用marked库。我们需要把this.props.children从React包裹的文本转化为一个字符串,因此我们显式的调用toString()。

    但仍然有问题!我们渲染好的评论在显示器中这样显示:”<p>This is<em>another</em> comment</p>“。我们想把这些标签真正的渲染成HTML。

    这是因为React会防止 XSS攻击。有种方法可以解决这个问题但建议尽量不这么做:

    // tutorial7.js

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

    这个特殊的API使得插入纯HTML变得困难,但是为了marked也不得不使用这个伎俩。

    记住: 使用这个要确保marked是安全的。在这里我们传递sanitize: true 来告诉marked不保留任何的HTML标识

    连接上数据模型

    到目前为止我们已经可以将评论直接插入到源代码中了。接下来我们将JSON对象传入评论列表。最终这个数据将来源于服务器,但现在这样写你的代码:

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

    我们需要模块化的把这数据传入到CommentList。修改CommentBoxReactDOM.render()调用来将数据通过属性传入到CommentList:

    // tutorial9.js 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中数据可得,我们可以动态渲染评论:

    // tutorial10.js 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> ); } });

    这就可以了!

    从服务器取数据

    下面我们从服务器动态获取数据来代替固化的代码。我们去掉data属性并以一个URL来代替取数据:

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

    这个组件不同于前面的那些因为它必须重新渲染它自己。服务器端响应之前组件不会有任何数据,这时间段组件不会渲染新的评论。

    注意:代码在这个阶段是不能工作的。

    反应状态

    到目前为止,每个组件都可以基于自己的属性渲染自己。 属性props是不可改变的: 它们来自父节点并且属于父节点。为了实现交互,我们为组件引入一个可变的状态。 this.state是组件私有的并且可以通过调用this.setState()被改变。当状态state更新时,组件也会重新渲染。

    render()方法就像this.props和this.state函数一样以声明形式写入。框架却表UI始终与输入一致。

    当服务器端取数据,我们可以修改我们已有的评论。下面我们为 CommentBox组件增加一组数据作为它的状态:

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

    getInitialState()在组件生命周期中被执行并且建立组件初始状态。

    更新状态

    当组件一开始被创建时,我们想要从服务器端获取JSON,并且更新状态反映到最新的数据中。我们将使用jQuery来创造一个异步的请求给我们前面获取数据的服务器。数据已经在你的服务器上了(基于comments.json文件),所以一旦数据被取出,this.state.data将会变成这样:

    [ {"id": "1", "author": "Pete Hunt", "text": "This is one comment"}, {"id": "2", "author": "Jordan Walke", "text": "This is *another* comment"} ] // tutorial13.js 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是个在组件第一次被渲染后自动被React调用的方法。动态更新的关键是调用this.setState()。我们用来自服务器的评论组替代旧的评论组,并且UI可以自动更新。 由于这个反应特性,添加实时更新只需要有也很小的改变。我们在这里使用的是简单的轮询,但是你可以使用WebSocket或者其他技术。

    // tutorial14.js var CommentBox = React.createClass({ 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) }); }, getInitialState: function() { return {data: []}; }, 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调用移到一个单独的方法中,并且在组件第一次被加载时以及之后每隔两秒时被调用。区运行你的浏览器并且修改 comments.json文件 (在你服务端的同一个目录下),两秒钟后你就可以看到变化!

    增加新评论

    现在我们建立表单。我们的 CommentForm 组件应该获取用户的姓名和评论文本,并且发送一个请求给服务端将评论保存下来。

    // tutorial15.js var 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> ); } });

    控制组件

    传统的DOM,input元素是由浏览器管理其状态(它渲染的值)。结果DOM的状态和组件的状态不同。视图状态与组件状态不同,这不是理想的。在React中,组件状态和视图状态始终一样,不仅限于初始化时相同。

    因此我们使用this.state来保存用户的输入。我们定义初始状态state,并赋予两个属性作者名author和评论文本text并置空。在我们的<input>元素中,我们使用value属性并反映组件的状态 state,另外附上onChange事件。 这些 含有待赋值的<input>元素叫做控制组件。更多关于控制组件的内容可以参考表单。

    // tutorial16.js var 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.text} onChange={this.handleTextChange} /> <input type="submit" value="Post" /> </form> ); } });

    事件

    React为组件附上事件并用骆驼拼写法命名。我们为两个<input>元素附上 onChange 事件。现在作为用户在<input>区域输入文本,被附上的 onChange被激活,组件状态被改变。随后,<input>元素渲染的值将会被刷新,当前组件状态state也随之改变。

    提交表单

    下面我们让表单交互化。当用户提交表单,我们应该清空表单,并向服务端发送请求,刷新评论列表。首先我们要坚监听表单提交事件并清空它。

    // tutorial17.js var 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}); }, handleSubmit: function(e) { e.preventDefault(); var author = this.state.author.trim(); var text = this.state.text.trim(); if (!text || !author) { 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} onChange={this.handleTextChange} /> <input type="submit" value="Post" /> </form> ); } });

    我们为表单附上onSubmit句柄,这样表单被有效填写并提交后表单区域会立即清空。

    调用 preventDefault() 来避免浏览器默认提交表单的动作。

    作为属性回调

    当一个用户提交一个评论,我们需要刷新列表把这个新评论放进来。在 CommentBox中完成这个逻辑非常合理,因为CommentBox拥有评论列表的状态。

    我们需要从子组件中将数据传给父组件。我们在父组件 render方法中传一个回调函数 (handleCommentSubmit)给子组件,将它绑定到子组件的onCommentSubmit事件。一旦事件被激活,回调就被唤醒:

    // tutorial18.js var CommentBox = React.createClass({ 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) { // TODO: submit to the server and refresh the list }, getInitialState: function() { return {data: []}; }, 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> ); } });

    既然 CommentBox 已经让 CommentForm通过onCommentSubmit属性获得回调,当用户提交表单时CommentForm可以调用回调:

    // tutorial19.js var 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}); }, handleSubmit: function(e) { e.preventDefault(); var author = this.state.author.trim(); var text = this.state.text.trim(); if (!text || !author) { 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} onChange={this.handleTextChange} /> <input type="submit" value="Post" /> </form> ); } });

    现在回调已经在了,我们要去做的就是提交给服务端并刷新列表:

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

    优化: 优化的刷新策略

    我们的应用现在已经具备了各功能了,但是在提交的评论出现在列表上之前还需要等待相应,这感觉上有点慢。我们可以直接将评论添加到列表中来使应用更快。

    // tutorial21.js var CommentBox = React.createClass({ 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; // Optimistically set an id on the new comment. It will be replaced by an // id generated by the server. In a production application you would likely // not use Date.now() for this and would have a more robust system in place. comment.id = Date.now(); 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) }); }, getInitialState: function() { return {data: []}; }, 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> ); } });

    转载自 并发编程网 - ifeve.com

    相关资源:react-tutorial::tiger2:一些React教程-《 React学习笔记》-源码
    最新回复(0)