如何在 React/Redux/Type 脚本通知消息中从组件本身卸载、取消呈现或删除组件

我知道这个问题已经被问过好几次了,但是大多数时候,解决方法是在父母中处理这个问题,因为责任流只是在降低。但是,有时需要从组件的某个方法中删除组件。 我知道我不能修改它的道具,如果我开始添加布尔值作为状态,对于一个简单的组件来说,它会变得非常混乱。以下是我试图实现的目标: 一个小的错误框组件,带有一个“ 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); 这会在控制台中抛出一个很好的错误:

警告: unmount 组件 AtNode () : 您试图卸载的节点是由 React 呈现的,不是顶级容器。相反,让父组件更新其状态并重新呈现,以便删除此组件。

我是否应该以 ErrorBox 状态复制传入的道具,并仅在内部操作它?

295578 次浏览

就像你得到的那个很好的警告一样,你正在尝试做一些反模式的事情。绝对不行。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/Type 脚本设置通知系统的

首先需要注意的是,这是在类型脚本中,所以您需要删除类型声明:)

我使用 npm 包 loash 进行操作,使用 classname (cx 别名)进行内联 classname 赋值。

这个设置的美妙之处在于,当操作创建每个通知时,我都会使用一个唯一标识符。(例如通知 _ id)。这个唯一的 ID 是 Symbol()。这样,如果您想在任何时候删除任何通知,您可以这样做,因为您知道要删除哪个通知。这个通知系统将让你堆栈多少你想要的,他们将离开时,动画完成。我挂钩到动画事件,当它完成时,我触发了一些代码,以删除通知。我还设置了一个回退超时来删除通知,以防动画回调没有触发。

通知-行动

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

通知-减少通知

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

在应用程序的基础呈现中,您将呈现通知

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

用户通知

用户通知类

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

而不是利用

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

尝试使用

ReactDOM.unmountComponentAtNode(document.getElementById('root'));

在大多数情况下,仅仅隐藏元素就足够了,例如:

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 节点,但我真的不知道这是一个好主意。也许知道内部消息的人会说点什么。

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

这个帖子我已经看了10遍了,我只是想把我的意见留在这里。您可以有条件地卸载它。

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

您所需要做的就是从 DOM 中删除它,以便卸载它。

只要 renderMyComponent = true,组件就会呈现。如果你设置 renderMyComponent = false,它就会从 DOM 卸载。

这并不适用于所有情况,但是如果满足或不满足某个条件,您可以有条件地在组件本身内部使用 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>
);
}
}