什么是 React 高阶组件

React 高阶组件就是以高阶函数的方式包裹需要修饰的 React 组件,并返回处理完成后的 React 组件。React 高阶组件在 React 生态中使用的非常频繁,比如react-router 中的 withRouter 以及 react-redux 中 connect 等许多 API 都是以这样的方式来实现的。

使用 React 高阶组件的好处

在工作中,我们经常会有很多功能相似,组件代码重复的页面需求,通常我们可以通过完全复制一遍代码的方式实现功能,但是这样页面的维护可维护性就会变得极差,需要对每一个页面里的相同组件去做更改。因此,我们可以将其中共同的部分,比如接受相同的查询操作结果、组件外同一的标签包裹等抽离出来,做一个单独的函数,并传入不同的业务组件作为子组件参数,而这个函数不会修改子组件,只是通过组合的方式将子组件包装在容器组件中,是一个无副作用的纯函数,从而我们能够在不改变这些组件逻辑的情况下将这部分代码解耦,提升代码可维护性。

自己动手实现一个高阶组件

前端项目里,带链接指向的面包屑导航十分常用,但由于面包屑导航需要手动维护一个所有目录路径与目录名映射的数组,而这里所有的数据我们都能从 react-router 的路由表中取得,因此我们可以从这里入手,实现一个面包屑导航的高阶组件。

首先我们看看我们的路由表提供的数据以及目标面包屑组件所需要的数据:

// 这里展示的是 react-router4 的route示例

