What are React higher-order components? Let’s talk about how to use React high-order components to create a breadcrumb navigation. I hope it will be helpful to everyone!
React high-order components wrap React components that need to be modified in the form of high-order functions , and return the React component after processing is completed. React high-order components are used very frequently in the React ecosystem, such as withRouter
in react-router
and connect
in react-redux
Many APIs are implemented in this way.
At work, we often have many page requirements with similar functions and duplicate component codes. Usually we can completely copy The function is implemented in one pass of code, but the maintainability of the page will become extremely poor, and the same components in each page need to be changed. Therefore, we can extract the common parts, such as accepting the same query operation results, wrapping the same label outside the component, etc., make a separate function, and pass in different business components as sub-component parameters, and this function It will not modify the sub-component, but wrap the sub-component in the container component through combination. It is a pure function without side effects, so that we can decouple this part of the code without changing the logic of these components, and improve the code Maintainability.
In front-end projects, breadcrumb navigation with link pointing is very commonly used, but because breadcrumb navigation requires manual maintenance of all directories An array of mappings between paths and directory names, and all the data here can be obtained from the routing table of react-router
, so we can start from here to implement a high-order component for breadcrumb navigation.
First, let’s take a look at the data provided by our routing table and the data required by the target breadcrumb component:
// 这里展示的是 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 }) => ( <div> {breadcrumbs.map((breadcrumb, index) => ( <span key={breadcrumb.props.path}> <link to={breadcrumb.props.path}>{breadcrumb}</link> {index < breadcrumbs.length - 1 && <i> / </i>} </span> ))} </div> );
Here we can see that there are three types of data that the breadcrumb component needs to provide. One is the path of the current page, the other is the text carried by the breadcrumbs, and the other is the navigation link of the breadcrumbs.
First of all, we can use the withRouter high-order component package provided by react-router, which allows the sub-component to obtain the location attribute of the current page and thereby obtain the page path.
The latter two require us to operate routes. First, flatten the data provided by routes into the format required for breadcrumb navigation. We can use a function to implement it.
/** * 以递归的方式展平react router数组 */ const flattenRoutes = arr => arr.reduce(function(prev, item) { prev.push(item); return prev.concat( Array.isArray(item.items) ? flattenRoutes(item.items) : item ); }, []);
Then put the flattened directory path mapping and the current page path into the processing function to generate a breadcrumb navigation structure.
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; };
Then for each breadcrumb path part, generate the directory name and attach a link attribute pointing to the corresponding routing location.
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, }); };
Then the render function generates the final single breadcrumb navigation style. A single breadcrumb component needs to provide the render function with the two props of the path path
that the breadcrumb points to, and the breadcrumb content mapping content
.
/** * */ const render = ({ content, path }) => { const componentProps = { path }; if (typeof content === 'function') { return <content {...componentProps} />; } return <span {...componentProps}>{content}</span>; };
With these functions, we can implement a React higher-order component that can pass the current path and routing properties to the wrapped component. Pass in a component and return a new and identical component structure, so that it will not damage any functions and operations outside the component.
const BreadcrumbsHoc = ( location = window.location, routes = [] ) => Component => { const BreadComponent = ( <Component breadcrumbs={getBreadcrumbs({ flattenRoutes: flattenRoutes(routes), location, })} /> ); return BreadComponent; }; export default BreadcrumbsHoc;
The method of calling this high-order component is also very simple. You only need to pass in the current path and the routes
attribute generated by the entire react router
.
As for how to obtain the current path, we can use the withRouter
function provided by react router
. Please refer to the relevant documentation for how to use it.
It is worth mentioning that withRouter
itself is a high-order component that can provide several routing attributes including the location
attribute for the wrapped component. Therefore, this API can also be used as a good reference for learning higher-order components.
withRouter(({ location }) => BreadcrumbsHoc(location, routes)(BreadcrumbsComponent) );
If the routes
generated by react router
are not manually maintained by themselves, they do not even exist locally , but it is pulled through requests and stored in redux. When wrapped through the connect
high-order function provided by react-redux
, the route will not cause the package to change. Crumb component updated. The usage is as follows:
function mapStateToProps(state) { return { routes: state.routes, }; } connect(mapStateToProps)( withRouter(({ location }) => BreadcrumbsHoc(location, routes)(BreadcrumbsComponent) ) );
This is actually a bug
of the connect function. Because the connect high-order component of react-redux will implement the shouldComponentUpdate hook function for the incoming parameter component, the update-related life cycle function (including render) will only be triggered when the prop changes, and obviously , our location object is not passed into the parameter component as prop.
withRouter to wrap the
return value of
connect, that is,
withRouter( connect(mapStateToProps)(({ location, routes }) => BreadcrumbsHoc(location, routes)(BreadcrumbsComponent) ) );
其实我们从这里也可以看出,高阶组件同高阶函数一样,不会对组件的类型造成任何更改,因此高阶组件就如同链式调用一样,可以任意多层包裹来给组件传入不同的属性,在正常情况下也可以随意调换位置,在使用上非常的灵活。这种可插拔特性使得高阶组件非常受React生态的青睐,很多开源库里都能看到这种特性的影子,有空也可以都拿出来分析一下。
The above is the detailed content of What are React higher-order components? How does it create a breadcrumb navigation?. For more information, please follow other related articles on the PHP Chinese website!