React 起源於 Facebook 的內部項目,由於該公司對市場上全部 JavaScript MVC 框架,都不滿意,就決定本身寫一套,用來架設 Instagram 的網站。作出來之後,發現這套東西很好用,就在2013年5月開源了javascript
這個項目自己也越滾越大,從最先的UI引擎變成了一整套先後端通吃的 Web App 解決方案。衍生的 React Native 項目,目標更是宏偉,但願用寫 Web App 的方式去寫 Native App。若是可以實現,整個互聯網行業都會被顛覆,由於同一組人只須要寫一次 UI ,就能同時運行在服務器、瀏覽器和手機(參見《也許,DOM 不是答案》)。html
須要說明的是,React 能夠在瀏覽器運行,也能夠在服務器運行,可是本教程只涉及瀏覽器。一方面是爲了儘可能保持簡單,另外一方面 React 的語法是一致的,服務器的用法與瀏覽器差異不大。java
使用 React 的網頁源碼,結構大體以下。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"
。git
其次,上面代碼一共用了三個庫: react.js
、react-dom.js
和 Browser.js
,它們必須首先加載。其中,react.js
是 React 的核心庫,react-dom.js
是提供與 DOM 相關的功能,Browser.js
的做用是將 JSX 語法轉爲 JavaScript 語法,這一步很消耗時間,實際上線的時候,應該將它放到服務器完成。github
ReactDOM.render 是 React 的最基本方法,用於將模板轉爲 HTML 語言,並插入指定的 DOM 節點。算法
ReactDOM.render( <h1>Hello, world!</h1>, document.getElementById('example') );
HTML 語言直接寫在 JavaScript 語言之中,不加任何引號,這就是 JSX語法。JSX 是 Javascript 代碼裏直接寫XML的語法。
後端
var names = ['Alice', 'Emily', 'Kate']; ReactDOM.render( <div style={{color: '#f00', height:'40px', lineHeight: '40px'}}> { names.map(function (name) { return <div>Hello, {name}!</div> }) } </div>, document.getElementById('example') );
上面代碼體現了 JSX 的基本語法規則:遇到 HTML 標籤(以 <
開頭),就用 HTML 規則解析;遇到代碼塊(以 {
開頭),就用 JavaScript 規則解析。api
JSX 容許直接在模板插入 JavaScript 變量。若是這個變量是一個數組,則會展開這個數組的全部成員數組
var arr = [ <h1>Hello world!</h1>, <h2>React is awesome</h2>, ]; ReactDOM.render( <div>{arr}</div>, document.getElementById('example') );
上面代碼的arr
變量是一個數組,結果 JSX 會把它的全部成員,添加到模板,運行結果以下。
React 容許將代碼封裝成組件(component),而後像插入普通 HTML 標籤同樣,在網頁中插入這個組件。React.createClass 方法就用於生成一個組件類.
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
組件包含了兩個頂層標籤:h1
和p
。
組件的用法與原生的 HTML 標籤徹底一致,能夠任意加入屬性,好比 <HelloMessage name="John">
,就是 HelloMessage
組件加入一個 name
屬性,值爲 John
。組件的屬性能夠在組件類的 this.props
對象上獲取,好比 name
屬性就能夠經過 this.props.name
讀取。運行結果以下:
添加組件屬性,有一個地方須要注意,就是 class
屬性須要寫成 className
,for
屬性須要寫成 htmlFor
,這是由於 class
和 for
是 JavaScript 的保留字。
this.props
對象的屬性與組件的屬性一一對應,可是有一個例外,就是 this.props.children
屬性。它表示組件的全部子節點
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
的方法,請參考官方文檔。
組件的屬性能夠接受任意值,字符串、對象、函數等等均可以。有時,咱們須要一種機制,驗證別人使用組件時,提供的參數是否符合要求。
組件類的PropTypes
屬性,就是用來驗證組件實例的屬性是否符合要求.
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"。
組件並非真實的 DOM 節點,而是存在於內存之中的一種數據結構,叫作虛擬 DOM (virtual DOM)。只有當它插入文檔之後,纔會變成真實的 DOM 。根據 React 的設計,全部的 DOM 變更,都先在虛擬 DOM 上發生,而後再將實際發生變更的部分,反映在真實 DOM上,這種算法叫作 DOM diff ,它能夠極大提升網頁的性能表現。
可是,有時須要從組件獲取真實 DOM 的節點,這時就要用到 ref
屬性
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
、Copy
、Scroll
等,完整的事件清單請查看官方文檔。
組件免不了要與用戶互動,React 的一大創新,就是將組件當作是一個狀態機,一開始有一個初始狀態,而後用戶互動,致使狀態變化,從而觸發從新渲染 UI
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
是會隨着用戶互動而產生變化的特性
用戶在表單填入的內容,屬於用戶跟組件的互動,因此不能用 this.props
讀取
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
元素都屬於這種狀況,更多介紹請參考官方文檔。
組件的生命週期分紅三個狀態:
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):組件判斷是否從新渲染時調用
這些方法的詳細說明,能夠參考官方文檔。下面是一個例子
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 語法,第二重大括號表示樣式對象
組件的數據來源,一般是經過 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對象傳入組件,
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> ); } } });