React组件复用方式

Component

React核心思想,一切皆是组件,通过组件可以达到最理想的代码复用

栗子

import React from 'react';
export default class ContainerComponent extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            count: 0
        }
        this.handlePlus = this.handlePlus.bind(this);
    }

    handlePlus() {
        this.setState({
            count: this.state.count + 1
        })
    }

    render() {
        return <div>
            <h1>{`I am a container component.`}</h1>
            <p>{`当前计数:${this.state.count}`}</p>
            <button onClick={this.handlePlus}>plus</button>
        </div>
    }
}

很简单的一个组件,如果其他需要用,直接引用即可

import ContainerComponent from './ContainerComponent';

但是为了满足最佳实践,我们将该组件分成容器组件跟展示组件

容器组件:

import React from 'react';
import ShowComponent from './ShowComponent';
export default class ContainerComponent extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            count: 0
        }
        this.handlePlus = this.handlePlus.bind(this);
    }

    handlePlus() {
        this.setState({
            count: this.state.count + 1
        })
    }

    render() {
        return <div>
            <h1>{`I am a container component.`}</h1>
            <ShowComponent
                count={this.state.count}
                handlePlus={this.handlePlus}
            />
        </div>
    }
}

展示组件

export default ShowComponent;

function ShowComponent(props) {
    return <div>
        <p>{`当前计数:${props.count}`}</p>
        <button onClick={props.handlePlus}>plus</button>
    </div>
}

这里啥是容器组件,啥是展示组件?

容器组件:简单理解就是含有处理逻辑,处理数据的容器

展示组件:只关心展示,跟数据无关,像控件,无状态组件更贴切。

现在需求变了,如果展示组件不仅限于ShowComponent,现在多了ShowComponent2,showComponent3等等,怎么处理,噢?首先想到了什么?没错就是this.prop.children,

我们尝试下。

children

栗子

ContainerComponent

import React from 'react';
export default class ContainerComponent extends React.Component {
    constructor(props) {
        super(props)
    }

    render() {
        return <div>
            <h1>{`I am a container component.`}</h1>
            {this.props.children}
        </div>
    }
}

ShowComponent、ShowComponent2、ShowComponent3等等

export default ShowComponent;

function ShowComponent(props) {
    return <div>
        <p>{`当前计数:${props.count}`}</p>
        <button onClick={props.handlePlus}>plus</button>
    </div>
}

class ShowComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        }
        this.handlePlus = this.handlePlus.bind(this);
    }

    handlePlus() {
        this.setState({
            count: this.state.count + 1
        })
    }

    render() {
        return <div>
            <p>{`当前计数:${this.state.count}`}</p>
            <button onClick={this.handlePlus}>plus</button>
        </div>
    }
}

可以实现我们的目的,但是缺点也比较明显,就是没办法将容器组件与展示组件分开,如果想分开,我们需要在ShowComponent中进行分,其实不难看出,this.props.children更像是一种layout的解决方案,仅用来定制layout,具体逻辑放到每个具体Component中进行。那么怎么处理这种情况更好呢~我们来看看一个解决方案—>回调渲染。

回调渲染

什么是回调渲染呢,简单来讲,就是采用调用的形式使用children API,就是this.props.children(),看个例子

栗子

import React from "react";
import ReactDOM from "react-dom";
import ContainerComponent from "./ContainerComponent";
import ShowComponent from "./ShowComponent";

{
  ReactDOM.render(
    <ContainerComponent>
      {({ count, handlePlus }) => (
        <ShowComponent count={count} handlePlus={handlePlus} />
      )}
    </ContainerComponent>,
    document.getElementById("app")
  );
}

ContainerComponent

export default class ContainerComponent extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            count: 0
        }
        this.handlePlus = this.handlePlus.bind(this);
    }

    handlePlus() {
        this.setState({
            count: this.state.count + 1
        })
    }

    render() {
        return <div>
            <h1>{`I am a container component.`}</h1>
            {
                this.props.children(
                    {
                        count: this.state.count,
                        handlePlus: this.handlePlus
                    }
                )
            }
        </div>
    }
}

ShowComponent

export default ShowComponent;

function ShowComponent(props) {
    return <div>
        <p>{`当前计数:${props.count}`}</p>
        <button onClick={props.handlePlus}>plus</button>
    </div>
}

