首页 > 解决方案 > 使用 React ES6 被动监听子事件(不使用 jquery)

问题描述

我想制作一种手风琴扩展面板:但我不想有一个用于手风琴的组件和另一个用于单个扩展面板的组件。我的目标是做这样的事情:

注意:我让它在另一个环境中工作,该环境使用来自特定框架的命名事件侦听器,但我想在没有框架的情况下制作它,只对 ES6 做出反应

<ExpandingGroup id={'groupOne'} uniqOpen={true}>
  <ExpandingPanel id={'panelOne'}  />
  <ExpandingPanel id={'panelTwo'}  />
  <ExpandingPanel id={'panelThree'}/>
  <ExpandingPanel id={'panelFour'} />
</ExpandingGroup>

因此,当我单击展开面板一时,它会自行展开,当我单击面板二(并且 uniqOpen 标志打开)时,面板二将打开,面板一将关闭;我玩道具和回调的问题是我必须污染代码并让两个不同的组件做同样的事情。为什么不在 ExpandingGroup 范围内嵌套一组 ExpandingPanel。

这是我的 ExpandingPanel 代码:(忽略行、列和标签布局)

import React from 'react';
import PropTypes from 'prop-types';
import Label from 'Base/Components/Label';
import Column from 'Base/Layouts/Column';
import Row from 'Base/Layouts/Row';

class ExpandPanel extends React.Component {
    constructor(props) {
        super(props);

        this.id = this.props.id;
        this.groupId = undefined;
        this.anchorId = `anchor_${this.id}`;
        this.panelId = `panel_${this.id}`;
        this.colors = {
            white: '#ffffff',
            border: '#333333',
        };
        this.dim = {
            minWidth: 40
        }

        this.styles = styles.bind(this);
        this.getMaxHeight = getMaxHeight.bind(this);
        this.handleUniq = handleUniq.bind(this);
        this.handleClose = handleClose.bind(this);
        this.handleOpenPanel = handleOpenPanel.bind(this);
        this.handleToggle = handleToggle.bind(this);
        this.checkExpanded = checkExpanded.bind(this);
        this.setMyGroup = setMyGroup.bind(this);

        this.state = {
            isDrawerOpen: this.props.startOpen,
            height: 'fit-content',
        }
    };

    componentDidMount() {
        setTimeout(() => {
            this.setState({ height: this.getMaxHeight() });
        });
        let myInstance = document.getElementById(this.anchorId);
        this.setMyGroup(myInstance.parentNode.parentNode.parentNode.id);
        window.addEventListener('click', this.checkExpanded);
    };

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

    renderAnchor() {
        return (
            <Row id={this.anchorId} height={this.props.anchorHeight} onClick={ this.handleToggle } style={ this.styles('anchor') } >
                {this.props.anchor}
            </Row>
        );
    };

    renderPanel() {
        let height = this.props.autoHeight ? 'auto' : this.props.panelHeight;
        return (
            <Column id={this.panelId} height={height} style={ this.styles('panel') } >
                {this.props.panel}
            </Column>
        )
    };

    render() {
        return (
            <div style={this.styles('margin')}>
                <Column id={'ExpandPanel'} width={'100%'} height={'auto'} boxShadow={true} style={this.styles('root')}>
                    { this.renderAnchor() }
                    { this.renderPanel() }
                </Column>
            </div>
        );
    };

};
function setMyGroup(id) {
    this.groupId = id;
}

function checkExpanded(click) {
    let groupNode = click.path.filter( c => c && c.id && c.id.includes('ExpandGroup_') )[0];
    let groupName = '';
    if (groupNode && groupNode.id) groupName = groupNode.id;
    else {
        console.log('Not ExpandGroup click');
        return null;
    }
    let isUniq = groupName.split('_');
        isUniq = !!(isUniq[isUniq.length -1]);

    if (isUniq) {
        let isMyGroup = groupName === this.groupId;
        if (!isMyGroup) return null;

        let anchorNode = click.path.filter( c => c && c.id && c.id.includes('ExpandPanel') )[0];
        let anchorName = anchorNode.firstChild.id;

        if (isMyGroup && anchorName !== this.anchorId) this.handleClose();
    } else return null;
};

