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 

{`I am a container component.`}

{`当前计数:${this.state.count}`}

} }

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

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 

{`I am a container component.`}

<ShowComponent count={this.state.count} handlePlus={this.handlePlus} />
} }

展示组件

export default ShowComponent;

function ShowComponent(props) {
    return 

{`当前计数:${props.count}`}

}

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

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

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

现在需求变了,如果展示组件不仅限于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 

{`I am a container component.`}

{this.props.children}
} }

ShowComponent、ShowComponent2、ShowComponent3等等

export default ShowComponent;

function ShowComponent(props) {
    return 

{`当前计数:${props.count}`}

} 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

{`当前计数:${this.state.count}`}

} }

可以实现我们的目的,但是缺点也比较明显,就是没办法将容器组件与展示组件分开,如果想分开,我们需要在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(
    
      {({ count, handlePlus }) => (
        
      )}
    ,
    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 

{`I am a container component.`}

{ this.props.children( { count: this.state.count, handlePlus: this.handlePlus } ) }
} }

ShowComponent

export default ShowComponent;

function ShowComponent(props) {
    return 

{`当前计数:${props.count}`}

}

既可以达到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 }) => (
        
      )}
    />,
    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 

{`I am a container component.`}

{ this.props.render({ count: this.state.count, handlePlus: this.handlePlus }) }
} }

ShowComponent

export default ShowComponent;

function ShowComponent(props) {
    return 

{`当前计数:${props.count}`}

}

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

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

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

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

Hook

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

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

{
  ReactDOM.render(
    <ContainerComponent
      render={({ count, 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 

{`I am a container component.`}

{ props.render({ count: count, handlePlus: handlePlus }) }
} export default ContainerComponent;

ShowComponent

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


function ShowComponent(props) {
    return 

{`当前计数:${props.count}`}

} 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 (
    
App1
); }

组件App2

function App2() {
  let data = useFetch();
  return (
    
App2
); }

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