既可以达到ShowComponent与ContainerComponent解耦的目的(这里的意思是可以随意更改ShowComponet,可以换成其他组件),又可以实现了展示组件与容器组件分离。这只是个demo

那么回调渲染到底解决了什么问题呢~回调渲染可以实现外层组件与内层组件的数据传递,可以很好的分离组件以及逻辑复用。

谈到回调渲染,我们就会联想到另一个方案,Render Props,那么什么是Render Props呢

Render Props

RenderProps我认为是回调渲染的一种衍生方式,利用一个函数,进行组件传递,这个组件就是可变的部分,其余部分实现代码复用。我们试着用RenderProps方式对上个栗子进行改造,我们看个栗子。

栗子

import ContainerComponent from "./ContainerComponent";
import ShowComponent from "./ShowComponent";

{
  ReactDOM.render(
    <ContainerComponent
      render={({ count, handlePlus }) => (
        <ShowComponent count={count} handlePlus={handlePlus} />
      )}
    />,
    document.getElementById("app")
  );
}

ContainerComponent

import React from 'react';
import ShowComponent from './ShowComponent';
export default class ContainerComponent extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            count: 0
        }
        this.handlePlus = this.handlePlus.bind(this);
    }

    handlePlus() {
        this.setState({
            count: this.state.count + 1
        })
    }

    render() {
        return <div>
            <h1>{`I am a container component.`}</h1>
            {
                this.props.render({
                    count: this.state.count,
                    handlePlus: this.handlePlus
                })
            }
        </div>
    }
}

ShowComponent

export default ShowComponent;

function ShowComponent(props) {
    return <div>
        <p>{`当前计数:${props.count}`}</p>
        <button onClick={props.handlePlus}>plus</button>
    </div>
}

其实跟回调渲染大同小异,回调渲染用的是React原生方法(this.props.children),RenderProps更灵活一些,可以定义多个函数,随意传递,比如

 ReactDOM.render(
    <ContainerComponent
      render={({ count, handlePlus }) => (
        <ShowComponent count={count} handlePlus={handlePlus} />
      )}
      renderMsg={() => <div>我是msg</div>}
      renderColor={() => <div>我是color</div>}
    />,
    document.getElementById("app")
  );

好的总结一下,RenderProps解决了什么问题?

同样达到了代码复用,组件拆解,降低耦合性,同时具备了回调渲染没有的灵活性。

Hook

在解决上面demo的例子,hook只是换了一种代码展示形式,解决方法是没变的,我们看改完的栗子

import ContainerComponent from "./ContainerComponent";
import ShowComponent from "./ShowComponent";

{
  ReactDOM.render(
    <ContainerComponent
      render={({ count, handlePlus }) => (
        <ShowComponent count={count} handlePlus={handlePlus} />
      )}
    />,
    document.getElementById("app")
  );
}

ContainerComponent

import React, { useState, useEffect } from "react";
import ShowComponent from './ShowComponent';
function ContainerComponent(props) {
    const [count, setCount] = useState(0);
    const handlePlus = () => {
        setCount(count + 1);
    }
    return <div>
        <h1>{`I am a container component.`}</h1>
        {
            props.render({
                count: count,
                handlePlus: handlePlus
            })
        }
    </div>
}


export default ContainerComponent;

ShowComponent

import React, { useState, useEffect } from "react";


function ShowComponent(props) {
    return <div>
        <p>{`当前计数:${props.count}`}</p>
        <button onClick={props.handlePlus}>plus</button>
    </div>
}

export default ShowComponent;

依然利用了RenderProps,只是将Class组件变成了Hook组件,这个其实体现不出来Hook的要解决的问题,

Hook解决的问题跟HOC是比较类似的,侧重点是逻辑复用。看个栗子。

公用部分

function useFetch() {
  const [data, setData] = useState();
  useEffect(() => {
    new Promise((resolve) => {
      resolve({
        name: "dqhan",
        age: 28,
      });
    }).then((res) => {
      setData(res);
    });
  }, []);

  return data;
}

组件App

function App() {
  let data = useFetch();
  return (
    <div>
      <div>App1</div>
    </div>
  );
}

组件App2

function App2() {
  let data = useFetch();
  return (
    <div>
      <div>App2</div>
    </div>
  );
}

提取组件加载完成时获取数据的复用逻辑,这样能让代码复用看起来更直观。当然还有最后一种方式实现代码复用,就是HOC。这里就不谈了。

原文地址:https://www.cnblogs.com/moran1992/p/13500505.html