和其餘庫(jquery,backbone)一塊兒使用react

注:因爲譯者水平有限,不免會出現錯誤,但願你們能指出,謝謝。html

react 能夠被用在任何的web 應用中。它能夠被嵌入到其餘的應用中,要是你當心一點,其餘的應用也能被嵌入到react中。這篇文章將會從一些經常使用的使用場景入手,重點會關注與jQuery 和backbone 的交互。可是裏面的思想在咱們和其餘庫交互時都是能夠被參考的。react

和操縱DOM的插件的交互

react 感知不到它管理以外的dom 的變化。react的更新是取決於其內部的表現,若是一樣的DOM節點被其餘可操做dom節點的插件更改了,react內部狀態就會變的很混亂而且沒法恢復了。jquery

這並不意味着react 沒法和那些操縱DOM 的插件一塊兒共用,你只須要更加清楚每一個插件作了什麼。web

最簡單的避免這種衝突發生的方式是阻止react 組件的更新。你能夠經過渲染一個react 沒有必要去更新的元素,好比一個空的<div/>服務器

如何處理這種問題

爲了更好的闡述這個問題,讓咱們來對一個通常的jquery 插件添加一個wrapper。app

首先,咱們在這個節點上添加一個ref屬性。在componentDidMount 方法裏,咱們經過獲取這個節點的引用,將它傳給jquery 插件。框架

爲了不react 在渲染期間對這個節點進行改變, 咱們在render() 方法裏面返回了一個空的<div/>.這個空的節點沒有任何的屬性或子節點,因此React 不會對該節點進行更新,這個節點的控制權徹底在jQuery插件上。這樣就不會出現react 和jquery 插件都操做一樣的dom 的問題了。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插件都將事件監聽綁定在DOM上,因此在componentWillUnmount 中必定要移除事件監聽。若是這個插件沒有提供移除的方法,那你就要本身寫了。必定要記得移除插件所註冊的事件,不然可能會出現內存泄露。函數

和jQuery 的選擇器插件共用

爲了對這些概念有更深刻的瞭解,咱們爲Chosen 插件寫了一個小型的wrapper。Chosen 插件的參數是一個<select>this

注意,雖然能夠這樣用,但這並非最好的方式。咱們推薦儘量的使用react組件。這樣在react應用中能夠更好的複用,並且會有更好的使用效果

首先,讓咱們來看看Chosen 插件對DOM元素作了什麼。
若是你在對一個<select> 節點應用了該組件。它會讀取原始DOM節點的屬性,使用內聯樣式隱藏它。而且使用本身的展現方式在<select>節點後面插入新的DOM節點。而後它觸發jQuery的事件來通知咱們這些改變。

這就是咱們想要咱們的Chosen 插件包裝完成的功能

function Example() {
  return (
    <Chosen onChange={value => console.log(value)}>
      <option>vanilla</option>
      <option>chocolate</option>
      <option>strawberry</option>
    </Chosen>
  );
}

爲了簡單起見,咱們使用一個非受控組件來實現它
首先,咱們建立一個只有render方法的組件。在render方法中咱們返回一個<div><select></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>標籤。這頗有必要,由於咱們後續會在<select>標籤後面添加一個傳入的節點。然而,就React而言,<div>標籤一般只有一個孩子節點。這就是咱們如何確保React 的更新不會和經過Chosen 插入的額外的DOM節點衝突的緣由。很重要的一點是,若是你在React 流以外修改了DOM節點,你必須確保React 不會由於任何緣由再對這些DOM節點進行操做。

接下來,咱們繼續實現生命週期的鉤子函數。咱們須要在componentDidMount裏使用<select>節點的引用來初始化Chosen.而且在componentDidUnmount 裏面銷燬它。

componentDidMount() {
  this.$el = $(this.el);
  this.$el.chosen();
}

componentWillUnmount() {
  this.$el.chosen('destroy');
}

記住,react 不會對this.el 字段賦予任何特殊的含義。除非你以前在render方法裏面對它進行賦值。

<select className="Chosen-select" ref={el => this.el = el}>

以上對於在render 裏面獲取你的組件就足夠了,可是咱們還但願值變化時能給實現通知。由於,咱們經過Chosen 在<select>上 訂閱了jQuery 的change事件。

咱們不會直接的將this.props.onChange傳給Chosen. 由於組件的屬性可能會一直改變,並且這裏還包含着事件的處理。由於,咱們聲明瞭一個handleChange方法來調用this.props.onChange.而且爲它訂閱了jQuery的change事件中。也就是說,只要一發生change 事件。就會自動執行handleChange 方法。

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);
}

最後,咱們還有一件事要作。在React 中,因爲屬性是能夠一直改變的。例如,<Chosen>組件可以獲取不一樣的children 若是父組件狀態改變的話。這意味着在交互過程當中,很重要的一點是,當屬性改變時,咱們須要手動的控制DOM的更新,再也不須要react 來爲咱們管理DOM節點了。

Chosen 的文檔建議咱們使用jQuery 的trigger() 方法來通知原始DOM元素的變化。咱們將使React重點關注在<select>中的屬性this.props.children 的更新。可是咱們同時也在componentDidUpdate 的生命週期函數裏添加通知Chosen 他的children 列表變化的函數。

componentDidUpdate(prevProps) {
    if (preProps.children !== this.props.children) {
        this.$el.trigger("chosen:updated");
    }
}

經過這種方式,當經過React 管理的<select> 節點改變的時候,Chosen 就會知道須要更新DOM元素了。

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('destory');
    }
    
    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>
        );
    }
}

和其餘的View 庫共用

因爲ReactDOM.render()方法的靈活性使得React能夠被嵌入到其餘的應用中。

因爲React 一般被用來將一個React 節點渲染到某個DOM元素中,並且ReactDOM.render()能夠被UI的各個獨立的部分屢次調用,小到一個按鈕,大到一個app。

事實上,這就是React 在Facebook 被使用的方式。這使得咱們能夠在React 中一塊一塊的開發一個應用,而且能夠把它整合在現有的服務器渲染的模版中或者其餘的客戶端代碼中。

使用React替換基於字符串的渲染

在一些老的web 應用,一種常見的方式是寫一大段DOM結構做爲字符串,而後使用$el.html(htmlString) 的方式插入到DOM節點中。若是你的代碼庫中有相似的場景,那麼推薦你使用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組件中去而且採用更多react 實踐方式。例如,組件最好不要依賴id,由於一樣的組件可能會被渲染屢次。並且,咱們推薦使用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')
);

你能夠有不少這樣獨立的組件,而且使用ReactDOM.render()方法將他們渲染到不一樣的DOM節點中。慢慢的,你在app 中使用愈來愈多的react 技術,你就能夠將這些獨立的組件整合成更大的組件。同時,將一些ReactDOM.render() 的調用移動到不一樣的層級中。

將React 嵌入到Backbone 的視圖中

Backbone 的視圖就是典型的使用HTML 字符串,或者使用一些字符串模版函數來生成這樣的字符串,而後將之做爲DOM元素的內容。這種處理方式,也能被替換爲使用React 組件渲染的方式。

下面,咱們將會建立一個Backbone 的視圖ParagraphView. 咱們會經過渲染一個React <Paragraph> 組件,而後使用Backbone 提供的(this.el)方式將它插入到DOM元素中的方式來重寫Backbone 的render() 方法. 固然,咱們也會使用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);
  }
});

很重要的一件事是,咱們必須在remove方法中調用 ReactDOM.unmountComponentAtNode() 方法來解除經過react 註冊的事件和一些其餘的資源。

當一個組件從react樹中移除時,一些清理工做會被自動執行。可是由於咱們手動的移除了整個樹,因此咱們必需要調用這個方法來進行清理工做。

和Model 層進行交互

一般咱們推薦使用單向數據流好比React state, Flux 或者Redux來管理react 應用。其實,react 也能使用一些其餘框架或者庫的Model 層來進行管理。

在react 應用中使用Backbone 的model層

在React 組件中消費Backbone中model和collections 最簡單的方法是監聽各類change 事件並手動進行強制更新。

渲染models 的組件會監聽 'change'事件,渲染collections 的組件會監聽‘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>
    );
  }
}

從Backbone 的Models 中提取數據

上述的處理方式要求你的React 組件可以感知到Backbone 的Models 和 Collections .若是你後續要整合其餘的數據管理方案,你可能須要更多關注Backbone 的實現細節了。

解決這個問題的一個方法是,當model 的屬性改變時,將它提取爲普通的數據,並將這段邏輯保存在一個單獨的地方。下面演示的是一個高階組件,這個組件將Backbone 的model層的屬性轉換爲state,而後把數據傳遞給被包裹的組件。

經過這種方式,只有這個高階組件須要知道Backbone Models的內部細節信息,大部分的組件對Backbone 都是透明的。

下面的例子中,咱們會對Model 的屬性進行一份拷貝來做爲初始state。咱們註冊了change 事件(在unmounting 中取消註冊),當監聽到change事件的時候,咱們用model 當前的屬性來更新state。最後,咱們要確保,若是model 的屬性本身改變的話,咱們不要忘記從老的model上取消訂閱,而後訂閱新的model。

注意,這個例子不是爲了說明和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} />;
    }
  }
}

爲了闡述如何來使用它,咱們會將一個react組件NameInput 和Backbone 的model 層結合起來使用,而且每次輸入發生改變時,就會更新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')
);

這些處理技巧不只限於Backbone. 你也可使用React 和其餘的model 庫進行整合,經過在生命週期中訂閱它的變化,而且,選擇性的,將數據複製到react 的state中。

相關文章
相關標籤/搜索