搬一篇我崇拜的阮大神的一篇博客,但願能幫助到各位,能學到更多

React 入門實例教程

 

做者: 阮一峯javascript

日期: 2015年3月31日html

如今最熱門的前端框架,毫無疑問是 React 。前端

上週,基於 React 的 React Native 發佈,結果一天以內,就得到了 5000 顆星,受矚目程度可見一斑。java

React 起源於 Facebook 的內部項目,由於該公司對市場上全部 JavaScript MVC 框架,都不滿意,就決定本身寫一套,用來架設Instagram 的網站。作出來之後,發現這套東西很好用,就在2013年5月開源了。react

因爲 React 的設計思想極其獨特,屬於革命性創新,性能出衆,代碼邏輯卻很是簡單。因此,愈來愈多的人開始關注和使用,認爲它多是未來 Web 開發的主流工具。git

這個項目自己也越滾越大,從最先的UI引擎變成了一整套先後端通吃的 Web App 解決方案。衍生的 React Native 項目,目標更是宏偉,但願用寫 Web App 的方式去寫 Native App。若是可以實現,整個互聯網行業都會被顛覆,由於同一組人只須要寫一次 UI ,就能同時運行在服務器、瀏覽器和手機(參見《也許,DOM 不是答案》)。github

既然 React 這麼熱門,看上去充滿但願,固然應該好好學一下。從技術角度,能夠知足好奇心,提升技術水平;從職業角度,有利於求職和晉升,有利於參與潛力大的項目。可是,好的 React 教程卻不容易找到,這一方面由於這項技術太新,剛剛開始走紅,你們都沒有經驗,還在摸索之中;另外一方面由於 React 自己還在不斷變更,API 一直在調整,至今沒發佈1.0版。算法

我學習 React 時,就很苦惱。有的教程討論一些細節問題,對入門沒幫助;有的教程寫得不錯,但比較短,無助於看清全貌。我斷斷續續學了幾個月,看過二十幾篇教程,在這個過程當中,將對本身有幫助的 Demo 都收集下來,作成了一個庫 React Demos 。後端

下面,我就根據這個庫,寫一篇全面又易懂的 React 入門教程。你只須要跟着每個 Demo 作一遍,就能初步掌握 React 。固然,前提是你必須擁有基本 JavaScript 和 DOM 知識,可是你讀完就會發現,React 所要求的預備知識真的不多。react-native

零、安裝

React 的安裝包,能夠到官網下載。不過,React Demos 已經自帶 React 源碼,不用另外安裝,只需把這個庫拷貝到你的硬盤就好了。

$ git clone .com:ruanyf/react-demos.git 

若是你沒安裝 git, 那就直接下載 zip 壓縮包

下面要講解的12個例子在各個 Demo 子目錄,每一個目錄都有一個 index.html 文件,在瀏覽器打開這個文件(大多數狀況下雙擊便可),就能馬上看到效果。

須要說明的是,React 能夠在瀏覽器運行,也能夠在服務器運行,可是本教程只涉及瀏覽器。一方面是爲了儘可能保持簡單,另外一方面 React 的語法是一致的,服務器的用法與瀏覽器差異不大。Demo13 是服務器首屏渲染的例子,有興趣的朋友能夠本身去看源碼。

1、HTML 模板

使用 React 的網頁源碼,結構大體以下。

<!DOCTYPE html> <html> <head> <script src="../build/react.js"></script> <script src="../build/react-dom.js"></script> <script src="../build/browser.min.js"></script> </head> <body> <div id="example"></div> <script type="text/babel">  // ** Our code goes here! ** </script> </body> </html> 

上面代碼有兩個地方須要注意。首先,最後一個 <script> 標籤的 type 屬性爲 text/babel 。這是由於 React 獨有的 JSX 語法,跟 JavaScript 不兼容。凡是使用 JSX 的地方,都要加上 type="text/babel" 。

其次,上面代碼一共用了三個庫: react.js 、react-dom.js 和 Browser.js ,它們必須首先加載。其中,react.js 是 React 的核心庫,react-dom.js 是提供與 DOM 相關的功能,Browser.js 的做用是將 JSX 語法轉爲 JavaScript 語法,這一步很消耗時間,實際上線的時候,應該將它放到服務器完成。

$ babel src --out-dir build 

上面命令能夠將 src 子目錄的 js 文件進行語法轉換,轉碼後的文件所有放在 build 子目錄。

2、ReactDOM.render()

