我似乎无法使用componentWillUnmount可靠地删除侦听器

时间:2015-11-19 16:05:16

标签: javascript reactjs

我在窗口中添加了一个侦听器来检测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   未安装的组件。

此代码中有任何可能导致此问题的内容?

1 个答案:

答案 0 :(得分:4)

您正在尝试删除未绑定的函数。 .bind会返回函数,即this.onDocumentClick.bind(this) !== this.onDocumentClick

您应该在构造函数中绑定方法一次,然后在整个过程中使用该方法:

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