手写react10-高阶组件和renderProps

126 阅读2分钟

如果想对第三方组件进行功能扩展,可能就会用到高阶组件

import React from 'react';
import ReactDOM from 'react-dom';
class Button extends React.Component{
    state = {name:'张三'}
    render(){
        return <button name={this.state.name} title={this.props.title}/>
    }
}
const wrapper = OldComponent =>{
    return class NewComponent extends OldComponent{
        state = {number:0}
        handleClick = ()=>{
            this.setState({number:this.state.number+1});
        }
        render(){
            let renderElement = super.render();
            let newProps = {
                ...renderElement.props,
                ...this.state,
                onClick:this.handleClick
            }
            return  React.cloneElement(
                renderElement,
                newProps,
                this.state.number
            );
        }
    }
}
let WrappedButton = wrapper(Button);
ReactDOM.render(
    <WrappedButton title="标题"/>, document.getElementById('root'));

注:上面的代码中,由于子类的state被赋值为{number:0},该对象会覆盖父类中的state,所以子类中就取不到父类中定义的state = {name:'张三'}中的name属性了

需要注意,根据MDN的文档描述,以下两种写法是等价的:

developer.mozilla.org/en-US/docs/…

class Button extends React.Component{
    state;
		constructor () {
    		this.state = {name:'张三'}
    }
    render(){
        return <button name={this.state.name} title={this.props.title}/>
    }
}

class Button extends React.Component{
		constructor () {
    		this.state = {name:'张三'}
    }
    render(){
        return <button name={this.state.name} title={this.props.title}/>
    }
}

整个过程,并没有创建父组件的实例,而只是通过super.render()得到了父组件对应的虚拟DOM,返回了一个新的节点

为便于理解,我们将代码稍作修改,主要是把state属性放到constructor构造函数中去

import React from 'react';
import ReactDOM from 'react-dom';
class Button extends React.Component{
  	constructor (props) {
      	super(props);
    		this.state = {name:'张三'}
    }
    render(){
        return <button name={this.state.name} title={this.props.title}/>
    }
}
const wrapper = OldComponent =>{
    return class NewComponent extends OldComponent{
        constructor (props) {
            super(props);
            this.state = {number:0}
        }
        handleClick = ()=>{
            this.setState({number:this.state.number+1});
        }
        render(){
            let renderElement = super.render();
            let newProps = {
                ...renderElement.props,
                ...this.state,
                onClick:this.handleClick
            }
            return  React.cloneElement(
                renderElement,
                newProps,
                this.state.number
            );
        }
    }
}
let WrappedButton = wrapper(Button);
ReactDOM.render(
    <WrappedButton title="标题"/>, document.getElementById('root'));

上面的代码在执行完第15行以后,this.state就变成了{name:'张三'}(父组件中赋的值),紧接着在第16行我们又将this.state重新赋值为{number:0},所以原来的{name:'张三'}被覆盖,之后就再也取不到了

所以解决的方案是将第16行改为

this.state = {...this.state, number:0}

这样我们就也可以用父组件中赋值过的name了

比较优雅的写法还可以用注解:

@wrapper
class Button extends React.Component{
  	constructor (props) {
      	super(props);
    		this.state = {name:'张三'}
    }
    render(){
        return <button name={this.state.name} title={this.props.title}/>
    }
}

\

renderProps:组件的属性或children是函数,返回值是要渲染的元素

class MouseTracker extends React.Component {
    constructor(props) {
        super(props);
        this.state = { x: 0, y: 0 };
    }

    handleMouseMove = (event) => {
        this.setState({
            x: event.clientX,
            y: event.clientY
        });
    }

    render() {
        return (
            <div onMouseMove={this.handleMouseMove}>
                {this.props.children(this.state)}
            </div>
        );
    }
}
ReactDOM.render(<MouseTracker>
    {
        (props) => (
            <div>
                <h1>移动鼠标!</h1>
                <p>当前的鼠标位置是 ({props.x}, {props.y})</p>
            </div>
        )
    }
</MouseTracker >, document.getElementById('root'));

HOC高阶组件和renderProps可以互相转换