Як відключити, скасувати чи видалити компонент із себе у повідомленні про повідомлення React / Redux / Typescript


114

Я знаю, що це питання вже задавались декілька разів, але більшість випадків рішення - це вирішити це питання у батьків, оскільки потік відповідальності лише зменшується. Однак іноді потрібно вбити компонент одним із його методів. Я знаю, що я не можу змінити його реквізити, і якщо я почну додавати булеві як стан, він почне бути справді безладним для простого компонента. Ось чого я намагаюся досягти: Невеликий компонент поля помилок, з "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 та маніпулювати ним лише внутрішньо?


Ви використовуєте Redux?
Арнау Лакамбра

Чому така вимога "Отримання помилки через реквізит відобразить її, але я хотів би спосіб її закрити з власного коду."? Нормальним підходом було б відправлення дії, яка очистила б стан помилки, а потім закриється в циклі візуалізації батьківського, як ви нагадали.
ken4z

Я хотів би запропонувати можливість для обох насправді. Дійсно, це можна буде закрити, як ви пояснили, але мій випадок - "що, якщо я теж хочу мати можливість закрити його зсередини"
Sephy

Відповіді:


97

Як і те приємне попередження, яке ви отримали, ви намагаєтесь зробити щось, що є анти-шаблоном в 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}
    }

}

це дуже простий приклад. але ви можете бачити приблизний спосіб передати батькові дію

Зважаючи на те, що ви, ймовірно, повинні пройти через магазин (диспетчерська дія), щоб ваш магазин міг містити правильні дані, коли він відображатиметься

Я робив повідомлення про помилки / статус для двох окремих програм, обидва пройшли через магазин. Це кращий метод ... Якщо ви хочете, я можу опублікувати якийсь код, як це зробити.

EDIT: Ось, як я налаштував систему повідомлень за допомогою React / Redux / Typescript

Дещо слід зазначити спочатку. це в машинописі, тому вам потрібно буде видалити декларації типу :)

Я використовую lodash пакетів npm для операцій, а також імена класів (cx псевдонім) для вбудованого вбудованого імені класу.

Краса цієї установки полягає в тому, що я використовую унікальний ідентифікатор для кожного повідомлення, коли дія створює його. (наприклад, notify_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);
    };
};

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

користувач-сповіщення.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>
        );
    }
}

1
"через магазин"? Я думаю, мені не вистачає декількох важливих уроків з цього приводу: D Спасибі за відповідь та код, але ви не думаєте, що це серйозно надмірно для простого компонента відображення повідомлення про помилку? Батько не повинен відповідати за дії, визначені щодо дитини ...
Сефі

Це має бути батьком насправді, оскільки батько несе відповідальність за те, щоб поставити дитину в DOM на перше місце. Як я казав, хоча, хоча це спосіб зробити це, я б не рекомендував це. Ви повинні використовувати дію, яка оновлює ваш магазин. і Flux, і Redux повинні використовуватися таким чином.
Джон Руддел

Гаразд, я буду радий отримати деякий покажчик фрагментів коду. Я повернусь до цього фрагмента коду, коли я прочитав трохи про Flux і Reduc!
Сефі

Гаразд, я думаю, я зроблю просте репо з github, показуючи спосіб це зробити. В останньому, що я зробив, я використовував css-анімацію, щоб згасати, зникаючи елемент, який міг би відображати рядкові або html-елементи, а потім, коли анімація завершена, я використовував javascript, щоб прослухати це, а потім очистити себе (видалити з DOM), коли або анімація закінчена або ви натиснули кнопку відхилення.
Джон Руддел

Будь ласка, зробіть це, якщо це може допомогти іншим, як я, які трохи намагаються зрозуміти філософію React. Крім того, я радий розлучитися з моїми балами за витрачений час, якщо ви все-таки покладете на це git repo! Давайте скажемо, що сто очок (бонуси доступні за 2 дні)
Сефі

25

замість використання

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

спробуйте використовувати

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

Хтось пробував це з React 15? Це здається і потенційно корисним, і, можливо, анти-закономірним.
thetherSide

4
@theUtherSide це протидію реакції. Документи React рекомендують від’єднати дитину від батьків через стан / реквізит
Джон Рудделл,

1
Що робити, якщо компонент, який не відключений, є коренем програми React, але не замінюється кореневим елементом? Наприклад <div id="c1"><div id="c2"><div id="react-root" /></div></div>. Що робити, якщо внутрішній текст c1замінюється?
flipdoubt

1
Це корисно, якщо ви хочете відключити свій кореневий компонент, особливо якщо у вас є реагуючий додаток, що знаходиться в додатку, який не реагує. Мені довелося скористатися цим, оскільки я хотів відреагувати на модальний режим, який обробляється іншим додатком, і їхні модальні кнопки закривають, які приховуватимуть модаль, але моє реагування все ще залишатиметься встановленим. reactjs.org/blog/2015/10/01/react-render-and-top-level-api.html
Abba

10

У більшості випадків досить просто приховати елемент, наприклад таким чином:

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

Але якщо я хочу відключити дитину, яка знаходиться у списку дітей ... Що я можу зробити, якщо хочу замінити клонований компонент тим самим ключем у цьому списку?
roadev

1
наскільки я розумію, ти хочеш зробити щось подібне: document.getElementById (CHILD_NODE_ID) -> .remove (); -> document.getElementById (PARENT_NODE_ID) -> .appendChild (NEW_NODE)? Маю рацію? Забути про це. Це НЕ реагує на підхід. Використовуйте стан компонентів для надання умови
Саша Кос

2

Я був на цій посаді близько 10 разів зараз, і я просто хотів залишити свої два центи тут. Ви можете просто відключити його умовно.

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

Все, що вам потрібно зробити - це видалити його з DOM, щоб відключити його.

Поки renderMyComponent = trueкомпонент відображатиметься. Якщо ви встановите renderMyComponent = false, він відключиться від DOM.


-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>
        );
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.