ReactDOM.render 是 React 的最基本方法,用於將模板轉爲 HTML 語言,並插入指定的 DOM 節點。

ReactDOM.render( <h1>Hello, world!</h1>, document.getElementById('example') ); 

上面代碼將一個 h1 標題,插入 example 節點(查看 demo01),運行結果以下。

3、JSX 語法

上一節的代碼, HTML 語言直接寫在 JavaScript 語言之中,不加任何引號,這就是 JSX 的語法,它容許 HTML 與 JavaScript 的混寫(查看 Demo02 )。

var names = ['Alice', 'Emily', 'Kate']; ReactDOM.render( <div> { names.map(function (name) { return <div>Hello, {name}!</div> }) } </div>, document.getElementById('example') ); 

上面代碼體現了 JSX 的基本語法規則:遇到 HTML 標籤(以 < 開頭),就用 HTML 規則解析;遇到代碼塊(以 { 開頭),就用 JavaScript 規則解析。上面代碼的運行結果以下。

JSX 容許直接在模板插入 JavaScript 變量。若是這個變量是一個數組,則會展開這個數組的全部成員(查看 demo03 )。

var arr = [ <h1>Hello world!</h1>, <h2>React is awesome</h2>, ]; ReactDOM.render( <div>{arr}</div>, document.getElementById('example') ); 

上面代碼的arr變量是一個數組,結果 JSX 會把它的全部成員,添加到模板,運行結果以下。

4、組件

React 容許將代碼封裝成組件(component),而後像插入普通 HTML 標籤同樣,在網頁中插入這個組件。React.createClass 方法就用於生成一個組件類(查看 demo04)。

var HelloMessage = React.createClass({ render: function() { return <h1>Hello {this.props.name}</h1>; } }); ReactDOM.render( <HelloMessage name="John" />, document.getElementById('example') ); 

上面代碼中,變量 HelloMessage 就是一個組件類。模板插入 <HelloMessage /> 時,會自動生成 HelloMessage 的一個實例(下文的"組件"都指組件類的實例)。全部組件類都必須有本身的 render 方法,用於輸出組件。

注意,組件類的第一個字母必須大寫,不然會報錯,好比HelloMessage不能寫成helloMessage。另外,組件類只能包含一個頂層標籤,不然也會報錯。

var HelloMessage = React.createClass({ render: function() { return <h1> Hello {this.props.name} </h1><p> some text </p>; } }); 

上面代碼會報錯,由於HelloMessage組件包含了兩個頂層標籤:h1p

組件的用法與原生的 HTML 標籤徹底一致,能夠任意加入屬性,好比 <HelloMessage name="John"> ,就是 HelloMessage 組件加入一個 name 屬性,值爲 John。組件的屬性能夠在組件類的 this.props 對象上獲取,好比 name 屬性就能夠經過 this.props.name 讀取。上面代碼的運行結果以下。

添加組件屬性,有一個地方須要注意,就是 class 屬性須要寫成 className ,for 屬性須要寫成 htmlFor ,這是由於 class 和 for 是 JavaScript 的保留字。

5、this.props.children

this.props 對象的屬性與組件的屬性一一對應,可是有一個例外,就是 this.props.children 屬性。它表示組件的全部子節點(查看 demo05)。

var NotesList = React.createClass({ render: function() { return ( <ol> { React.Children.map(this.props.children, function (child) { return <li>{child}</li>; }) } </ol> ); } }); ReactDOM.render( <NotesList> <span>hello</span> <span>world</span> </NotesList>, document.body ); 

上面代碼的 NoteList 組件有兩個 span 子節點,它們均可以經過 this.props.children 讀取,運行結果以下。

這裏須要注意, this.props.children 的值有三種可能:若是當前組件沒有子節點,它就是 undefined ;若是有一個子節點,數據類型是 object ;若是有多個子節點,數據類型就是 array 。因此,處理 this.props.children 的時候要當心。

React 提供一個工具方法 React.Children 來處理 this.props.children 。咱們能夠用 React.Children.map 來遍歷子節點,而不用擔憂 this.props.children 的數據類型是 undefined 仍是 object。更多的 React.Children 的方法,請參考官方文檔

6、PropTypes

組件的屬性能夠接受任意值,字符串、對象、函數等等均可以。有時,咱們須要一種機制,驗證別人使用組件時,提供的參數是否符合要求。

組件類的PropTypes屬性,就是用來驗證組件實例的屬性是否符合要求(查看 demo06)。

var MyTitle = React.createClass({ propTypes: { title: React.PropTypes.string.isRequired, }, render: function() { return <h1> {this.props.title} </h1>; } }); 

上面的Mytitle組件有一個title屬性。PropTypes 告訴 React,這個 title 屬性是必須的,並且它的值必須是字符串。如今,咱們設置 title 屬性的值是一個數值。

var data = 123; ReactDOM.render( <MyTitle title={data} />, document.body ); 

這樣一來,title屬性就通不過驗證了。控制檯會顯示一行錯誤信息。

Warning: Failed propType: Invalid prop `title` of type `number` supplied to `MyTitle`, expected `string`.

更多的PropTypes設置,能夠查看官方文檔

此外,getDefaultProps 方法能夠用來設置組件屬性的默認值。

var MyTitle = React.createClass({ getDefaultProps : function () { return { title : 'Hello World' }; }, render: function() { return <h1> {this.props.title} </h1>; } }); ReactDOM.render( <MyTitle />, document.body ); 

上面代碼會輸出"Hello World"。

7、獲取真實的DOM節點

組件並非真實的 DOM 節點,而是存在於內存之中的一種數據結構,叫作虛擬 DOM (virtual DOM)。只有當它插入文檔之後,纔會變成真實的 DOM 。根據 React 的設計,全部的 DOM 變更,都先在虛擬 DOM 上發生,而後再將實際發生變更的部分,反映在真實 DOM上,這種算法叫作 DOM diff ,它能夠極大提升網頁的性能表現。

可是,有時須要從組件獲取真實 DOM 的節點,這時就要用到 ref 屬性(查看 demo07 )。

var MyComponent = React.createClass({ handleClick: function() { this.refs.myTextInput.focus(); }, render: function() { return ( <div> <input type="text" ref="myTextInput" /> <input type="button" value="Focus the text input" onClick={this.handleClick} /> </div> ); } }); ReactDOM.render( <MyComponent />, document.getElementById('example') ); 

上面代碼中,組件 MyComponent 的子節點有一個文本輸入框,用於獲取用戶的輸入。這時就必須獲取真實的 DOM 節點,虛擬 DOM 是拿不到用戶輸入的。爲了作到這一點,文本輸入框必須有一個 ref 屬性,而後 this.refs.[refName] 就會返回這個真實的 DOM 節點。

須要注意的是,因爲 this.refs.[refName] 屬性獲取的是真實 DOM ,因此必須等到虛擬 DOM 插入文檔之後,才能使用這個屬性,不然會報錯。上面代碼中,經過爲組件指定 Click 事件的回調函數,確保了只有等到真實 DOM 發生 Click 事件以後,纔會讀取 this.refs.[refName] 屬性。

React 組件支持不少事件,除了 Click 事件之外,還有 KeyDown 、CopyScroll 等,完整的事件清單請查看官方文檔

8、this.state

組件免不了要與用戶互動,React 的一大創新,就是將組件當作是一個狀態機,一開始有一個初始狀態,而後用戶互動,致使狀態變化,從而觸發從新渲染 UI (查看 demo08 )。

var LikeButton = React.createClass({ getInitialState: function() { return {liked: false}; }, handleClick: function(event) { this.setState({liked: !this.state.liked}); }, render: function() { var text = this.state.liked ? 'like' : 'haven\'t liked'; return ( <p onClick={this.handleClick}> You {text} this. Click to toggle. </p> ); } }); ReactDOM.render( <LikeButton />, document.getElementById('example') ); 

上面代碼是一個 LikeButton 組件,它的 getInitialState 方法用於定義初始狀態,也就是一個對象,這個對象能夠經過 this.state 屬性讀取。當用戶點擊組件,致使狀態變化,this.setState 方法就修改狀態值,每次修改之後,自動調用 this.render 方法,再次渲染組件。

因爲 this.props 和 this.state 都用於描述組件的特性,可能會產生混淆。一個簡單的區分方法是,this.props 表示那些一旦定義,就再也不改變的特性,而 this.state 是會隨着用戶互動而產生變化的特性。

9、表單

用戶在表單填入的內容,屬於用戶跟組件的互動,因此不能用 this.props 讀取(查看 demo9 )。

var Input = React.createClass({ getInitialState: function() { return {value: 'Hello!'}; }, handleChange: function(event) { this.setState({value: event.target.value}); }, render: function () { var value = this.state.value; return ( <div> <input type="text" value={value} onChange={this.handleChange} /> <p>{value}</p> </div> ); } }); ReactDOM.render(<Input/>, document.body); 

上面代碼中,文本輸入框的值,不能用 this.props.value 讀取,而要定義一個 onChange 事件的回調函數,經過 event.target.value 讀取用戶輸入的值。textarea 元素、select元素、radio元素都屬於這種狀況,更多介紹請參考官方文檔

10、組件的生命週期

組件的生命週期分紅三個狀態:

  • Mounting:已插入真實 DOM
  • Updating:正在被從新渲染
  • Unmounting:已移出真實 DOM

React 爲每一個狀態都提供了兩種處理函數,will 函數在進入狀態以前調用,did 函數在進入狀態以後調用,三種狀態共計五種處理函數。

  • componentWillMount()
  • componentDidMount()
  • componentWillUpdate(object nextProps, object nextState)
  • componentDidUpdate(object prevProps, object prevState)
  • componentWillUnmount()

此外,React 還提供兩種特殊狀態的處理函數。

  • componentWillReceiveProps(object nextProps):已加載組件收到新的參數時調用
  • shouldComponentUpdate(object nextProps, object nextState):組件判斷是否從新渲染時調用

這些方法的詳細說明,能夠參考官方文檔。下面是一個例子(查看 demo10 )。

var Hello = React.createClass({ getInitialState: function () { return { opacity: 1.0 }; }, componentDidMount: function () { this.timer = setInterval(function () { var opacity = this.state.opacity; opacity -= .05; if (opacity < 0.1) { opacity = 1.0; } this.setState({ opacity: opacity }); }.bind(this), 100); }, render: function () { return ( <div style={{opacity: this.state.opacity}}> Hello {this.props.name} </div> ); } }); ReactDOM.render( <Hello name="world"/>, document.body ); 

上面代碼在hello組件加載之後,經過 componentDidMount 方法設置一個定時器,每隔100毫秒,就從新設置組件的透明度,從而引起從新渲染。

另外,組件的style屬性的設置方式也值得注意,不能寫成

style="opacity:{this.state.opacity};" 

而要寫成

style={{opacity: this.state.opacity}} 

這是由於 React 組件樣式是一個對象,因此第一重大括號表示這是 JavaScript 語法,第二重大括號表示樣式對象。

11、Ajax

組件的數據來源,一般是經過 Ajax 請求從服務器獲取,可使用 componentDidMount 方法設置 Ajax 請求,等到請求成功,再用 this.setState 方法從新渲染 UI (查看 demo11 )。

var UserGist = React.createClass({ getInitialState: function() { return { username: '', lastGistUrl: '' }; }, componentDidMount: function() { $.get(this.props.source, function(result) { var lastGist = result[0]; if (this.isMounted()) { this.setState({ username: lastGist.owner.login, lastGistUrl: lastGist.html_url }); } }.bind(this)); }, render: function() { return ( <div> {this.state.username}'s last gist is <a href={this.state.lastGistUrl}>here</a>. </div> ); } }); ReactDOM.render( <UserGist source="https://api.github.com/users/octocat/gists" />, document.body ); 

上面代碼使用 jQuery 完成 Ajax 請求,這是爲了便於說明。React 自己沒有任何依賴,徹底能夠不用jQuery,而使用其餘庫。

咱們甚至能夠把一個Promise對象傳入組件,請看Demo12

ReactDOM.render( <RepoList promise={$.getJSON('https://api.github.com/search/repositories?q=javascript&sort=stars')} />, document.body ); 

上面代碼從Github的API抓取數據,而後將Promise對象做爲屬性,傳給RepoList組件。

若是Promise對象正在抓取數據(pending狀態),組件顯示"正在加載";若是Promise對象報錯(rejected狀態),組件顯示報錯信息;若是Promise對象抓取數據成功(fulfilled狀態),組件顯示獲取的數據。

var RepoList = React.createClass({ getInitialState: function() { return { loading: true, error: null, data: null}; }, componentDidMount() { this.props.promise.then( value => this.setState({loading: false, data: value}), error => this.setState({loading: false, error: error})); }, render: function() { if (this.state.loading) { return <span>Loading...</span>; } else if (this.state.error !== null) { return <span>Error: {this.state.error.message}</span>; } else { var repos = this.state.data.items; var repoList = repos.map(function (repo) { return ( <li> <a href={repo.html_url}>{repo.name}</a> ({repo.stargazers_count} stars) <br/> {repo.description} </li> ); }); return ( <main> <h1>Most Popular JavaScript Projects in Github</h1> <ol>{repoList}</ol> </main> ); } } });
相關文章
相關標籤/搜索