วิธีการยกเลิกการต่อเชื่อมยกเลิกการเชื่อมต่อหรือลบส่วนประกอบออกจากตัวเองในข้อความแจ้งเตือน 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 หรือไม่?
Arnau Lacambra

เหตุใดจึงมีข้อกำหนดนี้ "การรับข้อผิดพลาดผ่านอุปกรณ์ประกอบฉากจะแสดงขึ้น แต่ฉันต้องการวิธีปิดจากรหัสของตัวเอง" วิธีการปกติคือการส่งการดำเนินการที่จะล้างสถานะข้อผิดพลาดจากนั้นจึงถูกปิดในวงจรการแสดงผลของพาเรนต์ตามที่คุณพูดถึง
ken4z

ฉันต้องการเสนอความเป็นไปได้สำหรับทั้งสองอย่างจริงๆ อันที่จริงมันจะปิดได้ตามที่คุณอธิบายไว้ แต่กรณีของฉันคือ "จะเกิดอะไรขึ้นถ้าฉันต้องการให้สามารถปิดได้จากด้านใน"
Sephy

คำตอบ:


97

เช่นเดียวกับคำเตือนที่ดีที่คุณได้รับคุณกำลังพยายามทำอะไรบางอย่างที่ต่อต้านรูปแบบในการตอบสนอง นี่คือไม่มี การตอบสนองมีจุดมุ่งหมายเพื่อให้การยกเลิกการต่อเชื่อมเกิดขึ้นจากความสัมพันธ์ระหว่างพ่อแม่กับลูก ตอนนี้ถ้าคุณต้องการให้เด็กยกเลิกการต่อเชื่อมเองคุณสามารถจำลองสิ่งนี้ด้วยการเปลี่ยนแปลงสถานะในพาเรนต์ที่เด็กถูกทริกเกอร์ ให้ฉันแสดงรหัส

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 package lodash สำหรับการดำเนินการและชื่อคลาส (นามแฝง cx) สำหรับการกำหนดชื่อคลาสแบบอินไลน์

ความสวยงามของการตั้งค่านี้คือฉันใช้ตัวระบุที่ไม่ซ้ำกันสำหรับการแจ้งเตือนแต่ละครั้งเมื่อการดำเนินการสร้างขึ้น (เช่น alert_id) รหัสเฉพาะนี้คือไฟล์Symbol(). วิธีนี้หากคุณต้องการลบการแจ้งเตือนเมื่อใดก็ได้เพราะคุณรู้ว่าจะลบการแจ้งเตือนใด ระบบการแจ้งเตือนนี้จะช่วยให้คุณวางซ้อนกันได้มากเท่าที่คุณต้องการและจะหายไปเมื่อภาพเคลื่อนไหวเสร็จสมบูรณ์ ฉันกำลังเชื่อมต่อกับเหตุการณ์แอนิเมชั่นและเมื่อเสร็จสิ้นฉันจะทริกเกอร์โค้ดเพื่อลบการแจ้งเตือน ฉันยังตั้งค่าการหมดเวลาสำรองเพื่อลบการแจ้งเตือนในกรณีที่การเรียกกลับของภาพเคลื่อนไหวไม่เริ่มทำงาน

การแจ้งเตือน-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 ขอบคุณสำหรับคำตอบและรหัส แต่คุณไม่คิดว่านี่เป็นการใช้งานมากเกินไปสำหรับองค์ประกอบการแสดงข้อความแสดงข้อผิดพลาดธรรมดา ๆ หรือไม่? ไม่ควรเป็นความรับผิดชอบของผู้ปกครองในการจัดการกับการกระทำที่กำหนดไว้กับเด็ก ...
ก.ย.

ควรเป็นผู้ปกครองจริง ๆ เนื่องจากผู้ปกครองมีหน้าที่วางเด็กไว้ใน DOM ตั้งแต่แรก เหมือนที่ฉันพูดแม้ว่านี่จะเป็นวิธีที่ฉันไม่อยากแนะนำ คุณควรใช้การดำเนินการที่อัปเดตร้านค้าของคุณ ควรใช้ทั้งรูปแบบ Flux และ Redux ด้วยวิธีนี้
John Ruddell

โอเคยินดีที่จะรับตัวชี้ของส่วนรหัสถ้าคุณต้องการ ฉันจะกลับไปที่ส่วนของรหัสนั้นเมื่อฉันได้อ่านเกี่ยวกับทั้ง Flux และ Reduc!
Sephy

โอเคฉันคิดว่าฉันจะสร้าง github repo ง่ายๆที่แสดงวิธีการทำ สิ่งสุดท้ายที่ฉันใช้ฉันใช้ภาพเคลื่อนไหว css เพื่อเลือนองค์ประกอบที่สามารถแสดงองค์ประกอบสตริงหรือ html จากนั้นเมื่อภาพเคลื่อนไหวเสร็จสิ้นฉันใช้จาวาสคริปต์เพื่อฟังสิ่งนั้นจากนั้นทำความสะอาดตัวเอง (ลบออกจาก DOM) เมื่อทั้ง ภาพเคลื่อนไหวเสร็จสิ้นหรือคุณคลิกปุ่มปิด
John Ruddell

ได้โปรดทำเถอะถ้ามันสามารถช่วยคนอื่น ๆ เช่นฉันที่ดิ้นรนเล็กน้อยเพื่อเข้าใจปรัชญาของ React นอกจากนี้ฉันยินดีที่จะมีส่วนร่วมกับคะแนนของฉันในช่วงเวลาที่ดำเนินการหากคุณใส่ git repo สำหรับสิ่งนี้! ให้บอกว่าหนึ่งร้อยคะแนน (รางวัลมีให้ใน 2 วัน)
Sephy

25

แทนที่จะใช้

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

ลองใช้

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

มีใครลองใช้ React 15 บ้าง? สิ่งนี้ดูเหมือนทั้งอาจเป็นประโยชน์และอาจเป็นการต่อต้านรูปแบบ
theUtherSide

4
@theUtherSide นี่คือรูปแบบการต่อต้านในการตอบสนอง เอกสารตอบกลับแนะนำให้คุณยกเลิกการต่อเชื่อมเด็กจากผู้ปกครองผ่านรัฐ / อุปกรณ์ประกอบฉาก
John Ruddell

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
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 แต่ฉันไม่รู้จริงๆว่ามันเป็นความคิดที่ดี บางทีคนที่รู้ React จาก internal จะพูดอะไรเกี่ยวกับเรื่องนี้

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)? ฉันถูกไหม? ลืมมันซะ. มันไม่ใช่วิธีการตอบสนอง ใช้สถานะส่วนประกอบสำหรับการแสดงเงื่อนไข
Sasha Kos

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>
        );
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.