使用React並做一個簡單的to-do-list

王福朋發表於2016-03-22

1. 前言

說到React,我從一年之前就開始試著瞭解並且看了相關的入門教程,而且還買過一本《React:引領未來的使用者介面開發框架 》拜讀。React的輕量元件化的思想及其virtual-dom的這種技術創新,也算是早就有了初步瞭解。一來沒有學的太深入,二來後來在工作中和業餘專案中都沒有用到,因此慢慢的就更加生疏了。

近期,因為我想把自己的開源專案wangEditor能放在React、angular和vuejs中使用。先從react開始,順手自己也重試一下React的基礎知識,順便再做一個小demo,體驗一下React寫程式的一些提倡的思路。經過幾天的回顧學習,我也寫了一個在React中整合wangEditor的簡單demo:http://www.kancloud.cn/wangfupeng/wangeditor2/129376

不得不說一下,React風靡世界、功能強大、適應性強,但是入門起來相當簡單。反觀angularjs,學習成本就比較高,我還沒弄明白1.x呢,2.0已經出來了。縱然我非常努力,但是某些方面還是擺脫不了out的命運(見笑...)。

2. 基礎入門

想入門React,首先你得有比較紮實的javascript語法基礎以及前端開發的基礎知識,否則我下面推薦的教程講的再好,你也咂摸不出啥滋味來。所以如果你是初學者,不要被現在前端這些琳琅滿目的框架、庫迷惑了眼睛,以為學會了這個那個就行了——基礎不行學啥都白搭。

閒話不多扯。前人栽樹後人乘涼,給大家推薦兩個我看過的非常好的React入門教程,一個文字一個視訊。

這兩篇教程的篇幅都不長 ,閱讀加練習的話,兩個晚上(正常下班吃完飯之後的剩餘時間)絕對能搞定的。當然你如果具備程式設計師優質的熬夜技能,一晚上搞定也說不定啊,創造奇蹟的同時照顧一下身體,哈哈。看完這兩篇教程,你能基本瞭解react的設計思想,技術特點,使用的語法和技巧。做個hello word什麼的,完全沒啥問題的。

 

3. 入門之後

記得幾年前上大學乃至剛畢業那會兒,無論是學java還是php還是.net的,會了語法、會寫個hello world肯定不能算是入門的。當時跟hello world齊名還有一個東西叫做『留言板』。師生之間經常有這樣的對話。

  • 老師:xxx技術會用了嗎?
  • 學生:會了
  • 老師:那寫個留言板系統吧,能留言、檢視、刪除、回覆
  • 學生:不會
  • 老師:....

上述的『留言板』也隨著幾年之前流行的bbs、論壇、校內網等沒幾年就河了西了(《大宅門》學的),目前用來做demo的一般都是todolist,例如backbone的官方demo就是一個todolist。

無論是『留言板』還是『todolist』,我們需要用它來表達的就是——我們如何通過這項技術去實現基本的『增刪改查』 這種能力,因為一個系統其他所有的業務邏輯操作,都是『增刪改查』這幾個功能的拼接。所以,我們在剛剛接觸一個新東西的時候,就用它來做一個簡單的todolist吧。

 

4. todolist

做出來大約是這樣子的,很簡單很醜,too 羊 too 森破 sometime native 。不過沒關係,雖然它很醜,但是很溫柔啊。我們只是拋開了其他內容,專注於這項技術實現的本身而已。如果你想漂亮一點,自己寫一個css樣式嘍。

下面我將一步一步講解如何使用React來製作出一個簡單的todolist,不過還是需要你耐心把文章讀完,我也儘量寫的可讀性強一些,不至於太乏味。

4.1 整體分析

React最大的賣點是輕量元件化。我們分析一下以上截圖中的頁面,如果要分元件的話,我們大約可以分成一個總元件和兩個子元件。一個輸入內容的元件,一個顯示內容列表(帶刪除功能)的元件,外面再用一個總元件將兩個子元件包括起來。

