Cara unmount, unrender, atau hapus komponen, dari dirinya sendiri dalam pesan notifikasi React / Redux / Typescript


114

Saya tahu pertanyaan ini telah diajukan beberapa kali tetapi sebagian besar waktu, solusinya adalah menangani ini di orang tua, karena aliran tanggung jawab hanya menurun. Namun, terkadang, Anda perlu mematikan komponen dari salah satu metodenya. Saya tahu saya tidak dapat memodifikasi propsnya, dan Jika saya mulai menambahkan boolean sebagai status, itu akan mulai menjadi sangat berantakan untuk komponen sederhana. Inilah yang saya coba capai: Komponen kotak kesalahan kecil, dengan "x" untuk menutupnya. Menerima kesalahan melalui alat peraga akan menampilkannya tetapi saya ingin cara untuk menutupnya dari kodenya sendiri.

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;

Dan saya akan menggunakannya seperti ini di komponen induk:

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

Di bagian Apa yang harus saya taruh di sini? , Saya sudah mencoba:

ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(this).parentNode); Yang melempar kesalahan bagus di konsol:

Peringatan: unmountComponentAtNode (): Node yang Anda coba unmount dirender oleh React dan bukan container level atas. Sebaliknya, minta komponen induk memperbarui status dan perenderannya untuk menghapus komponen ini.

Haruskah saya menyalin alat peraga yang masuk dalam status ErrorBox, dan memanipulasinya hanya secara internal?


Apakah Anda menggunakan Redux?
Arnau Lacambra

Mengapa ini menjadi persyaratan "Menerima kesalahan melalui properti akan menampilkannya tetapi saya ingin cara untuk menutupnya dari kodenya sendiri."? Pendekatan normal akan mengirimkan tindakan yang akan menghapus status kesalahan dan kemudian ditutup dalam siklus render induk seperti yang Anda singgung.
ken4z

Saya sebenarnya ingin menawarkan kemungkinan untuk keduanya. Memang, ini akan bisa ditutup seperti yang Anda jelaskan, tapi kasus saya adalah "bagaimana jika saya juga ingin bisa menutupnya dari dalam"
Sephy

Jawaban:


97

Sama seperti peringatan bagus yang Anda dapatkan, Anda mencoba melakukan sesuatu yang merupakan Anti-Pola di React. Ini adalah tidak-tidak. React dimaksudkan agar terjadi unmount dari hubungan orang tua ke anak. Sekarang jika Anda ingin seorang anak melepas dirinya sendiri, Anda dapat mensimulasikan ini dengan perubahan status pada induk yang dipicu oleh anak tersebut. biarkan saya menunjukkan kode.

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

}

ini adalah contoh yang sangat sederhana. tetapi Anda dapat melihat cara kasar untuk menyampaikan kepada orang tua suatu tindakan

Karena itu, Anda mungkin harus melalui toko (tindakan pengiriman) untuk memungkinkan toko Anda berisi data yang benar ketika akan dirender

Saya telah melakukan pesan kesalahan / status untuk dua aplikasi terpisah, keduanya melalui toko. Ini adalah metode yang disukai ... Jika Anda mau, saya dapat memposting beberapa kode tentang cara melakukannya.

EDIT: Berikut adalah cara saya mengatur sistem notifikasi menggunakan React / Redux / Typecript

Beberapa hal yang perlu diperhatikan terlebih dahulu. ini ada di skrip ketikan jadi Anda harus menghapus deklarasi tipe :)

Saya menggunakan paket npm lodash untuk operasi, dan nama kelas (alias cx) untuk penetapan nama kelas inline.

Keindahan dari penyiapan ini adalah saya menggunakan pengenal unik untuk setiap notifikasi saat tindakan membuatnya. (mis. notify_id). ID unik ini adalah a Symbol(). Dengan cara ini jika Anda ingin menghapus pemberitahuan apa pun kapan saja Anda bisa karena Anda tahu mana yang harus dihapus. Sistem notifikasi ini akan memungkinkan Anda menumpuk sebanyak yang Anda inginkan dan mereka akan hilang ketika animasi selesai. Saya mengaitkan ke acara animasi dan ketika selesai saya memicu beberapa kode untuk menghapus notifikasi. Saya juga menyiapkan batas waktu fallback untuk menghapus notifikasi kalau-kalau animasi callback tidak aktif.

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

di render dasar untuk aplikasi Anda, Anda akan membuat notifikasi

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

kelas notifikasi pengguna

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

1
"melalui toko"? Saya pikir, saya melewatkan beberapa pelajaran penting tentang itu: D Terima kasih atas jawaban dan kodenya tetapi bukankah menurut Anda ini benar-benar berlebihan untuk komponen tampilan pesan kesalahan sederhana? Seharusnya tidak menjadi tanggung jawab orang tua untuk menangani tindakan yang ditentukan pada anak ...
Sephy