function handleToggle(e) {
    if (this.state.isDrawerOpen) this.handleClose();
    else this.handleOpenPanel();

    this.props.anchorClickCB({id: this.id, isDrawerOpen: !this.state.isDrawerOpen })
};

function handleClose() {
    this.setState({isDrawerOpen: false}, this.props.onClose);
};

function handleOpenPanel() {
    this.setState({isDrawerOpen: true});
};

function handleUniq({paramId, paramGroup}) {
    let sameGroup = this.props.uniqGroup === paramGroup;
    let otherId = this.id !== paramId;

    if ( sameGroup && otherId ) this.handleClose();
};

function getMaxHeight() {
    let panel = document.getElementById(this.panelId);

    if (this.props.autoHeight && panel) {
        return (panel.clientHeight + this.props.anchorHeight);
    }
    return (this.props.panelHeight + this.props.anchorHeight);
};

function styles(option) {
    let rootMargin = this.props.showMargin && this.state.isDrawerOpen ? 10 : 0;
    let panelHeightToogle = this.state.isDrawerOpen ? this.state.height : this.props.anchorHeight;
    let anchorHeight = this.props.anchorHeight < this.dim.minWidth ? this.dim.minWidth : this.props.anchorHeight;

    const styleObject = {
        root: {
            maxHeight: panelHeightToogle,
            minHeight: anchorHeight,
            transition: `max-height ${this.props.transition}s linear`,
            overflow: 'hidden',
            ...this.props.style
        },
        anchor: {
            cursor: 'pointer',
            minHeight: anchorHeight,
            ...this.props.anchorStyle
        },
        panel: {
            width: "100%",
            overflow: 'auto',
            ...this.props.panelStyle
        },
        margin:{
            marginBottom: rootMargin,
            marginTop: rootMargin,
            transition: `margin 0.15s linear`,
        }
    };
    return styleObject[option]
};

ExpandPanel.defaultProps = {
    id: 0,
    uniqOpen: false,
    uniqGroup: 'ExpandPanel',

    anchor: <Row><Label text={'Default Anchor'}/></Row>,
    anchorClickCB: ()=>{},
    onClose: ()=>{},
    anchorHeight: 50,
    anchorStyle: {},

    panel: <Column><Label text={'Default Panel'}/></Column>,
    panelHeight: 200,
    autoHeight: false,
    panelStyle: {},

    showMargin: false,
    startOpen: false,
    transition: 0.45,
    style: {},
};

ExpandPanel.propTypes = {
    id: PropTypes.oneOfType([ PropTypes.number, PropTypes.string ]),
    uniqOpen: PropTypes.bool,
    uniqGroup: PropTypes.string,

    anchor: PropTypes.object,
    anchorHeight: PropTypes.number,
    anchorClickCB: PropTypes.func,
    anchorStyle: PropTypes.object,

    panel: PropTypes.object,
    panelHeight: PropTypes.number,
    autoHeight: PropTypes.bool,
    panelStyle: PropTypes.object,

    startOpen: PropTypes.bool,
    transition: PropTypes.number,
    style: PropTypes.object,
};

export default ExpandPanel;

这是我的 ExpandGroup 代码:

import React from 'react';
import PropTypes from 'prop-types';

class ExpandGroup extends React.Component {
    constructor(props) {
        super(props);
        this.id = `ExpandGroup_${this.props.id}_${this.props.uniqOpen}`;
    };

    render() {
        return (
            <div id={this.id}>
                {this.props.children}
            </div>
        );
    };

};

ExpandGroup.defaultProps = {
    id: 'default',
    uniqOpen: false,
};

ExpandGroup.propTypes = {
    id: PropTypes.string,
    uniqOpen: PropTypes.bool,
};

export default ExpandGroup;

这是电话:

render() {
    return (
        <ExpandGroup id={'group-01'} uniqOpen={true}>
            <ExpandPanel id={'bloc-01'}/>,
            <ExpandPanel id={'bloc-02'}/>,
            <ExpandPanel id={'bloc-03'}/>,
        </ExpandGroup>
    );
};

标签: reactjsecmascript-6

解决方案


