24

我是 React.js 的新手,并且很难理解一些核心概念来决定我们是否应该将这个库用于我们的应用程序。我的主要问题实际上是处理从服务器获取的模型中的更新。

想象一下,我有一个页面应该显示五个不同的模型。我已经按照本文中描述的方式构建了它:http: //facebook.github.io/react/blog/2013/11/05/thinking-in-react.html,所以我有“根”组件,其中所有5 个模型通过并使用道具,它们将向下传递到包含该模型的组件。所以,现在更新了 2 个模型(我从我的模型代码中获取这些事件,这些事件位于反应组件之外),我需要在 UI 上反映这一点。做这个的最好方式是什么?

我正在考虑以下选项:

  1. 再次使用新数据运行 renderComponent 并依赖 DOM diff react 技术。我对此感到担忧,因为我需要对任何小的数据更改执行此操作。
  2. 为持有此模型的组件调用 setState。通过这种方式,数据不再是道具,而是状态(据我了解)不是一个好习惯。另外,我看不到任何方法可以在根组件之外获取对子组件的引用。
  3. 有多个 renderComponent 调用,所以这样我就可以访问任何这个组件的 setProps。但是我需要做一些模板工作(让页面上的所有容器都可用),它会扼杀所有的反应想法。
  4. 拥有一个根组件,其中包括向用户显示的应用程序中所有可能的模型,并调用 setProps 来更改模型。我在这里担心的是,这个组件会变得非常大,并在某些时候变成“意大利面条”+从第 1 点开始的担忧。

提前谢谢你,希望我能清楚地解释我的问题。

4

3 回答 3

6

使用相同的组件但不同的数据再次调用renderComponent相当于调用component.setProps()。因此,要么将所有模型保持在最小公分母中的状态,要么在它发生变化时再次调用 setProps/renderComponent。

于 2013-12-16T16:22:34.773 回答
5

如果您将数据作为道具传递给您的子组件,您可以简单地在更高级别更新它,它将强制渲染到使用相同属性对象的所有组件。考虑这个简单的例子:

var World = React.createClass({
    render: function() {
        return <strong>{this.props.name}</strong>;
    }
});

var Hello = React.createClass({
    clickHandler: function() {
        this.setProps({ name: 'earth' });
    },
    render: function() {
        return (
            <div>
                Hello <World name={this.props.name} />
                <button onClick={this.clickHandler}>Click me</button>
            </div>
        );
    }
});

现在,当用户单击按钮时,您会更改Hello组件上的属性,但是由于您将相同的属性(或数据)对象传递给子级,它们会对其做出反应并相应地更新它的影子 DOM。

这是我的意思的小提琴:http: //jsfiddle.net/xkCKR/

如果你有一个外部数据对象,你可以将它传递给顶层组件。请记住,这并不意味着存在双向绑定:

// simple example of a data model
var Data = { name: 'world' };

var World = React.createClass({
    render: function() {
        return <strong>{this.props.data.name}</strong>;
    }
});

var Hello = React.createClass({
    clickHandler: function() {
        this.setProps({
            data: { name: 'earth' }
        });
    },
    render: function() {
        return (
            <div>
                Hello <World data={this.props.data} />
                <button onClick={this.clickHandler}>Click me</button>
            </div>
        );
    }
});

React.renderComponent(<Hello data={Data} />, document.body);

这是可行的,因为 react 使用属性的单向绑定。但是如果说你的子组件会更新它的属性,它就不会爬上它的父组件。为此,您将需要ReactLink 插件或使用像 Backbone 提供的那样的发布/订阅界面。

于 2013-12-16T20:59:24.580 回答
5

目前我知道至少三种将新数据传递给组件的方法:

  1. 重新渲染组件。不要担心这种方法的效率,因为 React 似乎处理得很好。关于这方面有很多不错的文章:JavaScript 框架中的更改及其检测以及使用 React.render 进行更新
  2. 使用 PubSub 允许组件在数据更改时收到通知(您可以在如何在 React 组件之间进行通信帖子中找到一些有用的示例)。
  3. 与回调绑定(参见下面的三个 jsfiddles)

对于第三个选项,我受到StevenH的回答的启发,并对其进行了一些扩展。请在 j sfiddle.net/kb3gN/12002/检查我的实现。

var Data = { value: 1 };

var dataChange = function(callback){
    if(callback){
        callback(Data);
        setInterval(function(){
            Data.value++;
            callback(Data);
        }, 1000);
    }
    return Data;
};

var World = React.createClass({
    render: function() {
        return <strong>{this.props.data.value}</strong>;
    }
});