Seharusnya ini menjadi induk sebenarnya karena orang tua bertanggung jawab untuk menempatkan anak di DOM sejak awal. Seperti yang saya katakan, meskipun ini adalah cara untuk melakukannya, saya tidak akan merekomendasikannya. Anda harus menggunakan tindakan yang memperbarui toko Anda. baik pola Flux dan Redux harus digunakan dengan cara ini.
John Ruddell

Oke, saya akan senang mendapatkan beberapa pointer dari potongan kode Jika Anda mau. Saya akan kembali ke bagian kode itu ketika saya membaca sedikit tentang Flux dan Reduc!
Sephy

Ok ya saya rasa saya akan membuat repo github sederhana yang menunjukkan cara untuk melakukannya. Yang terakhir saya lakukan, saya menggunakan animasi css untuk memudarkan elemen yang dapat membuat elemen string atau html dan kemudian ketika animasi selesai saya menggunakan javascript untuk mendengarkannya dan kemudian membersihkan dirinya sendiri (hapus dari DOM) ketika salah satu animasi selesai atau Anda mengklik tombol tutup.
John Ruddell

Silakan, Jika itu dapat membantu orang lain seperti saya yang sedikit kesulitan untuk memahami filosofi React. Juga, saya senang untuk berpisah dengan sedikit poin saya untuk waktu yang dibutuhkan Jika Anda memasang repo git untuk ini! Katakanlah seratus poin (hadiah tersedia dalam 2 hari sekalipun)
Sephy

25

alih-alih menggunakan

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

coba gunakan

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

Adakah yang pernah mencoba ini dengan React 15? Ini tampaknya berpotensi berguna dan mungkin anti-pola.
theUtherSide

4
@theUtherSide ini adalah pola anti dalam bereaksi. React docs menyarankan Anda untuk melepas anak dari orang tua melalui state / props
John Ruddell

1
Bagaimana jika komponen yang di-unmount adalah root dari aplikasi React Anda tetapi bukan elemen root yang diganti? Misalnya <div id="c1"><div id="c2"><div id="react-root" /></div></div>. Bagaimana jika teks bagian dalam c1diganti?
flipdoubt

1
Ini berguna jika Anda ingin melepas komponen root Anda terutama jika Anda memiliki aplikasi react yang berada di aplikasi non-react. Saya harus menggunakan ini karena saya ingin membuat reaksi di dalam modal yang ditangani oleh aplikasi lain, dan modal mereka memiliki tombol tutup yang akan menyembunyikan modal tetapi reactdom saya akan tetap terpasang. reactjs.org/blog/2015/10/01/react-render-and-top-level-api.html
Abba

10

Dalam kebanyakan kasus, cukup menyembunyikan elemen, misalnya dengan cara ini:

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

Atau Anda dapat merender / rerender / tidak merender melalui komponen induk seperti ini

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

Akhirnya, ada cara untuk menghapus node html, tetapi saya benar-benar tidak tahu apakah itu ide yang bagus. Mungkin seseorang yang mengetahui React dari internal akan mengatakan sesuatu tentang ini.

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

Tetapi, jika saya ingin melepas anak yang ada di dalam daftar anak ... Apa yang dapat saya lakukan jika saya ingin mengganti komponen kloning dengan kunci yang sama di daftar itu?
roadev

1
seperti yang saya mengerti Anda ingin melakukan sesuatu seperti ini: document.getElementById (CHILD_NODE_ID) -> .remove (); -> document.getElementById (PARENT_NODE_ID) -> .appendChild (NEW_NODE)? Apakah saya benar? Lupakan saja. Ini BUKAN pendekatan bereaksi. Gunakan status komponen untuk rendering kondisi
Sasha Kos

2

Saya telah mengunjungi posting ini sekitar 10 kali sekarang dan saya hanya ingin meninggalkan dua sen saya di sini. Anda bisa melepasnya secara bersyarat.

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

Yang harus Anda lakukan adalah menghapusnya dari DOM untuk melepasnya.

Selama renderMyComponent = true, komponen akan dirender. Jika Anda menyetelnya renderMyComponent = false, ini akan dilepas dari DOM.


-1

Ini tidak sesuai di semua situasi tetapi Anda dapat bersyarat return falsedi dalam komponen itu sendiri jika kriteria tertentu terpenuhi atau tidak.

Itu tidak melepas komponen, tetapi menghapus semua konten yang dirender. Ini hanya akan berdampak buruk, dalam pikiran saya, jika Anda memiliki pendengar acara di komponen yang harus dihapus saat komponen tidak lagi diperlukan.

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>
        );
    }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.