《React官方文檔》之教程Tutorial
教程Tutorial
我們提供:
- 可以看到所有評論的視圖
- 提交評論的表單
- 通過Hooks可以自定義後端
它有如下特點:
- 優化的評論: 評論將會在保存到服務器上之前就出現在列表中,這樣看上去非常快。
- 實時更新: 其他用戶的評論會實時的被放到評論界麵。
- Markdown格式: 用戶可以使用Markdown來格式化他們的文本。
想要跳過這些隻看源碼?
運行一個服務器
首先,我們需要一個運行的服務器。它將作為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(自動重新加載),因此你需要在保存修改刷新瀏覽器。啟動服務後在瀏覽器中打開https://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知道如何處理的數據或者標識。Re
act是安全的。我們並不產生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
。修改CommentBox和
ReactDOM.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(th最後更新:2017-05-19 15:02:48
上一篇:
哪個線程執行 CompletableFuture’s tasks 和 callbacks?
下一篇:
《ELK Stack權威指南 》第3章 場景示例