?
本文檔使用 php中文網(wǎng)手冊(cè) 發(fā)布
React可以用于任何Web應(yīng)用程序。它可以嵌入到其他應(yīng)用程序中,并且可以將其他應(yīng)用程序嵌入到React中。本指南將檢查一些更常見(jiàn)的用例,重點(diǎn)介紹與jQuery和Backbone的集成,但是可以將相同的思想應(yīng)用于將組件與任何現(xiàn)有代碼集成。
React不知道在React之外對(duì)DOM做出的更改。它根據(jù)自己的內(nèi)部表示來(lái)確定更新,并且如果相同的DOM節(jié)點(diǎn)被另一個(gè)庫(kù)操縱,則React會(huì)感到困惑并且無(wú)法恢復(fù)。
這并不意味著將React與其他影響DOM的方式結(jié)合起來(lái)是不可能的,甚至是一定困難的,您只需要注意每個(gè)人正在做什么。
避免沖突的最簡(jiǎn)單方法是防止更新React組件。你可以通過(guò)渲染React沒(méi)有理由更新的元素來(lái)做到這一點(diǎn),比如空白<div />
。
為了演示這一點(diǎn),讓我們勾勒一個(gè)通用jQuery插件的包裝。
我們將附加一個(gè)ref到根DOM元素。在里面componentDidMount
,我們會(huì)得到一個(gè)引用,所以我們可以將它傳遞給jQuery插件。
為了防止觸摸安裝后的DOM反應(yīng),我們會(huì)返回一個(gè)空<div />
從render()
方法。該<div />
元素沒(méi)有屬性或子元素,因此React沒(méi)有理由更新它,讓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} />; }}
請(qǐng)注意,我們定義了兩個(gè)componentDidMount
和componentWillUnmount
生命周期鉤子。許多jQuery插件將事件監(jiān)聽(tīng)器附加到DOM,因此將它們分開(kāi)是非常重要的componentWillUnmount
。如果插件沒(méi)有提供清理方法,則可能需要提供自己的插件,記住刪除插件注冊(cè)的任何事件偵聽(tīng)器,以防止內(nèi)存泄漏。
對(duì)于這些概念的更具體的例子,我們來(lái)為插件Chosen寫(xiě)一個(gè)最小包裝,它增加了<select>
輸入。
注意: 僅僅因?yàn)檫@可能,并不意味著它是React應(yīng)用程序的最佳方法。我們建議您盡可能使用React組件。React組件更容易在React應(yīng)用程序中重用,并且通??梢愿玫乜刂破湫袨楹屯庥^。
首先,我們來(lái)看看選擇DOM對(duì)于什么。
如果您在<select>
DOM節(jié)點(diǎn)上調(diào)用它,它會(huì)從原始DOM節(jié)點(diǎn)讀取屬性,將其隱藏為內(nèi)聯(lián)樣式,然后在其后面附加一個(gè)單獨(dú)的DOM節(jié)點(diǎn),并在其后面附帶自己的可視化表示<select>
。然后它會(huì)觸發(fā)jQuery事件來(lái)通知我們有關(guān)更改。
假設(shè)這是我們用<Chosen>
包裝器React組件爭(zhēng)取的API :
function Example() { return ( <Chosen onChange={value => console.log(value)}> <option>vanilla</option> <option>chocolate</option> <option>strawberry</option> </Chosen> ); }
我們將把它作為一個(gè)不受控制的組件來(lái)簡(jiǎn)化。
首先,我們將創(chuàng)建一個(gè)空的成分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>
是如何包裹在一個(gè)額外的<div>
。這是必要的,因?yàn)镃hosen會(huì)在<select>
我們傳遞給它的節(jié)點(diǎn)之后追加另一個(gè)DOM元素。但是,就React而言,<div>
總是只有一個(gè)孩子。這就是我們?nèi)绾未_保React更新不會(huì)與由Chosen附加的額外DOM節(jié)點(diǎn)發(fā)生沖突的原因。重要的是,如果您在React流的外部修改DOM,則必須確保React沒(méi)有理由觸摸這些DOM節(jié)點(diǎn)。
接下來(lái),我們將實(shí)現(xiàn)生命周期掛鉤。我們需要初始化選中的參考<select>
節(jié)點(diǎn)componentDidMount
,并將其拆分為componentWillUnmount
:
componentDidMount() { this.$el = $(this.el); this.$el.chosen();}componentWillUnmount() { this.$el.chosen('destroy');}
在CodePen上試用它。
請(qǐng)注意,React不會(huì)為該this.el
字段賦予特殊含義。它只能工作,因?yàn)槲覀円郧?code>ref在該render()
方法中從a分配了該字段:
<select className="Chosen-select" ref={el => this.el = el}>
這足以讓我們的組件渲染,但我們也希望得到關(guān)于值更改的通知。為此,我們將訂閱由Chosen管理的jQuery change
事件<select>
。
我們不會(huì)this.props.onChange
直接通過(guò)選擇,因?yàn)榻M件的道具可能隨時(shí)間而改變,并且包括事件處理程序。相反,我們將聲明一個(gè)handleChange()
調(diào)用方法this.props.onChange
,并將其訂閱到j(luò)Query 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中,道具可以隨時(shí)間變化。例如,<Chosen>
如果父組件的狀態(tài)更改,組件可以獲得不同的子項(xiàng)。這意味著在集成點(diǎn),我們手動(dòng)更新DOM以響應(yīng)prop更新非常重要,因?yàn)槲覀儾辉僮孯eact為我們管理DOM。
Chosen的文檔建議我們可以使用jQuery trigger()
API來(lái)通知它對(duì)原始DOM元素的更改。我們將讓React負(fù)責(zé)this.props.children
內(nèi)部更新<select>
,但我們還將添加一個(gè)componentDidUpdate()
生命周期掛鉤,通知Chosen關(guān)于子列表中的更改:
componentDidUpdate(prevProps) { if (prevProps.children !== this.props.children) { this.$el.trigger("chosen:updated"); }}
這樣,當(dāng)<select>
由React管理的孩子發(fā)生變化時(shí),Chosen會(huì)知道更新其DOM元素。
Chosen
組件的完整實(shí)現(xiàn)如下所示:
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可以嵌入到其他應(yīng)用程序中ReactDOM.render()
。
雖然React在啟動(dòng)時(shí)通常用于將單個(gè)根React組件加載到DOM中,ReactDOM.render()
但也可以為UI的獨(dú)立部分多次調(diào)用,該部分可以像按鈕一樣小,也可以與應(yīng)用程序一樣大。
事實(shí)上,這正是Facebook如何使用React。這讓我們可以在React中逐個(gè)編寫(xiě)應(yīng)用程序,并將其與我們現(xiàn)有的服務(wù)器生成的模板和其他客戶端代碼結(jié)合使用。
舊Web應(yīng)用程序中的一種常見(jiàn)模式是將DOM的塊描述為字符串,并將其插入到DOM中,如下所示:$el.html(htmlString)
。代碼庫(kù)中的這些點(diǎn)非常適合引入React。只需將基于字符串的渲染重寫(xiě)為React組件即可。
所以下面的jQuery實(shí)現(xiàn)...
$('#container').html('<button id="btn">Say Hello</button>');$('#btn').click(function() { alert('Hello!');});
...可以使用React組件重寫(xiě):
function Button() { return <button id="btn">Say Hello</button>;}ReactDOM.render( <Button />, document.getElementById('container'), function() { $('#btn').click(function() { alert('Hello!'); }); });
從這里開(kāi)始,您可以開(kāi)始將更多邏輯轉(zhuǎn)移到組件中,并開(kāi)始采用更常見(jiàn)的React實(shí)踐。例如,在組件中,最好不要依賴(lài)ID,因?yàn)榭梢远啻武秩鞠嗤慕M件。相反,我們將使用React事件系統(tǒng),并將點(diǎn)擊處理程序直接注冊(cè)到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上試用它。
您可以擁有任意數(shù)量的此類(lèi)隔離組件,并使用ReactDOM.render()
它們將它們呈現(xiàn)給不同的DOM容器。逐漸地,當(dāng)您將更多應(yīng)用程序轉(zhuǎn)換為React時(shí),您將能夠?qū)⑺鼈兘M合成更大的組件,并將一些ReactDOM.render()
調(diào)用移動(dòng)到層次結(jié)構(gòu)中。
主干視圖通常使用HTML字符串或字符串生成模板函數(shù)來(lái)為其DOM元素創(chuàng)建內(nèi)容。這個(gè)過(guò)程也可以用渲染React組件來(lái)替換。
下面,我們將創(chuàng)建一個(gè)名為Backbone的視圖ParagraphView
。它將覆蓋Backbone的render()
函數(shù),將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
方法,以便作出反應(yīng)注銷(xiāo)事件處理程序,并與組件樹(shù)相關(guān)的其他資源,當(dāng)它被分離。
當(dāng)一個(gè)組件從一個(gè)React樹(shù)中被移除時(shí),清理會(huì)自動(dòng)執(zhí)行,但因?yàn)槲覀円謩?dòng)移除整個(gè)樹(shù),所以我們必須把它稱(chēng)為這個(gè)方法。
雖然通常建議使用單向數(shù)據(jù)流,例如React狀態(tài),F(xiàn)lux或Redux,但React組件可以使用其他框架和庫(kù)中的模型層。
使用React組件的Backbone模型和集合的最簡(jiǎn)單方法是偵聽(tīng)各種更改事件并手動(dòng)強(qiáng)制更新。
負(fù)責(zé)渲染模型的組件將監(jiān)聽(tīng)'change'
事件,而負(fù)責(zé)渲染集合的組件將監(jiān)聽(tīng)'add'
和'remove'
事件。在這兩種情況下,都需要this.forceUpdate()
使用新數(shù)據(jù)調(diào)用組件。
在下面的示例中,List
組件呈現(xiàn)Backbone集合,使用該Item
組件呈現(xiàn)單個(gè)項(xiàng)目。
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模型和集合。如果您后來(lái)計(jì)劃遷移到另一個(gè)數(shù)據(jù)管理解決方案,則可能需要盡可能少地將有關(guān)Backbone的知識(shí)集中在代碼中。
解決這個(gè)問(wèn)題的一個(gè)辦法是在模型的屬性發(fā)生變化時(shí)將模型的屬性作為普通數(shù)據(jù)提取出來(lái),并將這個(gè)邏輯放在一個(gè)地方。以下是一個(gè)高階組件,它將Backbone模型的所有屬性提取到狀態(tài)中,并將數(shù)據(jù)傳遞給包裝組件。
這樣,只有高階組件需要了解Backbone模型內(nèi)部,并且應(yīng)用程序中的大多數(shù)組件都可以不依賴(lài)于Backbone。
在下面的例子中,我們將復(fù)制模型的屬性以形成初始狀態(tài)。我們訂閱change
事件(并取消訂閱卸載),當(dāng)它發(fā)生時(shí),我們用模型的當(dāng)前屬性更新?tīng)顟B(tài)。最后,我們確保如果model
道具本身發(fā)生變化,我們不會(huì)忘記退訂舊模型,并訂閱新模型。
請(qǐng)注意,這個(gè)例子并不意味著在使用Backbone方面是詳盡的,但它應(yīng)該給你一個(gè)關(guān)于如何以一種通用的方式來(lái)解決這個(gè)問(wèn)題的想法:
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
每次輸入更改時(shí)更新其屬性:
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上試用它。
這項(xiàng)技術(shù)不限于Backbone。您可以通過(guò)訂閱其生命周期掛鉤中的更改并將數(shù)據(jù)復(fù)制到本地React狀態(tài),從而將React用于任何模型庫(kù)。