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 入門例項教程》
- 慕課網的《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-mobile,適用於手機的富文字編輯器
-------------------------------------------------------------------------------------------------------------