soulcm / blog

2017开通
6 stars 0 forks source link

react部分组件写法的思考 #3

Open soulcm opened 7 years ago

soulcm commented 7 years ago

react部分组件写法的思考

此处所说的组件,是指modal、tooltip、toast、message等等这类公共组件

因为最近在对项目进行一些优化,tooltip组件需要进行重构,因此在写的过错中发现前面的思路感觉根本不对了,在此记录一下

以前的tooltip长这样

class Tooltip extends React.component {
    //省略部分代码,直接看render

    render() {
        const {placement} = this.props;
        return(
            <div className="tooltip tooltip-right">
                {this.props.children}
            </div>
        )
    }
}
//调用的地方
import Tooltip from 'somepath/Tooltip';
class BusinessComp extends React.component {
    constructor(props) {
        super(props);
        this.state = {
            showTip: false
        }
        this.handleShowTip = this.handleShowTip.bind(this);
    }

    handleShowTip() {
        //计算tip位置的代码,并放入tipData中

        this.setState({
            showTip: !showTip
            tipData: tipData
        })
    }

    tipRender() {
        if (this.state.showTip) {
            return (
                <Tooltip style={style}>我是tip</Tooltip>
            )
        }
    }

    render() {
        return (
            <div>
                <span onClick={this.handleShowTip}></span>

                {this.tipRender()}
            </div>

        )
    }
}

这样造成每次页面都要去计算tip位置,并通过props传入,而且render出来的tip也是属于当前页面元素内部,有可能嵌套过多时样式会出现问题,后来通过观察ant-design的组件样式以及源码发现,一般都是把这类组件渲染到body里面,因此开始了重构之路。

首先不应该每次都要手动计算位置然后传入,应该是组件自己操作,而且应该想办法渲染到body上面,因此改造成下面这样

//引入方式改变
import Tooltip from 'somepath/Tooltip';
class BusinessComp extends React.component {
    render() {
        return (
            <div>
                <Tooltip content="我是tip" placement="left"><span>点击我弹出tip</span></Tooltip>
            </div>

        )
    }
}

//Tooltip
class Tooltip extends React.component {
    constructor(props) {
        super(props);
        this.state = {
            visible: !!props.visible
        }
        this.handleClick = this.handleClick.bind(this);
    }

    componentDidMount() {
        this.getContainer();//获取到需要插入进body的元素
    }

    getContainer() {
        this.getInstancePosition(); //计算tip的位置
        this.container = document.createElement('div');
        const parent = this.props.parentSelector;
        parent.appendChild(this.container); //插入container
        this.renderTip(this.props); //渲染tip
    }

   renderTip(props) {
        const {placement, title, content, style} = props;
        ReactDOM.unstable_renderSubtreeIntoContainer(this,
            <div className={`tooltip tooltip-${placement}`}
                style={assign({width: 300, opacity: 0, display: 'none'}, style)}
                ref={(c) => this.tipDom = c}>
                <div className="title">{title}</div>
                <div dangerouslySetInnerHTML={{__html: content}}></div>
            </div>, this.container
        )
    }

    handleClick() {
        this.setState({
            visible: !this.state.visible
        })
    }

    render() {
        const {children, placement, style} = this.props;
        const child = React.isValidElement(children) ? children : <span>{children}</span>; //判断是否为react element
        const newChildProps = {}; //绑定方法或添加其他属性, 如onClick/onMouseEnter等等
        newChildProps.onClick = this.handleClick;
        return React.cloneElement(child, newChildProps) //render中只需要渲染child
    }
}

上述只是部分代码和实现思路

写这个时候,开始是直接创建的div,但发现那样传进来的props属性没法加到弹出的div上面去,后来一直翻阅蚂蚁的源码,会发现都是在react-componet各组件上面进行的封装,而react-component里面用到了ReactDOM.unstable_renderSubtreeIntoContainer这个方法,但react官网API里面并没有把这个给列出来,估计是一个不太正式的API,但还是希望react把这个API放出来,因为很多情况下确实需要用到。

PS. 听说facebook正在研究react专门针对dom的处理方式,还是期待下的

总结:

soulcm commented 6 years ago

React v16 ReactDOM.createPortal(child, container)