跳到主要内容

在 Markdown 中使用 Prettier

· 阅读需 1 分钟
1adybug
子虚伊人

prettier 也支持 Markdown 文件的格式化,但是有以下注意点:

  1. 中文用户可以安装插件 prettier-plugin-lint-md
  2. JavaScript 代码块语言标识需要改为 javascript 或者 jsTypeScript 改为 typescript 或者 ts,驼峰命名的语言标识需要改为全小写,否则 prettier 无法识别
  3. tabWidth 要设置为 2,否则 Markdown 文件中的文本缩进会有问题

使用 Grid 布局实现的 Flow 组件

· 阅读需 1 分钟
1adybug
子虚伊人
.container {
display: grid;
grid-template-columns: repeat(auto-fill, 100px); /* 固定宽度为 100px */
grid-auto-rows: 100px; /* 固定高度为 100px */
gap: 10px; /* 网格间距 */
}

太牛了

selecto.js 配置项

· 阅读需 9 分钟
1adybug
子虚伊人

Selecto.js 是一个用于在网页上进行拖拽选择的 JavaScript 库,允许用户通过绘制选择框来选择页面上的元素。通过配置选项,您可以自定义 Selecto 的行为和功能。以下是每个配置选项的详细说明:

  1. container

    • 类型HTMLElement | string
    • 默认值document.body
    • 作用:指定选择区域的容器元素。Selecto 会在该容器内检测和选择元素。可以直接传入 DOM 元素或选择器字符串。
  2. dragContainer

    • 类型HTMLElement | string
    • 默认值container
    • 作用:指定触发拖拽事件的容器。通常用于在特定区域内启用或禁用拖拽选择。
  3. selectableTargets

    • 类型string[]
    • 默认值[]
    • 作用:定义可被选择的目标元素的选择器数组。只有匹配这些选择器的元素才会响应选择操作。
  4. hitRate

    • 类型number
    • 默认值100
    • 作用:设置元素被选择所需的覆盖率(百分比)。值为 0-100,表示选择框覆盖元素的比例达到该值时,元素即被选中。
  5. selectByClick

    • 类型boolean
    • 默认值true
    • 作用:是否允许通过单击来选择元素。设置为 true 时,用户可以直接点击元素进行选择。
  6. selectFromInside

    • 类型boolean
    • 默认值true
    • 作用:是否允许从被选择元素的内部开始拖拽选择。设置为 false 时,用户无法从目标元素内部开始绘制选择框。
  7. continueSelect

    • 类型boolean
    • 默认值false
    • 作用:是否在新的选择操作中保留之前选中的元素。设置为 true,新的选择结果会累加到已有的选择中。
  8. toggleContinueSelect

    • 类型string | string[]
    • 默认值null
    • 作用:指定用于切换 continueSelect 状态的按键。当按下指定的键时,continueSelect 状态会被激活或关闭。例如:'shift'['ctrl', 'meta']
  9. keyContainer

    • 类型HTMLElement | Document | Window
    • 默认值window
    • 作用:指定用于监听键盘事件的容器。当使用 toggleContinueSelect 功能时,需要监听键盘事件以切换选择模式。
  10. ratio

    • 类型number
    • 默认值0
    • 作用:设置选择框的固定宽高比。值为 0 时,不限制宽高比。非零值会使选择框按照指定比例缩放。
  11. scrollOptions

    • 类型object | null
    • 默认值null
    • 作用:配置自动滚动选项,当选择框到达容器边缘时,容器会自动滚动。包含以下属性:
      • container: 滚动的容器元素或选择器。
      • threshold: 触发滚动的距离阈值。
      • speed: 滚动速度。
      • getScrollPosition: 自定义获取滚动位置的方法。
  12. boundContainer

    • 类型HTMLElement | string | null
    • 默认值null
    • 作用:限制选择框的活动范围。选择框无法超出指定的容器边界。
  13. preventDefault

    • 类型boolean
    • 默认值false
    • 作用:是否在拖拽选择时调用 event.preventDefault(),以防止默认的浏览器行为(如文本选中、图片拖拽等)。
  14. cspNonce

    • 类型string
    • 默认值null
    • 作用:用于 Content Security Policy(内容安全策略)的 nonce 值,确保内联样式在启用了 CSP 的环境下被正确应用。
  15. checkInput

    • 类型boolean
    • 默认值false
    • 作用:是否在选择操作中检查输入元素(如 <input><textarea>)。设置为 true 时,选择操作不会影响这些输入元素的交互。
  16. preventDragFromInside

    • 类型boolean
    • 默认值true
    • 作用:是否防止从目标元素内部开始拖拽选择。设置为 true,可以避免与内部元素的拖拽操作冲突。
  17. getElementRect

    • 类型function
    • 默认值(el) => el.getBoundingClientRect()
    • 作用:自定义获取元素位置和尺寸的方法。可用于处理特殊情况或优化性能。
  18. dragCondition

    • 类型function
    • 默认值() => true
    • 作用:自定义拖拽开始的条件。返回 true 表示允许开始拖拽选择,false 则阻止拖拽。
  19. className

    • 类型string
    • 默认值''
    • 作用:为选择框元素添加自定义的 CSS 类名,方便进行样式定制。
  20. hoverClassName

    • 类型string
    • 默认值''
    • 作用:当元素被鼠标悬停或被选择时,添加到元素上的 CSS 类名。
  21. toggleContinueSelectWithoutDeselect

    • 类型boolean
    • 默认值false
    • 作用:在切换 continueSelect 状态时,是否保留已选中的元素而不取消选择。
  22. preventClickEvent

    • 类型boolean
    • 默认值true
    • 作用:是否在选择操作中阻止点击事件的触发,避免与其他点击交互冲突。
  23. appendTo

    • 类型HTMLElement | string
    • 默认值container
    • 作用:指定选择框元素添加到的容器。可用于调整选择框的层级关系。
  24. dragCondition

    • 类型function
    • 默认值null
    • 作用:自定义判断是否可以开始拖拽选择的条件函数。返回 true 允许拖拽,false 阻止拖拽。

