注:因为译者水平有限,不免会出现错误,但愿你们能指出,谢谢。html
react 能够被用在任何的web 应用中。它能够被嵌入到其余的应用中,要是你当心一点,其余的应用也能被嵌入到react中。这篇文章将会从一些经常使用的使用场景入手,重点会关注与jQuery 和backbone 的交互。可是里面的思想在咱们和其余库交互时都是能够被参考的。react
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 中必定要移除事件监听。若是这个插件没有提供移除的方法,那你就要本身写了。必定要记得移除插件所注册的事件,不然可能会出现内存泄露。函数
为了对这些概念有更深刻的了解,咱们为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> ); } }
因为ReactDOM.render()方法的灵活性使得React能够被嵌入到其余的应用中。
因为React 一般被用来将一个React 节点渲染到某个DOM元素中,并且ReactDOM.render()能够被UI的各个独立的部分屡次调用,小到一个按钮,大到一个app。
事实上,这就是React 在Facebook 被使用的方式。这使得咱们能够在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() 的调用移动到不一样的层级中。
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树中移除时,一些清理工做会被自动执行。可是由于咱们手动的移除了整个树,因此咱们必需要调用这个方法来进行清理工做。
一般咱们推荐使用单向数据流好比React state, Flux 或者Redux来管理react 应用。其实,react 也能使用一些其余框架或者库的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> ); } }
上述的处理方式要求你的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中。