[reactjs] React / Redux / Typescript 알림 메시지에서 컴포넌트를 마운트 해제, 렌더링 해제 또는 제거하는 방법

이 질문은 이미 몇 번 요청되었지만 대부분의 경우 해결책은 책임의 흐름이 하강하기 때문에 부모에서 이것을 처리하는 것입니다. 그러나 때로는 메서드 중 하나에서 구성 요소를 종료해야합니다. props를 수정할 수 없다는 것을 알고 있으며, 상태로 부울을 추가하기 시작하면 간단한 구성 요소에 대해 정말 지저분해질 것입니다. 내가 달성하려는 것은 다음과 같습니다. “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에 의해 렌더링되었으며 최상위 컨테이너가 아닙니다. 대신이 구성 요소를 제거하려면 상위 구성 요소가 상태를 업데이트하고 다시 렌더링하도록하십시오.

들어오는 props를 ErrorBox 상태로 복사하고 내부적으로 만 조작해야합니까?



답변

당신이받은 좋은 경고처럼, 당신은 React에서 Anti-Pattern 인 무언가를하려고합니다. 이것은 아니오입니다. 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 / 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>
        );
    }
}


답변

사용하는 대신

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 노드를 제거하는 방법이 있지만 정말 좋은 생각인지 모르겠습니다. 내부에서 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 번 정도 갔는데 여기에 2 센트를 남기고 싶었습니다. 조건부로 마운트 해제 할 수 있습니다.

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


답변