React高级指南(十二)【Integrating with Other Libraries】
React与其他库的集成React可以在任何web应用中使用。React可以嵌入其他的应用中,也可以将其他的应用嵌入React中,不过需要多加小心。本篇教程将介绍部分常见的使用场景,主要包括集成jQuery和Backbone,但是同样的思想可以用来集成组件到其他任何现有的代码。 与DOM操作插件的集成React 无法感知到React之外的DOM变化。这决定了更新只能基于React内部的表示,如果相同的DOM节点被其他库所操作,React会对此产生疑惑并无法恢复。 这并不意味着很难或者无法将React于其他影响DOM的方式相结合,你需要更加注意两者各自的行为。 避免冲突最简单的方式就是阻止React的更新。你可以通过渲染React无法更新的元素来实现,例如空的 如何处理这个问题为了展示这个问题,我们来为通用的jQuery插件绘制一个包装器(wrapper)。 我们给根DOM元素添加ref。在 为了防止React在mount之后处理DOM元素,我们将在
元素没有属性或者子元素,因此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} />;
}
}
注意,我们定义了 与jQuery的Chosen插件集成为了更具体地描述这个概念,让我们写一个最小化的Chosen插件的包装器(wrapper),其中插件Chosen接受
首先,我们来看看Chosen对DOM的行为。 如果你在 让我们了解一下我们所设计的React组件包装器(wrapper) function Example() {
return (
<Chosen onChange={value => console.log(value)}> <option>vanilla</option> <option>chocolate</option> <option>strawberry</option> </Chosen> ); }
为了简单起见,我们将其实现为一个不受控组件。 首先我们先创建一个空的组件,其中包含 class Chosen extends React.Component {
render() {
return (
<div> <select className="Chosen-select" ref={el => this.el = el}> {this.props.children} </select> </div> ); } }
需要注意为什么我们为 接下来,我们实现生命周周期函数。我们在 componentDidMount() {
this.$el = $(this.el);
this.$el.chosen();
}
componentWillUnmount() {
this.$el.chosen('destroy');
}
在CodePen中尝试 注意,React对 <select className="Chosen-select" ref={el => this.el = el}>
上述对于组件的渲染已经足够,但是我们也想要获得值改变的通知(notifies about the value changes),为了实现这个目的,我们在 我们不直接给Chosen传递 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里props会随时间而改变。例如,如果父组件state改变, Chosen的文档建议我们使用jQuery的 componentDidUpdate(prevProps) {
if (prevProps.children !== this.props.children) {
this.$el.trigger("chosen:updated");
}
}
这样一来,当React导致
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通常在启动时将单个根节点的React组件加载进DOM节点,但 事实上,在Facebook中React就是这么用的。这使得我们可以一步一步地使用React编写程序,并与我们现存的服务器生成的模板与其他客户端代码相结合。 用React替换字符串渲染在之前的web应用中,一种常见的模式是将DOM块作为字符串描述,并将其插入DOM节点,例如: 因此下面的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事件系统,直接在React的 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中尝试 你可以按照你的想法创建多个独立的组件,并使用 在Backbone视图中集成ReactBackbone视图(View)是典型的使用字符串或者字符串产生函数来生成DOM元素的内容。这个过程可以通过渲染React组件来替代。 下面我们将创建一个名为 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中尝试 在 当组件从组件树中删除时,清除将自动执行,但是因为我们手动地移除整个组件树,我们必须调用这个方法。 React与Model层集成尽管我们推荐使用单向数据流例如:React state、Flux或者Redux,但React组件仍然可以使用其他框架和库的model层。 在React组件中使用Backbone Models对React组件而言,使用Backbone models最简单的方式就是监听不同的change事件并手动强制刷新。 负责渲染model的React组件必须监听 在下面的例子中, 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',this.handleChange);
}
render() {
return (
<ul> {this.props.collection.map(model => ( <Item key={model.cid} model={model} /> ))} </ul> ); } }
在CodePen中尝试 从Backbone Models中提取数据上述方法需要你的React组件了解Backbone的模型和集合。如果你随后计划迁移到另一个数据管理方案,你可能希望将Backbone的概念集中在尽可能少的代码中。 一个解决这个问题的方案是,每当模型的数据改变时将模型的属性提取成一个纯数据,并将逻辑保存在一个单一的位置。接下来的高阶组件提取Backbone中的属性作为state,并将数据传递给被包裹的组件。 这样,仅有高阶组件需要了解Backbone内部的model,应用中大多数的组件可以与Backbone保持独立。 在下面的例子中,我们将复制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} />;
}
}
}
为了演示如何使用这个例子,我们将 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。你可以通过在生命周期函数中订阅model改变并且可选地将model中的数据复制进React内部的state的方式,实现React与其他model库集成使用。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |