更快助你弄懂React高阶组件

12次阅读

共计 5527 个字符,预计需要花费 14 分钟才能阅读完成。

谈到 react, 我们第一个想到的应该是组件, 在 react 的眼中可真的是万物皆组件。就连我们获取数据用到的 axios 也可以用组件来表示 … 比如, 我们可以这样封装

<Request
  instance={axios.create({})} /* custom instance of axios - optional */
  method="" /* get, delete, head, post, put and patch - required */
  url="" /*  url endpoint to be requested - required */
  data={} /* post data - optional */
  params={} /* queryString data - optional */
  config={} /* axios config - optional */
  debounce={200} /* minimum time between requests events - optional */
  debounceImmediate={true} /* make the request on the beginning or trailing end of debounce - optional */
  isReady={true} /* can make the axios request - optional */
  onSuccess={(response)=>{}} /* called on success of axios request - optional */
  onLoading={()=>{}} /* called on start of axios request - optional */
  onError=(error)=>{} /* called on error of axios request - optional */
/>

在项目中我们可以这样写

import {AxiosProvider, Request, Get, Delete, Head, Post, Put, Patch, withAxios} from 'react-axios'
...
render() {
  return (
    <div>
      <Get url="/api/user" params={{id: "12345"}}>
        {(error, response, isLoading, makeRequest, axios) => {if(error) {return (<div>Something bad happened: {error.message} <button onClick={() => makeRequest({ params: { reload: true} })}>Retry</button></div>)
          }
          else if(isLoading) {return (<div>Loading...</div>)
          }
          else if(response !== null) {return (<div>{response.data.message} <button onClick={() => makeRequest({ params: { refresh: true} })}>Refresh</button></div>)
          }
          return (<div>Default message before request is made.</div>)
        }}
      </Get>
    </div>
  )
}

有点过分了 … 至少我是觉得还是要根据个人的代码习惯来吧, 如果所有组件都是这么处理请求的,包括一些简单的 get 请求,我觉得真的没这个必要,并且我们的一些通用 API 也不太好统一管理

那么,高阶组件到底是什么?

a higher-order component is a function that takes a component and returns a new component.

右键翻译 ——> 高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。
嗯,看起来就是这么简单,其实用起来也是

1、具体来说一下,我们先用高阶函数来举个例子,一个 showUserPermit, 一个 showUserVipInfo, 两个函数先从 localStorage 读取了 userVIP,之后针对 userVIP 做了一些处理。

function showUserPermit() {let vip = localStorage.getItem('u_V');
   console.log(` 您可以享受的 ${u_V}的特权...`);
}

function showUserVipInfo() {let vip = localStorage.getItem('u_V');
   console.log(` 您当前 VIP 等级为 ${u_V}, 升级立刻...`);
}

showUserPermit();
showUserVipInfo();

2、我们发现了两个 API 中有两个完全一样的代码, 很冗余, 这样不好, 我们改一下吧

function showUserPermit(u_V) {console.log(` 您可以享受的 ${u_V}的特权...`);
}

function showUserVipInfo(u_V) {console.log(` 您当前 VIP 等级为 ${u_V}, 升级立刻...`);
} 

3、这样写看上去确实简单了一些,但是这两个 API 要想保证功能完全必须依赖参数 u_V, 所有在调用这两个函数之前我们都必须要拿到这个参数, 这样未免有点耦合性, 我们再次改造
function showUserPermit(u_V) {

   console.log(` 您可以享受的 ${u_V}的特权...`);
}

function showUserVipInfo(u_V) {console.log(` 您当前 VIP 等级为 ${u_V}, 升级立刻...`);
}

function wrapU_V(wrappedFunc) {let newFunc = () => {let vip = localStorage.getItem('u_V');
        wrappedFunc(vip);
};
    return newFunc;
}

module.exports = {showUserPermit: wrapU_V(showUserPermit), 
    showUserVipInfo: wrapU_V(showUserVipInfo)
}

4、wrapU_V 就是一个没有任何副作用的高阶函数, 那么他的意义是什么?又做了什么?它帮我们处理了 u_V, 并且调用了目标函数(函数参数), 这样当你再次使用导出的 showUserPermit 的时候根本不必要去关心 u_V 高低是怎么来的,到底需求什么外部条件,你只要知道它能帮我实现我想要做的事情就可以了!同时省去了每一次调用前都先要看一下它的参数是什么?怎么来?甚至根本不用关心 wrapU_V 内部是如何实现的,Array.map,setTimeout 都可以称为高阶函数