示例代码:

const selecto = new Selecto({
container: document.querySelector(".selecto-area"),
dragContainer: window,
selectableTargets: [".selectable"],
hitRate: 50,
selectByClick: true,
selectFromInside: false,
continueSelect: false,
toggleContinueSelect: "shift",
keyContainer: window,
ratio: 0,
scrollOptions: {
container: document.querySelector(".scroll-container"),
threshold: 30,
speed: 10,
},
boundContainer: document.querySelector(".bound-area"),
preventDefault: true,
cspNonce: "your-csp-nonce",
checkInput: true,
preventDragFromInside: false,
getElementRect: el =>
// 自定义获取元素位置的方法
({
left: el.offsetLeft,
top: el.offsetTop,
width: el.offsetWidth,
height: el.offsetHeight,
}),
className: "custom-selecto",
hoverClassName: "selected",
toggleContinueSelectWithoutDeselect: true,
preventClickEvent: true,
appendTo: document.body,
dragCondition: e =>
// 仅当按下左键时允许拖拽选择
e.inputEvent.button === 0,
})

通过正确配置以上选项,您可以根据具体需求定制 Selecto.js 的功能,例如:

  • 多选功能:使用 continueSelect: true 实现多次选择累加。
  • 限制选择范围:通过 boundContainer 限制选择框的活动区域。
  • 自定义样式:使用 classNamehoverClassName 为选择框和被选中元素添加自定义样式。

注意事项:

  • 事件监听:Selecto.js 提供了丰富的事件回调,如 select, dragStart, dragEnd,可用于响应用户的选择操作。
  • 性能优化:在大量可选元素时,合理使用 selectableTargetsgetElementRect 以提升性能。

希望以上解释能帮助您全面了解 Selecto.js 的配置选项,从而更好地应用到项目中。

FAQ

dragContainercontainer 的区别

深入理解 selecto.js 中 containerdragContainer 的区别

selecto.js 中,containerdragContainer 都是用于配置选择行为的选项,但它们的作用不同。


1. container

  • 类型HTMLElement | string
  • 默认值document.body
  • 作用:定义选择操作的范围,以及可被选择的元素所在的区域。

container 是选择操作发生的区域,Selecto 会在这个容器内查找和选择元素。当您指定 selectableTargets 时,selecto.js 会在 container 内根据这些选择器查找目标元素。

container 的关键点:

  • 操作范围:它是 selecto.js 进行选择操作的主要区域。
  • 元素定位:所有可被选择的元素都应位于此容器内。
  • 坐标计算:选择框的位置和尺寸是基于该容器的坐标系计算的。
  • 默认容器:如果未指定,默认为 document.body

