So heben Sie die Bereitstellung, Aufhebung oder Entfernung einer Komponente in einer React / Redux / Typescript-Benachrichtigungsnachricht auf

114

Ich weiß, dass diese Frage bereits einige Male gestellt wurde, aber die meiste Zeit besteht die Lösung darin, dies beim Elternteil zu behandeln, da der Verantwortungsfluss nur absteigend ist. Manchmal müssen Sie jedoch eine Komponente mit einer ihrer Methoden beenden. Ich weiß, dass ich seine Requisiten nicht ändern kann, und wenn ich anfange, Boolesche Werte als Status hinzuzufügen, wird es für eine einfache Komponente wirklich chaotisch. Folgendes versuche ich zu erreichen: Eine kleine Fehlerbox-Komponente mit einem "x", um sie zu schließen. Wenn Sie einen Fehler über die Requisiten erhalten, wird er angezeigt, aber ich möchte einen Weg finden, ihn aus seinem eigenen Code zu schließen.

class ErrorBoxComponent extends React.Component {

  dismiss() {
    // What should I put here?
  }
  
  render() {
    if (!this.props.error) {
      return null;
    }

    return (
      <div data-alert className="alert-box error-box">
        {this.props.error}
        <a href="#" className="close" onClick={this.dismiss.bind(this)}>&times;</a>
      </div>
    );
  }
}


export default ErrorBoxComponent;

Und ich würde es so in der übergeordneten Komponente verwenden:

<ErrorBox error={this.state.error}/>

Im Abschnitt Was soll ich hier setzen? , Ich habe es schon versucht :

ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(this).parentNode); Was einen schönen Fehler in der Konsole auslöst:

Warnung: unmountComponentAtNode (): Der Knoten, den Sie aushängen möchten, wurde von React gerendert und ist kein Container der obersten Ebene. Lassen Sie stattdessen die übergeordnete Komponente ihren Status aktualisieren und erneut rendern, um diese Komponente zu entfernen.

Sollte ich die eingehenden Requisiten in den ErrorBox-Status kopieren und nur intern bearbeiten?

Sephy
quelle
Verwenden Sie Redux?
Arnau Lacambra
Warum ist dies eine Anforderung? "Wenn ein Fehler über seine Requisiten empfangen wird, wird er angezeigt, aber ich möchte eine Möglichkeit, ihn aus seinem eigenen Code zu schließen." Der normale Ansatz wäre, eine Aktion auszulösen, die den Fehlerstatus löscht und dann in einem Renderzyklus des übergeordneten Elements geschlossen wird, wie Sie angedeutet haben.
Ken4z
Ich möchte eigentlich die Möglichkeit für beide anbieten. In der Tat wird es verschließbar sein, wie Sie es erklärt haben, aber mein Fall ist "Was ist, wenn ich es auch von innen schließen möchte"
Sephy

Antworten:

97

Genau wie diese nette Warnung, die Sie erhalten haben, versuchen Sie, etwas zu tun, das ein Anti-Pattern in React ist. Dies ist ein Nein-Nein. React soll dazu führen, dass eine Beziehung zwischen Eltern und Kind aufgehoben wird. Wenn Sie nun möchten, dass sich ein Kind selbst abmeldet, können Sie dies mit einer Statusänderung im Elternteil simulieren, die vom Kind ausgelöst wird. Lass mich dir im Code zeigen.

class Child extends React.Component {
    constructor(){}
    dismiss() {
        this.props.unmountMe();
    } 
    render(){
        // code
    }
}

class Parent ...
    constructor(){
        super(props)
        this.state = {renderChild: true};
        this.handleChildUnmount = this.handleChildUnmount.bind(this);
    }
    handleChildUnmount(){
        this.setState({renderChild: false});
    }
    render(){
        // code
        {this.state.renderChild ? <Child unmountMe={this.handleChildUnmount} /> : null}
    }

}

Dies ist ein sehr einfaches Beispiel. Sie können jedoch einen groben Weg erkennen, um eine Aktion an das übergeordnete Element weiterzuleiten

Davon abgesehen sollten Sie wahrscheinlich das Geschäft durchlaufen (Versandaktion), damit Ihr Geschäft beim Rendern die richtigen Daten enthält

Ich habe Fehler- / Statusmeldungen für zwei separate Anwendungen erstellt. Beide haben den Speicher durchlaufen. Es ist die bevorzugte Methode ... Wenn Sie möchten, kann ich einen Code dazu veröffentlichen.

BEARBEITEN: So richte ich ein Benachrichtigungssystem mit React / Redux / Typescript ein