高阶组件
高阶组件就是一个没有副作用的 纯函数 ,对就是一个函数
我们将上面的例子用 component 来重构一下

import React, {Component} from 'react'
...

class showUserPermit extends Component {constructor(props) {super(props);
        this.state = {VIP: ''}
    }

    componentWillMount() {let VIP = localStorage.getItem('u_V');
        this.setState({VIP})
    }

    render() {
        return (<div>showUserPermit... {this.state.VIP}</div>
        )
    }
}

export default showUserPermit;

/* - */

import React, {Component} from 'react'
...

class showUserVipInfo extends Component {constructor(props) {super(props);
        this.state = {VIP: ''}
    }

    componentWillMount() {let VIP = localStorage.getItem('u_V');
        this.setState({VIP})
    }

    render() {
        return (<div>showUserVipInfo... {this.state.VIP}</div>
        )
    }
}

export default showUserVipInfo;

刚才发现的问题都可以映射在这两个组件里了
按照上面的思路我们做一个处理

import React, {Component} from 'react'

module.exports = Wrap: (WrappedComponent) => {
    class reComponent extends Component {constructor() {super();
            this.state = {VIP: ''}
        }

        componentWillMount() {let VIP = localStorage.getItem('u_V');
            this.setState({VIP})
        }

        render() {return <WrappedComponent VIP={this.state.VIP}/>
        }
    }

    return reComponent
}

再来简化一下 showUserVipInfo 和 showUserPermit 组件


import React, {Component} from 'react';
import {Wrap} as templete from 'wrapWithUsername';

class showUserPermit extends Component {render() {
        return (<div>showUserPermit {this.props.username}</div>
        )
    }
}

showUserPermit = templete(showUserPermit);

export default showUserPermit;  

/*--*/
import React, {Component} from 'react';
import {Wrap} as templete from 'wrapWithUsername';

class showUserVipInfo extends Component {render() {
        return (<div>showUserVipInfo {this.props.username}</div>
        )
    }
}

showUserPermit = templete(showUserPermit);

export default showUserVipInfo; 

并且高阶组件中可以分布多个目标组件,举一个我们项目中的例子


这里面右上角的时间选择组件以及 echarts 组件是两种不同身份特有的一些行为和样式,其它的完全是一样的,包括 state 以及共用方法都一模一样。上代码