即使我不太擅长克隆孩子,我也使用了@Cameron Downer 的建议,令我惊讶的是它完美地工作。这是我的解决方案:

import React from 'react';
import PropTypes from 'prop-types';
// import ExpandPanel from '../ExpandPanel';

let count = 0;

class ExpandGroup extends React.Component {
    constructor(props) {
        super(props);
        this.id = `${this.props.id}_${count++}`;

        this.state = {
            openChildId: '',
        };

        this.styles = styles.bind(this);
        this.updateOpenChildId = updateOpenChildId.bind(this);
        this.panelPropsFix = panelPropsFix.bind(this);
        this.checkStartOpen = checkStartOpen.bind(this);
    };

    render() {
        return (
            <div id={this.id} style={this.styles('root')}>
                {this.panelPropsFix()}
            </div>
        );
    };
};

function panelPropsFix() {
    return React.Children.map(this.props.children, 
        (child, key) => { 
            let childClass = child.type.toString().split(' ')[1];
            if (childClass === 'ExpandPanel') {
                let childId = child.props.id;
                return React.cloneElement(child, {
                    key: key,
                    startOpen: this.checkStartOpen(childId),
                    style: this.styles('child'),
                    openId: (this.props.singleOpen) ? this.state.openChildId : childId,
                    singleOpenCB: id => this.updateOpenChildId(id),
                })
            }
        }
    );
};

function checkStartOpen(childId) {
    if (typeof this.props.openPanels === 'boolean') return this.props.openPanels;
    else if (this.props.openPanels instanceof Array) {
        return this.props.openPanels.includes(childId);
    }
    return false;
};

function updateOpenChildId(id) {
    if (id === this.state.openChildId) {
        this.setState({openChildId: ''});
    } else {
        this.setState({openChildId: id});
    }
};

function styles(option) {
    const styleObject = {
        root: {
            margin: this.props.margin,
            padding: this.props.padding,
            ...this.props.style
        },
        child: {
            ...this.props.panelsStyle,
        }
    };
    return styleObject[option]
};

ExpandGroup.defaultProps = {
    id: 'ExpandGroup',

    children: undefined,
    singleOpen: false,
    openPanels: false,
    panelsStyle: {},

    margin: 0,
    padding: 0,
    style: {},
};

ExpandGroup.propTypes = {
    id: PropTypes.string,

    // children: PropTypes.arrayOf(PropTypes.instanceOf(ExpandPanel)),
    singleOpen: PropTypes.bool,
    openPanels: PropTypes.oneOfType([ PropTypes.bool, PropTypes.array ]),
    panelsStyle: PropTypes.object,

    margin: PropTypes.oneOfType([ PropTypes.number, PropTypes.string ]),
    padding: PropTypes.oneOfType([ PropTypes.number, PropTypes.string ]),
    style: PropTypes.object,
};

export default ExpandGroup;

import React from 'react';
import PropTypes from 'prop-types';
import Label from 'Base/Components/Label';
import Column from 'Base/Layouts/Column';
import Row from 'Base/Layouts/Row';

class ExpandPanel extends React.Component {
    constructor(props) {
        super(props);

        this.id = this.props.id;
        this.colors = {
            white: '#ffffff',
            border: '#333333',
        };
        this.dim = {
            minWidth: 40
        }

        this.styles = styles.bind(this);
        this.getMaxHeight = getMaxHeight.bind(this);
        this.handleClose = handleClose.bind(this);
        this.handleOpenPanel = handleOpenPanel.bind(this);
        this.handleToggle = handleToggle.bind(this);

        this.state = {
            isDrawerOpen: this.props.startOpen,
            height: 'fit-content',
        }
    };

    componentDidMount() {
        setTimeout(() => {
            this.setState({ height: this.getMaxHeight() });
        });
    };

    componentWillReceiveProps(nextProps) {
        if (nextProps && nextProps.openId !== this.id)
            this.handleClose();
    };

    renderAnchor() {
        return (
            <Row id={'anchor'} height={this.props.anchorHeight} boxShadow={this.props.boxShadow} onClick={ this.handleToggle } style={ this.styles('anchor') } >
                {this.props.anchor}
            </Row>
        );
    };

