我刚开始使用ReactJS,我有一个问题。

我的应用程序本质上是一个带有过滤器和更改布局的按钮的列表。 目前我正在使用三个组件:<list />, < Filters />和<TopBar />,现在显然,当我在< Filters />中更改设置时,我想在<list />中触发一些方法来更新我的视图。

我如何使这3个组件相互交互,或者我是否需要某种全局数据模型,我只需要对其进行更改?


当前回答

扩展@MichaelLaCroix的回答,当组件不能在任何类型的父子关系之间进行通信时,文档建议设置一个全局事件系统。

在<Filters />和<TopBar />没有任何上述关系的情况下,一个简单的全局发射器可以像这样使用:

componentDidMount -订阅事件

componentWillUnmount -从事件中取消订阅

js和EventSystem代码

EventSystem.js

class EventSystem{

    constructor() {
        this.queue = {};
        this.maxNamespaceSize = 50;
    }

    publish(/** namespace **/ /** arguments **/) {
        if(arguments.length < 1) {
            throw "Invalid namespace to publish";
        }

        var namespace = arguments[0];
        var queue = this.queue[namespace];

        if (typeof queue === 'undefined' || queue.length < 1) {
            console.log('did not find queue for %s', namespace);
            return false;
        }

        var valueArgs = Array.prototype.slice.call(arguments);

        valueArgs.shift(); // remove namespace value from value args

        queue.forEach(function(callback) {
            callback.apply(null, valueArgs);
        });

        return true;
    }

    subscribe(/** namespace **/ /** callback **/) {
        const namespace = arguments[0];
        if(!namespace) throw "Invalid namespace";
        const callback = arguments[arguments.length - 1];
        if(typeof callback !== 'function') throw "Invalid callback method";

        if (typeof this.queue[namespace] === 'undefined') {
            this.queue[namespace] = [];
        }

        const queue = this.queue[namespace];
        if(queue.length === this.maxNamespaceSize) {
            console.warn('Shifting first element in queue: `%s` since it reached max namespace queue count : %d', namespace, this.maxNamespaceSize);
            queue.shift();
        }

        // Check if this callback already exists for this namespace
        for(var i = 0; i < queue.length; i++) {
            if(queue[i] === callback) {
                throw ("The exact same callback exists on this namespace: " + namespace);
            }
        }

        this.queue[namespace].push(callback);

        return [namespace, callback];
    }

    unsubscribe(/** array or topic, method **/) {
        let namespace;
        let callback;
        if(arguments.length === 1) {
            let arg = arguments[0];
            if(!arg || !Array.isArray(arg)) throw "Unsubscribe argument must be an array";
            namespace = arg[0];
            callback = arg[1];
        }
        else if(arguments.length === 2) {
            namespace = arguments[0];
            callback = arguments[1];
        }

        if(!namespace || typeof callback !== 'function') throw "Namespace must exist or callback must be a function";
        const queue = this.queue[namespace];
        if(queue) {
            for(var i = 0; i < queue.length; i++) {
                if(queue[i] === callback) {
                    queue.splice(i, 1); // only unique callbacks can be pushed to same namespace queue
                    return;
                }
            }
        }
    }

    setNamespaceSize(size) {
        if(!this.isNumber(size)) throw "Queue size must be a number";
        this.maxNamespaceSize = size;
        return true;
    }

    isNumber(n) {
        return !isNaN(parseFloat(n)) && isFinite(n);
    }

}

NotificationComponent.js

class NotificationComponent extends React.Component {

    getInitialState() {
        return {
            // optional. see alternative below
            subscriber: null
        };
    }

    errorHandler() {
        const topic = arguments[0];
        const label = arguments[1];
        console.log('Topic %s label %s', topic, label);
    }

    componentDidMount() {
        var subscriber = EventSystem.subscribe('error.http', this.errorHandler);
        this.state.subscriber = subscriber;
    }

    componentWillUnmount() {
        EventSystem.unsubscribe('error.http', this.errorHandler);

        // alternatively
        // EventSystem.unsubscribe(this.state.subscriber);
    }

