?
This document uses PHP Chinese website manual Release
React可以用于任何Web應用程序。它可以嵌入到其他應用程序中,并且可以將其他應用程序嵌入到React中。本指南將檢查一些更常見的用例,重點介紹與jQuery和Backbone的集成,但是可以將相同的思想應用于將組件與任何現有代碼集成。
React不知道在React之外對DOM做出的更改。它根據自己的內部表示來確定更新,并且如果相同的DOM節(jié)點被另一個庫操縱,則React會感到困惑并且無法恢復。
這并不意味著將React與其他影響DOM的方式結合起來是不可能的,甚至是一定困難的,您只需要注意每個人正在做什么。
避免沖突的最簡單方法是防止更新React組件。你可以通過渲染React沒有理由更新的元素來做到這一點,比如空白<div />
。
為了演示這一點,讓我們勾勒一個通用jQuery插件的包裝。
我們將附加一個ref到根DOM元素。在里面componentDidMount
,我們會得到一個引用,所以我們可以將它傳遞給jQuery插件。
為了防止觸摸安裝后的DOM反應,我們會返回一個空<div />
從render()
方法。該<div />
元素沒有屬性或子元素,因此React沒有理由更新它,讓jQuery插件可以自由地管理DOM的這一部分:
class SomePlugin extends React.Component { componentDidMount() { this.$el = $(this.el); this.$el.somePlugin(); } componentWillUnmount() { this.$el.somePlugin('destroy'); } render() { return <div ref={el => this.el = el} />; }}
請注意,我們定義了兩個componentDidMount
和componentWillUnmount
生命周期鉤子。許多jQuery插件將事件監(jiān)聽器附加到DOM,因此將它們分開是非常重要的componentWillUnmount
。如果插件沒有提供清理方法,則可能需要提供自己的插件,記住刪除插件注冊的任何事件偵聽器,以防止內存泄漏。
對于這些概念的更具體的例子,我們來為插件Chosen寫一個最小包裝,它增加了<select>
輸入。
注意: 僅僅因為這可能,并不意味著它是React應用程序的最佳方法。我們建議您盡可能使用React組件。React組件更容易在React應用程序中重用,并且通??梢愿玫乜刂破湫袨楹屯庥^。
首先,我們來看看選擇DOM對于什么。
如果您在<select>
DOM節(jié)點上調用它,它會從原始DOM節(jié)點讀取屬性,將其隱藏為內聯(lián)樣式,然后在其后面附加一個單獨的DOM節(jié)點,并在其后面附帶自己的可視化表示<select>
。然后它會觸發(fā)jQuery事件來通知我們有關更改。
假設這是我們用<Chosen>
包裝器React組件爭取的API :
function Example() { return ( <Chosen onChange={value => console.log(value)}> <option>vanilla</option> <option>chocolate</option> <option>strawberry</option> </Chosen> ); }
我們將把它作為一個不受控制的組件來簡化。
首先,我們將創(chuàng)建一個空的成分render()
,我們返回方法<select>
裹著<div>
:
class Chosen extends React.Component { render() { return ( <div> <select className="Chosen-select" ref={el => this.el = el}> {this.props.children} </select> </div> ); } }
注意我們<select>
是如何包裹在一個額外的<div>
。這是必要的,因為Chosen會在<select>
我們傳遞給它的節(jié)點之后追加另一個DOM元素。但是,就React而言,<div>
總是只有一個孩子。這就是我們如何確保React更新不會與由Chosen附加的額外DOM節(jié)點發(fā)生沖突的原因。重要的是,如果您在React流的外部修改DOM,則必須確保React沒有理由觸摸這些DOM節(jié)點。
接下來,我們將實現生命周期掛鉤。我們需要初始化選中的參考<select>
節(jié)點componentDidMount
,并將其拆分為componentWillUnmount
:
componentDidMount() { this.$el = $(this.el); this.$el.chosen();}componentWillUnmount() { this.$el.chosen('destroy');}
在CodePen上試用它。
請注意,React不會為該this.el
字段賦予特殊含義。它只能工作,因為我們以前ref
在該render()
方法中從a分配了該字段:
<select className="Chosen-select" ref={el => this.el = el}>
這足以讓我們的組件渲染,但我們也希望得到關于值更改的通知。為此,我們將訂閱由Chosen管理的jQuery change
事件<select>
。
我們不會this.props.onChange
直接通過選擇,因為組件的道具可能隨時間而改變,并且包括事件處理程序。相反,我們將聲明一個handleChange()
調用方法this.props.onChange
,并將其訂閱到jQuery change
事件中:
componentDidMount() { this.$el = $(this.el); this.$el.chosen(); this.handleChange = this.handleChange.bind(this); this.$el.on('change', this.handleChange); } componentWillUnmount() { this.$el.off('change', this.handleChange); this.$el.chosen('destroy'); } handleChange(e) { this.props.onChange(e.target.value);}
在CodePen上試用。
最后還有一件事要做。在React中,道具可以隨時間變化。例如,<Chosen>
如果父組件的狀態(tài)更改,組件可以獲得不同的子項。這意味著在集成點,我們手動更新DOM以響應prop更新非常重要,因為我們不再讓React為我們管理DOM。
Chosen的文檔建議我們可以使用jQuery trigger()
API來通知它對原始DOM元素的更改。我們將讓React負責this.props.children
內部更新<select>
,但我們還將添加一個componentDidUpdate()
生命周期掛鉤,通知Chosen關于子列表中的更改:
componentDidUpdate(prevProps) { if (prevProps.children !== this.props.children) { this.$el.trigger("chosen:updated"); }}
這樣,當<select>
由React管理的孩子發(fā)生變化時,Chosen會知道更新其DOM元素。
Chosen
組件的完整實現如下所示:
class Chosen extends React.Component { componentDidMount() { this.$el = $(this.el); this.$el.chosen(); this.handleChange = this.handleChange.bind(this); this.$el.on('change', this.handleChange); } componentDidUpdate(prevProps) { if (prevProps.children !== this.props.children) { this.$el.trigger("chosen:updated"); } } componentWillUnmount() { this.$el.off('change', this.handleChange); this.$el.chosen('destroy'); } handleChange(e) { this.props.onChange(e.target.value); } render() { return ( <div> <select className="Chosen-select" ref={el => this.el = el}> {this.props.children} </select> </div> ); } }
在CodePen上試用它。
由于靈活性,React可以嵌入到其他應用程序中ReactDOM.render()
。
雖然React在啟動時通常用于將單個根React組件加載到DOM中,ReactDOM.render()
但也可以為UI的獨立部分多次調用,該部分可以像按鈕一樣小,也可以與應用程序一樣大。
事實上,這正是Facebook如何使用React。這讓我們可以在React中逐個編寫應用程序,并將其與我們現有的服務器生成的模板和其他客戶端代碼結合使用。
舊Web應用程序中的一種常見模式是將DOM的塊描述為字符串,并將其插入到DOM中,如下所示:$el.html(htmlString)
。代碼庫中的這些點非常適合引入React。只需將基于字符串的渲染重寫為React組件即可。
所以下面的jQuery實現...
$('#container').html('<button id="btn">Say Hello</button>');$('#btn').click(function() { alert('Hello!');});
...可以使用React組件重寫:
function Button() { return <button id="btn">Say Hello</button>;}ReactDOM.render( <Button />, document.getElementById('container'), function() { $('#btn').click(function() { alert('Hello!'); }); });
從這里開始,您可以開始將更多邏輯轉移到組件中,并開始采用更常見的React實踐。例如,在組件中,最好不要依賴ID,因為可以多次渲染相同的組件。相反,我們將使用React事件系統(tǒng),并將點擊處理程序直接注冊到React <button>
元素上:
function Button(props) { return <button onClick={props.onClick}>Say Hello</button>; }function HelloButton() { function handleClick() { alert('Hello!'); } return <Button onClick={handleClick} />; }ReactDOM.render( <HelloButton />, document.getElementById('container'));
在CodePen上試用它。
您可以擁有任意數量的此類隔離組件,并使用ReactDOM.render()
它們將它們呈現給不同的DOM容器。逐漸地,當您將更多應用程序轉換為React時,您將能夠將它們組合成更大的組件,并將一些ReactDOM.render()
調用移動到層次結構中。
主干視圖通常使用HTML字符串或字符串生成模板函數來為其DOM元素創(chuàng)建內容。這個過程也可以用渲染React組件來替換。
下面,我們將創(chuàng)建一個名為Backbone的視圖ParagraphView
。它將覆蓋Backbone的render()
函數,將React <Paragraph>
組件渲染到由Backbone(this.el
)提供的DOM元素中。在這里,我們也在使用ReactDOM.render()
:
function Paragraph(props) { return <p>{props.text}</p>;} const ParagraphView = Backbone.View.extend({ render() { const text = this.model.get('text'); ReactDOM.render(<Paragraph text={text} />, this.el); return this; }, remove() { ReactDOM.unmountComponentAtNode(this.el); Backbone.View.prototype.remove.call(this); }});
在CodePen上試用它。
我們也呼吁是非常重要ReactDOM.unmountComponentAtNode()
的remove
方法,以便作出反應注銷事件處理程序,并與組件樹相關的其他資源,當它被分離。
當一個組件從一個React樹中被移除時,清理會自動執(zhí)行,但因為我們要手動移除整個樹,所以我們必須把它稱為這個方法。
雖然通常建議使用單向數據流,例如React狀態(tài),Flux或Redux,但React組件可以使用其他框架和庫中的模型層。
使用React組件的Backbone模型和集合的最簡單方法是偵聽各種更改事件并手動強制更新。
負責渲染模型的組件將監(jiān)聽'change'
事件,而負責渲染集合的組件將監(jiān)聽'add'
和'remove'
事件。在這兩種情況下,都需要this.forceUpdate()
使用新數據調用組件。
在下面的示例中,List
組件呈現Backbone集合,使用該Item
組件呈現單個項目。
class Item extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); } handleChange() { this.forceUpdate(); } componentDidMount() { this.props.model.on('change', this.handleChange); } componentWillUnmount() { this.props.model.off('change', this.handleChange); } render() { return <li>{this.props.model.get('text')}</li>; } } class List extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); } handleChange() { this.forceUpdate(); } componentDidMount() { this.props.collection.on('add', 'remove', this.handleChange); } componentWillUnmount() { this.props.collection.off('add', 'remove', this.handleChange); } render() { return ( <ul> {this.props.collection.map(model => ( <Item key={model.cid} model={model} />)) } </ul> ); } }
在CodePen上試用它。
上述方法要求您的React組件知道Backbone模型和集合。如果您后來計劃遷移到另一個數據管理解決方案,則可能需要盡可能少地將有關Backbone的知識集中在代碼中。
解決這個問題的一個辦法是在模型的屬性發(fā)生變化時將模型的屬性作為普通數據提取出來,并將這個邏輯放在一個地方。以下是一個高階組件,它將Backbone模型的所有屬性提取到狀態(tài)中,并將數據傳遞給包裝組件。
這樣,只有高階組件需要了解Backbone模型內部,并且應用程序中的大多數組件都可以不依賴于Backbone。
在下面的例子中,我們將復制模型的屬性以形成初始狀態(tài)。我們訂閱change
事件(并取消訂閱卸載),當它發(fā)生時,我們用模型的當前屬性更新狀態(tài)。最后,我們確保如果model
道具本身發(fā)生變化,我們不會忘記退訂舊模型,并訂閱新模型。
請注意,這個例子并不意味著在使用Backbone方面是詳盡的,但它應該給你一個關于如何以一種通用的方式來解決這個問題的想法:
function connectToBackboneModel(WrappedComponent) { return class BackboneComponent extends React.Component { constructor(props) { super(props); this.state = Object.assign({}, props.model.attributes); this.handleChange = this.handleChange.bind(this); } componentDidMount() { this.props.model.on('change', this.handleChange); } componentWillReceiveProps(nextProps) { this.setState(Object.assign({}, nextProps.model.attributes)); if (nextProps.model !== this.props.model) { this.props.model.off('change', this.handleChange); nextProps.model.on('change', this.handleChange); } } componentWillUnmount() { this.props.model.off('change', this.handleChange); } handleChange(model) { this.setState(model.changedAttributes()); } render() { const propsExceptModel = Object.assign({}, this.props); delete propsExceptModel.model; return <WrappedComponent {...propsExceptModel} {...this.state} />; } } }
為了演示如何使用它,我們將NameInput
React組件連接到Backbone模型,并在firstName
每次輸入更改時更新其屬性:
function NameInput(props) { return ( <p> <input value={props.firstName} onChange={props.handleChange} /> <br /> My name is {props.firstName}. </p> );} const BackboneNameInput = connectToBackboneModel(NameInput);function Example(props) { function handleChange(e) { model.set('firstName', e.target.value); } return ( <BackboneNameInput model={props.model} handleChange={handleChange} /> );} const model = new Backbone.Model({ firstName: 'Frodo' }); ReactDOM.render( <Example model={model} />, document.getElementById('root'));
在CodePen上試用它。
這項技術不限于Backbone。您可以通過訂閱其生命周期掛鉤中的更改并將數據復制到本地React狀態(tài),從而將React用于任何模型庫。