msforest / notebook

好记性不如烂笔头,记录知识的点点滴滴。
https://github.com/msforest/notebook/wiki
0 stars 0 forks source link

react 父子之间组件通信的意外发现 #24

Open msforest opened 6 years ago

msforest commented 6 years ago
  1. 场景 有A,B两个组件,A组件里嵌套B组件,B组件的数据来源于A组件,现在对B组件的数据进行删除,还需要与A组件的数据保持同步,B组件关闭时会被unmount,重新打开B组件时原有的状态丢失。 image

简便的方法就是把B组件设为无状态的组件,所有状态全部放在A组件里,增加删除什么的方法全部写在A组件里,这是一种不好的写法,违背了react的component思想。该属于B组件的操作的状态和方法都应该写在自身组件里。

但是,B组件数据来源A组件,修改了B的数据也要同时修改A的数据,修改了A的数据还要同时反映到B的数据,也就是同一份数据需要应用到两个组件。使用第三方库时,每次打开B组件时B都被重新Mount/Unmount。

S1: A把数据组装成对象:[{}, ...], B直接修改props的数据【ps:一直以为props是read only,没想到object还是可以修改,看来还是没用摆脱js的引用】

class B extends React.Component{
  constructor(props) {
        super(props);
        this.state = {
            tags: [...this.props.tags]
        }
    }
    handleChange(tag) {
        this.setState((prevState) => prevState.tags.map((tagItem) => {
            if (tagItem.key === tag.key) {
                if (!tag.selected && this.props.handleAdd) {
                    this.props.handleAdd(tag.key);
                } else if (tag.selected && this.props.handleDelete) {
                    this.props.handleDelete(tag.key);
                }
                tagItem.selected = !tagItem.selected;
            }
            return tagItem;
        }))
    }

    render(){
        const selectedTags = [], unselectedTags = [];
        props.tags.forEach((tag) => {
            if (tag.selected) {
                selectedTags.push((<CheckableTag
                    key={tag.key}
                    checked={false}
                    onChange={(checked) => this.handleChange(tag)}
                    style={tagStyle}
                >
                    {tag.tag || 'N/A'}
                </CheckableTag>))
            } else {
                unselectedTags.push((
                    <CheckableTag
                        key={tag.key}
                        checked={false}
                        onChange={(checked) => this.handleChange(tag)}
                        style={tagStyle}
                    >
                        {tag.tag || 'N/A'}
                    </CheckableTag>
                ))
            }
        })

        return (
            <div>
                <Panel header="Selected Columns" bsStyle="success">
                    {selectedTags}
                </Panel>
                <Panel header="Unselected Columns" bsStyle="danger">
                    {unselectedTags}
                </Panel>
            </div>
        );
    }
}

class A extends React.Component{
    constructor(props){
        super(props)

        this.state = {
            data: [{}, ...]
        }
    }

    handleDelete(){
        // this.state.data.splice(index, 1)
    }

    handleAdd(){
        // this.state.data.push()
    }

    render(){
        return (<OverlayTrigger trigger='click' placement="bottom" overlay={
                        <Popover id="tooltip" title="Columns Filter">
                            <ColumnTag
                                data={this.data}
                                handleDelete={this.handleDelete}
                                handleAdd={this.handleAdd}/>
                        </Popover>
                    } rootClose={true}>
                        <Button bsStyle="primary" >Columns Filter</Button>
                    </OverlayTrigger>)
    }
}

S2: 在A组件里获取B组件实例,再次加载时使用上一次保存的实例

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

        this.state = {
            tags: [...this.props.data],
        }
    }

    handleChange(tag, checked) {
        const tags = this.state.tags;
        const find = tags.find(item=>item.key===tag.key);
        if (checked) {
            find.flag = true;
            this.props.handleAdd.call(this, tag.key);
        } else {
            find.flag = false;
            this.props.handleDelete.call(this, tag.key);
        }

        this.setState({tags: [...tags]})
    }

    render() {
        const { tags } = this.state;
        const selectedTags=[], unselectedTags = [];
        tags.forEach(tag=>{
            if(tag.flag){
                selectedTags.push(<CheckableTag
                    key={tag.key}
                    checked={false}
                    onChange={(checked) => this.handleChange(tag, false)}
                    style={tagStyle}
                >
                    {tag.tag||'N/A'}
                </CheckableTag>)
            }else{
                unselectedTags.push(<CheckableTag
                    key={tag.key}
                    checked={false}
                    onChange={(checked) => this.handleChange(tag,true)}
                    style={tagStyle}
                >
                    {tag.tag||'N/A'}
                </CheckableTag>)
            }
        })

        return (
            <div className={css.tag}>
                <h2>{this.props.title}</h2>
                <h3>Selected Columns</h3>
                {selectedTags}
                <h3>Unselected Columns</h3>
                {unselectedTags}
            </div>
        );
    }
}

B.propTypes = {
    title: PropTypes.string.isRequired,
    handleDelete: PropTypes.func.isRequired,
    handleAdd: PropTypes.func.isRequired,
}

class A extends React.Component{
    constructor(props){
        super(props)

        this.state = {
            data: [{}, ...],
            colContent: 'test',
        }
    }

    handleDelete(){
        // this.state.data.splice(index, 1)
    }

    handleAdd(){
        // this.state.data.push()
    }

    columnFilter = ()=>{
        let colContent = <span></span>;
        if(!React.isValidElement(this.state.colContent)){
            colContent = <Popover id="tooltip" style={{maxWidth: '50%', maxHeight: '60%', left: '40%'}}>
                    <B title="Columns Filter"
                        data={this.state.tags}
                        handleDelete={this.handleDelete}
                        handleAdd={this.handleAdd}/></Popover>
            this.setState({colContent})
        }else{
            colContent = this.ref._overlay.props.children //【此种方式可以保存instance】
            this.setState({colContent})
        }
    }

    render(){
        return (
            <OverlayTrigger ref={node=>this.ref=node} trigger='click' placement="bottom" overlay={this.state.colContent} rootClose={true}>
                <Button bsStyle="primary" onClick={this.columnFilter}>Columns Filter</Button>
            </OverlayTrigger>)
    }
}