2. dragContainer

  • 类型HTMLElement | string
  • 默认值:如果未指定,则与 container 相同
  • 作用:指定监听鼠标或触摸事件以开始选择过程的区域或元素。

dragContainer 是用户可以开始拖拽以创建选择框的区域。换句话说,它是用户可以点击并拖动以开始选择的区域。

dragContainer 的关键点:

  • 事件监听区域:它是用于监听鼠标或触摸事件的区域。
  • 选择启动:用户只能在此区域内开始点击并拖动以进行选择。
  • 可独立设置:可以与 container 不同,允许在一个区域启动选择,但选择另一个区域内的元素。
  • 用途多样:可用于限制选择操作的启动区域,防止干扰其他交互。

区别和使用场景

区别总结:

  • container:定义选择操作的目标区域和元素所在位置。
  • dragContainer:定义用户可以开始拖拽选择的区域。

使用场景:

  1. 默认行为(containerdragContainer 相同)

    • 用户可以在容器内的任何位置开始拖拽选择。
    • 适用于简单的选择场景,没有特殊的交互限制。
  2. 不同的 containerdragContainer

    • 场景一:防止干扰元素的正常交互
      • 示例:在一个图片库中,您希望用户在图片上点击时查看大图,而不是开始选择。
      • 解决:将 dragContainer 设置为不包含图片的区域,如空白背景。
    • 场景二:从特定区域启动选择
      • 示例:只有在按住某个按钮或在特定面板上拖拽时才开始选择。
      • 解决:将 dragContainer 设置为该按钮或面板的元素选择器。

示例代码:

const selecto = new Selecto({
// 定义可被选择的元素所在的容器
container: ".items-container",
// 定义用户可以开始拖拽选择的区域
dragContainer: ".selection-area",
// 可被选择的元素
selectableTargets: [".items-container .item"],
// 其他配置...
})

解释:

  • .items-container:包含了所有可被选择的项目。
  • .selection-area:用户只能在此区域内开始拖拽选择。
  • 结果:避免了用户在与项目交互时(如点击、拖动项目)意外启动选择操作。

总结

  • container(容器):选择操作的范围,决定哪些元素可以被选择,以及选择框的位置计算。
  • dragContainer(拖拽容器):用户可以开始拖拽以进行选择的区域,决定了选择操作的触发区域。

通过正确配置这两个选项,您可以:

  • 控制选择行为:防止选择操作干扰其他交互(如点击、拖动元素本身)。
  • 优化用户体验:使选择操作更加直观,符合应用的交互逻辑。
  • 增强功能:实现复杂的交互需求,如在特定区域或条件下才允许选择。

建议:

  • 根据需求设置:如果不需要特殊的选择启动区域,保持默认配置即可。
  • 测试交互效果:在应用中测试不同配置,确保选择操作符合用户预期。

希望以上解释能帮助您充分理解 containerdragContainer 的区别,并在项目中灵活应用。

https 详解

· 阅读需 7 分钟
1adybug
子虚伊人

对称加密

对称加密是一种加密算法,其中加密和解密使用同一个密钥。它的工作原理是,发送方用密钥对数据进行加密,接收方使用相同的密钥来解密数据。由于加密和解密过程中使用的密钥是相同的,因此被称为“对称”。

对称加密的基本流程

  1. 密钥生成:系统生成一个共享的秘密密钥。
  2. 加密:发送方使用密钥和对称加密算法对原始数据进行加密,生成密文。
  3. 传输密文:将密文传输给接收方。
  4. 解密:接收方使用相同的密钥解密密文,恢复原始数据。

常见的对称加密算法

  • DES(数据加密标准):一种较早的对称加密算法,密钥长度为 56 位,安全性较低。
  • AES(高级加密标准):一种现代的对称加密算法,密钥长度可以是 128 位、192 位或 256 位,具有较高的安全性和效率。

对称加密的优点

  • 加密速度快:对称加密比非对称加密在处理大数据时速度更快。
  • 算法简单:实现相对简单,资源消耗较低。

对称加密的缺点

  • 密钥管理困难:双方必须安全地共享和管理密钥。如果密钥被泄露,数据就不再安全。客户端是不可信的,所以客户端的密钥泄露后果很严重。
  • 不适用于大量用户的环境:因为每对用户需要单独的一套密钥,管理多个密钥对会非常复杂。

非对称加密

非对称加密是一种加密算法,它使用一对密钥:公钥私钥。这对密钥中的一个用于加密,另一个则用于解密。公钥可以公开给任何人,而私钥必须由所有者保密。非对称加密解决了对称加密中“密钥分发”的难题,因为加密和解密使用不同的密钥。

非对称加密的基本流程

  1. 密钥对生成:系统生成一对密钥,包含一个公钥和一个私钥。
  2. 公钥加密:发送方使用接收方的公钥对消息进行加密。
  3. 私钥解密:接收方使用自己的私钥解密密文,恢复原始数据。

由于使用不同的密钥,加密和解密过程更加安全,尤其是在公钥公开的情况下,只有持有对应私钥的人才能解密密文。

公钥私钥是非对称加密算法中的两种密钥,它们共同构成一对密钥,用于加密和解密数据。这两个密钥具有特殊的数学关系:使用一个密钥加密的数据,必须用另一个密钥来解密。

公钥和私钥

公钥(Public Key)

  • 定义:公钥是非对称加密中的一个密钥,可以公开给任何人使用。
  • 用途:主要用于加密数据和验证数字签名。任何人都可以使用接收方的公钥对消息进行加密。
  • 特点:即使公钥是公开的,没有对应的私钥,外部的人也无法解密数据,因此公钥的公开不会带来风险。

私钥(Private Key)

  • 定义:私钥是与公钥成对的密钥,必须严格保密,仅由密钥所有者掌握。
  • 用途:用于解密通过公钥加密的数据,或者用来生成数字签名,证明消息的真实性。
  • 特点:只有持有私钥的人才能解密用公钥加密的消息,或者验证私钥签署的数字签名的真实性。

公钥和私钥的工作原理

  1. 加密和解密

    • 发送方使用接收方的公钥加密数据。
    • 接收方使用自己的私钥解密数据。

    由于公钥可以公开,任何人都能加密消息,但只有持有私钥的人能解密。

  2. 数字签名

    • 发送方使用自己的私钥对消息进行签名,证明消息是由自己发出的。
    • 接收方使用发送方的公钥验证签名,确保消息的真实性。

    这种机制确保了消息的完整性和发送者的身份,防止了数据篡改和伪造。

公钥和私钥的应用场景

  • 加密通信:使用接收方的公钥加密数据,确保只有接收方能使用私钥解密。
  • 数字签名和认证:使用私钥进行签名,确保接收方能够验证消息的来源和真实性。
  • 密钥交换:通过非对称加密安全地交换对称加密所用的密钥。

关键点总结

  • 公钥可以公开,主要用于加密和验证签名。
  • 私钥必须保密,主要用于解密和生成签名。
  • 公钥和私钥是互补的,一个加密的数据只能通过另一个来解密。

常见的非对称加密算法

  • RSA(Rivest-Shamir-Adleman):一种广泛使用的非对称加密算法,常用于数字签名和密钥交换。
  • ECC(椭圆曲线加密):一种效率更高的非对称加密算法,提供相同安全性所需的密钥长度更短,计算更快。

非对称加密的优点

  • 安全性更高:由于公钥可以公开,私钥可以保持私密,非对称加密解决了对称加密中的密钥分发问题。
  • 支持数字签名:可以验证消息发送者的身份,确保消息的完整性和真实性。

非对称加密的缺点

  • 加密速度较慢:相比对称加密,非对称加密在处理大量数据时速度较慢,计算复杂度高。
  • 密钥长度较长:为了提供相同的安全性,非对称加密所需的密钥通常比对称加密的密钥长得多。

HTTPS

HTTPS(超文本传输安全协议)使用了对称加密、非对称加密和数字证书来保证通信的安全性。这个过程大致可以分为以下几个步骤:

  1. 建立安全连接:

    当浏览器连接到 HTTPS 网站时,服务器会发送其 SSL 证书给浏览器。这个证书包含了服务器的公钥,以及由可信任的证书颁发机构(CA)签名的证书信息。

  2. 验证证书:

    浏览器会验证证书的有效性,包括检查证书是否过期、是否由可信的 CA 签发等。这一步骤可以确保浏览器连接到的确实是预期的服务器,而不是某个冒充的恶意服务器。

  3. 生成会话密钥:

    验证通过后,浏览器会生成一个随机的对称密钥(会话密钥)。然后使用服务器的公钥对这个会话密钥进行加密。

  4. 密钥交换:

    浏览器将加密后的会话密钥发送给服务器。服务器使用自己的私钥解密,获得会话密钥。

  5. 安全通信:

    此后,浏览器和服务器就可以使用这个共享的会话密钥,通过对称加密算法来加密它们之间的所有通信内容。