因此,我們的程式碼的整體結構大約是這麼寫的:

 1 // TodoList 元件是一個整體的元件,最終的React渲染也將只渲染這一個元件
 2 // 該元件用於將『新增』和『列表』兩個元件整合起來
 3 var TodoList = React.createClass({
 4     render: function () {
 5         return (
 6             <div>
 7                 <TypeNew  />
 8                 <ListTodo />
 9             </div>
10         );
11     }
12 });
13 
14 // TypeNew 元件用於新增資料,
15 var TypeNew = React.createClass({
16     render: function () {
17         return (
18             <form>
19                 <input type="text" placeholder="typing a newthing todo" autoComplete="off" />
20             </form>
21         );
22     }
23 });
24 
25 // ListTodo 元件用於展示列表,並可以刪除某一項內容,
26 var ListTodo = React.createClass({
27     render: function () {
28         return (
29             <ul id="todo-list">
30                 {/* 其中顯示資料列表 */}
31             </ul>
32         );
33     }
34 });
35 
36 // 將 TodoList 元件渲染到頁面
37 React.render(<TodoList />, document.getElementById('container'));

 

4.2. 顯示資料

下面,我們要把todolist的資料,顯示到列表中,並且每個資料項後面都顯示一個『刪除』按鈕,就像這樣:

既然是展示資料,首先要考慮資料儲存在哪裡,來自於哪裡。現在這裡放一句話——React提倡所有的資料都是由父元件來管理,通過props的形式傳遞給子元件來處理——先記住,接下來再解釋這句話。

上文提到,做一個todolist頁面需要一個父元件,兩個子元件。父元件當然就是todolist的『總指揮』,兩個子元件分別用來add和show、delete。用通俗的方式講來,父元件就是領導,兩個子元件就是協助領導開展工作的,一切的資源和調動資源的權利,都在領導層級,子元件配合領導工作,需要資源或者調動資源,只能申請領導的批准

這麼說來就明白了吧。資料完全由父元件來管理和控制,子元件用來顯示、運算元據,得經過父元件的批准,即——父元件通過props的形式將資料傳遞給子元件,子元件拿到父元件傳遞過來的資料,再進行展示。

另外,根據React開發的規範,元件內部的資料由state控制,外部對內部傳遞資料時使用 props 。這麼看來,針對父元件來說,要儲存todolist的資料,那就是內部資訊(本身就是自己可控的資源,而不是『領導』控制的資源),用state來儲存即可。而父元件要將todolist資料傳遞給子元件,對子元件來說,那就是傳遞進來的外部資訊(是『領導』的資源,交付給你來處理),需要使用props。

好了,我們再修改一下程式碼,用程式碼表述一下這個問題:

 1 // TodoList 元件是一個整體的元件,最終的React渲染也將只渲染這一個元件
 2 // 該元件用於將『新增』和『列表』兩個元件整合起來
 3 var TodoList = React.createClass({
 4     // 初始化資料,todolist的資料由state來控制
 5     getInitialState: function () {
 6         return {
 7             todolist: []
 8         };
 9     },
10     render: function () {
11         return (
12             <div>
13                 <TypeNew  />
14                 {/*
15                     整合 ListTodo 元件
16                     todo - 將todolist的資料傳入到元件,用於元件展示資料
17                 */}
18                 <ListTodo todo={this.state.todolist} />
19             </div>
20         );
21     }
22 });
23 
24 // TypeNew 元件用於新增資料,
25 var TypeNew = React.createClass({
26     // 此處省略 ... 字
27 });
28 
29 // ListTodo 元件用於展示列表,並可以刪除某一項內容,
30 var ListTodo = React.createClass({
31     render: function () {
32         return (
33             <ul id="todo-list">
34             {
35                 // this.props.todo 獲取父元件傳遞過來的資料
36                 // {/* 遍歷資料 */}
37                 this.props.todo.map(function (item, i) {
38                     return (
39                         <li>
40                             <label>{item}</label>
41                             <button>delete</button>
42                         </li>
43                     );
44                 })
45             }
46             </ul>
47         );
48     }
49 });
50 
51 // 將 TodoList 元件渲染到頁面
52 React.render(<TodoList />, document.getElementById('container'));

 

4.3 新增資料

剛才都把資料展示講完了,但是想展示一下,目前還沒有資料呢,那就新增一個吧。如下圖:

根據剛才的拐彎抹角、高談闊論、旁徵博引的那幾句話,我們知道,子元件得到資料後,就需要將新資料新增到todolist的資料中。而todolist的資料是由父元件來管理的,子元件不能說改就改呀,得申請父元件的允許和同意呀。因此,我們需要讓父元件開放一個可以修改資料的介面,然後將這個介面作為props傳遞給子元件,讓其能修改資料。

