React剛開始紅的時候,由於對其不甚了解,覺得JSX的寫法略非主流,故一直沒打算將其應用在項目上,隨着身邊大神們的科普,才后知后覺是個好東西。
好在哪里呢?個人拙見,有倆點:
1. 虛擬DOM —— 在DOM樹的狀態需要發生變化時,虛擬DOM機制會將同一Event loop前后的DOM樹進行對比(自然通過一系列高效的算法),如果倆個DOM樹存在不一樣的地方,那么React僅僅會針對這些不一樣的區域(DOM diff)來進行響應的DOM修改,從而實現最高效的DOM操作和渲染。
如下圖,我們修改了DOM樹上一些節點(或UI組件)對應綁定的state(不知道state是什么沒關系,后續我們會提及),React會即刻將其標記為“臟狀態”,在一個Event loop結束時(即使過程中你對某個組件的state進行了多次修改),React會計算得出DOM樹上需要修改的地方(“臟”了的地方,如下圖紅點)及其最終的狀態,並僅僅針對這些地方進行一次性的重新渲染。
於是好處顯而易見,並非每修改一次組件的state,就會重新渲染一次,而是在Event loop結束后做一次“秋后算賬”,減少冗余的DOM操作。另外React只針對需要修改的地方來做新的渲染,而非重新渲染整個DOM樹,自然效率很是不錯。
2. 組件可嵌套,而且,可以模版化嘛 —— 其實在React里提及的“組件”,常規是一些可封裝起來、復用的UI模塊,說的接地氣了可以理解為“帶有細粒度UI功能的部分DOM區域”。然后我們可以把這些組件層層嵌套起來使用(當然這樣組件間會存在依賴關系)。
至於模塊化,類似於ejs那樣可以作為獨立的模塊被引用到頁面上來復用,不過咧,它可以直接把UI組件當作腳本模塊那樣來使用,咱完全可以配合CommonJS、AMD、CMD等規范來require需要的組件模塊,並處理好它們的依賴關系(是不是碉堡了)。
基於上述的倆點,自然的也打算投入React懷抱了。不過在這之前得先理清倆點事情:
1. React是一個純View層,不擅長於和動態數據打交道(哎喲咱也不談Flux了,Flux的概念其實也不完善),因此它不同於,也替代不了常規的MV*框架;
2. React很擅長於處理組件化的頁面,在頁面上搭組件的形式有點像搭樂高一樣,因此用上React的項目需求常規為界面組件化。另外React只支持到IE8+,就天朝的情況,是否使用React還是得稍微斟酌一番。
嘮嗑了這么多,下面開始進入React的入門課程。本章提及的代碼都可以在我的Github上下載到。
JSX
JSX是React編寫組件的一種語法規范,可以看為是js的擴展,它支持將HTML和JS混寫在一起,最終會通過React提供的 JSXTransformer.js 來將其編譯為常規的js,方便瀏覽器解析。我們來個最簡單的例子:
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>最簡單的jsx</title> <script src="react.js"></script> <script src="JSXTransformer.js"></script> </head> <body> <div id="a">123</div> <script type="text/jsx"> var name = 'VaJoy'; React.render(<h1>{name}</h1>, document.getElementById('a')); </script> </body> </html>
頁面上我們引入了 react.js 和 JSXTransformer.js 倆個重要的腳本,前者自然是react的核心,后者則是將JSX語法轉為js語法。
實際上使用 JSXTransformer.js 在客戶端來解析JSX語法是一件冗余、耗性能的事情,我們可以在上線項目之前,事先使用諸如 gulp-react 的工具先把所有的JSX均轉換為js文件再引入到頁面中。
當然如果你掌握了react的JS寫法(當然相比JSX的學習成本要高不少),你可以直接在頁面上書寫相應的 原生js 即可,來個對比:
//使用JSX React.render( <div> <div> <div>content</div> </div> </div>, document.getElementById('example') ); //不使用JSX React.render( React.createElement('div', null, React.createElement('div', null, React.createElement('div', null, 'content') ) ), document.getElementById('example')
回到開頭第一段代碼段,我們將頁面的JSX直接寫在 <script type="text/jsx"> 中,注意 type 類型要標明為 "text/jsx" 。
然后我們定義了個變量name,並使用了React最基礎和最常用的一個方法 React.render() 來渲染DOM:
var name = 'VaJoy'; React.render(<h1>{name}</h1>, document.getElementById('a'));
React.render() 支持兩個參數(其實還有第三個可選的參數,作為渲染后的回調),第一個參數為模板的渲染內容(HTML形式),第二個參數表示要插入這段模板的DOM節點(DOM node)。
這里要提及一個知識點 —— 在JSX中,遇到 HTML 標簽(以 < 開頭),將用 HTML 規則解析;遇到代碼塊(以 { 開頭),則用 JavaScript 規則解析。所以我們在<h1>中嵌入變量 name 時是以花括號的形式 {name} 來實現的。
至於執行結果,相信大家很容易猜出來:
即往div里插入了(其實應該說是徹底替換為)JSX中的模板內容(<h1>元素)。
鑒於JSX支持HTML跟JS的混寫,故其靈活性很高,我們試着把變量換為數組:
var arr = ['HELLO', "here is", 123, "VaJoy`s blog"]; React.render( <ul>{ arr.filter(function(v){ return typeof v === 'string' }).map(function(v){ return <li> {v} </li> }) }</ul>, document.getElementById('a') );
結果如下:
組件
開頭已經提及React是用於組件化開發的,組件可看為是其最小組成單位,那么什么是組件(component)呢?我們看看下方這張圖:
這是一個移動端上的UI界面,用於查詢員工列表和某員工的具體信息。那么我們按頁面上各功能來細分,以左側的搜索主頁界面而言,可以把它細分為SearchBar、EmployeeList、EmployeeListItem等UI組件,其中EmployeeList組件嵌套了多個EmployeeList組件,而這眾多組件的搭配組成了整個Hompage界面。
常規我們用React開發的頁面可以看為一個大組件,它由多個小組件搭建而成。
在JSX中,我們可以通過 React.createClass() 方法來創建一個組件(注意組件的名稱必須為大寫字母開頭),並以命名標簽的形式(<MyClassName />)來引用:
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>組件</title> <style> .nameStyle{ color: dodgerblue; } </style> <script src="react.js"></script> <script src="JSXTransformer.js"></script> </head> <body> <div id="a"></div> <script type="text/jsx"> var name = 'VaJoy'; var Demo = React.createClass({ //隨便建了個叫Demo的組件 render: function() { //每個組件都需要有自己的render方法 var a = "Hello,"; return ( <p className="nameStyle" >{a,name}</p> //當然你可以寫為{a}{name} ); } }); React.render( <Demo />, //引入組件Demo document.getElementById('a') ); </script> </body> </html>
注意每個組件都需要有一個render方法,用於輸出組件內容。另外組件DOM元素上的 class 屬性需要寫成 className ,for 屬性需要寫成 htmlFor ,這是因為 class 和 for 是 JavaScript 的保留字。
執行效果如下:
可以看到,如果在一個容器中引用了多個變量,React會對應每個文本節點來自動生成對應span標簽(React防止XSS的機制,因此要注意規避span的樣式污染!)
再來看個組件嵌套的示例,其實都很簡單:

<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>組件嵌套</title> <style> .nameStyle{ color: dodgerblue; } </style> <script src="react.js"></script> <script src="JSXTransformer.js"></script> </head> <body> <div id="a"></div> <script type="text/jsx"> var name = 'VaJoy', Demo = React.createClass({ render: function() { var a = "Hello,"; return <span className="nameStyle" >{a,name}</span>; } }), DemoWrap = React.createClass({ render: function() { return <ul>{ [1,2,3].map(function(v){ return <li style={{fontSize:'20px'}}>{v} <Demo /></li> //嵌套組件Demo }) }</ul>; } }); React.render( <DemoWrap />, //引入組件DemoWrap document.getElementById('a') ); </script> </body> </html>
注意在JSX里,給DOM設置 style 必須寫成 {{ marginRight : '10px', fontSize : '18px' }} 的映射形式(預防XSS),否則會報錯
執行結果(注意連空格都生成了一個span):
組件的“數據”
我們在前頭提及了,React只是一個純view層,並不涉及model。不過但對於React組件而言,它有兩種特殊的data —— Props 和 States。其中的 States 有點類似於各MV*框架中的model部分,可以稱為React的狀態機制,用於與用戶進行交互。
1. Props
props表示組件自身的屬性,也可以用於在嵌套的內外層組件中傳遞信息(常規是由父層組件傳遞給子層組件)。要注意它是不變的、也不應該嘗試去改變的。我們來個簡單的示例:
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>組件Props</title> <script src="react.js"></script> <script src="JSXTransformer.js"></script> </head> <body> <div id="a"></div> <script type="text/jsx"> var Component1 = React.createClass({ render: function() { return <p> {this.props.abc, this.props.name} </p>; } }); var Component2 = React.createClass({ render: function() { return ( <div className="commentList" onClick={this.handleClick}> <Component1 abc="你好!" name="張三" /> <Component1 abc="Hi!" name="李四" /> </div> ); }, handleClick: function (e) { console.log(this.props.name, e.target); } }); React.render( <Component2 name="我是Component2的name哦!" />, document.getElementById('a') ); </script> </body> </html>
這里我們注冊了倆個組件類 Component1 和 Component2 ,其中 Component2 內嵌了 Component1 。
我們先看 Component1 的定義:
var Component1 = React.createClass({ render: function() { return <p> {this.props.abc, this.props.name} </p>; } });
它返回了一個p標簽,其中的內容是 this.props.abc 和 this.props.name,這里的 this 指的是組件 Component1 本身,因此比如 this.props.name 獲取到的便是組件 Component1 自身的屬性 name 的值(abc 和 name 的值我們都在 Component2 中進行了定義)。
我們接着看 Component2:
var Component2 = React.createClass({ render: function() { return ( <div className="commentList" onClick={this.handleClick}> <Component1 abc="你好!" name="張三" /> <Component1 abc="Hi!" name="李四" /> </div> ); }, handleClick: function (e) { console.log(this.props.name, e.target); } });
在這里我們除了嵌入組件 Component1 並定義了它們的屬性值 abc 和 name ,也使用了React的事件機制——我們用 onClick 方法來觸發點擊事件,其對應的 this.handleClick 是我們自定義的一個方法,用於輸出 Component2 的 name 屬性和當前被點擊的目標元素。
最后我們用 React.render 方法渲染組件 Component2 ,並定義了它的 name 屬性:
React.render( <Component2 name="我是Component2的name哦!" />, document.getElementById('a') );
執行結果如下:
這里有點要留意的,無論是 props 組件屬性,或者是 onClick 事件,都是 React 內部的東西,我們在HTML上是看不到它們的:
順便提個Props的語法糖 —— 我們可以通過 “...this.props” 來將父層組件綁定的全部屬性都直接寫到子組件中:

<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>test</title> <style> .a{margin-right:30px;} </style> <script src="react.js"></script> <script src="JSXTransformer.js"></script> </head> <body> <script type="text/jsx"> var CheckLink = React.createClass({ render: function() { // 這樣會把 CheckList 所有的 props 復制到 <a> return <a {...this.props}>{this.props.children}</a>; } }); var arr = ['第一個連接', '第二個連接']; React.render( <div> <CheckLink href="/a.html" name="abc" className="a">{arr[0]}</CheckLink> <CheckLink href="/b.html" name="efg" className="a">{arr[1]}</CheckLink> </div> ,document.body ); </script> </body> </html>
執行結果:
Props也並非都是直接寫在組件標簽上的屬性,有一個例外 —— props.children,它表示當前組件的所有子節點(它們常規是在外部的組件賦予的):
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>props.children</title> <style> p:active{ color: deeppink; } </style> <script src="react.js"></script> <script src="JSXTransformer.js"></script> </head> <body> <div id="a"></div> <script type="text/jsx"> var Component1 = React.createClass({ render: function() { return <li>{this.props.children}</li> } }); var Component2 = React.createClass({ list: ['張三', '李四', '王五'], render: function() { return ( <ul> { this.list.map(function(item){ return <Component1>{item}</Component1> }) } </ul> ); } }); React.render( <Component2 />, document.getElementById('a') ); </script> </body> </html>
留意第27行的代碼 return <Component1>{item}</Component1> ,其中的{item}會作為組件Component1的子節點(即 props.children)來處理。
執行如下:
2. State
State 是組件的狀態,它是可變的,因此常用於跟用戶的交互。
不同於Props,我們需要在組件中使用 getInitialState() 方法來初始化組件的State,並使用 this.setState() 來修改State的值:
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>state</title> <style> p:active{ color: deeppink; } </style> <script src="react.js"></script> <script src="JSXTransformer.js"></script> </head> <body> <div id="a"></div> <script type="text/jsx"> var Component = React.createClass({ getInitialState: function() { return { isClick: !1 } }, render: function() { var text = this.state.isClick ? 'clicked!' : 'none click'; return <div> <input type="checkbox" onChange={this.clickCb} />{ text } </div>; }, clickCb: function(){ this.setState({ isClick : !this.state.isClick //點擊checkbox時改變state.isClick }) } }); React.render( <Component />, document.getElementById('a') ); </script> </body> </html>
我們先通過 getInitialState() 方法初始化了組件Component的State對象,里面有個 isClick 的屬性,默認值為 false。
接着在組件的 render 方法里定義了一個 text 變量,其值會根據 state.isClick 的變化而變化:
var text = this.state.isClick ? 'clicked!' : 'none click';
另外給checkbox控件綁定了React的 onChange 事件,checkbox的勾選狀態改變時觸發組件的 clickCb 自定義回調事件,它是這樣的:
clickCb: function(){ this.setState({ isClick : !this.state.isClick //點擊checkbox時改變state.isClick }) }
如注釋,它會通過 this.setState 方法來改變 state.isClick 的值,進而依賴於 state.isClick 的變量 text 的值也會間接變化:
本系列的第一章就暫時講到這里,也算是一個最基礎的入門吧。
共勉~