    renderPanel() {
        let height = this.props.autoHeight ? 'auto' : this.props.panelHeight;
        return (
            <Column id={'panel'} height={height} boxShadow={this.props.boxShadow} style={ this.styles('panel') } >
                {this.props.panel}
            </Column>
        )
    };

    render() {
        return (
            <Column id={this.id} width={'100%'} height={'auto'} boxShadow={this.props.boxShadow} style={this.styles('root')}>
                { this.renderAnchor() }
                { this.renderPanel() }
            </Column>
        );
    };

};

function handleToggle(e) {
    if (this.state.isDrawerOpen) this.handleClose();
    else this.handleOpenPanel();

    this.props.singleOpenCB(this.id);
    this.props.anchorClickCB({id: this.id, isDrawerOpen: !this.state.isDrawerOpen });
};

function handleClose() {
    this.setState({isDrawerOpen: false}, this.props.onClose);
};

function handleOpenPanel() {
    this.setState({isDrawerOpen: true},  this.props.onOpen);
};

function getMaxHeight() {
    let panel = document.getElementById(this.panelId);

    if (this.props.autoHeight && panel) {
        return (panel.clientHeight + this.props.anchorHeight);
    }
    return (this.props.panelHeight + this.props.anchorHeight);
};

function styles(option) {
    let rootMargin = this.props.showMargin && this.state.isDrawerOpen ? 10 : 0;
    let panelHeightToogle = this.state.isDrawerOpen ? this.state.height : this.props.anchorHeight;
    let anchorHeight = this.props.anchorHeight < this.dim.minWidth ? this.dim.minWidth : this.props.anchorHeight;

    const styleObject = {
        root: {
            maxHeight: panelHeightToogle,
            minHeight: anchorHeight,
            transition: `max-height ${this.props.transition}s linear`,
            overflow: 'hidden',
            ...this.props.style
        },
        anchor: {
            cursor: 'pointer',
            minHeight: anchorHeight,
            ...this.props.anchorStyle
        },
        panel: {
            width: "100%",
            overflow: 'auto',
            ...this.props.panelStyle
        },
        margin:{
            marginBottom: rootMargin,
            marginTop: rootMargin,
            transition: `margin 0.15s linear`,
        }
    };
    return styleObject[option]
};

ExpandPanel.defaultProps = {
    id: 0,

    anchor: <Row><Label text={'Default Anchor'}/></Row>,
    anchorClickCB: ()=>{},
    singleOpenCB: ()=>{}, // for ExpandGroup internal use
    openId: '',           // for ExpandGroup internal use
    anchorHeight: 35,
    anchorStyle: {},

    panel: <Column><Label text={'Default Panel'}/></Column>,
    onOpen: ()=>{},
    onClose: ()=>{},
    autoHeight: false,
    panelHeight: 200,
    panelStyle: {},

    startOpen: false,
    boxShadow: true,
    transition: 0.45,
    style: {},
};

ExpandPanel.propTypes = {
    id: PropTypes.oneOfType([ PropTypes.number, PropTypes.string ]),

    anchor: PropTypes.object,
    anchorClickCB: PropTypes.func,
    singleOpenCB: PropTypes.func,   // for ExpandGroup internal use
    openId: PropTypes.string,       // for ExpandGroup internal use
    anchorHeight: PropTypes.number,
    anchorStyle: PropTypes.object,

    panel: PropTypes.object,
    onOpen: PropTypes.func,
    onClose: PropTypes.func,
    autoHeight: PropTypes.bool,
    panelHeight: PropTypes.number,
    panelStyle: PropTypes.object,

    startOpen: PropTypes.bool,
    boxShadow: PropTypes.bool,
    transition: PropTypes.number,
    style: PropTypes.object,
};

export default ExpandPanel;

所以调用可以是这样的:

<ExpandGroup singleOpen={true}>
    <ExpandPanel id={'bloc-01'}/>
    <ExpandPanel id={'bloc-02'}/>
    <ExpandPanel id={'bloc-03'}/>
    <ExpandPanel id={'bloc-04'}/>
    <ExpandPanel id={'bloc-05'}/>
</ExpandGroup>

推荐阅读