本文將首先講述如何經過React nodes建立基礎的React組件,而後進一步剖析React組件內部的點滴,包括該如何理解React組件,獲取React組件實例的兩種辦法,React事件系統,對React生命週期函數的理解,獲取React組件的子組件和子節點的方法,字符串
ref
和函數式ref
,以及觸發React組件從新渲染的四種方法。
本文是React啓蒙系列的第六章,依舊講的是React的基礎使用方法,可是若是你對上面提到的概念有不理解或不熟悉的地方,跳到對應地方觀看閱讀,你應該會能有所收穫。html
在具體說明如何建立React組件的語法以前,對什麼是React組件,其存在的意思及其劃分依據等作一個論述是頗有必要的。node
咱們設想如今有一個webApp,這個app能夠用來實現不少功能,依據功能,咱們能夠把其劃分爲多個功能碎片。要實現這麼一個功能碎片,可能須要更多更小的邏輯單元,甚至還能夠繼續分。而咱們編程其實就是在有一個整體輪廓的前提下,經過解決一個個小小的問題來解決一個小問題,解決一個個小問題來實現軟件的開發。React組件就是這樣,你能夠就把它當作一個個可組合的功能單元。react
以一個登錄框爲例,登陸框自己就是網站的一個組件,可是其內包含諸如文本輸入框,登錄按鈕等,固然若是你想要作的只是最基礎的功能,輸入框和按鈕等能夠只是一個個React 節點,可是若是你想爲輸入框加上輸入檢測,輸入框可能就有必要寫成一個單獨的組件了,這樣也有利於複用,以後須要作的可能只是簡單的經過props
傳入不一樣的參數就能夠實現不一樣的檢測。假想咱們如今的登陸框組件,包含React <Button>
元素造成登陸按鈕,也包含多個文本輸入檢測組件。那麼父組件的做用一方面在於聚合小組件造成更復雜的功能單元,另外一方面在於爲子組件信息的溝通提供渠道(好比說在知足必定的輸入條件後,登陸按鈕的狀態從不可點擊變爲可點擊)。git
React組件經過調用React.createClass()
方法建立,該方法須要傳入一個對象形式的參數。在該對象中能夠爲所建立組件配置各類參數,其可用參數以下表:github
方法(配置參數)名稱 | 描述 |
---|---|
render() | 必填,一般爲一個返回React nodes或者其它組件的函數 |
getInitialState() | 一個用於設置最初的state的函數,返回一個對象 |
getDefaultProps() | 一個用於設置默認props 的函數,返回值爲一個對象 |
propTypes | 一個用於驗證特定props 類型的對象 |
mixins | 組件間共享方法的途徑 |
statics | 一個由多個靜態方法組成的對象,靜態方法中不能直接調用props 和state (可經過參數) |
displayName | 是一個用於命名組件的字符串,用於展現調試信息,使用JSX時將自動設置?? |
componentWillMount() | 在組件首次渲染前觸發,只會觸發一次 |
componentDidMount() | 在組件首次渲染後觸發,只會觸發一次 |
componentWillReceiveProps() | 在組件將接受新props 時觸發 |
shouldComponentUpdate() | 組件再次渲染前觸發,可用於判斷是否須要再次渲染 |
componentWillUpdate() | 組件再次渲染前當即觸發 |
componentDidUpdate() | 組件渲染後當即觸發 |
componentWillUnmount() | 組件卸載前當即觸發 |
在上述因此方法中,最重要且必不可少的是render()
,它的做用是返回React節點和組件,其它全部的方法是可選的。web
實際寫一個例子總比空說要容易理解,如下是使用React的React.createClass()
建立的Timer組件編程
var Timer = React.createClass({ getInitialState: function() { return { secondsElapsed: Number(this.props.startTime) || 0 }; }, tick: function() { //自定義方法 this.setState({ secondsElapsed: this.state.secondsElapsed + 1 }); }, componentDidMount: function() {//生命週期函數 this.interval = setInterval(this.tick, 1000); }, componentWillUnmount: function() {//生命週期函數 clearInterval(this.interval); }, render: function() { //使用JSX返回節點 return ( <div> Seconds Elapsed: {this.state.secondsElapsed} </div> ); } }); ReactDOM.render(< Timer startTime = "60" / >, app); //pass startTime prop, used for state
如今若是對上述組件建立的代碼有所疑惑也沒關係,本文接下來將一步步的介紹上述代碼中設計都的各個概念,包括this
,生命週期函數,React返回值的格式,如何在React中自定義函數,以及React組件中事件的定義等等。瀏覽器
在此須要注意的是組件名是以大寫開頭的。babel
當一個組件被建立(掛載)之後,咱們就可使用組件的API了,一個組件包含如下四個API
this.setState()
,
this.setState({mykey: 'my new value'}); this.setState(function(previousState, currentProps) { return {myInteger: previousState.myInteger + 1}; });
做用:
用以從新渲染組件或者子組件
replaceState()
this.replceState({mykey: 'my new value'});
做用:
效果和`setState()`相似,不過並不會和老的狀態合併,而是直接刪除老的狀態,應用新的狀態。
forceUpdate()
this.forceUpdate(function(){//callback});
做用:
調用此方法將跳過組件的`shouldComponentUpdate()`事件,直接調用`render()`
isMounted()
this.isMounted()
做用
判斷組件是否被掛載在DOM中,組件被掛載返回`true`,不然返回`false`
最經常使用的組件API是setState()
,後文還會細講。
小結
componentWillUnmount
, componentDidUpdate
, componentWillUpdate
, shouldComponentUpdate
, componentWillReceiveProps
, componentDidMount
, componentWillMount
等方法被稱做React 組件的生命週期函數,它們會在組件生命過程的不一樣階段被觸發。
React.createClass()
是一個方便的建立組件實例的方法;
render()
方法應該保持純潔;
render()
方法中不能更改組件狀態
上文已經提到每一個React組件必須有的方法就是render()
,這個方法的返回值只能是一個react 節點或一個react組件,這個節點或組件中能夠包含任意多的子節點或者子元素。在下面的例子中咱們能夠看到在<reactNode>
中包含了多個子節點。
var MyComponent = React.createClass({ render: function() { return <reactNode> <span>test</span> <span>test</span> </reactNode>; } }); ReactDOM.render(<MyComponent />, app);
值得注意的地方在於,若是你想返回的react 節點超過一行,應該用括號把返回值包圍起來,以下所示
var MyComponent = React.createClass({ render: function() { return ( <reactNode> <span>test</span> <span>test</span> </reactNode> ); } }); ReactDOM.render(<MyComponent />, app);
另外一個值得注意的地方是返回值最外層不能出現多個節點(組件),否者會報錯
var MyComponent = React.createClass({ render: function() { return ( <span>test</span> <span>test</span> ); } }); ReactDOM.render(<MyComponent />, app);
上述代碼就會報錯,報錯信息以下
babel.js:62789 Uncaught SyntaxError: embedded: Adjacent JSX elements must be wrapped in an enclosing tag (10:3) 8 | return ( 9 | <span>test</span> > 10 | <span>test</span> | ^ 11 | ); 12 | } 13 | });
通常來講開發者會在最外層加上一個<div>
元素包裹其它節點以免此類錯誤。
一樣,若是return()
中的最外層出現了多個組件,也會出錯。
當一個組件被render
後,一個組件便經過傳入的參數實例化了,咱們有兩種辦法獲取這個實例及其內部屬性(this.props
和this.setState()
)。
第一種方法就是使用this
關鍵字,在組件內部的方法中使用this
咱們發現,這個this
指向的就是該組件實例。
var Foo = React.createClass({ componentWillMount:function(){ console.log(this) }, componentDidMount:function(){ console.log(this) }, render: function() { return <div>{console.log(this)}</div>; } }); ReactDOM.render(<Foo />, document.getElementById('app'));
獲取某組件實例的另一種方法是調用ReactDOM.render()
方法,這個方法的返回值是最外層的組件實例。
看以下代碼能夠更好的理解這句話
var Bar = React.createClass({ render: function() { return <div></div>; } }); var foo; //store a reference to the instance outside of function var Foo = React.createClass({ render: function() { return <Bar>{foo = this}</Bar>; } }); var FooInstance = ReactDOM.render(<Foo />, document.getElementById('app')); console.log(FooInstance === foo); //true,說明返回值和指向一致
小結this
的最多見用法就是在一個組件內調用該組件的各個屬性和方法,如this.props.[NAME OF PROP]
,this.props.children
,this.state
,this.setState()
,this.replaceState()
等。
第四章和第五章已經屢次介紹過React的事件系統,事件能夠被直接添加都React節點上,下面的代碼示例中,咱們添加了兩個React事件(onClick
&onMouseOver
)到React<div>
節點中
var MyComponent = React.createClass({ mouseOverHandler:function mouseOverHandler(e) { console.log('you moused over'); console.log(e); //e is sysnthetic event instance }, clickHandler:function clickhandler(e) { console.log('you clicked'); console.log(e); //e is sysnthetic event instance }, render:function(){ return ( <div onClick={this.clickHandler} onMouseOver={this.mouseOverHandler}>click or mouse over</div> ) } }); ReactDOM.render(<MyComponent />, document.getElementById('app'));
事件能夠被看作是特殊的props
,只是React對這些特殊的props
的處理方式和普通的props
有所不一樣。
這種不一樣表如今會自動爲事件的回調函數綁定上下文,在下面的示例中,回調函數中的this
指向了組件實例自己。
var MyComponent = React.createClass({ mouseOverHandler:function mouseOverHandler(e) { console.log(this); //this is component instance console.log(e); //e is sysnthetic event instance }, render:function(){ return ( <div onMouseOver={this.mouseOverHandler}>mouse over me</div> ) } }); ReactDOM.render(<MyComponent />, document.getElementById('app'));
React所支持的因此事件可見此表
小結
React規範化了事件在不一樣瀏覽器中的表現,你能夠放心的跨瀏覽器使用;
React事件默認在事件冒泡階段(bubbling)觸發,若是想在事件捕獲階段觸發須要在事件名後加上Capture
(如onClick
變爲onClickCapture
);
若是你想獲知瀏覽器事件的詳情,你能夠經過在回調函數中查看SyntheticEvent
對象中的nativeEvent
值;
React實際上並未直接爲React nodes添加事件,它使用的是event delegation事件委託機制
想要阻止事件冒泡,須要手動調用e.stopPropagation()
或e.preventDefault()
,不要直接使用returning false
,
React其實並無支持全部的JS事件,不過它還提供額外的生命週期函數以供使用React lifecycle methods.
React組件的render()
方法中能夠包含對其它組件的引用,這使得組件之間能夠嵌套,通常咱們把被嵌套的組件稱爲嵌套組件的子組件。
下例中組件BadgeList包含了BadgeBill和BadgeTom兩個組件。
var BadgeBill = React.createClass({ render: function() {return <div>Bill</div>;} }); var BadgeTom = React.createClass({ render: function() {return <div>Tom</div>;} }); var BadgeList = React.createClass({ render: function() { return (<div> <BadgeBill/> <BadgeTom /> </div>); } }); ReactDOM.render(<BadgeList />, document.getElementById('app'));
此處爲展現嵌套關係,代碼有所簡化。
小結
編寫可維護性UI的關鍵之一在於可組合組件,React組件自然適用這一原理;
在render
方法中,組件和HTML
能夠組合使用;
每一個組件都具備一系列的發生在其生命中不一樣階段的事件,這些事件被稱爲生命週期函數。
生命週期函數能夠理解爲React爲組件的不一樣階段提供了的鉤子函數,用以更好的操做組件,下例是一個定時器組件,其在不一樣生命週期函數中執行了不一樣的事件
var Timer = React.createClass({ getInitialState: function() { console.log('getInitialState lifecycle method ran!'); return {secondsElapsed: Number(this.props.startTime) || 0}; }, tick: function() { console.log(ReactDOM.findDOMNode(this)); if(this.state.secondsElapsed === 65){ ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(this).parentNode); return; } this.setState({secondsElapsed: this.state.secondsElapsed + 1}); }, componentDidMount: function() { console.log('componentDidMount lifecycle method ran!'); this.interval = setInterval(this.tick, 1000); }, componentWillUnmount: function() { console.log('componentWillUnmount lifecycle method ran!'); clearInterval(this.interval); }, render: function() { return (<div>Seconds Elapsed: {this.state.secondsElapsed}</div>); } }); ReactDOM.render(< Timer startTime = "60" / >, app);
組件的生命週期可被分爲掛載(Mounting),更新(Updating)和卸載(UnMounting)三個階段。
下面將對不一樣階段各函數的功能及用途進行描述,弄清這一點很重要
掛載階段
這是React組件生命週期的第一個階段,也能夠稱爲組件出生階段,這個階段組件被初始化,得到初始的
props
並定義將會用到的state
,此階段結束時,組件及其子元素都會在UI中被渲染(DOM,UIview等),咱們還能夠對渲染後的組件進行進一步的加工。這個階段的全部方法在組件生命中只會被觸發一次。React-in-depth
對掛載階段的生命週期函數的描述
| 方法 | 描述 |
| --------- | ------ |
|getInitialState()
| 在組件掛載前被觸發,富狀態組件應該調用此方法以得到初始的狀態值 |
|componentWillMount()
|在組件掛載前被觸發,富狀態組件應該調用此方法以得到初始的狀態值|
|componentDidMount()
| 組件被掛載後當即觸發,在此能夠對DOM進行操做了 |
更新階段
這個階段的函數會在組件的整個生命週期中不斷被觸發,這是組件一輩子中最長的時期。這個階段的函數能夠得到新的
props
,能夠更改state
,能夠對用戶的交互進行反應。React-in-depth
對更新階段的生命週期函數的描述
方法 | 描述 |
---|---|
componentWillReceiveProps(object nextProps) |
在組件接受新的props 時被觸發,能夠用來比較新老props ,並使用this.setState() 來改變組件狀態 |
shouldComponentUpdate(object nextProps, object nextState) |
此組件能夠對比新老props 和state ,用以確認該組件是否須要從新渲染,若是返回值爲false ,將跳過這次渲染,此方法經常使用於優化React性能 |
componentWillUpdate(object nextProps, object nextState) |
在組件從新渲染前被觸發,此時不能再調用this.setState() 對state 進行更改 |
componentDidUpdate(object prevProps, object prevState) |
在從新渲染後當即被觸發,此時可調用新的DOM了 |
卸載階段
這是組件生命的最後一個階段,也能夠被稱爲是組件的死亡階段,此階段對應組件從Native UI中卸載之時,具體說來多是用戶切換了頁面,或者頁面改變去除了某個組件,卸載階段的函數只會被觸發一次,而後該組件就會被加入瀏覽器的垃圾回收機制。React-in-depth
對此階段的生命週期函數的描述
方法 | 描述 |
---|---|
componentWillUnmount() |
組件卸載前當即被觸發,此階段經常使用來執行一些清理工做(好比說清除setInterval ) |
小結
componentDidMount
和 componentDidUpdate
經常使用來加載第三方的庫(此時真實DOM存在,可加載各類圖表庫)。
組件掛載階段的各事件執行順序以下
Initialize / Construction
獲取初始的props,ES5中使用 getDefaultProps()
(React.createClass),ES6中使用 MyComponent.defaultProps
(ES6 class)
初始組件的state
值,ES5中使用getInitialState()
(React.createClass) ,ES6中使用 this.state = ...
(ES6 constructor)
componentWillMount()
render()
第一次渲染
Children initialization & life cycle kickoff,子組件重複上述(1~5步)過程;
componentDidMount()
經過上面的過程分析,咱們能夠知道,在父元素執行
componentDidMount()
時,子元素和子組件都已經存在於真實DOM中了,所以在此能夠放心調用。
組件更新階段各函數執行順序以下
componentWillReceiveProps()
:比較新老props
,對state
進行改變;
shouldComponentUpdate()
:判斷組件是否須要從新渲染
render()
:從新渲染
Children Life cycle methods
:子元素重複上述過程
componentWillUpdate()
:此階段能夠調用新的DOM了
組件卸載階段各函數執行順序以下
componentWillUnmount()
Children Life cycle methods:觸發子元素的生命週期函數,也將被卸載
被瀏覽器從內存中清除;
若是一個組件包含子組件或React節點(如<Parent><Child /></Parent>
或 <Parent><span>test<span></Parent>
),這些子節點和子組件能夠經過React的this.props.children
的方法來獲取。
下面的例子展現瞭如何使用this.props.children
var Parent2 = React.createClass({ componentDidMount: function() { //將會得到<span>child2text</span>, console.log(this.props.children); //將會得到 child2text, 或得了子元素<span>的子元素 console.log(this.props.children.props.children); }, render: function() {return <div />;} }); var Parent = React.createClass({ componentDidMount: function() { //得到了一個數組 <div>test</div> <div>test</div> console.log(this.props.children); //得到了這個數組中的對應子元素中的子元素 childtext, console.log(this.props.children[1].props.children); }, render: function() {return <Parent2><span>child2text</span></Parent2>;} }); ReactDOM.render( <Parent><div>child</div><div>childtext</div></Parent>, document.getElementById('app') );
觀察上述的代碼能夠看出如下幾點
Parent組件實例的this.props.children
獲取到由直系子元素組成的數組,能夠對子元素套用此方法得到子元素(組件)的子元素(組件)(this.props.children[1].props.children
);
子元素指的是由該實例圍起來的元素,而非該實例內部元素;
爲了更好的操做this.props.children
包含的是一組元素,React還提供瞭如下方法
方法 | 描述 |
---|---|
React.Children.map(this.props.children, function(){}) |
在每個直接子級(包含在 children 參數中的)上調用 fn 函數,此函數中的 this 指向 上下文。若是 children 是一個內嵌的對象或者數組,它將被遍歷,每一個鍵值對都會添加到新的 Map。若是 children 參數是 null 或者 undefined,那麼返回 null 或者 undefined 而不是一個空對象。 |
React.Children.forEach(this.props.children, function(){}) |
相似於Children.map() 可是不會反回數組 |
React.Children.count(this.props.children) |
返回組件子元素的總數量,其數目等於Children.map() 和Children.forEach() 的執行次數。 |
React.Children.only(this.props.children) |
返回惟一的子元素不然報錯 |
React.Children.toArray(this.props.children) |
返回一個由各子元素組成的數組,若是你想在render事件中操做子元素的集合時,這個方法特別有用,尤爲是在從新排序或分割子元素時 |
小結
當只有一個子元素時,this.props.children
之間返回該子元素,不會用一個數組包裹着該子元素;
須要注意的是children
並不是某組件內部的節點,而是由該組件包裹的組件或節點‘
ref
ref
屬性使得咱們獲取了對某一個React節點或某一個子組件的引用,這個在你須要直接操做DOM時很是有用。
字符串ref
的使用很簡單,可分爲兩步:
一是給你想引用的的子元素或組件添加ref
屬性,
而後在本組件中經過this.refs.value(你所設置的屬性名)
便可引用;
不過還存在一種函數式的ref,看下面的例子
var C2 = React.createClass({ render: function() {return <span ref={function(span) {console.log(span)}} />} }); var C1 = React.createClass({ render: function() {return( <div> <C2 ref={function(c2) {console.log(c2)}}></C2> <div ref={function(div) {console.log(div)}}></div> </div>)} }); ReactDOM.render(<C1 ref={function(ci) {console.log(ci)}} />,document.getElementById('app'));
上述例子的console結果都是指向ref所在的組件或元素,經過console的結果咱們也能夠發現,打印結果說明其指向的是真實的HTML DOM而非Virtual DOM。
若是不想用字符串ref
,經過下面的方法也能夠引用到你想引用的節點
var MyComponent = React.createClass({ handleClick: function() { // focus()對真實DOM元素有效 this.textInput.focus(); }, render: function() { // ref中傳入了一個回調函數,把該節點自己賦值給this.input return ( <div> <input type="text" ref={(thisInput) => {this.textInput = thisInput}} /> <input type="button" value="Focus the text input" onClick={this.handleClick} /> </div> ); } }); ReactDOM.render( <MyComponent />, document.getElementById('app') );
小結
對無狀態函數式組件不能使用ref
,由於這種組件並不會返回一個實例;
ref有兩種,字符串ref和函數式ref,不過字符串ref(經過refs調用這種)在將來可能被放棄,函數式ref是趨勢;
組件有ref,能夠經過ref調用該組件內部的方法;
使用行內函數表達式使用ref意味着每次更新React都會將其視爲一個不一樣的函數對象,ref中的函數會以null爲參數被當即執行(和在實例中調用不衝突)。好比說,當ref所指向的對象被卸載時,或者ref改變時,老的的ref函數都會以null爲參數被調用。
對應ref的使用,React官方有兩點建議:
ref容許你直接操做節點,這一點有些狀況下是很是方便的,不過須要注意的是,若是能夠經過更改state
來達到你想要的效果,那就不要隨便使用ref啦;
若是你剛剛接觸React,在你想用ref的時候,仍是儘可能多思考一下看能不能用state
來解決,仔細思考你會發現,state
能夠解決大部分操做問題的,比較直接操做DOM並未React的初衷。
咱們已經接觸了ReactDOM.render()
方法,這個方法使得組件及其子組件被初始化渲染。在此次渲染以後,React爲咱們提供了兩種方法來從新渲染某個組件
在組件內調用setState()
方法;
在組件中調用fouceUpdate()
方法;
每當一個組件被從新渲染時,其子組件也會被從新渲染(在Virtual DOM中發生,在真實DOM中表現出來)。不過須要注意的是Virtual DOM的改變並非必定在真實DOM中就會有所表現。
在下面的例子中,ReactDOM.render(< App / >, app)
初始化渲染了<App/>
及其子組件<Timer/>
,接下來的<App/>
中的setInterval()
事件調用this.setState()
導致兩個組件被從新渲染。在5秒後,setInterval()
被清除,而在十秒後this.forceUpdate()
被觸發又使得頁面被從新渲染。
var Timer = React.createClass({ render: function() { return ( <div>{this.props.now}</div> ) } }); var App = React.createClass({ getInitialState: function() { return {now: Date.now()}; }, componentDidMount: function() { var foo = setInterval(function() { this.setState({now: Date.now()}); }.bind(this), 1000); setTimeout(function(){ clearInterval(foo); }, 5000); //DON'T DO THIS, JUST DEMONSTRATING .forceUpdate() setTimeout(function(){ this.state.now = 'foo'; this.forceUpdate() }.bind(this), 10000); }, render: function() { return ( <Timer now={this.state.now}></Timer> ) } }); ReactDOM.render(< App / >, app);
從開始翻譯本書到如今已有一個多月,基礎的翻譯工做終於算是告一段落。
《React Enlightenment》的第七章和第八章講述的是React的props
和state
已由@linda102翻譯完成。
在大概一個多月前看到本書原文時,我已經用了快五個月React,可是看完本書仍是挺有收穫。
翻譯本書的初衷有兩點,一是增強本身對React基礎的理解,二是回想起,我在初學React時曾購買過國內的一本關於React的基礎書籍,價格是四十多,可是其實看完並未有太多收穫,該書大多就是翻譯的官方文檔,並且翻譯的也不全面,並不那麼容易理解,因此但願這篇譯文對初學者友好,讓初學者少走彎路。
因爲翻譯時間和水平都有限,譯文內部不可避免存在一些不恰當的地方,若是您在閱讀的過程當中有好的建議,請直接提出,我會盡快修改。謝謝