Einige Dinge, die zuerst zu beachten sind. Dies ist in Typoskript, so dass Sie die Typdeklarationen entfernen müssten :)

Ich verwende die npm-Pakete lodash für Operationen und Klassennamen (cx-Alias) für die Zuweisung von Inline-Klassennamen.

Das Schöne an diesem Setup ist, dass ich für jede Benachrichtigung eine eindeutige Kennung verwende, wenn die Aktion sie erstellt. (zB notify_id). Diese eindeutige ID ist aSymbol() . Auf diese Weise können Sie Benachrichtigungen zu jedem Zeitpunkt entfernen, da Sie wissen, welche Sie entfernen müssen. Mit diesem Benachrichtigungssystem können Sie so viele stapeln, wie Sie möchten, und diese verschwinden, wenn die Animation abgeschlossen ist. Ich bin mit dem Animationsereignis verbunden und wenn es beendet ist, löse ich einen Code aus, um die Benachrichtigung zu entfernen. Ich habe auch ein Fallback-Timeout eingerichtet, um die Benachrichtigung zu entfernen, falls der Animationsrückruf nicht ausgelöst wird.

Benachrichtigungsaktionen.ts

import { USER_SYSTEM_NOTIFICATION } from '../constants/action-types';

interface IDispatchType {
    type: string;
    payload?: any;
    remove?: Symbol;
}

export const notifySuccess = (message: any, duration?: number) => {
    return (dispatch: Function) => {
        dispatch({ type: USER_SYSTEM_NOTIFICATION, payload: { isSuccess: true, message, notify_id: Symbol(), duration } } as IDispatchType);
    };
};

export const notifyFailure = (message: any, duration?: number) => {
    return (dispatch: Function) => {
        dispatch({ type: USER_SYSTEM_NOTIFICATION, payload: { isSuccess: false, message, notify_id: Symbol(), duration } } as IDispatchType);
    };
};

export const clearNotification = (notifyId: Symbol) => {
    return (dispatch: Function) => {
        dispatch({ type: USER_SYSTEM_NOTIFICATION, remove: notifyId } as IDispatchType);
    };
};

Benachrichtigungsreduzierer.ts

const defaultState = {
    userNotifications: []
};

export default (state: ISystemNotificationReducer = defaultState, action: IDispatchType) => {
    switch (action.type) {
        case USER_SYSTEM_NOTIFICATION:
            const list: ISystemNotification[] = _.clone(state.userNotifications) || [];
            if (_.has(action, 'remove')) {
                const key = parseInt(_.findKey(list, (n: ISystemNotification) => n.notify_id === action.remove));
                if (key) {
                    // mutate list and remove the specified item
                    list.splice(key, 1);
                }
            } else {
                list.push(action.payload);
            }
            return _.assign({}, state, { userNotifications: list });
    }
    return state;
};

app.tsx

Im Basis-Rendering für Ihre Anwendung würden Sie die Benachrichtigungen rendern

render() {
    const { systemNotifications } = this.props;
    return (
        <div>
            <AppHeader />
            <div className="user-notify-wrap">
                { _.get(systemNotifications, 'userNotifications') && Boolean(_.get(systemNotifications, 'userNotifications.length'))
                    ? _.reverse(_.map(_.get(systemNotifications, 'userNotifications', []), (n, i) => <UserNotification key={i} data={n} clearNotification={this.props.actions.clearNotification} />))
                    : null
                }
            </div>
            <div className="content">
                {this.props.children}
            </div>
        </div>
    );
}

user-notification.tsx

Benutzerbenachrichtigungsklasse

/*
    Simple notification class.

    Usage:
        <SomeComponent notifySuccess={this.props.notifySuccess} notifyFailure={this.props.notifyFailure} />
        these two functions are actions and should be props when the component is connect()ed

    call it with either a string or components. optional param of how long to display it (defaults to 5 seconds)
        this.props.notifySuccess('it Works!!!', 2);
        this.props.notifySuccess(<SomeComponentHere />, 15);
        this.props.notifyFailure(<div>You dun goofed</div>);

*/

interface IUserNotifyProps {
    data: any;
    clearNotification(notifyID: symbol): any;
}

export default class UserNotify extends React.Component<IUserNotifyProps, {}> {
    public notifyRef = null;
    private timeout = null;

