React實踐(一)
該實踐取自官方教程:https://github.com/reactjs/react-tutorial
主要是自實現的過程以及一些心得體會
該實踐是實現一個評論框。
- 一個展示所有評論的視圖
- 一個提交評論的表單
- 與后台的接口hook
特點:
- 評論提交之前就先顯示在列表中,提高體驗
- 其他用戶的評論實時更新
- 可用markdown格式編寫文本
開始
下面就是我們的index.html模板文件,看官copy過去吧。之后的所有代碼都寫在script里面
1 <!-- index.html --> 2 <html> 3 <head> 4 <title>Hello React</title> 5 <script src="http://fb.me/react-0.13.0.js"></script> 6 <script src="http://fb.me/JSXTransformer-0.13.0.js"></script> 7 <script src="http://code.jquery.com/jquery-1.10.0.min.js"></script> 8 </head> 9 <body> 10 <div id="content"></div> 11 <script type="text/jsx"> 12 // Your code here 13 </script> 14 </body> 15 </html>
其中jquery不是對React必要的,只是我們想簡化ajax的代碼
組件結構
React是全組件化的,可組裝的。我們的組件結構如下:
- CommentBox
- CommentList
- Comment
- CommentForm
CommentBox
讓我們先來把最基本的組件構造出來
1 var CommentBox = React.createClass({
2 render: function() {
3 return (
4 <div className="commentBox">
5 Hello, world! I am a CommentBox.
6 </div>
7 );
8 }
9 });
10 React.render(
11 <CommentBox />,
12 document.getElementById('content')
13 );
從代碼不難看出,也不過是簡單的div罷了
CommentList、CommentForm
1 var CommentList = React.createClass({
2 render: function() {
3 return (
4 <div className="commentList">
5 Hello, world! I am a CommentList.
6 </div>
7 );
8 }
9 });
10
11 var CommentForm = React.createClass({
12 render: function() {
13 return (
14 <div className="commentForm">
15 Hello, world! I am a CommentForm.
16 </div>
17 );
18 }
19 });
這兩個組件也不過就是div而已
那我們根據組件結構,把這兩個組件放進CommentBox:
1 var CommentBox = React.createClass({
2 render: function() {
3 return (
4 <div className="commentBox">
5 <h1>Comments</h1>
6 <CommentList />
7 <CommentForm />
8 </div>
9 );
10 }
11 });
Comment
組件結構里,現在還剩CommentList里的Comment。我們想在評論中傳評論人和評論文本過去。那我們來實現一下:
1 var CommentList = React.createClass({
2 render: function() {
3 return (
4 <div className="commentList">
5 <Comment author="Pete Hunt">This is one comment</Comment>
6 <Comment author="Jordan Walke">This is *another* comment</Comment>
7 </div>
8 );
9 }
10 });
我們已經從父組件CommenList傳遞了一些數據給子組件Comment
那我們來實現Comment組件,看官應該還記得,我們通過this.props在子組件中獲取數據:
1 var Comment = React.createClass({
2 render: function() {
3 return (
4 <div className="comment">
5 <h2 className="commentAuthor">
6 {this.props.author}
7 </h2>
8 {this.props.children}
9 </div>
10 );
11 }
12 });
其中,this.props.children是任何內嵌的元素。
而前面說了,我們提供markdown格式的輸入,那就修改一下。
添加Markdown
這里我們要用到第三方庫Showdown,作用是處理Markdown文本且轉換成原始HTML。
我們先添加<script src="http://cdnjs.cloudflare.com/ajax/libs/showdown/0.3.1/showdown.min.js"></script>到head中
接着我們修改this.props.children,且添加Showdown的調用
1 var converter = new Showdown.converter();
2 var Comment = React.createClass({
3 render: function() {
4 return (
5 <div className="comment">
6 <h2 className="commentAuthor">
7 {this.props.author}
8 </h2>
9 {converter.makeHtml(this.props.children.toString())}
10 </div>
11 );
12 }
13 });
其中,為了轉換成Showdown能處理的原始字符串,所以顯示調用了toString()
但是React為了防止XSS,我們的顯示會類似這樣,<p>This is<em>another</em> comment</p>
並沒有渲染成真正的HTML
當然我們這里有一個方法:
1 var converter = new Showdown.converter();
2 var Comment = React.createClass({
3 render: function() {
4 var rawMarkup = converter.makeHtml(this.props.children.toString());
5 return (
6 <div className="comment">
7 <h2 className="commentAuthor">
8 {this.props.author}
9 </h2>
10 <span dangerouslySetInnerHTML={{__html: rawMarkup}} />
11 </div>
12 );
13 }
14 });
注意:框架會警告你別使用這種方法
接入數據模型
上面,我們是把數據直接寫入React中,實際開發中,數據是來自於數據庫的,那我們這里就暫且用hard code的形式寫在JSON對象中。
1 var data = [
2 {author: "Pete Hunt", text: "This is one comment"},
3 {author: "Jordan Walke", text: "This is *another* comment"}
4 ];
我們需要把數據通過props的形式傳到CommentList。而首先就是把數據傳入到CommentBox
這里是React的單向數據流。關於這個,會在之后另開一篇文章來研究下。
1 var CommentBox = React.createClass({
2 render: function() {
3 return (
4 <div className="commentBox">
5 <h1>Comments</h1>
6 <CommentList data={this.props.data} />
7 <CommentForm />
8 </div>
9 );
10 }
11 });
12
13 React.render(
14 <CommentBox data={data} />,
15 document.getElementById('content')
16 );
這時候,CommentList就可以使用數據了。讓我們來動態地去渲染評論,而不是之前一條一條地寫<Comment >xxx</Comment>
來看下代碼:
1 var CommentList = React.createClass({
2 render: function() {
3 var commentNodes = this.props.data.map(function (comment) {
4 return (
5 <Comment author={comment.author}>
6 {comment.text}
7 </Comment>
8 );
9 });
10 return (
11 <div className="commentList">
12 {commentNodes}
13 </div>
14 );
15 }
16 });
就這樣了。
從數據庫獲取數據
實際開發中,往往是后台提供了數據接口,而這時候,我們就需要這個接口跟我們上面的實現結合起來了。
而且,現在的這個組件在請求數據回來之前,是沒有數據的。並且,評論是需要更新的。
我們之前是通過data傳給CommentBox,每個組件也只在初始化的時候更新一次。
props是不可變的,它們從父節點傳過來,被父節點所擁有。而為了實現交互,這里就需要用到state,this.state是組件私有的,當state變化的時候,組件會重新渲染自己。
關於props和state,之后也會寫一篇來具體介紹一下。
1 React.render(
2 <CommentBox url="comments.json" />,
3 document.getElementById('content')
4 );
因為,我們這里沒有去實現后台,所以姑且用本地的文件comments.json來返回這個JSON對象,就創建一個comments.json文件放在index.html同目錄下,把下面的復制進去:
// comments.json
[
{"author": "Pete Hunt", "text": "This is one comment"},
{"author": "Jordan Walke", "text": "This is *another* comment"}
]
這里我們用的是JQ的AJAX,下面是修改后的CommentBox:
1 var CommentBox = React.createClass({
2 getInitialState: function() {
3 return {data: []};
4 },
5 componentDidMount: function() {
6 $.ajax({
7 url: this.props.url,
8 dataType: 'json',
9 success: function(data) {
10 this.setState({data: data});
11 }.bind(this),
12 error: function(xhr, status, err) {
13 console.error(this.props.url, status, err.toString());
14 }.bind(this)
15 });
16 },
17 render: function() {
18 return (
19 <div className="commentBox">
20 <h1>Comments</h1>
21 <CommentList data={this.state.data} />
22 <CommentForm />
23 </div>
24 );
25 }
26 });
這里我們給CommentBox添加了一個data數組的state,作為取到的評論的保存。
組件會在組件構建完后,去取數據,動態更新的要點就是this.setState()
而我們的評論是實時更新的,即別人如果在數據庫里添加了評論,那我們是要實時去檢測是否更新了的。
這里我們就簡單的用輪訓的方法:
1 var CommentBox = React.createClass({
2 loadCommentsFromServer: function() {
3 $.ajax({
4 url: this.props.url,
5 dataType: 'json',
6 success: function(data) {
7 this.setState({data: data});
8 }.bind(this),
9 error: function(xhr, status, err) {
10 console.error(this.props.url, status, err.toString());
11 }.bind(this)
12 });
13 },
14 getInitialState: function() {
15 return {data: []};
16 },
17 componentDidMount: function() {
18 this.loadCommentsFromServer();
19 setInterval(this.loadCommentsFromServer, this.props.pollInterval);
20 },
21 render: function() {
22 return (
23 <div className="commentBox">
24 <h1>Comments</h1>
25 <CommentList data={this.state.data} />
26 <CommentForm />
27 </div>
28 );
29 }
30 });
31
32 React.render(
33 <CommentBox url="comments.json" pollInterval={2000} />,
34 document.getElementById('content')
35 );
關於顯示的,我們就弄的差不多了。
接下來是我們的表單提交部分
添加新評論
我們的表單要求用戶輸入評論人和評論內容,當用戶提交表單的時候,會把數據提交到服務器,然后保存這條數據。
1 var CommentForm = React.createClass({
2 render: function() {
3 return (
4 <form className="commentForm">
5 <input type="text" placeholder="Your name" />
6 <input type="text" placeholder="Say something..." />
7 <input type="submit" value="Post" />
8 </form>
9 );
10 }
11 });
我們的表單是可交互的,所以這里要添加表單的提交事件,且刷新評論列表。為了更好的體驗,在提交完表單之后,表單應該是清空了的。
1 var CommentForm = React.createClass({
2 handleSubmit: function(e) {
3 e.preventDefault();
4 var author = this.refs.author.getDOMNode().value.trim();
5 var text = this.refs.text.getDOMNode().value.trim();
6 if (!text || !author) {
7 return;
8 }
9 // TODO: send request to the server
10 this.refs.author.getDOMNode().value = '';
11 this.refs.text.getDOMNode().value = '';
12 return;
13 },
14 render: function() {
15 return (
16 <form className="commentForm" onSubmit={this.handleSubmit}>
17 <input type="text" placeholder="Your name" ref="author" />
18 <input type="text" placeholder="Say something..." ref="text" />
19 <input type="submit" value="Post" />
20 </form>
21 );
22 }
23 });
其中,
- 我們利用了ref屬性給子組件命名,this.refs引用組件,getDOMNode()獲取本地的DOM元素。
- React使用駝峰命名的方式給組件綁定事件,我們給表單綁定了onSubmit()事件,當數據合法,清空輸入框
當用戶提交了表單后,我們需要添加我們的評論到評論列表。上面我們是給了commentBox一個state來保存評論列表。正是因為這樣,我們的所有邏輯在commentBox中完成是最合理的。因為我們需要從子組件傳回數據給父組件,這里我們把回調函數作為屬性傳給子組件。
1 var CommentBox = React.createClass({
2 loadCommentsFromServer: function() {
3 $.ajax({
4 url: this.props.url,
5 dataType: 'json',
6 success: function(data) {
7 this.setState({data: data});
8 }.bind(this),
9 error: function(xhr, status, err) {
10 console.error(this.props.url, status, err.toString());
11 }.bind(this)
12 });
13 },
14 handleCommentSubmit: function(comment) {
15 // TODO: submit to the server and refresh the list
16 },
17 getInitialState: function() {
18 return {data: []};
19 },
20 componentDidMount: function() {
21 this.loadCommentsFromServer();
22 setInterval(this.loadCommentsFromServer, this.props.pollInterval);
23 },
24 render: function() {
25 return (
26 <div className="commentBox">
27 <h1>Comments</h1>
28 <CommentList data={this.state.data} />
29 <CommentForm onCommentSubmit={this.handleCommentSubmit} />
30 </div>
31 );
32 }
33 });
當用戶提交表單的時候,調用回調函數:
1 var CommentForm = React.createClass({
2 handleSubmit: function(e) {
3 e.preventDefault();
4 var author = this.refs.author.getDOMNode().value.trim();
5 var text = this.refs.text.getDOMNode().value.trim();
6 if (!text || !author) {
7 return;
8 }
9 this.props.onCommentSubmit({author: author, text: text});
10 this.refs.author.getDOMNode().value = '';
11 this.refs.text.getDOMNode().value = '';
12 return;
13 },
14 render: function() {
15 return (
16 <form className="commentForm" onSubmit={this.handleSubmit}>
17 <input type="text" placeholder="Your name" ref="author" />
18 <input type="text" placeholder="Say something..." ref="text" />
19 <input type="submit" value="Post" />
20 </form>
21 );
22 }
23 });
回調函數等一切都搞定,現在把提交到服務器的代碼和刷新評論的代碼補上來:
1 var CommentBox = React.createClass({
2 loadCommentsFromServer: function() {
3 $.ajax({
4 url: this.props.url,
5 dataType: 'json',
6 success: function(data) {
7 this.setState({data: data});
8 }.bind(this),
9 error: function(xhr, status, err) {
10 console.error(this.props.url, status, err.toString());
11 }.bind(this)
12 });
13 },
14 handleCommentSubmit: function(comment) {
15 $.ajax({
16 url: this.props.url,
17 dataType: 'json',
18 type: 'POST',
19 data: comment,
20 success: function(data) {
21 this.setState({data: data});
22 }.bind(this),
23 error: function(xhr, status, err) {
24 console.error(this.props.url, status, err.toString());
25 }.bind(this)
26 });
27 },
28 getInitialState: function() {
29 return {data: []};
30 },
31 componentDidMount: function() {
32 this.loadCommentsFromServer();
33 setInterval(this.loadCommentsFromServer, this.props.pollInterval);
34 },
35 render: function() {
36 return (
37 <div className="commentBox">
38 <h1>Comments</h1>
39 <CommentList data={this.state.data} />
40 <CommentForm onCommentSubmit={this.handleCommentSubmit} />
41 </div>
42 );
43 }
44 });
代碼基本上都已經完成了。
還有一個點,我們可以做的,就是為了提高體驗,我們可以本地先把用戶提交的評論顯示出來,之后再異步提交到服務器,讓用戶覺得應用快快快。
1 var CommentBox = React.createClass({
2 loadCommentsFromServer: function() {
3 $.ajax({
4 url: this.props.url,
5 dataType: 'json',
6 success: function(data) {
7 this.setState({data: data});
8 }.bind(this),
9 error: function(xhr, status, err) {
10 console.error(this.props.url, status, err.toString());
11 }.bind(this)
12 });
13 },
14 handleCommentSubmit: function(comment) {
15 var comments = this.state.data;
16 var newComments = comments.concat([comment]);
17 this.setState({data: newComments});
18 $.ajax({
19 url: this.props.url,
20 dataType: 'json',
21 type: 'POST',
22 data: comment,
23 success: function(data) {
24 this.setState({data: data});
25 }.bind(this),
26 error: function(xhr, status, err) {
27 console.error(this.props.url, status, err.toString());
28 }.bind(this)
29 });
30 },
31 getInitialState: function() {
32 return {data: []};
33 },
34 componentDidMount: function() {
35 this.loadCommentsFromServer();
36 setInterval(this.loadCommentsFromServer, this.props.pollInterval);
37 },
38 render: function() {
39 return (
40 <div className="commentBox">
41 <h1>Comments</h1>
42 <CommentList data={this.state.data} />
43 <CommentForm onCommentSubmit={this.handleCommentSubmit} />
44 </div>
45 );
46 }
47 });
其實就添加了15-17行。
簡單吧~~~~~~~
這就是React官方提供的最基本的實踐~~~~~~
看到了這里的都是真愛~~~~~~~~~
晚安~~~~~~~~

