《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(自動重新載入),因此你需要在儲存修改重新整理瀏覽器。啟動服務後在瀏覽器中開啟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知道如何處理的資料或者標識。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(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
相關文章
- lxml官方入門教程(The lxml.etree Tutorial)翻譯XML
- 跟著 React 官方文件學 HooksReactHook
- Python 官方文件:入門教程Python
- [譯] React-Redux 官方 Hooks 文件說明ReactReduxHook
- 通讀Python官方文件之cgitbPythonGit
- 通讀Python官方文件之ThreadingPythonthread
- 從官方文件去學習之FreeMarker
- 史上最全cudnn 安裝教程,來源與官方文件DNN
- nmap官方文件
- Django官方文件Django
- [react-control-center tutorial 1] 啟動ccReact
- MySQL8.0.28安裝教程全程參考MySQL官方文件MySql
- OKHttp 官方文件【一】HTTP
- OKHttp 官方文件【二】HTTP
- mORMot and Open Source friends SynProject Tutorial (SynProject教程)ORMProject
- 基於React 原始碼深入淺出setState:官方文件的啟示錄React原始碼
- kafka官方幫助文件Kafka
- Oracle OCP(33):官方文件Oracle
- Moya官方文件翻譯
- 比官方文件更易懂的Vue.js教程!包你學會!Vue.js
- Learn Forge tutorial - 嚮導式Forge進階教程
- EventBus官方教程
- 面向官方文件學習Pytoch
- UNITY官方文件:事件函式Unity事件函式
- Kafka官方文件V2.7Kafka
- [譯] AsyncDisplayKit/Texture 官方文件(2)
- docker官方文件翻譯3Docker
- [譯] AsyncDisplayKit/Texture 官方文件(1)
- 【Python】官方文件中文版Python
- rabbitmq 官方文件翻譯-2MQ
- docker官方文件翻譯4Docker
- docker官方文件翻譯5Docker
- docker官方文件翻譯2Docker
- docker官方文件翻譯1Docker
- LIRE教程之原始碼分析 | LIRE Tutorial of Analysis of the Source Code原始碼
- pytest用法(直接截圖官方文件)
- go package官方文件閱讀方式GoPackage
- HTTPie 官方文件中文翻譯版HTTP
- [譯] Retrofit官方文件最佳實踐