另外,子元件呼叫父元件的介面對todolist資料進行修改了之後,相當於修改了React物件的state資料,此時就會觸發React的自動更新(就是通過virtual-dom對比,然後更新真實的dom那一套),React會將UI實時隨著資料更新,就不用我們操心了,這也是React比較強大的地方之一。

因此,程式碼將改為:

 1 // TodoList 元件是一個整體的元件,最終的React渲染也將只渲染這一個元件
 2 // 該元件用於將『新增』和『列表』兩個元件整合起來
 3 var TodoList = React.createClass({
 4     // 初始化資料,todolist的資料由state來控制
 5     getInitialState: function () {
 6         return {
 7             todolist: []
 8         };
 9     },
10     // 接收一個傳入的資料,並將它實時更新到元件的 state 中,以便元件根據資料重新render
11     // 只要改變了 state ,react自動執行 reader 計算
12     handleChange: function (rows) {
13         this.setState({
14             todolist: rows
15         });
16     },
17     render: function () {
18         return (
19             <div>
20                 {/* 
21                     整合 TypeNews 元件,傳入兩個屬性 onAdd 和 todo
22                     todo - 將todolist的資料傳入到元件,當新增時,更新todolist資料
23                     onAdd -  將 handleChange 函式傳入到元件,新增時,用它來處理最新的todolist資料
24                 */}
25                 <TypeNew onAdd={this.handleChange} todo={this.state.todolist} />
26                 {/*
27                     整合 ListTodo 元件
28                     todo - 將todolist的資料傳入到元件,用於元件展示資料
29                 */}
30                 <ListTodo todo={this.state.todolist} />
31             </div>
32         );
33     }
34 });
35 
36 // TypeNew 元件用於新增資料,它需要 todo 和 onAdd 兩個屬性,上文已經提到過
37 // 基本邏輯是:當從 input 中獲取資料時,將新資料 push 到todo中,
38 // 然後使用 onAdd 呼叫 TodoList 的 handleChange 來更新state,然後react自動render
39 var TypeNew = React.createClass({
40     handleAdd: function (e) {
41         e.preventDefault();
42         // 通過 refs 獲取dom元素,然後獲取輸入的內容
43         var inputDom = this.refs.inputnew.getDOMNode();
44         var newthing = inputDom.value.trim();
45         // 獲取傳入的todolist資料
46         var rows = this.props.todo;
47         if (newthing !== '') {
48             // 更新資料,並使用 onAdd 更新到 TodoList 元件的 state 中
49             rows.push(newthing);
50             this.props.onAdd(rows);
51         }
52         inputDom.value = '';
53     },
54     render: function () {
55         return (
56             // form submit 時,觸發 handleAdd 事件
57             <form onSubmit={this.handleAdd}>
58                 <input type="text" ref="inputnew" id="todo-new" placeholder="typing a newthing todo" autoComplete="off" />
59             </form>
60         );
61     }
62 });
63 
64 // ListTodo 元件用於展示列表,並可以刪除某一項內容,
65 var ListTodo = React.createClass({
66     render: function () {
67         return (
68             <ul id="todo-list">
69             {
70                 // this.props.todo 獲取父元件傳遞過來的資料
71                 // {/* 遍歷資料 */}
72                 this.props.todo.map(function (item, i) {
73                     return (
74                         <li>
75                             <label>{item}</label>
76                             <button>delete</button>
77                         </li>
78                     );
79                 }) 
80             }
81             </ul>
82         );
83     }
84 });
85 
86 // 將 TodoList 元件渲染到頁面
87 React.render(<TodoList />, document.getElementById('container'));

 

4.4 刪除資料

