摘要:高阶组件可以封装公共逻辑,给当前组件传递方法属性,添加生命周期钩子等。二是基础组件的静态方法也会因为高阶组件的包裹会丢失。如果在开发中确实遇到了必须使用它们,就一定要注意高阶组件的这个问题并认真解决。
高阶组件可以封装公共逻辑,给当前组件传递方法属性,添加生命周期钩子等。
案例:
一个项目中有的页面需要判断所处环境,如果在移动端则正常显示页面,并向用户提示当前页面所处的移动端环境,如果不在移动端则显示提示让其在移动端打开。但是有的页面又不需要这个判断。
如果在每个页面都写一段判断逻辑未免麻烦,因此可以借助高阶组件来处理这部分逻辑。
先创建一个高阶组件
// src/container/withEnvironment/index.jsx import React from "react"; const envs = { weixin: "微信", qq: "QQ", baiduboxapp: "手机百度", weibo: "微博", other: "移动端" } function withEnvironment(BasicComponent) { const ua = navigator.userAgent; const isMobile = "ontouchstart" in document; let env = "other"; if (ua.match(/MicroMessenger/i)) { env = "weixin"; } if (ua.match(/weibo/i)) { env = "weibo"; } if (ua.match(/qq/i)) { env = "qq"; } if (ua.match(/baiduboxapp/i)) { env = "baiduboxapp" } // 不同逻辑下返回不同的中间组件 if (!isMobile) { return function () { return () } } // 通过定义的中间组件将页面所处环境通过props传递给基础组件 const C = props => (该页面只能在移动端查看,请扫描下方二维码打开。假设这里有张二维码) return C; } export default withEnvironment;
然后在基础组件中使用
// src/pages/Demo01/index.jsx import React from "react"; import withEnvironment from "../../container/withEnvironment"; function Demo01(props) { return (你现在正在{props.envdesc}中访问该页面) } export default withEnvironment(Demo01);
最后将基础组件渲染出来即可查看到效果。
// src/index.js import React from "react"; import { render } from "react-dom"; import Demo01 from "./pages/Demo01"; const root = document.querySelector("#root"); render(, root);
在上面这个例子中,我们将环境判断的逻辑放在了高阶组件中处理,以后只要需要判断环境的页面只需要在基础组件中这样执行即可。
export default withEnvironment(Demo01);
除此之外,我们在实际开发中还会遇到一个非常常见的需求,那就是在进入一个页面时需要判断登录状态,登录状态与非登录状态的不同显示,登录状态之后角色的不同显示都可以通过高阶组件统一来处理这个逻辑,然后将登录状态,角色信息等传递给基础组件。
// 大概的处理逻辑 import React from "react"; import $ from "jquery"; // 假设已经封装了一个叫做getCookie的方法获取cookie import { getCookie } from "cookie"; function withRule(BasicComponent) { return class C extends React.Component { state = { islogin: false, rule: -1, loading: true, error: null } componentDidMount() { // 如果能直接在cookie中找到uid,说明已经登录过并保存了相关信息 if (getCookie("uid")) { this.setState({ islogin: true, rule: getCookie("rule") || 0, loading: false }) } else { // 如果找不到uid,则尝试自动登录,先从kookie中查找是否保存了登录账号与密码 const userinfo = getCookie("userinfo"); if (userinfo) { // 调用登录接口 $.post("/api/login", { username: userinfo.username, password: userinfo.password }).then(resp => { this.setState({ islogin: true, rule: resp.rule, islogin: false }) }).catch(err => this.setState({ error: err.message })) } else { // 当无法自动登录时,你可以选择在这里弹出登录框,或者直接显示未登录页面的样式等都可以 } } } render() { const { islogin, rule, loading, error } = this.state; if (error) { return (登录接口请求失败!错误信息为:{error}) } if (loading) { return (页面加载中, 请稍后...) } return () } } } export default withRule;
与第一个例子相比,这个例子更加接近实际应用并且逻辑也更更加复杂。因此涉及到了异步数据,因此最好的方式是在中间组件的componentDidMount中来处理逻辑。并在render中根据不同的状态决定不同的渲染结果。
我们需要根据实际情况合理的使用react创建组件的两种方式。这一点至关重要。上面两个例子个人认为还是比较典型的能代表大多数情况。
react-router中的高阶组件我们在学习react的过程中,会逐渐的与高阶组件打交道,react-router 中的 withRouter应该算是会最早接触到的高阶组件。我们在使用的时候就知道,通过withRouter包装的组件,我们可以在props中访问到location, router等对象,这正是withRouter通过高阶组件的方式传递过来的。
import React, { Component } from "react"; import { withRouter } from "react-router"; class Home extends Component { componentDidMount() { const { router } = this.props; router.push("/"); } render() { return (...) } } export default withRouter(Home);
我们可以来看看在react-router v4中withRouter的源码。
import React from "react"; import PropTypes from "prop-types"; import hoistStatics from "hoist-non-react-statics"; import Route from "./Route"; // 传入基础组件作为参数 const withRouter = (Component) => { // 创建中间组件 const C = (props) => { const { wrappedComponentRef, ...remainingProps } = props; return (( // wrappedComponentRef 用来解决高阶组件无法正确获取到ref的问题 )}/> ) } C.displayName = `withRouter(${Component.displayName || Component.name})`; C.WrappedComponent = Component; C.propTypes = { wrappedComponentRef: PropTypes.func } // hoistStatics类似于Object.assign,用于解决基础组件因为高阶组件的包裹而丢失静态方法的问题 return hoistStatics(C, Component); } export default withRouter;
如果对于高阶组件的例子你已经熟知,那么withRouter的源码其实很容易理解。它做所的工作就仅仅只是把routeComponentProps传入基础组件而已。
另外还需要注意点是在该源码中,解决了两个因为高阶组件带来的问题,一个是经过高阶组件包裹的组件在使用时无法通过ref正确获取到对应的值。二是基础组件的静态方法也会因为高阶组件的包裹会丢失。不过好在这段源码已经给我们提供了对应的解决方案。因此如果我们在使用中需要处理这2点的话,按照这里的方式来做就可以了。
但是通常情况下,我们也很少会在自定义的组件中添加静态方法和使用ref。如果在开发中确实遇到了必须使用它们,就一定要注意高阶组件的这2个问题并认真解决。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/83704.html
摘要:前面有讲到过很多页面会在初始时验证登录状态与用户角色。这个时候就涉及到一个高阶组件的嵌套使用。而每一个高阶组件函数执行之后中所返回的组件,刚好可以作为下一个高阶组件的参数继续执行,而并不会影响基础组件中所获得的新能力。 前面有讲到过很多页面会在初始时验证登录状态与用户角色。我们可以使用高阶组件来封装这部分验证逻辑。封装好之后我们在使用的时候就可以如下: export default w...
摘要:在前端基础进阶八深入详解函数的柯里化一文中,我有分享柯里化相关的知识。虽然说高阶组件与柯里化都属于比较难以理解的知识点,但是他们组合在一起使用时并没有新增更多的难点。 可能看过我以前文章的同学应该会猜得到当我用New的方法来举例学习高阶组件时,接下来要分享的就是柯里化了。高阶组件与函数柯里化的运用是非常能够提高代码逼格的技巧,如果你有剩余的精力,完全可以花点时间学习一下。 在前端基础进...
摘要:创建一个普通函数因为的存在所以变成构造函数创建一个方法在方法中,创建一个中间实例对中间实例经过逻辑处理之后返回使用方法创建实例而恰好,高阶组件的创建逻辑与使用,与这里的方法完全一致。因为方法其实就是构造函数的高阶组件。 很多人写文章喜欢把问题复杂化,因此当我学习高阶组件的时候,查阅到的很多文章都给人一种高阶组件高深莫测的感觉。但是事实上却未必。 有一个词叫做封装。相信写代码这么久了,大...
阅读 1376·2021-10-08 10:04
阅读 2680·2021-09-22 15:23
阅读 2724·2021-09-04 16:40
阅读 1172·2019-08-29 17:29
阅读 1492·2019-08-29 17:28
阅读 2987·2019-08-29 14:02
阅读 2220·2019-08-29 13:18
阅读 837·2019-08-23 18:35