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


114

이 질문은 이미 몇 번 요청되었지만 대부분의 경우 해결책은 책임의 흐름이 하강하기 때문에 부모에서 이것을 처리하는 것입니다. 그러나 때로는 메서드 중 하나에서 구성 요소를 종료해야합니다. 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 상태로 복사하고 내부적으로 만 조작해야합니까?


Redux를 사용하고 있습니까?
Arnau Lacambra

왜 이것이 "props를 통해 오류를 수신하면 오류가 표시되지만 자체 코드에서 닫는 방법을 원합니다."라는 요구 사항입니까? 일반적인 접근 방식은 오류 상태를 지우고 언급 한대로 부모의 렌더링주기에서 닫히는 작업을 전달하는 것입니다.
ken4z

실제로 둘 다에 대한 가능성을 제공하고 싶습니다. 실제로 설명하신대로 닫을 수 있지만 제 경우는 "내부에서도 닫을 수 있도록하려면 어떻게해야하나요?"
Sephy

답변:


97

당신이받은 좋은 경고처럼, 당신은 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>
        );
    }
}

1
"가게를 통해"? 나는 그것에 대한 몇 가지 중요한 교훈을 놓치고 있다고 생각합니다 : D 대답과 코드에 감사하지만 이것이 간단한 오류 메시지 표시 구성 요소에 대해 심각하게 과잉이라고 생각하지 않습니까? 자녀에 대해 정의 된 행동을 처리하는 것은 부모의 책임이어서는 안됩니다 ...
Sephy

부모는 처음에 DOM에 자식을 넣는 책임이 있기 때문에 실제로 부모 여야합니다. 그래도 말했듯이 이것이 방법이지만 권장하지 않습니다. 상점을 업데이트하는 조치를 사용해야합니다. Flux와 Redux 패턴 모두 이런 식으로 사용해야합니다.
John Ruddell

좋아, 이드의 코드 조각에 대한 포인터를 얻을 수 있으면 좋겠다. Flux와 Reduc에 대해 조금 읽었을 때 그 코드로 돌아갈 것입니다!
Sephy

Ok 그래 나는 그것을하는 방법을 보여주는 간단한 github repo를 만들 것이라고 생각합니다. 내가 마지막으로 한 것은 css 애니메이션을 사용하여 문자열 또는 html 요소를 렌더링 할 수있는 요소를 페이드 아웃 한 다음 애니메이션이 완료되었을 때 자바 스크립트를 사용하여이를 수신 한 다음 자체 정리 (DOM에서 제거) 애니메이션이 완료되었거나 닫기 버튼을 클릭했습니다.
John Ruddell

React의 철학을 이해하기 위해 애쓰는 저와 같은 다른 사람들을 도울 수 있다면하십시오. 또한, 나는 당신이 이것을 위해 git repo를 올려 놓는다면 시간이 걸리는 동안 내 포인트를 조금 나누게되어 기쁩니다! 백 포인트를
가정 해 보겠습니다 (

25

사용하는 대신

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

사용해보십시오

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

누구든지 React 15로 이것을 시도 했습니까? 이것은 잠재적으로 유용하고 아마도 안티 패턴으로 보입니다.
theUtherSide

4
@theUtherSide 이것은 반응의 안티 패턴입니다. 반응 문서는 주 / 소품을 통해 부모로부터 아이를 마운트 해제 추천
존 러델

1
마운트 해제되는 구성 요소가 React 앱의 루트이지만 대체되는 루트 요소가 아닌 경우 어떻게됩니까? 예를 들면 <div id="c1"><div id="c2"><div id="react-root" /></div></div>. 의 내부 텍스트 c1가 바뀌면 어떻게됩니까?
flipdoubt

1
특히 비 반응 앱에 반응 앱이있는 경우 루트 구성 요소를 마운트 해제하려는 경우 유용합니다. 다른 앱에서 처리하는 모달 내부에서 반응을 렌더링하고 싶었 기 때문에 이것을 사용해야했고, 모달에는 모달을 숨기는 닫기 버튼이 있지만 내 reactdom은 여전히 ​​마운트 된 상태로 유지됩니다. reactjs.org/blog/2015/10/01/react-render-and-top-level-api.html
아바

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 노드를 제거하는 방법이 있지만 정말 좋은 생각인지 모르겠습니다. 내부에서 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>
        );
    }
}

하지만 자식 목록에있는 자식을 마운트 해제하려는 경우 ... 복제 된 구성 요소를 해당 목록의 동일한 키로 바꾸려면 어떻게해야합니까?
roadev

1
나는 당신이 다음과 같은 것을하고 싶다는 것을 이해합니다. document.getElementById (CHILD_NODE_ID)-> .remove (); -> document.getElementById (PARENT_NODE_ID)-> .appendChild (NEW_NODE)? 내가 맞아? 잊어 버려. 반응 방식이 아닙니다. 조건 렌더링을위한 구성 요소 상태 사용
사샤 코스

2

이 게시물에 10 번 정도 갔는데 여기에 2 센트를 남기고 싶었습니다. 조건부로 마운트 해제 할 수 있습니다.

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>
        );
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.