    componentDidMount() {
        const duration: number = _.get(this.props, 'data.duration', '');
       
        this.notifyRef.style.animationDuration = duration ? `${duration}s` : '5s';

        
        // fallback incase the animation event doesn't fire
        const timeoutDuration = (duration * 1000) + 500;
        this.timeout = setTimeout(() => {
            this.notifyRef.classList.add('hidden');
            this.props.clearNotification(_.get(this.props, 'data.notify_id') as symbol);
        }, timeoutDuration);

        TransitionEvents.addEndEventListener(
            this.notifyRef,
            this.onAmimationComplete
        );
    }
    componentWillUnmount() {
        clearTimeout(this.timeout);

        TransitionEvents.removeEndEventListener(
            this.notifyRef,
            this.onAmimationComplete
        );
    }
    onAmimationComplete = (e) => {
        if (_.get(e, 'animationName') === 'fadeInAndOut') {
            this.props.clearNotification(_.get(this.props, 'data.notify_id') as symbol);
        }
    }
    handleCloseClick = (e) => {
        e.preventDefault();
        this.props.clearNotification(_.get(this.props, 'data.notify_id') as symbol);
    }
    assignNotifyRef = target => this.notifyRef = target;
    render() {
        const {data, clearNotification} = this.props;
        return (
            <div ref={this.assignNotifyRef} className={cx('user-notification fade-in-out', {success: data.isSuccess, failure: !data.isSuccess})}>
                {!_.isString(data.message) ? data.message : <h3>{data.message}</h3>}
                <div className="close-message" onClick={this.handleCloseClick}>+</div>
            </div>
        );
    }
}
John Ruddell
quelle
1
"durch den Laden"? Ich denke, ich vermisse ein paar wichtige Lektionen dazu: D Danke für die Antwort und den Code, aber denkst du nicht, dass dies für eine einfache Komponente zur Anzeige von Fehlermeldungen ernsthaft übertrieben ist? Es sollte nicht in der Verantwortung des Elternteils liegen, eine für das Kind definierte Aktion
durchzuführen
Es sollte eigentlich der Elternteil sein, da der Elternteil dafür verantwortlich ist, das Kind überhaupt in das DOM zu bringen. Wie ich schon sagte, obwohl dies ein Weg ist, würde ich es nicht empfehlen. Sie sollten eine Aktion verwenden, mit der Ihr Geschäft aktualisiert wird. Auf diese Weise sollten sowohl Flux- als auch Redux-Muster verwendet werden.
John Ruddell
Ok, dann würde ich mich freuen, einen Zeiger auf Codefragmente zu bekommen. Ich komme auf diesen Code zurück, wenn ich ein bisschen über Flux und Reduc gelesen habe!
Sephy
Ok ja, ich denke, ich mache ein einfaches Github-Repo, das einen Weg zeigt, es zu tun. Als letztes habe ich CSS-Animationen verwendet, um das Element auszublenden, das String- oder HTML-Elemente rendern kann, und als die Animation abgeschlossen war, habe ich Javascript verwendet, um darauf zu warten und mich dann selbst zu bereinigen (aus dem DOM entfernen), wenn entweder das Animation beendet oder Sie haben auf die Schaltfläche "Entlassen" geklickt.
John Ruddell
Bitte tun Sie dies, wenn es anderen wie mir helfen kann, die ein wenig Schwierigkeiten haben, die Philosophie von React zu verstehen. Außerdem würde ich mich gerne von ein paar meiner Punkte für die benötigte Zeit trennen, wenn Sie dafür ein Git-Repo aufstellen! Sagen wir hundert Punkte (Kopfgeld in 2 Tagen verfügbar)
Sephy
25

anstatt zu verwenden

ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(this).parentNode);

versuchen Sie es mit

ReactDOM.unmountComponentAtNode(document.getElementById('root'));
M Rezvani
quelle
Hat jemand dies mit React 15 versucht? Dies scheint sowohl potenziell nützlich als auch möglicherweise ein Anti-Muster zu sein.
theUtherSide
4
@theUtherSide Dies ist ein Anti-Muster in Reaktion. React Docs empfehlen, dass Sie ein Kind vom Elternteil über State / Requisiten
abhängen
1
Was passiert, wenn die nicht gemountete Komponente das Stammverzeichnis Ihrer React-App ist, aber nicht das Stammelement, das ersetzt wird? Zum Beispiel <div id="c1"><div id="c2"><div id="react-root" /></div></div>. Was ist, wenn der innere Text von c1ersetzt wird?
Flipdoubt
1
Dies ist nützlich, wenn Sie die Bereitstellung Ihrer Root-Komponente aufheben möchten, insbesondere wenn sich eine Reaktions-App in einer Nicht-Reaktions-App befindet. Ich musste dies verwenden, weil ich die Reaktion in einem Modal rendern wollte, das von einer anderen App verwaltet wird, und deren Modal Schließen-Schaltflächen haben, die das Modal verbergen, aber meine Reaktion bleibt weiterhin aktiviert. reactjs.org/blog/2015/10/01/react-render-and-top-level-api.html
Abba
10

