標籤:
本文是在閱讀學習了官方的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組件就大功告成了,執行個體連結。
利用React寫一個評論區組件(React初探)