当前位置: 首页 > news >正文

ahooks.js:一款强大的React Hooks库及其API使用教程(一)

    • 一、ahooks.js简介
    • 二、ahooks.js安装
    • 三、ahooks.js API介绍与使用教程
      • 1. useRequest
      • 2. useAntdTable
      • 3. useSize
      • 4. useBoolean
      • 5. useToggle
      • 6. useHover
      • 7. useDebounce
      • 8. useEventListener
      • 9. useFusionTable
      • 10. useKeyPress
      • 11. useLoading
      • 12. usePrevious
      • 13. useForm
      • 14. useUpdate
      • 15. useUnmount
      • 16. useThrottle
      • 17. useToggle
      • 18. useUpdateEffect
      • 19. useWhyDidYouUpdate
      • 20.useInterval

一、ahooks.js简介

ahooks是一款由阿里巴巴开发团队设计的React Hooks库,提供了一系列实用的React Hooks,以便开发者更好地使用React的功能。ahooks的设计原则是“最小API,最大自由”,旨在提供最小的、最易于理解和使用的API,同时保留最大的使用自由度。

二、ahooks.js安装

使用npm或yarn安装ahooks:

npm install ahooks
# 或者
yarn add ahooks

三、ahooks.js API介绍与使用教程

1. useRequest

useRequest是ahooks中最常用的一个API,它用于处理异步请求和其相关状态。

import { useRequest } from 'ahooks';
import axios from 'axios';function App() {const { data, error, loading } = useRequest(() => axios.get('https://api.example.com'));if (loading) {return <div>Loading...</div>;}if (error) {return <div>Error: {error.message}</div>;}return <div>{data}</div>;
}

在上述代码中,useRequest接收一个函数作为参数,该函数返回一个Promise。useRequest会自动处理loading和error状态,使得你可以更专注于业务逻辑。

2. useAntdTable

useAntdTable是一个与Ant Design Table配合使用的Hook,它可以帮助你快速创建一个具有分页、排序和筛选功能的表格。

import { useAntdTable } from 'ahooks';
import { Table } from 'antd';const getTableData = ({ current, pageSize }) => {// 这是一个示例API,你应该替换为你的实际APIconst api = `https://api.example.com?page=${current}&size=${pageSize}`;return axios.get(api);
};function App() {const { tableProps, search } = useAntdTable(getTableData, { defaultPageSize: 5 });const { type, changeType, submit, reset } = search;return (<><Table {...tableProps} rowKey="id" /></>);
}

在上述代码中,useAntdTable接收一个获取表格数据的函数和一些配置作为参数。getTableData函数应该返回一个Promise,该Promise解析为{ list: [], total: 0 }的格式。

3. useSize

useSize是一个用于获取元素尺寸的Hook。

import { useSize } from 'ahooks';function App() {const [size, ref] = useSize();return (<div ref={ref}>width: {size.width}px, height: {size.height}px</div>);
}

在上述代码中,useSize返回一个数组,第一个元素是尺寸对象,第二个元素是需要测量尺寸的元素的ref。

4. useBoolean

useBoolean 是一个用于处理布尔值状态的 Hook。它返回一个数组,包含一个布尔值和一些操作这个值的函数。

import { useBoolean } from 'ahooks';function App() {const [state, { toggle, setTrue, setFalse }] = useBoolean(false);return (<div><p>{`状态: ${state}`}</p><button onClick={toggle}>切换</button><button onClick={setTrue}>设为真</button><button onClick={setFalse}>设为假</button></div>);
}

在上述代码中,useBoolean 接收一个初始值作为参数(默认为 false),返回一个数组。数组的第一个元素是当前的布尔值,第二个元素是一个对象,包含 togglesetTruesetFalse 三个函数,用于改变布尔值的状态。

5. useToggle

useToggle 是一个用于在两个值之间切换的 Hook。它返回一个数组,包含当前的值和一个切换函数。

import { useToggle } from 'ahooks';function App() {const [state, { toggle }] = useToggle('Hello', 'World');return (<div><p>{state}</p><button onClick={toggle}>切换</button></div>);
}

在上述代码中,useToggle 接收两个参数,返回一个数组。数组的第一个元素是当前的值,第二个元素是一个对象,包含一个 toggle 函数,用于在两个值之间切换。