    render() {

    }
}

其他回答

扩展@MichaelLaCroix的回答,当组件不能在任何类型的父子关系之间进行通信时,文档建议设置一个全局事件系统。

在<Filters />和<TopBar />没有任何上述关系的情况下,一个简单的全局发射器可以像这样使用:

componentDidMount -订阅事件

componentWillUnmount -从事件中取消订阅

js和EventSystem代码

EventSystem.js

class EventSystem{

    constructor() {
        this.queue = {};
        this.maxNamespaceSize = 50;
    }

    publish(/** namespace **/ /** arguments **/) {
        if(arguments.length < 1) {
            throw "Invalid namespace to publish";
        }

        var namespace = arguments[0];
        var queue = this.queue[namespace];

        if (typeof queue === 'undefined' || queue.length < 1) {
            console.log('did not find queue for %s', namespace);
            return false;
        }

        var valueArgs = Array.prototype.slice.call(arguments);

        valueArgs.shift(); // remove namespace value from value args

        queue.forEach(function(callback) {
            callback.apply(null, valueArgs);
        });

        return true;
    }

    subscribe(/** namespace **/ /** callback **/) {
        const namespace = arguments[0];
        if(!namespace) throw "Invalid namespace";
        const callback = arguments[arguments.length - 1];
        if(typeof callback !== 'function') throw "Invalid callback method";

        if (typeof this.queue[namespace] === 'undefined') {
            this.queue[namespace] = [];
        }

        const queue = this.queue[namespace];
        if(queue.length === this.maxNamespaceSize) {
            console.warn('Shifting first element in queue: `%s` since it reached max namespace queue count : %d', namespace, this.maxNamespaceSize);
            queue.shift();
        }

        // Check if this callback already exists for this namespace
        for(var i = 0; i < queue.length; i++) {
            if(queue[i] === callback) {
                throw ("The exact same callback exists on this namespace: " + namespace);
            }
        }

        this.queue[namespace].push(callback);

        return [namespace, callback];
    }

    unsubscribe(/** array or topic, method **/) {
        let namespace;
        let callback;
        if(arguments.length === 1) {
            let arg = arguments[0];
            if(!arg || !Array.isArray(arg)) throw "Unsubscribe argument must be an array";
            namespace = arg[0];
            callback = arg[1];
        }
        else if(arguments.length === 2) {
            namespace = arguments[0];
            callback = arguments[1];
        }

        if(!namespace || typeof callback !== 'function') throw "Namespace must exist or callback must be a function";
        const queue = this.queue[namespace];
        if(queue) {
            for(var i = 0; i < queue.length; i++) {
                if(queue[i] === callback) {
                    queue.splice(i, 1); // only unique callbacks can be pushed to same namespace queue
                    return;
                }
            }
        }
    }

    setNamespaceSize(size) {
        if(!this.isNumber(size)) throw "Queue size must be a number";
        this.maxNamespaceSize = size;
        return true;
    }

    isNumber(n) {
        return !isNaN(parseFloat(n)) && isFinite(n);
    }

}

NotificationComponent.js

class NotificationComponent extends React.Component {

    getInitialState() {
        return {
            // optional. see alternative below
            subscriber: null
        };
    }

    errorHandler() {
        const topic = arguments[0];
        const label = arguments[1];
        console.log('Topic %s label %s', topic, label);
    }

    componentDidMount() {
        var subscriber = EventSystem.subscribe('error.http', this.errorHandler);
        this.state.subscriber = subscriber;
    }

    componentWillUnmount() {
        EventSystem.unsubscribe('error.http', this.errorHandler);

        // alternatively
        // EventSystem.unsubscribe(this.state.subscriber);
    }

    render() {

    }
}

如果您想探索组件之间通信的选项,并且感觉通信变得越来越困难,那么您可能会考虑采用一种好的设计模式:Flux。

它只是一组规则,定义了如何存储和更改应用程序范围内的状态,以及如何使用该状态来呈现组件。