render() {
  return (
    <div className="mk-genData home-module-common">
      <div className="module-header">
        <div className="module-title">...</div>
        <**GenTimerComponent** receiveTimeChange={this.getData.bind(this)}/>
      </div>
      <div className="genData-nav">
        ...
      </div>
      <div>
        <**EchartsComponent** chartData={this.state.chartData}/>
      </div>
    </div>
  )

其中 GenTimerComponent,和 EchartsComponent 都是目标组件,我们这样导出

豁然开朗了吧,其实就是把两个组件相同的地方或者都可能用到的地方抽离出来,说句题外话,其实本来是 ’ 高阶组件 ’ 嵌套了目标组件,但是重新生成的新组建反倒是继承了目标组件,看起来是一种控制反转,和 Vue 中的 extend+minix 也比较像, 通过继承目标组件,除了一些静态方法,包括生命周期,state,fun, 我们都可得到
现在理解 react-redux 的 connect 函数~
把 redux 的 state 和 action 创建函数,通过 props 注入给了 Component。
你在目标组件 Component 里面可以直接用 this.props 去调用 redux state 和 action 创建函数了。

ConnectedComment = connect(mapStateToProps, mapDispatchToProps)(Component);  

等价于

// connect 是一个返回函数的函数(就是个高阶函数)const enhance = connect(mapStateToProps, mapDispatchToProps);
// 返回的函数就是一个高阶组件,该高阶组件返回一个与 Redux store
// 关联起来的新组件
const ConnectedComment = enhance(Component);        

antd 的 Form 也是一样的

const WrappedNormalLoginForm = Form.create()(NormalLoginForm);   

总结一下: 高阶组件是对 React 代码进行更高层次重构的好方法,如果你想精简你的 state 和生命周期方法,那么高阶组件可以帮助你提取出可重用的函数。一般来说高阶组件能完成的用组件嵌套 + 继承也可以,用嵌套 + 继承的方式理解起来其实更容易一点,特别是去重构一个复杂的组件时,通过这种方式往往更快,拆分起来更容易。至于到底用哪个最佳还要具体看业务场景, 欢迎交流探讨

作者:易企秀——Yxaw

正文完
 0

更快助你弄懂React高阶组件

12次阅读

共计 5498 个字符,预计需要花费 14 分钟才能阅读完成。

谈到 react, 我们第一个想到的应该是组件, 在 react 的眼中可真的是万物皆组件。就连我们获取数据用到的 axios 也可以用组件来表示 … 比如, 我们可以这样封装

<Request
  instance={axios.create({})} /* custom instance of axios - optional */
  method="" /* get, delete, head, post, put and patch - required */
  url="" /*  url endpoint to be requested - required */
  data={} /* post data - optional */
  params={} /* queryString data - optional */
  config={} /* axios config - optional */
  debounce={200} /* minimum time between requests events - optional */
  debounceImmediate={true} /* make the request on the beginning or trailing end of debounce - optional */
  isReady={true} /* can make the axios request - optional */
  onSuccess={(response)=>{}} /* called on success of axios request - optional */
  onLoading={()=>{}} /* called on start of axios request - optional */
  onError=(error)=>{} /* called on error of axios request - optional */
/>

在项目中我们可以这样写

import {AxiosProvider, Request, Get, Delete, Head, Post, Put, Patch, withAxios} from 'react-axios'
...
render() {
  return (
    <div>
      <Get url="/api/user" params={{id: "12345"}}>
        {(error, response, isLoading, makeRequest, axios) => {if(error) {return (<div>Something bad happened: {error.message} <button onClick={() => makeRequest({ params: { reload: true} })}>Retry</button></div>)
          }
          else if(isLoading) {return (<div>Loading...</div>)
          }
          else if(response !== null) {return (<div>{response.data.message} <button onClick={() => makeRequest({ params: { refresh: true} })}>Refresh</button></div>)
          }
          return (<div>Default message before request is made.</div>)
        }}
      </Get>
    </div>
  )
}

有点过分了 … 至少我是觉得还是要根据个人的代码习惯来吧, 如果所有组件都是这么处理请求的,包括一些简单的 get 请求,我觉得真的没这个必要,并且我们的一些通用 API 也不太好统一管理

那么,高阶组件到底是什么?

a higher-order component is a function that takes a component and returns a new component.

右键翻译 ——> 高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。
嗯,看起来就是这么简单,其实用起来也是

1、具体来说一下,我们先用高阶函数来举个例子,一个 showUserPermit, 一个 showUserVipInfo, 两个函数先从 localStorage 读取了 userVIP,之后针对 userVIP 做了一些处理。

function showUserPermit() {let vip = localStorage.getItem('u_V');
   console.log(` 您可以享受的 ${u_V}的特权...`);
}

function showUserVipInfo() {let vip = localStorage.getItem('u_V');
   console.log(` 您当前 VIP 等级为 ${u_V}, 升级立刻...`);
}

showUserPermit();
showUserVipInfo();

2、我们发现了两个 API 中有两个完全一样的代码, 很冗余, 这样不好, 我们改一下吧

function showUserPermit(u_V) {console.log(` 您可以享受的 ${u_V}的特权...`);
}

function showUserVipInfo(u_V) {console.log(` 您当前 VIP 等级为 ${u_V}, 升级立刻...`);
} 

3、这样写看上去确实简单了一些,但是这两个 API 要想保证功能完全必须依赖参数 u_V, 所有在调用这两个函数之前我们都必须要拿到这个参数, 这样未免有点耦合性, 我们再次改造
function showUserPermit(u_V) {

   console.log(` 您可以享受的 ${u_V}的特权...`);
}

function showUserVipInfo(u_V) {console.log(` 您当前 VIP 等级为 ${u_V}, 升级立刻...`);
}

function wrapU_V(wrappedFunc) {let newFunc = () => {let vip = localStorage.getItem('u_V');
        wrappedFunc(vip);
};
    return newFunc;
}

module.exports = {showUserPermit: wrapU_V(showUserPermit), 
    showUserVipInfo: wrapU_V(showUserVipInfo)
}

4、wrapU_V 就是一个没有任何副作用的高阶函数, 那么他的意义是什么?又做了什么?它帮我们处理了 u_V, 并且调用了目标函数(函数参数), 这样当你再次使用导出的 showUserPermit 的时候根本不必要去关心 u_V 高低是怎么来的,到底需求什么外部条件,你只要知道它能帮我实现我想要做的事情就可以了!同时省去了每一次调用前都先要看一下它的参数是什么?怎么来?甚至根本不用关心 wrapU_V 内部是如何实现的,Array.map,setTimeout 都可以称为高阶函数

高阶组件
高阶组件就是一个没有副作用的 纯函数 ,对就是一个函数
我们将上面的例子用 component 来重构一下

import React, {Component} from 'react'
...

class showUserPermit extends Component {constructor(props) {super(props);
        this.state = {VIP: ''}
    }

    componentWillMount() {let VIP = localStorage.getItem('u_V');
        this.setState({VIP})
    }

    render() {
        return (<div>showUserPermit... {this.state.VIP}</div>
        )
    }
}

export default showUserPermit;

/* - */

import React, {Component} from 'react'
...

class showUserVipInfo extends Component {constructor(props) {super(props);
        this.state = {VIP: ''}
    }

    componentWillMount() {let VIP = localStorage.getItem('u_V');
        this.setState({VIP})
    }

    render() {
        return (<div>showUserVipInfo... {this.state.VIP}</div>
        )
    }
}

export default showUserVipInfo;

刚才发现的问题都可以映射在这两个组件里了
按照上面的思路我们做一个处理

import React, {Component} from 'react'

module.exports = Wrap: (WrappedComponent) => {
    class reComponent extends Component {constructor() {super();
            this.state = {VIP: ''}
        }

        componentWillMount() {let VIP = localStorage.getItem('u_V');
            this.setState({VIP})
        }

        render() {return <WrappedComponent VIP={this.state.VIP}/>
        }
    }

    return reComponent
}

再来简化一下 showUserVipInfo 和 showUserPermit 组件


import React, {Component} from 'react';
import {Wrap} as templete from 'wrapWithUsername';

class showUserPermit extends Component {render() {
        return (<div>welcome {this.props.username}</div>
        )
    }
}

showUserPermit = templete(showUserPermit);

export default showUserPermit;  

/*--*/
import React, {Component} from 'react';
import {Wrap} as templete from 'wrapWithUsername';

class showUserPermit extends Component {render() {
        return (<div>welcome {this.props.username}</div>
        )
    }
}

showUserPermit = templete(showUserPermit);

export default showUserPermit; 

并且高阶组件中可以分布多个目标组件,举一个我们项目中的例子


这里面右上角的时间选择组件以及 echarts 组件是两种不同身份特有的一些行为和样式,其它的完全是一样的,包括 state 以及共用方法都一模一样。上代码

render() {
  return (
    <div className="mk-genData home-module-common">
      <div className="module-header">
        <div className="module-title">...</div>
        <**GenTimerComponent** receiveTimeChange={this.getData.bind(this)}/>
      </div>
      <div className="genData-nav">
        ...
      </div>
      <div>
        <**EchartsComponent** chartData={this.state.chartData}/>
      </div>
    </div>
  )

其中 GenTimerComponent,和 EchartsComponent 都是目标组件,我们这样导出

豁然开朗了吧,其实就是把两个组件相同的地方或者都可能用到的地方抽离出来,说句题外话,其实本来是 ’ 高阶组件 ’ 嵌套了目标组件,但是重新生成的新组建反倒是继承了目标组件,看起来是一种控制反转,和 Vue 中的 extend+minix 也比较像, 通过继承目标组件,除了一些静态方法,包括生命周期,state,fun, 我们都可得到
现在理解 react-redux 的 connect 函数~
把 redux 的 state 和 action 创建函数,通过 props 注入给了 Component。
你在目标组件 Component 里面可以直接用 this.props 去调用 redux state 和 action 创建函数了。

ConnectedComment = connect(mapStateToProps, mapDispatchToProps)(Component);  

等价于

// connect 是一个返回函数的函数(就是个高阶函数)const enhance = connect(mapStateToProps, mapDispatchToProps);
// 返回的函数就是一个高阶组件,该高阶组件返回一个与 Redux store
// 关联起来的新组件
const ConnectedComment = enhance(Component);        

antd 的 Form 也是一样的

const WrappedNormalLoginForm = Form.create()(NormalLoginForm);   

总结一下: 高阶组件是对 React 代码进行更高层次重构的好方法,如果你想精简你的 state 和生命周期方法,那么高阶组件可以帮助你提取出可重用的函数。一般来说高阶组件能完成的用组件嵌套 + 继承也可以,用嵌套 + 继承的方式理解起来其实更容易一点,特别是去重构一个复杂的组件时,通过这种方式往往更快,拆分起来更容易。至于到底用哪个最佳还要具体看业务场景, 欢迎交流探讨

正文完
 0