web-dev-qa-db-ja.com

React / Redux / TypeScript通知メッセージ内でコンポーネントをアンマウント、レンダリング解除、またはコンポーネントから削除する方法

私はこの質問がすでに数回尋ねられていることを知っていますが、ほとんどの場合、解決策は責任の流れが下降するだけなので、親の中でこれを処理することです。ただし、場合によっては、そのメソッドの1つからコンポーネントを強制終了する必要があります。私はその小道具を修正することができないことを知っています、そして状態としてブール値を追加し始めるならば、それは単純なコンポーネントのために本当に面倒になり始めるでしょう。これがImが達成しようとしていることです:それを却下するために "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);これはコンソールにNiceエラーを投げます:

Warning:unmountComponentAtNode():アンマウントしようとしているノードはReactによってレンダリングされたもので、トップレベルのコンテナではありません。代わりに、親コンポーネントにその状態を更新させ、このコンポーネントを削除するために再描画させます。

入ってきた小道具をErrorBoxの状態でコピーして、内部でのみ操作する必要がありますか?

85
Sephy

そのニースの警告と同じように、あなたは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つの別々のアプリケーションのためにエラー/ステータスメッセージをしました、両方とも店を通りました。その推奨される方法...あなたがそれを行う方法について私がいくつかのコードを投稿することができたいのであれば。

編集:これは私がReact/Redux/TypeScriptを使って通知システムを設定する方法です。

最初に注意することはほとんどありません..これはTypeScriptですので、型宣言を削除する必要があります:)

操作にはnpmパッケージのlodashを、インラインクラス名の割り当てにはクラス名(cxエイリアス)を使用しています。

この設定の長所は、アクションによって作成されるときに、各通知に一意の識別子を使用することです。 (例:notify_id)この一意のIDはSymbol()です。あなたがどの通知を削除するのか知っているので、あなたがいつでも任意の時点で通知を削除したい場合はこの方法です。この通知システムはあなたが好きなだけスタックすることを可能にし、アニメーションが完了するとそれらは消えます。私はアニメーションイベントに夢中になっていて、それが終了したら通知を削除するためにいくつかのコードをトリガーします。アニメーションコールバックが起動しない場合に通知を削除するためのフォールバックタイムアウトも設定します。

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

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

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

user-notification.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>
        );
    }
}
76
John Ruddell

使用する代わりに

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

使ってみてください

ReactDOM.unmountComponentAtNode(document.getElementById('root'));
17
M Rezvani

ほとんどの場合、例えば次のように要素を隠すだけで十分です。

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>
        );
    }
}
10
Sasha Kos

私はこの記事に10回ほど行ったことがあるので、ここに2セントを残したいと思いました。あなたはただ条件付きでそれをアンマウントすることができます。

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

マウント解除するには、DOMから削除するだけです。

renderMyComponent = trueの間、コンポーネントはレンダリングされます。 renderMyComponent = falseを設定すると、DOMからマウント解除されます。

0
ihodonald