Flux有很多实现,Facebook的官方实现就是其中之一。虽然它被认为包含了最多的样板代码,但它更容易理解,因为大部分内容都是显式的。

其他的一些替代品是flumox, flumox和redux。

奇怪的是,没有人提到mobx。这个想法类似于redux。如果我有一段数据,多个组件订阅了它,那么我可以使用该数据来驱动多个组件。

即使他们不是父母-孩子关系,也有这样的可能性——那就是流动。有一个很好的(对我个人来说)实现称为Alt.JS(与Alt-Container)。

例如,您可以拥有依赖于组件详细信息设置的边栏。Component Sidebar与SidebarActions和SidebarStore连接,而Details是DetailsActions和DetailsStore。

你可以像那样使用AltContainer

<AltContainer stores={{
                    SidebarStore: SidebarStore
                }}>
                    <Sidebar/>
</AltContainer>

{this.props.content}

这将保留商店(我可以用“商店”而不是“商店”道具)。现在,{this.props。content}可以是细节,取决于路由。假设/Details将我们重定向到那个视图。 例如,细节将有一个复选框,如果选中的话,将把侧边栏元素从X更改为Y。

从技术上讲,它们之间没有关系,没有通量是很难做到的。但这样就相当容易了。

现在让我们进入DetailsActions。我们会在那里创建

class SiteActions {
constructor() {
    this.generateActions(
        'setSiteComponentStore'
    );
}

setSiteComponent(value) {
    this.dispatch({value: value});
}
}

和DetailsStore

class SiteStore {
constructor() {
    this.siteComponents = {
        Prop: true
    };

    this.bindListeners({
        setSiteComponent: SidebarActions.COMPONENT_STATUS_CHANGED
    })
}

setSiteComponent(data) {
    this.siteComponents.Prop = data.value;
}
}

现在,这就是魔法开始的地方。

如你所见,这里有SidebarActions的bindListener。如果使用setSiteComponent,将使用ComponentStatusChanged。

现在在SidebarActions

    componentStatusChanged(value){
    this.dispatch({value: value});
}

我们有这样的东西。它会在调用时分派那个对象。如果setSiteComponent in store将被使用(你可以在组件中使用例如onChange on Button或其他)它将被调用

现在在边栏商店中我们会有

    constructor() {
    this.structures = [];

    this.bindListeners({
        componentStatusChanged: SidebarActions.COMPONENT_STATUS_CHANGED
    })
}

    componentStatusChanged(data) {
    this.waitFor(DetailsStore);

    _.findWhere(this.structures[0].elem, {title: 'Example'}).enabled = data.value;
}

这里你可以看到,它会等待DetailsStore。这是什么意思?这或多或少意味着这个方法需要等待DetailsStoreto更新才能更新自己。

博士tl; 一个商店正在监听商店中的方法,并将从组件操作中触发一个操作,该操作将更新自己的商店。

我希望它能对你有所帮助。

下面的代码帮助我建立两个兄弟姐妹之间的通信。设置是在render()和componentDidMount()调用期间在它们的父节点中完成的。 它基于https://reactjs.org/docs/refs-and-the-dom.html 希望能有所帮助。

class App extends React.Component<IAppProps, IAppState> {
    private _navigationPanel: NavigationPanel;
    private _mapPanel: MapPanel;

    constructor() {
        super();
        this.state = {};
    }

    // `componentDidMount()` is called by ReactJS after `render()`
    componentDidMount() {
        // Pass _mapPanel to _navigationPanel
        // It will allow _navigationPanel to call _mapPanel directly
        this._navigationPanel.setMapPanel(this._mapPanel);
    }

    render() {
        return (
            <div id="appDiv" style={divStyle}>
                // `ref=` helps to get reference to a child during rendering
                <NavigationPanel ref={(child) => { this._navigationPanel = child; }} />
                <MapPanel ref={(child) => { this._mapPanel = child; }} />
            </div>
        );
    }
}