In den meisten Fällen reicht es aus, das Element nur auf folgende Weise auszublenden:

export default class ErrorBoxComponent extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            isHidden: false
        }
    }

    dismiss() {
        this.setState({
            isHidden: true
        })
    }

    render() {
        if (!this.props.error) {
            return null;
        }

        return (
            <div data-alert className={ "alert-box error-box " + (this.state.isHidden ? 'DISPLAY-NONE-CLASS' : '') }>
                { this.props.error }
                <a href="#" className="close" onClick={ this.dismiss.bind(this) }>&times;</a>
            </div>
        );
    }
}

Oder Sie können über diese übergeordnete Komponente rendern / erneut rendern / nicht rendern

export default class ParentComponent extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            isErrorShown: true
        }
    }

    dismiss() {
        this.setState({
            isErrorShown: false
        })
    }

    showError() {
        if (this.state.isErrorShown) {
            return <ErrorBox 
                error={ this.state.error }
                dismiss={ this.dismiss.bind(this) }
            />
        }

        return null;
    }

    render() {

        return (
            <div>
                { this.showError() }
            </div>
        );
    }
}

export default class ErrorBoxComponent extends React.Component {
    dismiss() {
        this.props.dismiss();
    }

    render() {
        if (!this.props.error) {
            return null;
        }

        return (
            <div data-alert className="alert-box error-box">
                { this.props.error }
                <a href="#" className="close" onClick={ this.dismiss.bind(this) }>&times;</a>
            </div>
        );
    }
}

Schließlich gibt es eine Möglichkeit, HTML-Knoten zu entfernen, aber ich weiß wirklich nicht, ob es eine gute Idee ist. Vielleicht sagt jemand, der React von intern kennt, etwas dazu.

export default class ErrorBoxComponent extends React.Component {
    dismiss() {
        this.el.remove();
    }

    render() {
        if (!this.props.error) {
            return null;
        }

        return (
            <div data-alert className="alert-box error-box" ref={ (el) => { this.el = el} }>
                { this.props.error }
                <a href="#" className="close" onClick={ this.dismiss.bind(this) }>&times;</a>
            </div>
        );
    }
}
Sasha Kos
quelle
Aber für den Fall, dass ich ein Kind aushängen möchte, das sich in einer Liste von Kindern befindet ... Was kann ich tun, wenn ich eine geklonte Komponente durch denselben Schlüssel in dieser Liste ersetzen möchte?
Roadev
1
Soweit ich weiß, möchten Sie Folgendes tun: document.getElementById (CHILD_NODE_ID) -> .remove (); -> document.getElementById (PARENT_NODE_ID) -> .appendChild (NEW_NODE)? Habe ich recht? Vergiss es. Es ist kein Reaktionsansatz. Verwenden Sie den Komponentenstatus für das Rendern von Bedingungen
Sasha Kos
2

Ich war jetzt ungefähr 10 Mal in diesem Beitrag und wollte nur meine zwei Cent hier lassen. Sie können es nur bedingt aushängen.

if (renderMyComponent) {
  <MyComponent props={...} />
}

Sie müssen es lediglich aus dem DOM entfernen, um die Bereitstellung aufzuheben.

Solange renderMyComponent = truedie Komponente gerendert wird. Wenn Sie festlegen renderMyComponent = false, wird die Bereitstellung aus dem DOM aufgehoben.

ihodonald
quelle
0

Dies ist nicht in allen Situationen angemessen, aber Sie können return falseinnerhalb der Komponente selbst bedingt arbeiten, wenn ein bestimmtes Kriterium erfüllt ist oder nicht.

Die Komponente wird nicht ausgehängt, aber alle gerenderten Inhalte werden entfernt. Dies wäre meiner Meinung nach nur dann schlecht, wenn die Komponente Ereignis-Listener enthält, die entfernt werden sollten, wenn die Komponente nicht mehr benötigt wird.

import React, { Component } from 'react';

export default class MyComponent extends Component {
    constructor(props) {
        super(props);

        this.state = {
            hideComponent: false
        }
    }

    closeThis = () => {
        this.setState(prevState => ({
            hideComponent: !prevState.hideComponent
        })
    });

    render() {
        if (this.state.hideComponent === true) {return false;}

        return (
            <div className={`content`} onClick={() => this.closeThis}>
                YOUR CODE HERE
            </div>
        );
    }
}
nebulousecho
quelle