let routes = [

{

breadcrumb: '一级目录',

path: '/a',

component: require('../a/index.js').default,

items: [

{

breadcrumb: '二级目录',

path: '/a/b',

component: require('../a/b/index.js').default,

items: [

{

breadcrumb: '三级目录1',

path: '/a/b/c1',

component: require('../a/b/c1/index.js').default,

exact: true,

},

{

breadcrumb: '三级目录2',

path: '/a/b/c2',

component: require('../a/b/c2/index.js').default,

exact: true,

},

}

]

}

]

// 理想中的面包屑组件

// 展示格式为 a / b / c1 并都附上链接

const BreadcrumbsComponent = ({ breadcrumbs }) => (

{breadcrumbs.map((breadcrumb, index) => (

{breadcrumb}

{index < breadcrumbs.length - 1 && / }

))}

);

这里我们可以看到,面包屑组件需要提供的数据一共有三种,一种是当前页面的路径,一种是面包屑所带的文字,一种是该面包屑的导航链接指向。

其中第一种我们可以通过 react-router 提供的 withRouter 高阶组件包裹,可使子组件获取到当前页面的 location 属性,从而获取页面路径。

后两种需要我们对 routes 进行操作,首先将 routes 提供的数据扁平化成面包屑导航需要的格式,我们可以使用一个函数来实现它。

/**

* 以递归的方式展平react router数组

*/

const flattenRoutes = arr =>

arr.reduce(function(prev, item) {

prev.push(item);

return prev.concat(

Array.isArray(item.items) ? flattenRoutes(item.items) : item

);

}, []);

之后将展平的目录路径映射与当前页面路径一同放入处理函数,生成面包屑导航结构。

export const getBreadcrumbs = ({ flattenRoutes, location }) => {

// 初始化匹配数组match

let matches = [];

location.pathname

// 取得路径名,然后将路径分割成每一路由部分.

.split('?')[0]

.split('/')

// 对每一部分执行一次调用`getBreadcrumb()`的reduce.

.reduce((prev, curSection) => {

// 将最后一个路由部分与当前部分合并,比如当路径为 `/x/xx/xxx` 时,pathSection分别检查 `/x` `/x/xx` `/x/xx/xxx` 的匹配,并分别生成面包屑

const pathSection = `${prev}/${curSection}`;

const breadcrumb = getBreadcrumb({

flattenRoutes,

curSection,

pathSection,

});

// 将面包屑导入到matches数组中

matches.push(breadcrumb);

// 传递给下一次reduce的路径部分

return pathSection;

});

return matches;

};

然后对于每一个面包屑路径部分,生成目录名称并附上指向对应路由位置的链接属性。

const getBreadcrumb = ({ flattenRoutes, curSection, pathSection }) => {

const matchRoute = flattenRoutes.find(ele => {

const { breadcrumb, path } = ele;

if (!breadcrumb || !path) {

throw new Error(

'Router中的每一个route必须包含 `path` 以及 `breadcrumb` 属性'

);

}

// 查找是否有匹配

// exact 为 react router4 的属性,用于精确匹配路由

return matchPath(pathSection, { path, exact: true });

});

// 返回breadcrumb的值,没有就返回原匹配子路径名

if (matchRoute) {

return render({

content: matchRoute.breadcrumb || curSection,

path: matchRoute.path,

});

}

// 对于routes表中不存在的路径

// 根目录默认名称为首页.

return render({

content: pathSection === '/' ? '首页' : curSection,

path: pathSection,

});

};

之后由 render 函数生成最后的单个面包屑导航样式。单个面包屑组件需要为 render 函数提供该面包屑指向的路径 path, 以及该面包屑内容映射content 这两个 props。

/**

*

*/

const render = ({ content, path }) => {

const componentProps = { path };

if (typeof content === 'function') {

return ;

}

return {content};

};

有了这些功能函数,我们就能实现一个能为包裹组件传入当前所在路径以及路由属性的 React 高阶组件了。传入一个组件,返回一个新的相同的组件结构,这样便不会对组件外的任何功能与操作造成破坏。

const BreadcrumbsHoc = (

location = window.location,

routes = []

) => Component => {

const BreadComponent = (

breadcrumbs={getBreadcrumbs({

flattenRoutes: flattenRoutes(routes),

location,

})}

/>

);

return BreadComponent;

};

export default BreadcrumbsHoc;

调用这个高阶组件的方法也非常简单,只需要传入当前所在路径以及整个 react router 生成的 routes 属性即可。

至于如何取得当前所在路径,我们可以利用 react router 提供的 withRouter 函数,如何使用请自行查阅相关文档。

值得一提的是,withRouter 本身就是一个高阶组件,能为包裹组件提供包括 location 属性在内的若干路由属性。所以这个 API 也能作为学习高阶组件一个很好的参考。

withRouter(({ location }) =>

BreadcrumbsHoc(location, routes)(BreadcrumbsComponent)

);

4. Q&A

如果react router 生成的 routes 不是由自己手动维护的,甚至都没有存在本地,而是通过请求拉取到的,存储在 redux 里,通过 react-redux 提供的 connect 高阶函数包裹时,路由发生变化时并不会导致该面包屑组件更新。使用方法如下:

function mapStateToProps(state) {

return {

routes: state.routes,

};

}

connect(mapStateToProps)(

withRouter(({ location }) =>

BreadcrumbsHoc(location, routes)(BreadcrumbsComponent)

)

);

这其实是 connect 函数的一个bug。因为 react-redux 的 connect 高阶组件会为传入的参数组件实现 shouldComponentUpdate 这个钩子函数,导致只有 prop 发生变化时才触发更新相关的生命周期函数(含 render),而很显然,我们的 location 对象并没有作为 prop 传入该参数组件。

官方推荐的做法是使用 withRouter 来包裹 connect 的 return value,即

withRouter(

connect(mapStateToProps)(({ location, routes }) =>

BreadcrumbsHoc(location, routes)(BreadcrumbsComponent)

)

);

其实我们从这里也可以看出,高阶组件同高阶函数一样,不会对组件的类型造成任何更改,因此高阶组件就如同链式调用一样,可以任意多层包裹来给组件传入不同的属性,在正常情况下也可以随意调换位置,在使用上非常的灵活。这种可插拔特性使得高阶组件非常受React生态的青睐,很多开源库里都能看到这种特性的影子,有空也可以都拿出来分析一下。

react实现汉堡_利用 React 高阶组件实现一个面包屑导航相关推荐

  1. web前端高级React - React从入门到进阶之高阶组件

    第二部分:React进阶 系列文章目录 第一章:React从入门到进阶之初识React 第一章:React从入门到进阶之JSX简介 第三章:React从入门到进阶之元素渲染 第四章:React从入门到 ...

  2. React总结篇之六_React高阶组件

    高阶组件的概念及应用 以函数为子组件的模式 这两种方式的最终目的都是为了重用代码,只是策略不同,各有优劣,开发者可以在实际工作中决定采用哪种方式. 一.高阶组件 1. 高阶组件(Higher Orde ...

  3. React高阶组件(HOC)

    你可以想象,在一个大型应用程序中,这种订阅DataSource和调用setState的模式将一次又一次地发生.我们需要一个抽象,允许我们在一个地方定义这个逻辑,并在许多组件之间共享它.这正是高阶组件擅 ...

  4. React高阶组件(HOC)模型理论与实践

    什么是HOC? HOC(全称Higher-order component)是一种React的进阶使用方法,主要还是为了便于组件的复用.HOC就是一个方法,获取一个组件,返回一个更高级的组件. 什么时候 ...

  5. React Native高阶组件(HOC)模型理论与实践

    什么是HOC? HOC(全称Higher-order component)是一种React的进阶使用方法,主要还是为了便于组件的复用.HOC就是一个方法,获取一个组件,返回一个更高级的组件. 什么时候 ...

  6. React 中的高阶组件及其应用场景

    本文目录 什么是高阶组件 React 中的高阶组件 属性代理(Props Proxy) 反向继承(Inheritance Inversion) 高阶组件存在的问题 高阶组件的约定 高阶组件的应用场景 ...

  7. react高阶组件和hooks

    1. react高阶组件 1.1 高阶组件的概念 高阶组件(Higher Order Component,简称:HOC ): 是 React 中用于重用组件逻辑的高级技术, 它本身不是react中的组 ...

  8. React高阶组件以及应用场景

    什么是高阶组件 在解释什么是高阶组件之前,可以先了解一下什么是 高阶函数,因为它们的概念非常相似,下面是 高阶函数 的定义: 如果一个函数 接受一个或多个函数作为参数或者返回一个函数 就可称之为 高阶 ...

  9. React的高阶组件详解

    文章目录 React的高阶组件 高阶组件基本介绍 高阶组件使用过程 高阶组件应用场景 高阶组件的意义 React的高阶组件 高阶组件基本介绍 什么是高阶组件呢? 在认识高阶组价之前, 我们先来回顾一下 ...

最新文章

  1. eruke注册中心搭建
  2. Confluence 6 推荐的更新通知设置和禁用
  3. postfix 遇到的问题
  4. Docker: Failed to get D-Bus connection: No connection to service
  5. Hortworks Hadoop 2.4.2安装、配置
  6. spring事务再研究
  7. 软件使用方法_视频录制软件进行电脑屏幕录像的使用方法
  8. 华为机试HJ44:Sudoku(数独问题,深度优先遍历DFS解法)
  9. 如何看待绿色数据中心
  10. 要开始Ubuntu之旅拉~
  11. 爬取豆瓣评论连接mysql_Scrapy爬取豆瓣图书数据并写入MySQL
  12. leetcode 75.颜色分类
  13. C语言二叉排序树的中序遍历,C语言实现二叉树的中序线索化及遍历中序线索二叉树...
  14. Java中Collections.singleton方法起什么作用呢?
  15. 阿里技术专家甘盘:浅谈双十一背后的支付宝LDC架构和其CAP分析(含phil补充)
  16. 文件系统模拟程序python_树莓派开发实战(第2版)
  17. noip普及组 c语言,厉害了,NOIP普及组竟然这样出题,你会做吗?
  18. loraserver 源码解析 (六) lora-app-server
  19. executeUpdate()和execute()的区别
  20. java设计模式————模板模式,手撸一个JDBCTemplate

热门文章

  1. 拨号云服务器怎么自动配置网关_如何解决路由器静态IP+PPPoE拨号双链路负载分担问题...
  2. poi word插入图片_豌豆BI工具看板Word导出技术详解
  3. unet训练自己的数据集_基于UNet网络实现的人像分割 | 附数据集
  4. 数据库-DQL练习(附答案)
  5. 1039 Course List for Student (25 分)_33行代码AC
  6. 测试点分析:1048 数字加密 (20分)_16行代码AC
  7. 蓝桥杯 试题 基础练习 龟龟龟龟龟兔赛跑预测——18行代码AC
  8. android wifi信号通道,Android获取当前连接wifi的信道
  9. java实现账号单一ip登录,使用Java实现简单后台访问并获取IP示例
  10. 深入理解linux文件系统( 理解inode与block,理解硬链接软链接,掌握恢复误删文件及其分析方法,掌握用户日志及其查询命令 )