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。这里就不谈了。