var Hello = React.createClass({
    getInitialState: function() {
        return {
          data: this.props.dataChange()
        };
    },
    componentDidMount: function() {
        this.props.dataChange(this.updateHandler)
    },
    updateHandler: function(data) {
        this.setState({
          data: data
        });
    },
    render: function() {
        return (
            <div>
                Value: <World data={this.state.data} />
            </div>
        );
    }
});

React.renderComponent(<Hello dataChange={dataChange} />, document.body);

jsfiddle.net/kb3gN/12007也有一个扩展版本。

function ListenersService(){
    var listeners = {};
    this.addListener = function(callback){
        var id;
        if(typeof callback === 'function'){
            id = Math.random().toString(36).slice(2);
            listeners[id] = callback;
        }
        return id;
    }
    this.removeListener = function( id){
        if(listeners[id]){
            delete listeners[id];
            return true;
        }
        return false;
    }
    this.notifyListeners = function(data){
        for (var id in listeners) {
          if(listeners.hasOwnProperty(id)){
            listeners[id](data);
          }
        }
    }
}

function DataService(ListenersService){
    var Data = { value: 1 };
    var self = this;

    var listenersService = new ListenersService();
    this.addListener = listenersService.addListener;
    this.removeListener = listenersService.removeListener;
    this.getData = function(){
        return Data;
    }

    setInterval(function(){
        Data.value++;
        listenersService.notifyListeners(Data);
    }, 1000);
}
var dataSevice = new DataService(ListenersService);

var World = React.createClass({
    render: function() {
        return <strong>{this.props.data.value}</strong>;
    }
});

var Hello = React.createClass({
    getInitialState: function() {
        return {
          data: this.props.dataService.getData()
        };
    },
    componentDidMount: function() {
        this.props.dataService.addListener(this.updateHandler)
    },
    updateHandler: function(data) {
        this.setState({
          data: data
        });
    },
    render: function() {
        return (
            <div>
                Value: <World data={this.state.data} />
            </div>
        );
    }
});

React.renderComponent(<Hello dataService={dataSevice} />, document.body);

这个实现并没有完全遵循隔离组件的想法(因为 Hello 组件依赖于 DataService API),但它可以被进一步抽象,并且取决于应用程序开发人员他的组件将遵循哪些应用程序特定的约定。例如,在 jsfiddle.net/kb3gN/12015 中查看第一个和第二个示例的组合(halloDataStatic 对象和 halloDataDynamic 回调)

注意:示例中使用的 ListenersService 遵循观察者模式,并且该模式本身在许多情况下利弊多于利弊。但除此之外,我想通过这些示例展示的是,有一种方法可以使用回调进行数据绑定

<div id="static"></div>
<div id="dynamic"></div>
<script>

function ListenersService(){
    var listeners = {};
    this.addListener = function(callback){
        var id;
        if(typeof callback === 'function'){
            id = Math.random().toString(36).slice(2);
            listeners[id] = callback;
        }
        return id;
    }
    this.removeListener = function( id){
        if(listeners[id]){
            delete listeners[id];
            return true;
        }
        return false;
    }
    this.notifyListeners = function(data){
        for (var id in listeners) {
          if(listeners.hasOwnProperty(id)){
            listeners[id](data);
          }
        }
    }
}

function DataService(ListenersService){
    var Data = { value: 1 };
    var self = this;

    var listenersService = new ListenersService();
    this.addListener = listenersService.addListener;
    this.removeListener = listenersService.removeListener;
    this.getData = function(){
        return Data;
    }

    setInterval(function(){
        Data.value++;
        listenersService.notifyListeners(Data);
    }, 100);
}
var dataSevice = new DataService(ListenersService);
var halloDataDynamic = function(callback){
    var data = dataSevice.getData();
    if(callback){
        dataSevice.addListener(function(data){
            callback(data);
        });
    }
    return data;
};
var halloDataStatic = dataSevice.getData();

var World = React.createClass({
    render: function() {
        return <strong>{this.props.data.value}</strong>;
    }
});

var Hello = React.createClass({
    getInitialState: function() {
        var data;
        if(typeof this.props.halloData === 'function'){
            data = this.props.halloData(this.updateHandler)
        }
        else data = this.props.halloData;
        return {
          data: data
        };
    },
    updateHandler: function(data) {
        this.setState({
          data: data
        });
    },
    render: function() {
        return (
            <div>
                Value {this.props.name}: <World data={this.state.data} />
            </div>
        );
    }
});
</script>

React.renderComponent(<Hello halloData={halloDataStatic} name="static"/>, document.getElementById('static'));
React.renderComponent(<Hello halloData={halloDataDynamic} name="dynamic"/>, document.getElementById('dynamic'));
于 2015-08-24T07:46:13.810 回答