6. useHover

useHover 是一个用于追踪元素 hover 状态的 Hook。它返回一个数组,包含当前的 hover 状态和一个 ref。

import { useHover } from 'ahooks';function App() {const [isHovering, hoverRef] = useHover();return (<div ref={hoverRef}>{isHovering ? '正在悬停' : '未悬停'}</div>);
}

在上述代码中,useHover 返回一个数组。数组的第一个元素是一个布尔值,表示当前元素是否正在被悬停,第二个元素是一个 ref,需要被赋给需要追踪 hover 状态的元素。

对不起,我理解错了你的要求。让我们按照你的要求,从第七个开始介绍,并提供代码示例和解释。

7. useDebounce

useDebounce 是一个用于防抖动操作的 Hook,它可以帮助我们控制某些频繁触发的操作。

import { useDebounce } from 'ahooks';
import { useState } from 'react';function App() {const [value, setValue] = useState('');const debouncedValue = useDebounce(value, { wait: 500 });return (<div><inputvalue={value}onChange={(e) => setValue(e.target.value)}placeholder="Typed value"style={{ width: 280, marginRight: 16 }}/><p>Debounced Value: {debouncedValue}</p></div>);
}

在上述代码中,useDebounce 接收两个参数。第一个参数是需要防抖的值,第二个参数是一个配置对象,其中的 wait 属性用于设置防抖延迟的时间。useDebounce 返回一个新的值,这个值会在指定的延迟时间后更新。

8. useEventListener

useEventListener 是一个用于添加事件监听器的 Hook。

import { useEventListener } from 'ahooks';function App() {useEventListener('click', () => {console.log('document clicked');});return <div>Hello World</div>;
}

在上述代码中,useEventListener 接收两个参数。第一个参数是需要监听的事件类型,例如 ‘click’、‘keydown’ 等。第二个参数是事件触发时的回调函数。这个 Hook 会在组件挂载时添加事件监听器,并在组件卸载时自动移除。

9. useFusionTable

useFusionTable 是一个用于处理表格数据的 Hook。

import { useFusionTable } from 'ahooks';function App() {const { tableProps, search } = useFusionTable(getTableData, {defaultPageSize: 5,});return (<Table {...tableProps} rowKey="id" />);
}

在上述代码中,useFusionTable 接收两个参数。第一个参数是一个获取表格数据的函数,第二个参数是一个配置对象,可以设置默认的页大小等。useFusionTable 返回一个对象,其中包含 tablePropssearch,分别用于控制表格的属性和搜索功能。

10. useKeyPress

useKeyPress 是一个用于监听键盘按键的 Hook。

import { useKeyPress } from 'ahooks';function App() {const isPressingA = useKeyPress('a');return <div>{isPressingA ? 'You are pressing A' : 'Not pressing A'}</div>;
}

在上述代码中,useKeyPress 接收一个参数,即需要监听的按键。它返回一个布尔值,表示该按键是否正在被按下。

11. useLoading

useLoading 是一个用于显示加载状态的 Hook。

import { useLoading } from 'ahooks';function App() {const [isLoading, { startLoading, endLoading }] = useLoading();return (<div>{isLoading ? 'Loading...' : 'Not Loading'}<button onClick={startLoading}>Start Loading</button><button onClick={endLoading}>End Loading</button></div>);
}

在上述代码中,useLoading 不需要接收任何参数,它返回一个数组。数组的第一个元素是一个布尔值,表示是否正在加载。第二个元素是一个对象,包含 startLoadingendLoading 方法,用于控制加载状态。

12. usePrevious

usePrevious 是一个用于获取前一个状态的 Hook。

import { usePrevious } from 'ahooks';
import { useState } from 'react';function App() {const [count, setCount] = useState(0);const previousCount = usePrevious(count);return (<div><p>Current Count: {count}</p><p>Previous Count: {previousCount}</p><button onClick={() => setCount(count + 1)}>Increase</button></div>);
}

在上述代码中,usePrevious 接收一个参数,即当前的状态值。它返回前一个状态的值。

13. useForm

useForm 是一个用于处理表单数据双向绑定的 Hook。

