一個 React
應用就是構建在React組件
之上的。react
組件有兩個核心概念:數組
props
promise
state
瀏覽器
一個組件就是經過這兩個屬性的值在 render
方法裏面生成這個組件對應的 HTML
結構。緩存
注意:組件生成的
HTML
結構只能有一個單一的根節點。架構
propsapp
props
就是組件的屬性,由外部經過 JSX
屬性傳入設置,一旦初始設置完成,就能夠認爲 this.props
是不可更改的,因此不要輕易更改設置 this.props
裏面的值(雖然對於一個 JS
對象你能夠作任何事)。less
statedom
state
是組件的當前狀態,能夠把組件簡單當作一個「狀態機」
,根據狀態 state
呈現不一樣的 UI
展現。ide
一旦狀態(數據)更改,組件就會自動調用 render
從新渲染 UI
,這個更改的動做會經過 this.setState
方法來觸發。
一條原則:讓組件儘量地少狀態。
這樣組件邏輯就越容易維護。
什麼樣的數據屬性能夠看成狀態?
當更改這個狀態(數據)須要更新組件 UI
的就能夠認爲是 state
,下面這些能夠認爲不是狀態:
可計算的數據:好比一個數組的長度
和 props
重複的數據:除非這個數據是要作變動的
你也能夠用純粹的函數來定義無狀態的組件(stateless function)
,這種組件沒有狀態,沒有生命週期
,只是簡單的接受 props
渲染生成 DOM
結構。無狀態組件很是簡單,開銷很低,若是可能的話儘可能使用無狀態組件。好比使用箭頭函數定義:
const HelloMessage = (props) => <div>Hello {props.name}</div>; render(<HelloMessage name="Jim"/>, app);
由於無狀態組件只是函數
,因此它沒有實例返回
,這點在想用 refs
獲取無狀態組件的時候要注意。
通常來講,一個組件類由 extends Component
建立,而且提供一個 render
方法以及其餘可選的生命週期函數
、組件相關的事件或方法
來定義。
getInitialState
初始化 this.state
的值,只在組件裝載以前調用一次。
若是是使用 ES6
的語法,你也能夠在構造函數中初始化狀態
,好比:
class Counter extends Component { constructor(props) { super(props); this.state = {count: props.initialCount}; } render() { } }
只在組件建立時調用一次並緩存返回的對象
(即在 React.createClass
以後就會調用)。
由於這個方法在實例初始化以前調用,因此在這個方法裏面不能依賴 this
獲取到這個組件的實例。
在組件裝載以後,這個方法緩存的結果會用來保證訪問 this.props
的屬性時,當這個屬性沒有在父組件中傳入(在這個組件的 JSX
屬性裏設置),也老是有值的。
若是是使用 ES6
語法,能夠直接定義 defaultProps
這個類屬性來替代,這樣能更直觀的知道 default props
是預先定義好的對象值:
Counter.defaultProps = {initialCount: 0};
render
組裝生成這個組件的 HTML
結構(使用原生 HTML
標籤或者子組件),也能夠返回 null
或者 false
,這時候 ReactDOM.findDOMNode(this)
會返回 null
。
componentWillMount
只會在裝載以前調用一次,在 render
以前調用,你能夠在這個方法裏面調用 setState
改變狀態,而且不會致使額外調用一次 render
。
componentDidMount
只會在裝載完成以後調用一次,在 render
以後調用,從這裏開始能夠經過 ReactDOM.findDOMNode(this)
獲取到組件的 DOM
節點。
這些方法不會在首次 render
組件的週期調用
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
componentDidUpdate
componentWillUnmount
能夠看到 React
裏面綁定事件的方式和在 HTML
中綁定事件相似,使用駝峯式命名指定要綁定的 onClick
屬性爲組件定義的一個方法 {this.handleClick.bind(this)}
。
注意要顯式調用 bind(this)
將事件函數上下文綁定要組件實例上
,這也是 React
推崇的原則:沒有黑科技,儘可能使用顯式的容易理解的 JavaScript
代碼。
React
實現了一個「合成事件」層(synthetic event system
),這個事件模型保證了和 W3C 標準保持一致,因此不用擔憂有什麼詭異的用法,而且這個事件層消除了 IE 與 W3C 標準實現之間的兼容問題。
「合成事件」還提供了額外的好處:
事件委託
「合成事件」會以事件委託
(event delegation
)的方式綁定到組件最上層
,而且在組件卸載
(unmount
)的時候自動銷燬綁定的事件
。
什麼是「原生事件」?
好比你在 componentDidMount
方法裏面經過 addEventListener
綁定的事件就是瀏覽器原生事件。
使用原生事件的時候注意在 componentWillUnmount
解除綁定 removeEventListener
。
全部經過 JSX
這種方式綁定的事件都是綁定到「合成事件」,除非你有特別的理由,建議老是用 React
的方式處理事件。
Tips
若是混用「合成事件」和「原生事件」,好比一種常見的場景是用原生事件在 document
上綁定,而後在組件裏面綁定的合成事件想要經過 e.stopPropagation()
來阻止事件冒泡到 document
,這時候是行不通的,參見 Event delegation,由於 e.stopPropagation
是內部「合成事件」 層面的,解決方法是要用 e.nativeEvent.stopImmediatePropagation()
「合成事件」 的 event
對象只在當前 event loop
有效,好比你想在事件裏面調用一個 promise
,在 resolve
以後去拿 event
對象會拿不到(而且沒有錯誤拋出):
handleClick(e) { promise.then(() => doSomethingWith(e)); }
給事件處理函數傳遞額外參數的方式:bind(this, arg1, arg2, ...)
render: function() { return <p onClick={this.handleClick.bind(this, 'extra param')}>; }, handleClick: function(param, event) { // handle click }
大部分狀況下你不須要經過查詢 DOM
元素去更新組件的 UI
,你只要關注設置組件的狀態(setState
)。可是可能在某些狀況下你確實須要直接操做 DOM
。
首先咱們要了解 ReactDOM.render
組件返回的是什麼?
它會返回對組件的引用
也就是組件實例
(對於無狀態狀態組件
來講返回 null
),注意 JSX
返回的不是組件實例,它只是一個 ReactElement
對象(還記得咱們用純 JS
來構建 JSX
的方式嗎),好比這種:
// A ReactElement const myComponent = <MyComponent /> // render const myComponentInstance = ReactDOM.render(myComponent, mountNode); myComponentInstance.doSomething();
findDOMNode()
當組件加載到頁面上以後(mounted
),你均可以經過 react-dom
提供的 findDOMNode()
方法拿到組件對應的 DOM
元素。
import { findDOMNode } from 'react-dom'; // Inside Component class componentDidMound() { const el = findDOMNode(this); }
findDOMNode()
不能用在無狀態組件上。
Refs
另一種方式就是經過在要引用的 DOM
元素上面設置一個 ref
屬性指定一個名稱,而後經過 this.refs.name
來訪問對應的 DOM
元素。
好比有一種狀況是必須直接操做 DOM
來實現的,你但願一個 <input/>
元素在你清空它的值時 focus
,你無法僅僅靠 state
來實現這個功能。
class App extends Component { constructor() { return { userInput: '' }; } handleChange(e) { this.setState({ userInput: e.target.value }); } clearAndFocusInput() { this.setState({ userInput: '' }, () => { this.refs.theInput.focus(); }); } render() { return ( <div> <div onClick={this.clearAndFocusInput.bind(this)}> Click to Focus and Reset </div> <input ref="theInput" value={this.state.userInput} onChange={this.handleChange.bind(this)} /> </div> ); } }
若是 ref
是設置在原生 HTML
元素上,它拿到的就是 DOM
元素,若是設置在自定義組件上,它拿到的就是組件實例,這時候就須要經過 findDOMNode
來拿到組件的 DOM
元素。
由於無狀態組件沒有實例,因此 ref
不能設置在無狀態組件上,通常來講這沒什麼問題,由於無狀態組件沒有實例方法,不須要 ref
去拿實例調用相關的方法,可是若是想要拿無狀態組件的 DOM
元素的時候,就須要用一個狀態組件封裝一層,而後經過 ref
和 findDOMNode
去獲取。
總結
你可使用 ref
到的組件定義的任何公共方法,好比 this.refs.myTypeahead.reset()
Refs
是訪問到組件內部 DOM
節點惟一可靠的方法
注意事項
不要在 render
或者 render 以前
訪問 refs
不要濫用 refs
,好比只是用它來按照傳統的方式操做界面 UI
:找到 DOM
-> 更新 DOM
使用組件的目的就是經過構建模塊化的組件,相互組合組件最後組裝成一個複雜的應用。
在 React
組件中要包含其餘組件做爲子組件,只須要把組件看成一個 DOM
元素引入就能夠了。
若是組件中包含經過循環插入的子元素,爲了保證從新渲染 UI
的時候可以正確顯示這些子元素,每一個元素都須要經過一個特殊的 key
屬性指定一個惟一值。
key
必須直接在循環中設置:
const MyComponent = (props) => { return ( <ul> {props.results.map((result) => { return <ListItemWrapper key={result.id} data={result}/>; })} </ul> ); };
你也能夠用一個 key
值做爲屬性,子元素做爲屬性值的對象字面量來顯示子元素列表。
實際上瀏覽器在遍歷一個字面量對象的時候會保持順序一致,除非存在屬性值能夠被轉換成整數值,這種屬性值會排序並放在其餘屬性以前被遍歷到,因此爲了防止這種狀況發生,能夠在構建這個字面量的時候在 key
值前面加字符串前綴。
HTML
元素會做爲 React
組件對象、JS
表達式結果是一個文字節點,都會存入 Parent
組件的 props.children
。
通常來講,能夠直接將這個屬性做爲父組件的子元素 render
:
const Parent = (props) => <div>{props.children}</div>;
props.children
一般是一個組件對象的數組,可是當只有一個子元素的時候,props.children
將是這個惟一的子元素,而不是數組了。
React.Children
提供了額外的方法方便操做這個屬性。
這種狀況下很簡單,就是經過 props
屬性傳遞,在父組件給子組件設置 props
,而後子組件就能夠經過 props
訪問到父組件的數據/方法,這樣就搭建起了父子組件間通訊的橋樑。
div
能夠看做一個子組件,指定它的 onClick
事件調用父組件的方法。
import React, {Component} from 'react'; import {render} from 'react-dom'; class GroceryList extends Component { handleClick(i) { console.log('You clicked: ' + this.props.items[i]); } render() { return ( <ul> {this.props.items.map((item, i) => { return ( <li onClick={this.handleClick.bind(this, i)} key={i}>{item}</li> ) })} </ul> ) } } render( <GroceryList items={['Apple', 'Banana', 'Cranberry']}/>, mountNode );
div
能夠看做一個子組件,指定它的 onClick
事件調用父組件的方法。
使用全局事件 Pub/Sub 模式
,在 componentDidMount
裏面訂閱事件
,在 componentWillUnmount
裏面取消訂閱
,當收到事件觸發的時候調用 setState
更新 UI
。
這種模式在複雜的系統裏面可能會變得難以維護,因此看我的權衡是否將組件封裝到大的組件,甚至整個頁面或者應用就封裝到一個組件。
通常來講,對於比較複雜的應用,推薦使用相似 Flux
這種單項數據流架構。