145

我知道这个问题已经被问过几次了,但大多数时候,解决方案是在父级中处理这个问题,因为责任流只会下降。但是,有时,您需要从其中一种方法中终止组件。我知道我不能修改它的 props,如果我开始添加布尔值作为状态,对于一个简单的组件来说它会开始变得非常混乱。这是我要实现的目标:一个小的错误框组件,带有“x”来关闭它。通过它的道具接收错误将显示它,但我想要一种从它自己的代码中关闭它的方法。

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;

我会在父组件中这样使用它:

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

我应该在这里放什么?,我已经尝试过:

ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(this).parentNode); 这在控制台中引发了一个很好的错误:

警告:unmountComponentAtNode():您尝试卸载的节点是由 React 渲染的,并且不是顶级容器。相反,让父组件更新其状态并重新渲染以删除此组件。

我应该在 ErrorBox 状态下复制传入的道具,并仅在内部对其进行操作吗?

4

5 回答 5

117

就像你得到的那个很好的警告一样,你正在尝试做一些 React 中的反模式。这是一个禁忌。React 旨在使父子关系发生卸载。现在,如果您希望子级自行卸载,您可以通过子级触发的父级状态更改来模拟这一点。让我用代码告诉你。

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}
    }

}

这是一个非常简单的例子。但是您可以看到一种粗略的方式将动作传递给父级

话虽这么说,您可能应该通过商店(调度操作)以允许您的商店在渲染时包含正确的数据

我已经为两个单独的应用程序完成了错误/状态消息,都通过了商店。这是首选方法...如果您愿意,我可以发布一些有关如何执行此操作的代码。

编辑:这是我使用 React/Redux/Typescript 设置通知系统的方法

首先要注意几点。这是在打字稿中,因此您需要删除类型声明:)

我使用 npm 包 lodash 进行操作,使用类名(cx 别名)进行内联类名分配。

此设置的美妙之处在于,当操作创建通知时,我为每个通知使用唯一标识符。(例如 notify_id)。这个唯一的 ID 是一个Symbol(). 这样,如果您想在任何时间点删除任何通知,您都可以,因为您知道要删除哪个通知。这个通知系统可以让你根据需要堆叠任意数量,动画完成后它们会消失。我正在连接动画事件,当它完成时我会触发一些代码来删除通知。我还设置了一个后备超时来删除通知,以防动画回调不触发。

通知-actions.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);
    };
};

通知-reducer.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;
};

应用程序.tsx

在您的应用程序的基本渲染中,您将渲染通知

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>
    );
}

用户通知.tsx

用户通知类

/*
    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>
        );
    }
}
于 2016-05-02T16:52:37.357 回答
29

而不是使用

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

尝试使用

ReactDOM.unmountComponentAtNode(document.getElementById('root'));
于 2017-07-04T08:05:15.413 回答
12

在大多数情况下,只需隐藏元素就足够了,例如以这种方式:

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>
        );
    }
}

或者您可以像这样通过父组件渲染/重新渲染/不渲染

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>
        );
    }
}

最后,有一种方法可以删除 html 节点,但我真的不知道这是一个好主意。也许从内部了解 React 的人会对此说些什么。

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>
        );
    }
}
于 2017-09-29T14:41:59.927 回答
4

我已经去过这个帖子大约 10 次了,我只是想把我的两分钱留在这里。您可以有条件地卸载它。

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

您所要做的就是将其从 DOM 中移除以卸载它。

只要renderMyComponent = true,组件就会渲染。如果设置renderMyComponent = false,它将从 DOM 中卸载。

于 2019-05-13T21:13:09.897 回答
-1

这并不适用于所有情况,但return false如果满足或不满足特定条件,您可以有条件地在组件本身内部。

它不会卸载组件,但会删除所有呈现的内容。在我看来,这只会很糟糕,如果您在组件中有事件侦听器,当不再需要该组件时应该将其删除。

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>
        );
    }
}
于 2020-01-28T20:20:04.163 回答