本文是一個篇 React JS快速入門教程 ,感興趣的同學參考學習下吧.
翻譯至官方文檔《Tutorial》http://非死book.github.io/react/docs/tutorial.html
React.js 是 非死book 推出的一個用來構建用戶界面的 JavaScript 庫。
在入門教程里,我們會創建一個簡單卻實用的評論盒子來作為我們的例子,你可以把它放進一個博客什么的。它實際上就是Disqus、LiveFyre、非死book等實時評論的基礎實現。
我們要實現的功能有:
- 瀏覽所有的評論
- 提交一個評論的表單
- 為你自定義的后端提供一個鉤子
此外,還有一些優化特性:
- 優化評論:在評論保存到服務器前,就在列表中將其顯示,這樣會感覺更快。
- 實時更新:當其它用戶做出評論后,評論列表就可以得到實時的更新。
- 支持Markdown格式:用戶可以用Markdown格式書寫內容。
第一步
在教程中,我們直接使用的是CDN上的Javascript框架文件。
下面,打開任意你喜歡的編輯器,創建一個新的HTML文檔:
<!-- template.html -->
<html>
<head>
<title>Hello React</title>
<script src="http://fb.me/react-0.12.0.js"></script>
<script src="http://fb.me/JSXTransformer-0.12.0.js"></script>
<script src="http://code.jquery.com/jquery-1.10.0.min.js"></script>
</head>
<body>
<div id="content"></div>
<script type="text/jsx">
// Your code here
</script>
</body>
</html>
此后的教程中,我們都將在這里的script標簽內編寫JavaScript代碼。
注意:
此處我們將jQuery包含了進來,但目的只是為了方便編寫ajax調用。但這不是在React中所必須做。
你的第一個組件
React所有的一切都是關于模塊化、復合化的組件。就我們的評論功能來說,我們將按照下面的組件結構來實現:
- CommentBox
- CommentList
- Comment
- CommentForm
我們先來創建一個CommentBox組件,它一開始只是一個簡單的<div>:
// tutorial1.js
var CommentBox = React.createClass({
render: function() {
return (
<div className="commentBox">
Hello, world! I am a CommentBox.
</div>
);
}
});
React.render(
<CommentBox />,
document.getElementById('content')
);
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."
)
);
}
});
React.render(
React.createElement(CommentBox, null),
document.getElementById('content')
);
這是一種可選的方式,但實際上可以發現JSX語法要比單純的Javascript語法要簡單。了解更多有關 JSX 語法的內容。
接下來做什么
下面我們要創建一個新的React組件,采取的方式是向 React.createClass() 傳遞一個Javascript對象,為組件添加一些方法。其中最重要的一個方法是 render,它會返回一個React組件樹,并最終被渲染成HTML。
div 標簽并不是真正的DOM節點,它們只是React div組件的實例。你可以把它想象成能由React識別并處理的一些標記或一段數據。React是安全的。我們并不生成HTML字符串,所以默認是XSS保護。
你可以返回一個由你或別人創建的組件樹,而不一定要返回基本的HTML。正因如此,React組件可以組合使用的:這是可維護前端的宗旨。
React.render() 初始化了一個根節點組件,然后啟動框架,并將標記注入到一個原生DOM元素中。這個DOM元素由第二個參數指定。
組建組件
接著我們創建 CommentList 和 CommentForm 基本骨架,它們同樣也是 div。注意,這段代碼要放在CommentBox 代碼的前面。
// 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>
);
}
});
下一步,更新 CommentBox 組件的代碼,使用新定義的兩個朋友:
// 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(tagName) ,并且不管其它的事情。這是了避免對全局命名空間的污染。
組件屬性
我們將創建一個第三方組件 Comment,它負責接收評論者的名字和評論的內容。對于每個單獨的評論,我們都可以重用這個組件的代碼。首先,我們向 CommentList 添加一些評論。
// tutorial4.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 傳遞了一些數據。比如,我們在一個 Comment中,向其傳遞了Pete Hunt(通過屬性)和一條評論(通過XML格式的子節點)。從父組件向子組件傳遞的數據被稱為props(單詞properties的縮寫)。
使用props
接下來,我們就來創建這個Comment組件。使用porps我們可以讀取從 CommentList 傳遞的數據,并渲染一些標記。
// tutorial5.js
var Comment = React.createClass({
render: function() {
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
{this.props.children}
</div>
);
}
});
在JSX中通過用括號括起來的JavaScript表達式,可以將文本或者React組件(可以是一個屬性或子元素)放進組件樹中。this.props 和嵌套元素 this.props.children 中的關鍵字props是傳遞給組件的命名屬性。
添加 Markdowen語法支持
Markdown文本支持內聯樣式。例如,用星號圍起的文本可以強調顯示。
首先,我們需要向程序中添加第三方的 Showdown 庫。這是一個支持Markdown并將其轉換為原始HTML代碼的JavaScript庫。我們需要向head中添加一段script標簽(我們已經包含了一些React的庫):
<!-- template.html -->
<head>
<title>Hello React</title>
<script src="http://fb.me/react-0.12.0.js"></script>
<script src="http://fb.me/JSXTransformer-0.12.0.js"></script>
<script src="http://code.jquery.com/jquery-1.10.0.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/showdown/0.3.1/showdown.min.js"></script>
</head>
下一步,我們將評論的文本做Markdown轉換并輸出:
// tutorial6.js
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>
);
}
});
此處,我們增加了對Showdown庫的調用。為了將 this.props.children 從React包裝了的文本轉換成Showdown可以接受的原始字符串,我們顯式地調用了 toString()。
但是,這里有一個問題需要解決。我們最后渲染出來的評論內容在瀏覽器中看起來卻是這樣的形式:"<p>This is <em>another</em> comment</p>"。我們想要的是讓這些標簽都能被渲染為實際的HTML。
這種處理方式是為了防止XSS攻擊。有一種方式可以跳過,但是框架會警告你不要使用這種方式。
// tutorial7.js
var converter = new Showdown.converter();
var Comment = React.createClass({
render: function() {
var rawMarkup = converter.makeHtml(this.props.children.toString());
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
<span dangerouslySetInnerHTML={{__html: rawMarkup}} />
</div>
);
}
});
這個特殊的API的目的是讓插入原生的HTML代碼顯得困難,但是為Showdown我們還是利用了這個后門。
記住:使用這個特征時,你必須確定Showdown是安全的。
連接數據模型
目前為止,我們是直接在源代碼中插入評論。下面,我們將在評論列表中渲染一段JSON數據。最終,我們將從服務器端獲取,但是現在,我們把它直接寫在代碼中:
// tutorial8.js
var data = [
{author: "Pete Hunt", text: "This is one comment"},
{author: "Jordan Walke", text: "This is *another* comment"}
];
我們需要以編寫模塊的方式將數據data添加進 CommentList.因此,我們修改 CommentBox 組件 以及 React.render() 調用的代碼,將data通過props進行傳遞。
// tutorial9.js
var CommentBox = React.createClass({
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.props.data} />
<CommentForm />
</div>
);
}
});
React.render(
<CommentBox data={data} />,
document.getElementById('content')
);
現在,data已經被傳遞進了 CommentList,那么讓我們來動態地呈現評論數據:
// tutorial10.js
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>
);
}
});
就是這樣!
從服務器讀取
下面,我們用從服務器端讀取的動態數據來替換硬性編碼的數據。我們刪除了 data 屬性,改為采用 URL 來獲取:
// tutorial11.js
React.render(
<CommentBox url="comments.json" />,
document.getElementById('content')
);
這個組件和之前的組件的不同之處在于它必須預先自行渲染。在從服務器端獲得請求應答之前,它沒有可用的數據,而這些數據是組件呈現評論所必須的。
反應state
到現在為止,所有的組件都只是根據自身的props進行一次性的渲染。props 是不可變的:它們是從父組件傳遞過來,并且為父組件所有。為了實現交互,我們為組件引入了可變的 state 。this.state屬于組件的私有成員,并且可以通過調用 this.setState() 進行修改。當state更新之后,組件會立即對其自身進行重新渲染。
實際上在React代碼中,render() 方法被被聲明為 this.props 和 this.state 的函數,并由框架保證了UI總是與輸入保持一致。
當從服務器取得數據后,就可以對我們的評論數據進行修改。首先,我們向 CommentBox 組件的state添加一個包含評論數據的數組data:
// 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() 只執行一次,它負責對組件的state進行初始化。
更新state
在組件創建完畢后,我們還想要從服務器GET到JSON,從而更新state來反映最新的數據。在實際的應用中,我們可能創建的是一個動態的應用。但是,在例子中為了簡單,還是使用一個靜態的JSON文件:
// tutorial13.json
[
{"author": "Pete Hunt", "text": "This is one comment"},
{"author": "Jordan Walke", "text": "This is *another* comment"}
]
我們打算使用jQuery對服務器進行異步的訪問。
注意: 由于這是一個AJAX應用,因此你需要在一個web服務器上運行,而不能仍停留在文件系統。最簡單的方式是在應用的目錄下運行python -m SimpleHTTPServer。
// tutorial13.js
var CommentBox = React.createClass({
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
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組件渲染以后將被React調用的方法。動態更新的關鍵取決于 this.state的調用。在從服務器取得數據以后,我們就使用新數組替換評論組件的舊數據,并且讓它動態地改變。這種反應的方式,使得動態更新只是做了小小的改變。此處,我們使用的投票數據很簡單,你也可以很容易使用WebSockets或其它技術來獲得。
// tutorial14.js
var CommentBox = React.createClass({
loadCommentsFromServer: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
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>
);
}
});
React.render(
<CommentBox url="comments.json" 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>
);
}
});
讓我們來創建與表單的交互。當用戶點擊submit提交以后,我們需要將表單清空,并將一個請求發送到服務器,然后更新評論列表。那么,首先我們需要監聽表單的submit事件,并將其清空。
// tutorial16.js
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: send request to the server
this.refs.author.getDOMNode().value = '';
this.refs.text.getDOMNode().value = '';
return;
},
render: function() {
return (
<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>
);
}
});
事件Events
React向組件添加的事件處理函數使用的是駝峰命名規則。我們向表單添加了一個 onSumbit 的處理函數,它負責在輸入數據合法的表單提交后,將表單的字段清空。
在事件處理中,調用 preventDefault 是為了阻止瀏覽器默認的與表單提交有關的行為。
Refs
我們使用 ref 屬性向子組件分配了一個名字,并且通過 this.refs對組件進行引用。我們可以在一個組件上調用 getDOMNode 獲取一個原生DOM元素。
在props中定義回調函數
當用戶提交一條評論時,我們還需要對之前的評論列表進行更新,讓新的評論顯示進來。對于含有與呈現評論有關數據的state的CommentBox 來說,需要定義這樣的行為邏輯。
我們需要從子組件傳送數據到它的父組件。我們在父組件的 render 方法中將一個新的回調函數(handleCommentSubmit)傳遞給子組件,并將其綁定在子組件的 onCommentSubmit 事件上。當事件被觸發后,回調函數就會被執行。
// tutorial17.js
var CommentBox = React.createClass({
loadCommentsFromServer: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
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>
);
}
});
當用戶提交表單的時候,我們就從 CommentForm 調用回調函數。
// tutorial18.js
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;
}
this.props.onCommentSubmit({author: author, text: text});
this.refs.author.getDOMNode().value = '';
this.refs.text.getDOMNode().value = '';
return;
},
render: function() {
return (
<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>
);
}
});
現在,回調函數已經定義完畢。我們要做的就是提交新的評論到服務器,并刷新評論列表。
// tutorial19.js
var CommentBox = React.createClass({
loadCommentsFromServer: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
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>
);
}
});
優化:優化更新
現狀我們已經實現了這個應用的所有功能。但是,在從服務器完成請求之前,你必須等待評論在列表中出現。因此,會感覺有點慢。我們可以對它再做一點優化,讓它感覺更快一點。
// tutorial20.js
var CommentBox = React.createClass({
loadCommentsFromServer: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
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;
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) {
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>
);
}
});
祝賀你!
通過一些簡單的步驟,你已成功創建了一個評論盒子。你可以了解更多有關為什么使用React 或者深入的學習 API參考。 祝你順利!