为什么要使用这些技术:

  1. 非对称加密(公钥加密):

    • 用于安全地交换对称密钥
    • 解决了密钥分发的问题
    • 但是计算速度较慢
  2. 对称加密:

    • 用于加密实际的通信内容
    • 计算速度快,适合大量数据的加密
  3. CA 证书:

    • 用于验证服务器的身份
    • 防止中间人攻击

通过结合使用这些技术,HTTPS 能够:

  • 确保通信的机密性(防止被窃听)
  • 保证数据的完整性(防止被篡改)
  • 验证通信双方的身份(防止身份伪造)

这种方式既保证了安全性,又兼顾了效率,是目前互联网上广泛使用的安全通信方式。

document 和 document.documentElement 之间的区别

· 阅读需 1 分钟
1adybug
子虚伊人

documentdocument.documentElement 确实有一些重要区别:

  1. 定义:

    • document 是整个 HTML 文档的根节点。
    • document.documentElementHTML 文档的根元素,通常是 <html> 标签。
  2. 层级:

    • documentDOM 树的顶层对象。
    • document.documentElementdocument 的直接子节点。
  3. 属性和方法:

    • document 包含许多特有的属性和方法,如 createElement(), getElementById() 等。
    • document.documentElement 主要继承自 Element 接口,拥有元素通用的属性和方法。
  4. 尺寸获取:

    • 获取视口大小时,通常使用 document.documentElement.clientWidth/clientHeight
    • document 本身没有这些尺寸属性。
  5. 滚动相关:

    • 处理页面滚动时,常用 document.documentElement.scrollTop/scrollLeft
    • 某些浏览器可能需要使用 document.body 而非 document.documentElement
  6. DOCTYPE:

    • document 包含 DOCTYPE 声明。
    • document.documentElement 不包含 DOCTYPE,仅从 <html> 开始。

React Native 和 Expo 开发中的问题汇总

· 阅读需 1 分钟
1adybug
子虚伊人

无法连接到开发服务器

注意

could not connect to the development server

解决方案:

  1. 在虚拟机中连接 WiFi
  2. 在 Android Studio 中将 GradleJava 位置设置为系统位置

项目编译失败

解决方案:

在 Android Studio 中打开 android 目录

动画暂停之后速度变慢

原因:

动画暂停之后再开始,会以设置的全部时间来完成剩余的进度

解决方案:

/** 初始值 */
const fromValue = 0

/** 重点值 */
const toValue = 100

/** 时间 */
const duration = 10000

/** 速度 */
const speed = (toValue - fromValue) / duration

/** 动画值 */
const translateX = useRef(new Animated.Value(fromValue)).current

/** 暂停值 */
const stopValue = useRef(fromValue)

/** 动画状态 */
const status = useRef(false)

function onClick() {
// 如果暂停值已经达到目标值,说明动画已经完成
if (stopValue.current === toValue) return

// 如果动画处于播放状态,暂停动画,并且将当前值赋予给暂停值
if (status.current) translateX.stopAnimation(value => (stopValue.current = value))
// 否则播放动画
else {
Animated.timing(translateX, {
toValue,
duration: (toValue - stopValue.current) / speed,
useNativeDriver: true,
easing: Easing.linear,
}).start(({ finished }) => finished && (stopValue.current = toValue))
}

status.current = !status.current
}

useForm 警告

· 阅读需 1 分钟
1adybug
子虚伊人

在使用 Ant DesignForm 表单时,经常会遇到这个警告:

注意

Warning: Instance created by `useForm` is not connected to any Form element. Forget to pass `form` prop?

如果确定传递了 form 属性,那么一般是由于调用 form 实例的方法时,Form 表单还没有被渲染的原因,常见于 Modal 中嵌套的 Form,解决办法为监听弹窗是否被打开:

useEffect(() => {
if (!open) return
form.setFieldsValue({})
}, [open, form])

在类的原型或者函数自身声明属性

· 阅读需 2 分钟
1adybug
子虚伊人

