IT story

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

hot-time 2020. 8. 30. 19:48
반응형

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를 사용하여 알림 시스템을 설정하는 방법은 다음과 같습니다.

먼저 주목할 몇 가지 사항 .. 이것은 타입 스크립트에 있으므로 타입 선언을 제거해야합니다. :)

작업에는 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'));

In most cases, it is enough just to hide the element, for example in this way:

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

Or you may render/rerender/not render via parent component like this

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

Finally, there is a way to remove html node, but i really dont know is it a good idea. Maybe someone who knows React from internal will say something about this.

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

I've been to this post about 10 times now and I just wanted to leave my two cents here. You can just unmount it conditionally.

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

All you have to do is remove it from the DOM in order to unmount it.

As long as renderMyComponent = true, the component will render. If you set renderMyComponent = false, it will unmount from the DOM.

참고URL : https://stackoverflow.com/questions/36985738/how-to-unmount-unrender-or-remove-a-component-from-itself-in-a-react-redux-typ

반응형