刪除資料和新增資料,邏輯上是一樣的,都是需要父元件提供一個修改資料的介面,通過props形式傳遞給子元件,然後讓子元件來呼叫。就不再贅述了,直接上程式碼,注意看註釋:

 1     // TodoList 元件是一個整體的元件,最終的React渲染也將只渲染這一個元件
 2     // 該元件用於將『新增』和『列表』兩個元件整合起來,並且儲存 todolist 的資料
 3     var TodoList = React.createClass({
 4         // 初始化資料
 5         getInitialState: function () {
 6             return {
 7                 todolist: []
 8             };
 9         },
10         // 接收一個傳入的資料,並將它實時更新到元件的 state 中,以便元件根據資料重新render
11         // 只要改變了 state ,react自動執行 reader 計算
12         handleChange: function (rows) {
13             this.setState({
14                 todolist: rows
15             });
16         },
17         render: function () {
18             return (
19                 <div>
20                     {/* 
21                         整合 TypeNews 元件,傳入兩個屬性 onAdd 和 todo
22                         todo - 將todolist的資料傳入到元件,當新增時,更新todolist資料
23                         onAdd -  將 handleChange 函式傳入到元件,新增時,用它來處理最新的todolist資料
24                     */}
25                     <TypeNew onAdd={this.handleChange} todo={this.state.todolist} />
26                     {/*
27                         整合 ListTodo 元件,傳入兩個屬性 onDel 和 todo
28                         todo - 將todolist的資料傳入到元件,當刪除時,更新todolist資料
29                         onDel - 將 handleChange 函式傳入到元件,刪除時,用它來處理最新的todolist資料
30                     */}
31                     <ListTodo onDel={this.handleChange} todo={this.state.todolist} />
32                 </div>
33             );
34         }
35     });
36 
37     // TypeNew 元件用於新增資料,它需要 todo 和 onAdd 兩個屬性,上文已經提到過
38     // 基本邏輯是:當從 input 中獲取資料時,將新資料 push 到todo中,
39     // 然後使用 onAdd 呼叫 TodoList 的 handleChange 來更新state,然後react自動render
40     var TypeNew = React.createClass({
41         handleAdd: function (e) {
42             e.preventDefault();
43             // 通過 refs 獲取dom元素,然後獲取輸入的內容
44             var inputDom = this.refs.inputnew.getDOMNode();
45             var newthing = inputDom.value.trim();
46             // 獲取傳入的todolist資料
47             var rows = this.props.todo;
48             if (newthing !== '') {
49                 // 更新資料,並使用 onAdd 更新到 TodoList 元件的 state 中
50                 rows.push(newthing);
51                 this.props.onAdd(rows);
52             }
53             inputDom.value = '';
54         },
55         render: function () {
56             return (
57                 // form submit 時,觸發 handleAdd 事件
58                 <form onSubmit={this.handleAdd}>
59                     <input type="text" ref="inputnew" id="todo-new" placeholder="typing a newthing todo" autoComplete="off" />
60                 </form>
61             );
62         }
63     });
64 
65     // ListTodo 元件用於展示列表,並可以刪除某一項內容,它有 noDel todo 兩個屬性,上文已經提到過
66     // 它的基本邏輯是:遍歷 todo 的內容,生成資料列表和刪除按鈕
67     // 對某一項執行刪除時,想將 todo 中的資料刪除,
68     // 然後通過 onDel 事件呼叫 TodoList 的 handleChange 來更新state,然後react自動render
69     var ListTodo = React.createClass({
70         handleDel: function (e) {
71             var delIndex = e.target.getAttribute('data-key');
72             // 更新資料,並使用 onDel 更新到 TodoList 的 state 中,以便 React自動render
73             this.props.todo.splice(delIndex, 1);
74             this.props.onDel(this.props.todo);
75         },
76         render: function () {
77             return (
78                 <ul id="todo-list">
79                 {
80                     // {/* 遍歷資料 */}
81                     this.props.todo.map(function (item, i) {
82                         return (
83                             <li>
84                                 <label>{item}</label>
85                                 <button className="destroy" onClick={this.handleDel} data-key={i}>delete</button>
86                             </li>
87                         );
88                     }.bind(this)) // {/* 繫結函式的執行this - 以便 this.handleDel */}
89                 }
90                 </ul>
91             );
92         }
93     });
94 
95     // 將 TodoList 元件渲染到頁面
96     React.render(<TodoList />, document.getElementById('container'));

 

5. 總結

入門React的基本語法和使用比較簡單,但是想要了解它的工作過程和基本的設計思想,還是需要一點時間的。接下來,在大型系統中使用React肯定又需要更多的時間,你可能還會遇到很多坑,等著你去填。

但是無論現在用還是不用,我們們都不能落伍,該學的還是得掌握一些比較好。大家共勉。

最後,此文章參考了 http://react-china.org/t/todolist/1075 感謝本文作者

-------------------------------------------------------------------------------------------------------------

歡迎關注我的教程:

使用grunt搭建全自動web前端開發環境json2.js原始碼解讀視訊

深入理解javascript原型和閉包系列》《css知多少》《微軟petshop4.0原始碼解讀視訊

------------------------------------------------------------------------------------------------------------

wangEditor,輕量化web富文字編輯器

wangEditor-mobile,適用於手機的富文字編輯器

-------------------------------------------------------------------------------------------------------------

 

相關文章