声明 class 的 prototype 上存在某个属性

  1. 接口扩展

    interface Person {
    sayName(): void
    }

    class Person {
    constructor(public name: string) {}
    }

    Person.prototype.sayName = function sayName() {
    console.log(this.name)
    }

    const person = new Person("Tom")

    person.sayName()
  2. 声明合并

    class Person {
    constructor(public name: string) {}
    }

    interface Person {
    sayName(): void
    }

    Person.prototype.sayName = function sayName() {
    console.log(this.name)
    }

    const person = new Person("Tom")

    person.sayName()

方法 1 和方法 2 看起来似乎只是 classinterface 的顺序区别,以下是 claude 给出的解释: 主要区别:

  1. 声明顺序:

    在接口扩展中,我们先声明接口,后声明类。 在声明合并中,我们先声明类,后声明接口。

  2. 语义和意图:

    接口扩展更像是在定义一个契约,然后让类去实现这个契约。 声明合并更像是在已有的类上添加额外的属性或方法。

  3. 使用场景:

    接口扩展通常用于你预先知道类需要实现某些属性或方法时。 声明合并通常用于你想要在已有的类上添加新的属性或方法,尤其是在处理第三方库或 legacy 代码时。

  4. 灵活性:

    声明合并提供了更大的灵活性,因为你可以在代码的不同部分逐步扩展类的定义。 接口扩展要求你在一个地方定义所有的接口成员。

  5. 代码组织:

    接口扩展可能导致更清晰的代码结构,因为所有的类型信息都在一个地方。 声明合并允许你在需要的地方添加类型信息,这在某些情况下可能更方便。

实际上,这两种方法在功能上是等价的。TypeScript 编译器会以相同的方式处理它们。选择使用哪种方法主要取决于你的代码风格偏好和具体的使用场景。

在实际开发中,声明合并(第二种方法)可能更常用,特别是在需要扩展现有类或处理第三方库时。但是,如果你正在从头开始设计一个新的类,使用接口扩展(第一种方法)可能会让你的意图更加清晰。

声明函数自身存在某些属性

现在可以直接声明属性了:

const Bar = () => console.log("Hello World")

Bar.color = "red"

console.log(Bar.color)

function Foo() {
console.log("Hello World")
}

Foo.color = "red"

console.log(Foo.color)

限制类的类型

· 阅读需 1 分钟
1adybug
子虚伊人

有的时候我们希望函数的参数能够是某一种类,有两种办法限制这种参数:

  1. 使用 typeof

    class Person {
    constructor(public name: string) {}
    }

    function createPerson(constructor: typeof Person) {
    return new constructor("Tom")
    }

    const person = createPerson(Person)
  2. 使用 new

    class Person {
    constructor(public name: string) {}
    }

    function createPerson(constructor: new (name: string) => Person) {
    return new constructor("Tom")
    }

    const person = createPerson(Person)

    或者,扩展一下:

    interface Animal {
    name: string
    }

    class Person {
    constructor(public name: string) {}
    }

    function createAnimal(constructor: new (name: string) => Animal) {
    return new constructor("Tom")
    }

    const person = createAnimal(Person)

这里我们可以学到,只要在一个函数类型的前面加一个 new 关键字,便变成了构造函数

useRequest 中的 cacheTime 和 staleTime

· 阅读需 2 分钟
1adybug
子虚伊人

cacheTime 表示缓存数据回收时间。默认为 5 分钟。如果设置为 -1, 则表示缓存数据永不过期

staleTime 表示缓存数据新鲜时间。默认为 0。在该时间间隔内,认为数据是新鲜的,不会重新发请求。如果设置为 -1,则表示数据永远新鲜

cacheTime 表示数据在全局的缓存时间,即使到期被销毁了,也不影响已经加载完成的请求,它们的 data 依然是有有效数据的,不会重新请求。但是如果有新的组件(请求)产生了,此时它 data 便无法从全局的缓存中读取数据,因此初始 dataundefined,会进行一次新的请求,在请求完成后会同步更新所有相同的 cacheKeyhooksdata

staleTime 表示数据的新鲜时间,在新鲜的时间内,产生了新的请求,不会真正地去请求,而是使用全局缓存中的数据,如果全局缓存中的数据不存在,依然会进行一次真正的请求,在请求完成后会同步更新所有相同的 cacheKeyhooksdata

cacheTimestaleTime 以请求完成那个配置为准,后续的更新也不会改变