Cách ngắt kết nối, hủy kết xuất hoặc xóa một thành phần khỏi chính nó trong tin nhắn thông báo React / Redux / Typescript


114

Tôi biết câu hỏi này đã được hỏi một vài lần nhưng hầu hết thời gian, giải pháp là xử lý vấn đề này ở phụ huynh, vì dòng chảy trách nhiệm chỉ giảm dần. Tuy nhiên, đôi khi, bạn cần phải giết một thành phần từ một trong các phương thức của nó. Tôi biết mình không thể sửa đổi các đạo cụ của nó và Nếu tôi bắt đầu thêm boolean làm trạng thái, nó sẽ bắt đầu thực sự lộn xộn đối với một thành phần đơn giản. Đây là những gì tôi đang cố gắng đạt được: Một thành phần hộp lỗi nhỏ, với dấu "x" để loại bỏ nó. Nhận lỗi thông qua các đạo cụ của nó sẽ hiển thị nó nhưng tôi muốn có một cách để đóng nó từ mã của chính nó.

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;

Và tôi sẽ sử dụng nó như thế này trong thành phần mẹ:

<ErrorBox error={this.state.error}/>

Trong phần Tôi nên đặt cái gì ở đây? , Tôi đã thử:

ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(this).parentNode); Cái nào gây ra một lỗi hay trong bảng điều khiển:

Cảnh báo: UnountComponentAtNode (): Nút mà bạn đang cố gắng hủy gắn kết đã được React hiển thị và không phải là vùng chứa cấp cao nhất. Thay vào đó, hãy yêu cầu thành phần mẹ cập nhật trạng thái của nó và kết xuất để loại bỏ thành phần này.

Tôi có nên sao chép các đạo cụ đến ở trạng thái ErrorBox và chỉ thao tác nội bộ không?


Bạn đang sử dụng Redux?
Arnau Lacambra

Tại sao đây là một yêu cầu "Nhận lỗi thông qua các đạo cụ của nó sẽ hiển thị nó nhưng tôi muốn có một cách để đóng nó từ mã của chính nó."? Cách tiếp cận thông thường sẽ là gửi một hành động để xóa trạng thái lỗi và sau đó được đóng lại trong một chu kỳ hiển thị của cha mẹ như bạn đã ám chỉ.
ken4z

Tôi thực sự muốn cung cấp khả năng cho cả hai. Thật vậy, nó sẽ có thể đóng lại như bạn đã giải thích, nhưng trường hợp của tôi là "điều gì sẽ xảy ra nếu tôi cũng muốn có thể đóng nó từ bên trong"
Sephy

Câu trả lời:


97

Cũng giống như cảnh báo tuyệt vời mà bạn nhận được, bạn đang cố gắng làm điều gì đó là Anti-Pattern trong React. Đây là điều không thể. React được thiết kế để không có mối quan hệ phụ huynh với con cái xảy ra một cách vô tận. Bây giờ nếu bạn muốn một đứa trẻ tự ngắt kết nối, bạn có thể mô phỏng điều này với sự thay đổi trạng thái ở cha mẹ do đứa trẻ kích hoạt. để tôi cho bạn xem mã.

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

}

đây là một ví dụ rất đơn giản. nhưng bạn có thể thấy một cách đơn giản để chuyển cho cha mẹ một hành động

Điều đó đang được nói rằng bạn có thể nên duyệt qua cửa hàng (hành động điều phối) để cho phép cửa hàng của bạn chứa dữ liệu chính xác khi nó chuyển sang hiển thị

Tôi đã thực hiện thông báo lỗi / trạng thái cho hai ứng dụng riêng biệt, cả hai đều đã qua cửa hàng. Đó là phương pháp ưa thích ... Nếu bạn muốn, tôi có thể đăng một số mã về cách làm điều đó.

CHỈNH SỬA: Đây là cách tôi thiết lập hệ thống thông báo bằng React / Redux / Typescript

Một số điều cần lưu ý đầu tiên. cái này ở dạng typecript nên bạn sẽ cần xóa khai báo kiểu :)

Tôi đang sử dụng gói npm lodash cho các hoạt động và tên lớp (bí danh cx) để gán tên lớp nội tuyến.

Cái hay của thiết lập này là tôi sử dụng một số nhận dạng duy nhất cho mỗi thông báo khi hành động tạo ra nó. (ví dụ: Inform_id). ID duy nhất này là một Symbol(). Bằng cách này nếu bạn muốn xóa bất kỳ thông báo nào bất kỳ lúc nào có thể vì bạn biết thông báo nào cần xóa. Hệ thống thông báo này sẽ cho phép bạn xếp chồng bao nhiêu tùy thích và chúng sẽ biến mất khi hoạt ảnh hoàn thành. Tôi đang kết nối sự kiện hoạt ảnh và khi nó kết thúc, tôi kích hoạt một số mã để xóa thông báo. Tôi cũng thiết lập thời gian chờ dự phòng để xóa thông báo trong trường hợp lệnh gọi lại hoạt ảnh không kích hoạt.

thông báo-hành động.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);
    };
};

thông báo-giảm thiểu.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

trong kết xuất cơ sở cho ứng dụng của bạn, bạn sẽ hiển thị thông báo

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

lớp thông báo người dùng

