簡體   English   中英

我似乎無法使用componentWillUnmount可靠地刪除偵聽器

[英]I can’t seem to reliably remove a listener using componentWillUnmount

我已經在窗口中添加了一個偵聽器,以檢測onClickOutside類型的場景(特別是在菜單外單擊時折疊菜單)。 這是帶有相關代碼的組件:

import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
import LinkedStateMixin from 'react-addons-linked-state-mixin';
import styles from './DescriptiveSelect.css';
import classNames from 'classnames';
import _ from 'underscore';

export default class DescriptiveSelect extends Component {

    static propTypes = {
        attrForOptionLabel: PropTypes.string,
        attrForOptionValue: PropTypes.string,
        children: PropTypes.string,
        className: PropTypes.string,
        disabled: PropTypes.bool,
        nullLabel: PropTypes.string,
        onUpdate: PropTypes.func,
        options: PropTypes.array,
        selectedLabel: PropTypes.string,
        selectedOption: PropTypes.number,
        valueLink: PropTypes.object
    }

    mixins: [LinkedStateMixin]

    static defaultProps = {
        attrForOptionLabel: 'name',
        attrForOptionValue: 'id',
        disabled: false,
        selectedOption: 0,
        selectedLabel: 'Select one…'
    }

    state = {
        isOpen: false,
        options: []
    }

    componentDidMount() {
        this.buildOptions(this.props.options);
        window.addEventListener('click', this.onDocumentClick.bind(this));
    }

    componentWillUnmount() {
        window.removeEventListener('click', this.onDocumentClick);
    }

    onDocumentClick(event) {
        const theLabel = ReactDOM.findDOMNode(this);

        if (theLabel && theLabel.contains(event.target)) {
            this.setState({isOpen: !this.state.isOpen});
        } else {
            this.setState({isOpen: false});
        }
        event.preventDefault();
    }

    handleSelection(option) {
        if (this.props.onUpdate) {
            this.props.onUpdate(option.id);
        }
        this.setState({'isOpen': false});
    }

    /**
     * Build out <select> menu options
     * Data must be formatted with the following attributes:
     *     const theData = [
     *         {
     *             id: 1,
     *             sequence: 0,
     *             short_name: 'App'
     *         }
     *     ];
     * @param  {array} data The data to convert, either from an endpoint
     *                      response or passed in via the `options` prop.
     */
    buildOptions(data) {
        const _this = this;
        const results = data;
        const resultLength = results.length;
        const {
            attrForOptionValue,
            attrForOptionLabel
        } = this.props;

        // Sort data by sequence attribute
        _.sortBy(results, 'sequence');

        // Cycle through JSON results and create <option> elements
        for (let i = 0; i < resultLength; i++) {
            const option = results[i];
            _this.state.options.push(
                <option key={option.id} value={option[attrForOptionValue]}>{option[attrForOptionLabel]}</option>
            );
            _this.forceUpdate();
        }
    }

    render() {
        const {
            className,
            nullLabel,
            options
        } = this.props;

        // Classes for select menu
        const selectClasses = classNames({
            [styles.LabelWrapper]: true,
            [className]: className
        });

        /**
         * Contents of the custom select.
         * Taken from the `options` prop that should be passed in.
         */
        const optionNodes = options.map((option) => {
            return (
                <li className={styles.Option} key={option.id} onClick={this.handleSelection.bind(this, option)}>
                    <div className={styles.OptionLabel}>a <strong>{option.name}</strong></div>
                    <div className={styles.OptionDetail}>{option.description}</div>
                </li>
            );
        });

        return (
            <label className={selectClasses} ref="theLabel">
                <select
                    className={styles.Select}
                    nullLabel={nullLabel}
                    options={options}
                    ref="theSelect"
                    valueLink={this.props.valueLink}
                >
                    { nullLabel ? <option value="">{nullLabel}</option> : null }
                    { this.state.options }
                </select>
                { this.state.isOpen ? <div className={styles.Menu}>
                    <ul className={styles.OptionsWrapper}>
                        {optionNodes}
                    </ul>
                    <footer className={styles.Footer}>
                        <p><a href="#">Learn more</a> about App.typography titles.</p>
                    </footer>
                </div> : null }
            </label>
        );
    }
}

我不確定為什么,但是它實際上並沒有刪除監聽器,因此最終在控制台中找到了其中幾個:

未捕獲的錯誤:始終違反:在未安裝的組件上調用findDOMNode。

這段代碼中有什么明顯的地方可能會引起問題?

您正在嘗試刪除未綁定的功能。 .bind返回一個函數,即this.onDocumentClick.bind(this) !== this.onDocumentClick

您應該在構造函數中綁定一次該方法,然后在整個過程中使用該方法:

constructor(props) {
  super(props);
  this.onDocumentClick = this.onDocumentClick.bind(this);
  // use this.onDocumentClick everywhere
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM