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