开始全局钩住失败_JS 真的可以为所欲为之绕过 ContextProvider 给 useRequest 注入全局配置...

news/2024/7/4 9:30:35

c8e3f72556604b843285a341770bfef3.png

引言

底层实现有时候不必循规蹈矩,能解放业务层才是关键。如果底层为了守规矩反而破坏了业务层的规矩才是得不偿失的添乱行为,行话叫过度设计。

其实这也是所谓封装的原则之一,从函数到模块到组件到页面再到工程的设计都应当秉持着为更上一层服务的原则,尽可能地把下一层的脏话累活揽到自己的作用域下,屏蔽到角落里。

说到这,突然想到一个词:金玉其外 败絮其中……

问题背景

项目使用了 hox + useRequest,详情见此文:

https://zhuanlan.zhihu.com/p/183806904​zhuanlan.zhihu.com
0af4bdcefd16aded141e4047a532d942.png

然而,上文中的轻松自然是依赖于 1.x 的 useRequest,该版本默认使用的请求库是 umi-request。如此工程可以基于 `umi-request` 的 middleware 来配置一些全局的请求特性,如:

  • 错误提示
  • loading 提示
  • mock 数据
  • 返回数据格式化
  • 鉴权
  • 等...

做了以上工作后,理论上能够实现所有的请求错误(http 或业务异常)都由「上层」来处理,在业务层完全不需要关心请求的异常情况,只需要去读取 datarun().then()就好:

const { run } = useRequest('users', { manual: true })

async function fetchUsers(params) {
  await run(params)
  tip('请求成功') // 能走到这一行就一定代表请求成功了,失败的情况完全不必关心
}

不难发现要实现上面的操作必须得在请求错误时不让 run 进行 resolve。 之前想当然地以为在「上层」抛出一个异常就会打断 resolve(即使触发了 runcatch也无所谓,虽然控制台会报错, 但流程上还是符合预期的)

然而我错了,run方法内部做了 catch,请求结束后对于上层来说会始终可以 then到。

于是搜索到相关的 issues:

3953df1126a290a2824a0dbb7eeec11e.png

绿框部分深得我心!这不就是我要的滑板鞋么?!

项目并没有独立安装 useRequest,而是使用的 umi hooks,那就整个一起更新吧!来拥抱 ahooks!这个里面集成的就是最新的 useRequest!

然后,然后项目就崩了。。