/*
    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
"qua cửa hàng"? Tôi nghĩ, tôi thiếu một vài bài học quan trọng về điều đó: D Cảm ơn vì câu trả lời và mã nhưng bạn có nghĩ rằng điều này là quá mức nghiêm trọng đối với một thành phần hiển thị thông báo lỗi đơn giản không?
Cha

Nó phải là cha mẹ thực sự vì cha mẹ chịu trách nhiệm đưa con vào DOM ở vị trí đầu tiên. Giống như tôi đã nói, mặc dù đây là một cách để làm điều đó, tôi sẽ không khuyến khích nó. Bạn nên sử dụng một hành động cập nhật cửa hàng của bạn. cả hai mẫu Flux và Redux nên được sử dụng theo cách này.
John Ruddell

Được rồi, tôi rất vui khi nhận được một số con trỏ của các đoạn mã Nếu bạn vui lòng. Tôi sẽ quay lại đoạn mã đó khi tôi đọc một chút về cả Flux và Reduce!
Sephy

Được rồi, tôi nghĩ tôi sẽ tạo một repo github đơn giản để chỉ ra cách thực hiện. Lần cuối cùng tôi đã làm, tôi đã sử dụng hoạt ảnh css để làm mờ dần phần tử có thể hiển thị các phần tử chuỗi hoặc html và sau đó khi hoạt ảnh hoàn thành, tôi sử dụng javascript để lắng nghe điều đó và sau đó tự dọn dẹp (xóa khỏi DOM) khi hoạt ảnh hoàn tất hoặc bạn đã nhấp vào nút loại bỏ.
John Ruddell

Hãy làm, nếu nó có thể giúp những người như tôi, những người đang gặp khó khăn một chút để nắm bắt triết lý của React. Ngoài ra, tôi rất vui được chia sẻ một chút số điểm của mình cho thời gian thực hiện Nếu bạn thiết lập một repo git cho việc này! Cho phép nói một trăm điểm (tiền thưởng có sẵn trong 2 ngày dù)
Sephy

25

thay vì sử dụng

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

thử sử dụng

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

Có ai đã thử điều này với React 15 chưa? Điều này có vẻ vừa hữu ích vừa có thể là một mô hình chống lại.
theUtherSide

4
@theUtherSide đây là một mô hình phản đối trong phản ứng. Phản ứng docs khuyên bạn unmount một đứa trẻ từ cha mẹ qua tiểu bang / props
John Ruddell

1
Điều gì sẽ xảy ra nếu thành phần đang được ngắt kết nối là phần tử gốc của ứng dụng React của bạn nhưng không phải phần tử gốc đang được thay thế? Ví dụ <div id="c1"><div id="c2"><div id="react-root" /></div></div>. Điều gì sẽ xảy ra nếu văn bản bên trong của c1được thay thế?
flipdoubt

1
Điều này rất hữu ích nếu bạn muốn ngắt kết nối thành phần gốc của mình, đặc biệt nếu bạn có ứng dụng phản ứng nằm trong ứng dụng không phản ứng. Tôi phải sử dụng điều này vì tôi muốn hiển thị phản ứng bên trong một phương thức được xử lý bởi một ứng dụng khác và phương thức của họ có các nút đóng sẽ ẩn phương thức nhưng tên miền phản ứng của tôi vẫn sẽ được gắn kết. reactjs.org/blog/2015/10/01/react-render-and-top-level-api.html
Abba

10

Trong hầu hết các trường hợp, chỉ cần ẩn phần tử là đủ, ví dụ theo cách này:

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

Hoặc bạn có thể kết xuất / kết xuất / không kết xuất thông qua thành phần mẹ như thế này

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

Cuối cùng, có một cách để loại bỏ nút html, nhưng tôi thực sự không biết đó có phải là một ý tưởng hay không. Có thể ai đó biết React từ nội bộ sẽ nói gì đó về điều này.

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

Tuy nhiên, trong trường hợp tôi muốn ngắt kết nối một phần tử nằm trong danh sách các phần tử con ... Tôi có thể làm gì nếu tôi muốn thay thế một thành phần nhân bản bằng cùng một khóa trong danh sách đó?
roadev

1
như tôi hiểu, bạn muốn làm điều gì đó như sau: document.getElementById (CHILD_NODE_ID) -> .remove (); -> document.getElementById (PARENT_NODE_ID) -> .appendChild (NEW_NODE)? Tôi nói đúng chứ? Quên nó đi. Đó là cách tiếp cận KHÔNG phản ứng. Sử dụng trạng thái thành phần để hiển thị điều kiện
Sasha Kos

2

Tôi đã đăng bài này khoảng 10 lần rồi và tôi chỉ muốn để lại hai xu ở đây. Bạn chỉ có thể ngắt kết nối nó có điều kiện.

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

Tất cả những gì bạn phải làm là xóa nó khỏi DOM để ngắt kết nối nó.

Miễn là renderMyComponent = true, thành phần sẽ hiển thị. Nếu bạn đặt renderMyComponent = false, nó sẽ ngắt kết nối khỏi DOM.


-1

Điều này không phù hợp trong mọi tình huống nhưng bạn có thể có điều kiện return false bên trong chính thành phần nếu một tiêu chí nhất định được hoặc không được đáp ứng.

Nó không ngắt kết nối thành phần, nhưng nó xóa tất cả nội dung được hiển thị. Điều này sẽ chỉ tệ, theo tôi, nếu bạn có các trình nghe sự kiện trong thành phần cần được loại bỏ khi thành phần không còn cần thiết nữa.

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>
        );
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.