1. ホーム
  2. reactjs

[解決済み] React/Redux/Typescriptの通知メッセージでコンポーネントをアンマウント、アンレンダリング、または削除する方法

2022-05-30 13:29:33

質問

この質問はすでに何度かされていると思いますが、ほとんどの場合、責任の流れが下降するのみなので、解決策は親でこれを処理することです。しかし、時にはコンポーネントをそのメソッドの1つから削除しなければならないことがあります。 プロップを変更できないことは分かっていますし、ステートとしてブール値を追加し始めると、単純なコンポーネントとしては本当に厄介なことになりそうです。以下は、私が達成しようとしていることです。 小さなエラーボックスコンポーネントで、"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の状態で受信したpropsをコピーし、内部でのみ操作すればよいのでしょうか?

どのように解決するのですか?

警告が出たように、あなたは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}
    }

}

これは非常に単純な例ですが、親にアクションを渡すための大まかな方法を見ることができます。

とはいえ、レンダリングするときに正しいデータを格納できるように、ストア (ディスパッチアクション) を通過させるべきでしょう。

私は 2 つの別々のアプリケーションでエラー/ステータス メッセージを作成しましたが、両方ともストアを経由していました。これは望ましい方法です... 必要であれば、それを行う方法についていくつかのコードを投稿することができます。

EDIT: React/Redux/Typescript を使って通知システムをセットアップする方法を紹介します。

これはtypescriptで書かれているので、型宣言を削除する必要があります :)

操作にはnpmパッケージのlodash、インラインでのクラス名付与にはclassnames(cx alias)を使用しています。

このセットアップの美点は、アクションがそれを作成するとき、それぞれの通知に対して一意な識別子を使うことです。(例: notify_id) です。このユニークなIDは Symbol() . この方法で、もしあなたが任意の時点で任意の通知を削除したい場合、あなたはどの通知を削除するべきか知っているので、できます。この通知システムは、好きなだけ通知を積み重ねることができ、アニメーションが完了すると消えていきます。私は、アニメーション・イベントにフックして、それが終了したときに、通知を削除するコードをトリガーしている。また、アニメーションのコールバックが発射されない場合に備えて、通知を削除するためのフォールバックタイムアウトを設定しました。

通知アクション.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);
    };
};

通知-減速機.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

アプリケーションのベースレンダリングでは、通知をレンダリングします。

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