原因是 2.x 的 useRequest 的默认请求库不再是 umi-request,而是 Fetch。那么理所当然之前配置给 umi-request的那些所谓「上层」配置都没卵用了。 不过没关系,看看能不能基于新版 useRequest 的 options 迁移配置。答案当然是:不能!(不然还水毛线文章。。

原因是 useRequest 提供的「钩子」有限,很多请求特性很难基于它们实现。想想也是, useRequest 的定位本就是一个方便管理请求状态的 hooks,更多请求层的东西应该交给专业的请求库来做,那么就是 requestMethod 配置闪亮登场的时候了!该配置项可以设置 useRequest 的默认请求库。

那么,当前项目最简单的兼容方案肯定就是把 requestMethod 设置成 umi-request 了,这样理论上就和之前的 1.x 就没啥区别了。说干就干,在入口组件处通过 UseRequestProvider 注入全局配置。

<UseRequestProvider value={options}>
  {children}
</UseRequestProvider>

就这?

当然不!尝试刷新页面,请求异常依旧。。。甚至和注入全局配置之前没啥两样。多年的写 bug 经验告诉我应该是我传入的配置没有生效,于是在 options.requestMethod中尝试 log,运行后果然并没有 log。又尝试在那个报错的请求处单独配置 requestMethod,成功 log!说明问题还是在全局注入这里。

可是明明我就是在入口文件的根结点注入的!翻了 UseRequestProvider 的源码,也的的确确是个普普通通的 ContextProvider,而 Context 注入不成功的原因基本就只有一个:Consumer 不是 Provider 的子节点。

于是开始定位第一个异常请求的发起方,果然发起方比较可疑,是一个 hox model。多年的白嫖经验又告诉我此时去 hox 仓库中搜索 context 肯定有小可爱已经提了 issue:

3db8c2bc8f969cd702a2119e3d434183.png

ok!问题定位到了!解决它吧!

解决方案

在官方方案(hox 2.x)之前,快速的解决方案应该就以下两种:

  1. 编译期方案:通过 WebPack.NormalModuleReplacementPlugin 插件替换 useRequest 的某些模块实现本地的快速 fix。
  2. 运行时方案:使用一些黑科技修改 useRequest 的某些模块或者业务层统一使用再包装过一层的 useRequest。

我选择了使用黑科技。原因是编译期方案过重,包装一层又会导致其他小伙伴在使用的时候得从一个新的路径导入模块,不够顺手。我这边也希望尽可能地不去干涉项目原本的开发方式。

那么问题就成了如何在运行时去修改 useRequest 的某些模块?

首先想尝试的是直接修改 ahooks的导出:

 import * as ahooks from 'ahooks'

// ahooks.useRequest = myUseRequest

// Object.definObject.defineProperty(ahooks, 'useRequest', { get: myUseRequest });

它们会分别报错:

  1. Cannot set property useRequest of #<Object> which has only a getter
  2. Cannot redefine property: useRequest

传统黑科技被 webpack 的导出模块安排地死死地。。(实现地真严谨啊,不过这里记得是 strict 模式下 es 模块才会这么严格,不过也懒得再去找这条路径的突破口了

再想!useRequest 在不配置 requestMethod的情况下,默认使用的是 Fetch。那可不可以。。。

window.fetch = () => {}

理论上可以,但实现起来难免得去翻 useRequest 的源码,兼容它调用 fetch 的行为基础上再去支持 umi-request,麻烦且 ugly。

再想!useRequest 既然是读取的 Context 注入的全局配置,这个过程里还能不能找点口子了?首先一定还有一个 Context 模块,即使我这边不 Provide,肯定他那边也是要 Consume 的。而一个没有被 Provide 的 Context,Consume 的时候拿到的一定是 Context 的默认值,而这个默认值一定藏在某个地方,如果我能找到它,我就可以嘿嘿嘿。。。于是:

import UseRequestContext from '@ahooksjs/use-request/es/configContext';

console.log(UseRequestContext)

25fa8a85e746bebce71365f5087fad4b.png

逮到!凭我多年的改 bug 经验,这两个值一定就是 Context 注入值的缓存,且 _currentValue2代表当前值,_currentValue是之前值,用于浅比较更新。

那么接下来就好说了:

import umiRequest from 'umi-request';
import UseRequestContext from '@ahooksjs/use-request/es/configContext';

const requestMethod = (options) => {
  if (typeof options === 'string') return umiRequest(options);
  const { url, ...umiRequestOptions } = options;
  return umiRequest(url, umiRequestOptions);
};

UseRequestContext._currentValue2 = {
  requestMethod,
};

刷新浏览器,成。。。功了一半?!

发现第一次请求是成功地走了 requestMethod,后面又开始走默认 fetch了!猜测可能和 Context 的更新机制有关,手动改了一把 configContext 的模块,令其有默认值后再输出:

23dfb08f1b240c47bf8574e56502ef78.png

那就抄呗!把_currentValue_currentValue2都赋值一遍后,搞定!

最后: @brickspert 出来打钱!我又来给你家产品做推广了!


最后的最后。。。水了这么多字再带个货吧!hh

上面这个足疗机你绝对值得拥有!能给你按地嗷嗷叫,随时按,随时爽,而且想按多久按多久,还省了保健钱。

另一篇文章里也有介绍过:

https://zhuanlan.zhihu.com/p/149078735​zhuanlan.zhihu.com
69347dce3bf45de10db5fb4f548d74ee.png

http://www.niftyadmin.cn/n/2419676.html

相关文章

python内置数据结构之set

内置数据结构set定义:可变的、无序的、不重复的元素的集合.常用于: 去重set是可迭代对象set中的元素必须是可hash的set中的值不能通过索引访问set定义,初始化 set() -> new empty set objectset(iterable) -> new set object注意:定义set时,可用{}来表示,但不能为空如…

mongodb 客户端_MongoDB安全|用于测试的OpenSSL客户端证书

MongoDB Manual (Version 4.2)> Security > Appendix > Appendix B - OpenSSL Server Certificates for Testing查看更多中文手册翻译和加入贡献请访问https://docs.mongoing.com/免责声明提供此页面仅用于测试目的&#xff0c;证书仅用于测试目的。以下教程提供了一些…

用正则表达式把用URLEncode的字符串转换成正常的字符串函数

<script languageJavaScript RUNATSERVER> // 利用正则表达式把用URLEncode的字符串转换成正常的字符串函数 function URLDecode(EncodeString) { var lsRegExp /\/g; return unescape(String(EncodeString).replace(lsRegExp, " ")); } </script>…

ppp协议数据抓包分析_CoAP协议详细讲解+服务器搭建测试+数据抓包分析

点上方蓝字关注我们每天都有好玩的东西等着你在IoT项目架构中&#xff0c;除了基于TCP的MQTT协议&#xff0c;使用最多的就是基于UDP的CoAP协议&#xff0c;两者可以说是平分秋色&#xff0c;各有优势。CoAP协议运行在UDP之上&#xff0c;消息格式非常紧凑&#xff0c;一个最小…

JS控制Video播放器(video详细介绍)(快进、后退、播放、暂停、音量大小)

思路&#xff1a; 一.首先监听触发事件。 比如&#xff1a;向上键对应的keyCode为38&#xff0c;向下键对应的keyCode为40&#xff0c;向左键对应的keyCode为37&#xff0c;向右键对应的keyCode为39&#xff0c;空格键对应的keyCode为32&#xff0c; 其他的keyCode可以通过 co…

express 使用https

var express require(express); var https require(https); var http require(http); var fs require(fs);//同步读取密钥和签名证书 var options {key:fs.readFileSync(./keys/server.key),cert:fs.readFileSync(./keys/server.crt) }; var app express(); var httpsSer…

sql 根据入离职日期查询月初人员信息_关于sql的一些总结

最近花了大量的时间研究算法、python编程的应用和kaggle/阿里天池的比赛&#xff0c;对于最基础的sql有些忽略&#xff0c;作为数据分析师的入门工具&#xff0c;必须要时常回顾一下。这篇文章将对sql进行一个总结概括&#xff0c;并且对自己在leetcode、牛客网以及sql50题练习…

有关TinyXML使用的简单总结

这次使用了TinyXML后&#xff0c;觉得这个东西真是不错&#xff0c;于是将使用方法坐下总结来和大家分享。该解析库在开源网站&#xff08;http://sourceforge.net&#xff09;上有下载&#xff0c;在本Blog也提供下载&#xff08;下载TinyXML&#xff09;TinyXML是一个开源的解…