Talk is cheap,Show me the code
javascript
近些時間一直在關注React,關於如何學習React能夠參照連接的文章自行制定計劃。千里之行,始於足下。本文是React官方的教程上的一個例子,經過詳細地學習,從中收穫很多,特此作了筆記,與你們共享交流進步。css
下載例子,而後進行解壓html
因爲採用的node環境,所以下載解壓以後,只需在所在目錄運行java
npm install node server.js
採用默認端口設置,只需打開瀏覽器,訪問http://localhost:3000/
node
react-tutorialreact
--node_modules --body-parser:express中間件,用於接收和解析json數據 --express:express框架 --public --css --base.css:基本樣式文件 --scripts -- example.js:React應用js文件 index.html:基本的HTML結構 --.editorconfig:用於在不一樣的編輯器中統一編輯風格(文件編碼)的配置文件 --.gitignore:git相關配置文件 --app.json:web app的相關信息 --comments.json:上傳的評論數據 --LICENSE:項目代碼使用協議 --package.json:項目所依賴的包,npm install的安裝包的配置文件 --README.md:項目說明書,裏面有使用說明 --requirements.txt:不清楚 --server.js:服務器端的js代碼
此項目構建了一個簡單的應用,如圖所示git
服務器端的功能仍是相對簡單的,經過代碼註釋的形式來分析github
導入了依賴的模塊web
var fs = require('fs'); //讀寫文件 var path = require('path'); //路徑 var express = require('express'); //express框架 var bodyParser = require('body-parser'); //中間件
生成app,而且進行配置ajax
//獲取comments.json文件的路徑 var COMMENTS_FILE = path.join(__dirname, 'comments.json'); //設置端口 app.set('port', (process.env.PORT || 3000)); //設置靜態文件的文件目錄路徑 app.use('/', express.static(path.join(__dirname, 'public'))); //啓用bodyParser中間件接收請求,而且接收並解析json數據 app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: true}));
設置響應頭部信息
app.use(function(req, res, next) { //容許跨域 CORS res.setHeader('Access-Control-Allow-Origin', '*'); //緩存設置 res.setHeader('Cache-Control', 'no-cache'); next(); });
設置get請求url對應的處理函數(獲取評論json數據)
app.get('/api/comments', function(req, res) { //讀取comments.json文件,而且解析爲json數據 fs.readFile(COMMENTS_FILE, function(err, data) { if (err) { console.error(err); process.exit(1); } //讀取成功後,返回 res.json(JSON.parse(data)); }); });
設置post請求url對應的處理函數(提交評論數據)
app.post('/api/comments', function(req, res) { //先讀取comments.json文件 fs.readFile(COMMENTS_FILE, function(err, data) { if (err) { console.error(err); process.exit(1); } //將文件內容解析爲json數據 var comments = JSON.parse(data); //獲取新評論 var newComment = { id: Date.now(), author: req.body.author, text: req.body.text, }; //添加json數組中 comments.push(newComment); //將json數據寫回到comments.json文件中,而且返回所有的評論數據 fs.writeFile(COMMENTS_FILE, JSON.stringify(comments, null, 4), function(err) { if (err) { console.error(err); process.exit(1); } res.json(comments); }); }); });
啓動,監聽端口
app.listen(app.get('port'), function() { console.log('Server started: http://localhost:' + app.get('port') + '/'); });
web端核心在於example.js
文件,結合官網的資料,咱們對這個應用進行分析,學習如何構建一個簡單的react應用。
React踐行了Web Components
的理念,依照組件化的開發方式,咱們來分析這個應用的組件結構(如圖所示):
便是:
-- CommentBox -- CommentList -- Comment -- CommentForm
組件之間的關係圖爲:
如上述的結構圖,咱們從最底層開始編寫組件Comment
,這個組件須要作兩件事情
接收上層組件CommentList
傳遞的數據,動態渲染虛擬DOM節點,則從props
中讀取數據
//評論人 {this.props.author} //評論的內容 {this.props.children}
因爲評論是支持MarkDown語法的,所以須要使用第三放庫marked
對用戶輸入的內容進行處理。
var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
此外,輸出的內容要解析爲HTML,而在默認狀況下,基於預防XSS攻擊的考慮,React對輸出的內容是不解析爲HTML的。此時,須要利用到特殊的屬性dangerouslySetInnerHTML
,要將內容放到一個對象的_html
屬性中,而後將這個對象賦值給dangerouslySetInnerHTML
屬性
var html = {_html:"輸出的html內容"}; <span dangerouslySetInnerHTML={html} />
var Comment = React.createClass({ rawMarkup : function() { var rawMarkup = marked(this.props.children.toString(),{sanitize:true}); return {_html : rawMarkup}; //React的規則,會讀取這個對象的_html內容, }, render : function() { return ( <div className="comment"> <h2 className="commentAuthor"> {this.props.author} </h2> <span dangerouslySetInnerHTML={this.rawMarkup()} /> </div> ); } });
組件CommentList
須要作的就是接收上一層組件CommentBox
傳遞過來的數據,而後根據數據生成多個子組件Comment
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> ); } })
在生成子組件Comment
時,將每一個子組件的key屬性設置爲comment.id
,這是由於key
是一個可選的惟一標識符,經過它能夠給組件設置一個獨一無二的鍵,並確保它在一個渲染週期中保持一致,使得React可以更加智能地決定應該重用一個組件,仍是銷燬並從新建立一個組件,進而提高渲染性能。
組件CommentForm
須要作的就是兩件事情
管理自身的狀態this.state
(即表單中輸入的評論人和評論內容)
當表單輸入發生變化時
當表單提交時
當submit事件觸發時,調用上一層組件CommentBox
的事件處理函數,改變組件CommentBox
的狀態。
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:''}); } });
在這裏有一個值得注意的點,那就是抽象的自定義事件commentSubmit
和真實的事件submit
之間的聯繫,這是一個至關實用的技巧,在接下來的章節能夠看到是如何實現的。
做爲整個應用的頂層組件,CommentBox
須要作的事情有:
從服務器端請求已有的評論數據
將新的評論數據上傳到服務器
管理自身的狀態,根據狀態對視圖進行渲染(狀態改變的示意圖以下)
var CommentBox = React.createClass({ getInitialState : function(){ return {data : []}; }, loadCommentsFromServer : function() { //使用了jQuery的Ajax $.ajax({ url : this.props.url, dataType : 'json', cache : false, success : function(data) { this.setState({data:data}); }.bind(this), error : function(xhr,status,err){ console.err(this.props.url,status,err.toString()); }.bind(this) }); }, componentDidMount : function() { /* 這個方法屬於React組件生命週期方法,在render方法成功調用而且真實的DOM 已經渲染以後,調用此方法,這個方法發送json數據請求,而且設置一個定時器 ,每隔一段時間就向服務器請求數據 */ this.loadCommentsFromServer(); setInterval(this.loadCommentsFromServer,this.props.pollInterval); }, handleCommentSubmit : function(comment) { /* 這個方法也是比較有意思: 1. 自定義了一個commentSubmit事件,而且此方法做爲該事件的處理函數。 2. 此方法是在子組件CommentForm的submit事件處理函數中調用 */ var comments = this.state.data; 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.err(this.props.url,status,err.toString()); }.bind(this) }); }, render : function() { return ( <div className="commentBox"> <h1>Comments</h1> <CommentList data={this.state.data} /> <CommentForm onCommentSubmit={this.handleCommentSubmit} /> </div> ); } });
最後,只需將組件CommentBox
掛載到真實的DOM節點上,就能夠看到效果了
ReactDOM.render( <CommentBox url="/api/comments" pollInterval={2000} />, document.getElementById('content') );