import { useForm } from 'ahooks';function App() {const { form, setForm, reset } = useForm();const handleSubmit = () => {// 处理表单提交逻辑console.log(form);};return (<div><inputtype="text"value={form.name}onChange={(e) => setForm({ ...form, name: e.target.value })}/><inputtype="text"value={form.email}onChange={(e) => setForm({ ...form, email: e.target.value })}/><button onClick={handleSubmit}>提交</button><button onClick={reset}>重置</button></div>);
}

在上述代码中,我们使用 useForm 创建一个表单对象。它返回一个包含 formsetFormreset 的对象。

  • form 是一个表示表单数据的对象,可以通过 form.nameform.email 来访问表单字段的值。
  • setForm 是一个函数,用于更新表单数据。在输入框的 onChange 事件中,我们使用 setForm 来更新相应字段的值。
  • reset 是一个函数,用于重置表单数据为初始状态。

通过使用 useForm,我们可以轻松地实现表单数据的双向绑定,并处理表单提交和重置的逻辑。

14. useUpdate

useUpdate 是一个用于强制更新组件的 Hook。

import { useUpdate } from 'ahooks';function App() {const update = useUpdate();return (<div>Current Time: {Date.now()}<button onClick={update}>Update</button></div>);
}

在上述代码中,useUpdate 不需要接收任何参数,它返回一个函数。调用这个函数可以强制更新组件。

15. useUnmount

useUnmount 是一个用于处理组件卸载时的逻辑的 Hook。

import { useUnmount } from 'ahooks';function App() {useUnmount(() => {console.log('Component is unmounting');});return <div>Hello World</div>;
}

在上述代码中,useUnmount 接收一个函数作为参数,这个函数将在组件卸载时被调用。

16. useThrottle

useThrottle 是一个用于实现节流操作的 Hook。

import { useThrottle } from 'ahooks';
import { useState } from 'react';function App() {const [value, setValue] = useState('');const throttledValue = useThrottle(value, { wait: 500 });return (<div><inputvalue={value}onChange={(e) => setValue(e.target.value)}placeholder="Typed value"style={{ width: 280, marginRight: 16 }}/><p>Throttled Value: {throttledValue}</p></div>);
}

在上述代码中,useThrottle 接收两个参数。第一个参数是需要节流的值,第二个参数是一个配置对象,其中的 wait 属性用于设置节流的时间。useThrottle 返回一个新的值,这个值会在指定的时间间隔内最多改变一次。

17. useToggle

useToggle 是一个用于切换布尔值的 Hook。

import { useToggle } from 'ahooks';function App() {const [state, { toggle }] = useToggle();return (<div><p>State: {state ? 'ON' : 'OFF'}</p><button onClick={toggle}>Toggle</button></div>);
}

在上述代码中,useToggle 返回一个数组。数组的第一个元素是当前的状态,第二个元素是一个对象,包含 toggle 方法,用于切换状态。

18. useUpdateEffect

useUpdateEffect 是一个用于处理组件更新时的逻辑的 Hook。

import { useUpdateEffect } from 'ahooks';
import { useState } from 'react';function App() {const [count, setCount] = useState(0);useUpdateEffect(() => {console.log('Count has changed');}, [count]);return (<div><p>Count: {count}</p><button onClick={() => setCount(count + 1)}>Increase</button></div>);
}

在上述代码中,useUpdateEffect 接收两个参数。第一个参数是一个函数,这个函数将在依赖项更新时被调用。第二个参数是一个依赖项数组。

19. useWhyDidYouUpdate

useWhyDidYouUpdate 是一个用于跟踪组件更新原因的 Hook。

import { useWhyDidYouUpdate } from 'ahooks';
import { useState } from 'react';function App(props) {useWhyDidYouUpdate('App', props);const [count, setCount] = useState(0);return (<div><p>Count: {count}</p><button onClick={() => setCount(count + 1)}>Increase</button></div>);
}

在上述代码中,useWhyDidYouUpdate 接收两个参数。第一个参数是组件的名字,第二个参数是组件的 props。这个 Hook 会在组件更新时打印出更新的原因。

当然,让我们继续介绍一个 API:useInterval

20.useInterval

useInterval 是一个用于设置定时器的 Hook。

import { useInterval } from 'ahooks';
import { useState } from 'react';function App() {const [count, setCount] = useState(0);useInterval(() => {setCount(count + 1);}, 1000);return <div>Count: {count}</div>;
}

在上述代码中,useInterval 接收两个参数。第一个参数是一个函数,这个函数将在指定的间隔时间被调用。第二个参数是间隔时间,单位是毫秒。

这个 Hook 会在组件挂载时开始定时器,并在组件卸载时自动清除定时器。所以你不需要手动管理定时器的生命周期。

更多关于ahooks.js的API介绍,请查看专栏:ahooks.js:一款强大的React Hooks库

相关文章:

ahooks.js:一款强大的React Hooks库及其API使用教程(一)

一、ahooks.js简介二、ahooks.js安装三、ahooks.js API介绍与使用教程1. useRequest2. useAntdTable3. useSize4. useBoolean5. useToggle6. useHover7. useDebounce8. useEventListener9. useFusionTable10. useKeyPress11. useLoading12. usePrevious13. useForm14. useUpdat…...

拟合圆算法源码(商业)

1、输入一些点 2、执行fitCircle算法 3、输出圆心(x,y)及半径r Box fitCircle(const std::vector<cv::Point2f>& points) {Box box;box.x = 0.0f;box.y = 0.0f;box.r = 0.0f;if (points.size() < 3){return box;}int i = 0;double X1 = 0;double Y1 = 0;doubl…...

第一章 IRIS 编程简介

文章目录 第一章 IRIS 编程简介简介ClassesRoutines 第一章 IRIS 编程简介 简介 IRIS 是一个高性能多模型数据平台&#xff0c;具有内置的通用编程语言 ObjectScript&#xff0c;以及对 Python 的内置支持。 IRIS 支持多进程并提供并发控制。每个进程都可以直接、高效地访问…...

Leetcode-每日一题【剑指 Offer 32 - III. 从上到下打印二叉树 III】

题目 请实现一个函数按照之字形顺序打印二叉树&#xff0c;即第一行按照从左到右的顺序打印&#xff0c;第二层按照从右到左的顺序打印&#xff0c;第三行再按照从左到右的顺序打印&#xff0c;其他行以此类推。 例如: 给定二叉树: [3,9,20,null,null,15,7], 3 / \ 9 20…...

.NET应用UI组件DevExpress XAF v23.1 - 全新的日程模块

DevExpress XAF是一款强大的现代应用程序框架&#xff0c;允许同时开发ASP.NET和WinForms。DevExpress XAF采用模块化设计&#xff0c;开发人员可以选择内建模块&#xff0c;也可以自行创建&#xff0c;从而以更快的速度和比开发人员当前更强有力的方式创建应用程序。 在新版中…...

UBuntu18.04 Qt之双HDMI屏切换

UBuntu18.04 Qt之双HDMI接2个4K屏并分别设置分辨率、主屏、副屏 一、设置HDMI-2为主屏 在main函数里面添加&#xff1a; #include "mainwindow.h" #include <QApplication>int main(int argc, char *argv[]) {QApplication a(argc, argv);{long nTotal 0;c…...

c#配置提供者

在 C# 中,配置系统是一种用于管理应用程序配置数据的机制。通常情况下,应用程序的配置数据包括连接字符串、应用程序设置、环境变量等。C# 配置系统允许您轻松地读取和使用这些配置数据,而不需要硬编码在代码中。 除了默认的配置提供者外,C# 配置系统还支持其他配置提供者…...

python rtsp 硬件解码 二

上次使用了python的opencv模块 述说了使用PyNvCodec 模块&#xff0c;这个模块本身并没有rtsp的读写&#xff0c;那么读写rtsp是可以使用很多方法的&#xff0c;我们为了输出到pytorch直接使用AI程序&#xff0c;简化rtsp 输入&#xff0c;可以直接使用ffmpeg的子进程 方法一 …...

搭载KaihongOS的工业平板、机器人、无人机等产品通过3.2版本兼容性测评,持续繁荣OpenHarmony生态

近日&#xff0c;搭载深圳开鸿数字产业发展有限公司&#xff08;简称“深开鸿”&#xff09;KaihongOS软件发行版的工业平板、机器人、无人机等商用产品均通过OpenAtom OpenHarmony&#xff08;以下简称“OpenHarmony”&#xff09;3.2 Release版本兼容性测评&#xff0c;获颁O…...

AIGC音视频工具分析和未来创新机会思考

编者按&#xff1a;相较于前两年&#xff0c;2023年音视频行业的使用量增长缓慢&#xff0c;整个音视频行业遇到瓶颈。音视频的行业从业者面临着相互竞争、不得不“卷”的状态。我们需要进行怎样的创新&#xff0c;才能从这种“卷”的状态中脱离出来&#xff1f;LiveVideoStack…...

Mybatis——返回值(resultType&resultMap)详解

之前的文章里面有对resultType和resultMap的简单介绍这一期出点详细的 resultType&#xff1a; 1&#xff0c;返回值为简单类型。 直接使用resultType“类型”&#xff0c;如string&#xff0c;Integer等。 String getEmpNameById(Integer id); <!-- 指定 result…...

多IP服务器有什么作用

1.利于搜索引擎收录&#xff1a; 使用多IP应用云服务器可使一个IP对应一个网站&#xff0c;使各个网站之间的独立性更强&#xff0c;这样搜索引擎会评定该网站质量更高&#xff0c; 更容易抓取到该网站的页面&#xff0c;便于搜索引擎收录。 2.提高网站的权重和排名&#xff…...

Python-主线程控制子线程结束

需求&#xff1a;主线程创建子线程和键盘输入监听线程&#xff0c;然后等待它们退出。当用户输入 q 后&#xff0c; 子线程会收到停止信号并退出&#xff0c;键盘输入监听线程也会退出&#xff0c;最终主线程退出。 import threading import time import keyboardclass Worker…...

水电站防雷工程综合解决方案

水电站防雷工程是指为了保护水电站的建筑物、设备和人员免受雷电危害而采取的一系列技术措施。水电站防雷工程的主要内容包括接地装置、引下线、接闪器、等电位连接、屏蔽、综合布线和电涌保护器等分项工程。水电站防雷工程的施工和质量验收应遵循国家标准《建筑物防雷工程施工…...

每日刷题(翻转+二分+BFS)

食用指南&#xff1a;本文为作者刷题中认为有必要记录的题目 ♈️今日夜电波&#xff1a;凄美地—郭顶 1:10 ━━━━━━️&#x1f49f;──────── 4:10 &#x1f504; ◀️ ⏸ ▶️ ☰…...

系统卡死问题分析

CPU模式 CPU Frequency Scaling (CPUFREQ) Introduction CPU频率调节设备驱动程序的功能。该驱动程序允许在运行过程中更改CPU的时钟频率。一旦CPU频率被更改,必要的电源供应电压也会根据设备树脚本(DTS)中定义的电压值进行变化。通过降低时钟速度,这种方法可以减少功耗…...

中大许少辉博士中国建筑出版传媒八一新书《乡村振兴战略下传统村落文化旅游设计》百度百科新闻

中大许少辉博士中国建筑出版传媒八一新书《乡村振兴战略下传统村落文化旅游设计》百度百科新闻&#xff1a; 乡村振兴战略下传统村落文化旅游设计 - 百度百科 https://baike.baidu.com/item/乡村振兴战略下传统村落文化旅游设计/62588677 概览 《乡村振兴战略下传统村落文化旅游…...

int和Integer的不同

一个奇怪的事情&#xff0c;在int[]用 Arrays.asList 转List 的时候&#xff0c;转过去的是List<int[]>。而不是List<int>类型的。于是试了String和Integer类型。发现只有Int[]有问题。 package com.test.lc;import java.util.ArrayList; import java.util.Arrays…...

eslintignore无效解决办法

项目的根目录下新建.eslintignore&#xff0c;但是无论怎么配置&#xff0c;该文件总是无法生效。本想解决不生效的问题&#xff0c;但是一直无法解决&#xff0c;于是换了一种解决问题的思路。 方法一&#xff1a; 在需要进行忽略的文件顶部加上 /* eslint-disable */这样e…...

C# 学习笔记

此笔记极水~ &#xff0c;来自两年前的库存。 是来自 B站 刘铁猛大佬 的视频&#xff0c;因为 好奇学了学。 其他 c# 变量的 内联赋值 vs. 构造函数内赋值 (引用自&#xff1a;https://www.iteye.com/blog/roomfourteen224-2208838) 上下文&#xff1a;c#中变量的内联赋值其…...

OpenSceneGraph 3.6.5 源码编译实战:从依赖配置到项目集成的完整指南

1. 环境准备&#xff1a;搭建编译OSG的基础舞台 在开始编译OpenSceneGraph 3.6.5之前&#xff0c;我们需要先搭建好开发环境。就像盖房子需要打好地基一样&#xff0c;环境配置决定了后续编译过程的顺利程度。我曾在多个项目中编译过不同版本的OSG&#xff0c;发现环境配置不当…...

Windows风扇控制终极解决方案:FanControl深度配置指南

Windows风扇控制终极解决方案&#xff1a;FanControl深度配置指南 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/GitHub_Trending/fa…...

5分钟彻底解决Windows软件DLL缺失问题:VisualCppRedist AIO完整修复方案

5分钟彻底解决Windows软件DLL缺失问题&#xff1a;VisualCppRedist AIO完整修复方案 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 你是否曾经遇到过新安装的软…...

QAbstractTableModel进阶实战:构建可编辑数据表格的完整指南

1. 从零理解QAbstractTableModel的核心机制 第一次接触Qt模型视图框架时&#xff0c;很多人会被QAbstractTableModel这个抽象类吓到。但当我真正用它完成第一个可编辑表格后&#xff0c;发现它的设计其实非常优雅。想象你正在开发一个学生管理系统&#xff0c;需要展示包含姓名…...

告别混乱:手把手教你用Python脚本整理ILSVRC2012验证集(附valprep.sh解析)

告别混乱&#xff1a;用Python脚本高效整理ILSVRC2012验证集 当你第一次打开ILSVRC2012验证集文件夹时&#xff0c;50000张图片杂乱堆放的场景可能让人头皮发麻——没有分类子目录&#xff0c;只有一堆以"ILSVRC2012_val_00000001.JPEG"命名的文件。这种原始结构与训…...

STM32+EMMC+GL3227E固件调试:从扇区偏移到数据同步的实战解析

1. 问题现象与背景分析 最近在调试一个嵌入式存储系统时遇到了奇怪的现象&#xff1a;STM32主控将数据写入EMMC存储后&#xff0c;通过GL3227E桥接芯片连接电脑却无法识别。更诡异的是&#xff0c;电脑格式化后的EMMC&#xff0c;STM32写入的数据在电脑端又"消失"了。…...

从原理到实战:使用Kali Linux进行WiFi安全渗透测试

1. WiFi安全渗透测试基础 很多人可能觉得WiFi密码破解是个神秘的黑客技术&#xff0c;其实它只是网络安全领域中一个基础的安全测试手段。作为一名安全研究员&#xff0c;我经常需要在获得授权的情况下&#xff0c;对客户的无线网络进行安全评估。Kali Linux作为专业的渗透测试…...

别再到处找激活码了!手把手教你用vlmcsd在Windows上自建KMS服务器(附各版本密钥)

企业级Windows批量激活解决方案&#xff1a;安全高效的本地KMS部署指南 在数字化办公环境中&#xff0c;批量激活Windows操作系统一直是IT管理员面临的常见挑战。传统单机激活方式效率低下&#xff0c;而依赖外部KMS服务器又存在连接不稳定、隐私泄露等潜在风险。本文将深入探讨…...

从F103到F407:老STM32玩家升级指南,详解性能差异与项目移植实战

从F103到F407&#xff1a;老STM32玩家升级指南&#xff0c;详解性能差异与项目移植实战 对于熟悉STM32F1系列开发的工程师来说&#xff0c;升级到F407系列既是一次性能跃迁的机会&#xff0c;也伴随着学习曲线和移植挑战。本文将深入剖析两款芯片的差异&#xff0c;并提供可落地…...

5分钟免费解锁iPhone激活锁:applera1n终极使用指南

5分钟免费解锁iPhone激活锁&#xff1a;applera1n终极使用指南 【免费下载链接】applera1n icloud bypass for ios 15-16 项目地址: https://gitcode.com/gh_mirrors/ap/applera1n 你是否刚入手了一部二手iPhone&#xff0c;却发现自己被困在激活锁界面无法前进&#xf…...