react课程19-ReactQuery
Last updated on September 24, 2024 pm
本节课学习能够远程管理supabase的React Query🤓
(Jonas终于能教他最爱的库了,他很激动🙉)
一·、什么是React Query
(1)特点
✅数据获取:提供简洁的 API 来发起异步请求,支持 REST API、GraphQL 等数据源。
✅缓存管理:自动缓存数据,避免不必要的网络请求,提高性能。
✅实时更新:支持实时数据更新,通过轮询、WebSocket 或后台刷新机制来保持数据的新鲜度。
✅自动重试:当请求失败时,可以自动重试请求,增强应用的稳定性。
✅查询和变更:使用 useQuery
来获取数据,使用 useMutation
来处理数据的增删改操作。
✅状态管理:提供丰富的状态管理,允许开发者轻松处理加载、错误和成功状态。
✅易于集成:可以与现有的 React 应用轻松集成,无需重构代码。
(2)与其他类似的库相比的优点
React Query 相对于其他类似库(如 Redux、Apollo Client、SWR 等)有几个独特的优势和特点:
- 数据获取与缓存分离
React Query 专注于数据获取和缓存管理,而不是全局状态管理。这使得它更加简洁和高效,专注于异步数据的流动,而不需要管理其他状态。
- 自动缓存和无缝更新
React Query 会自动缓存请求的数据,并在数据过期时自动重新获取。它提供了强大的数据同步机制,允许实时更新和后台刷新,保持数据的新鲜度。
- 轻量级的 API
与 Redux 等库相比,React Query 提供了更简单的 API。使用 useQuery
和 useMutation
进行数据操作,无需编写复杂的 reducer 和 action。
- 内置的状态管理
React Query 内置了加载、错误和成功等状态管理,无需额外处理。开发者可以轻松获取请求状态,从而更好地处理 UI 渲染。
- 自动重试和错误处理
它可以自动重试失败的请求,并允许开发者自定义重试逻辑。这种机制提高了应用的鲁棒性。
- 无缝集成
React Query 可以与任何后端 API(REST、GraphQL 等)无缝集成,无需特定的配置。这种灵活性使得它适用于各种项目。
- 支持服务端渲染(SSR)
React Query 提供了服务端渲染支持,允许在服务器端预取数据,并在客户端使用,提升页面加载性能。
- 开箱即用的 DevTools
React Query 提供了开发者工具,方便查看当前的查询状态、缓存和数据。这对于调试和优化应用非常有帮助。
二、set up React Query✅
npm i date-fns
日期功能(跟视频不一样了,导入方式,问GPT吧)
(1)安装配置
⭕npm i @tanstack/react-query@4
https://tanstack.com/query/v3
(23年是v4,现在已经是v5了🙉)
⭕npm i @tanstack/react-query-devtools@4
安装Dev Tool(这次不用浏览器内安装)
(2)初步使用
在App.js中写入const queryClient = new QueryClient({(可选项)})
用<QueryClientProvider client={queryClient}>
包裹整个App
子组件中写入<ReactQueryDevtools initialIsOpen={false} />
(3)示例
获取数据的方式:
1 |
|
设置数据由fresh到stale的时间:(在App.js)
1 |
|
getCabins:
(从API Docs中查看相应代码)(记得修改相应Policy权限)
1 |
|
三、mutating(变异)数据
当在supabase中修改数据后,网站上加载出的表单会跟随staleTime改变。
但是如果是远程修改数据呢?(比如删除,如下所示)
(1)删除方程(放置在apiCabins中)
修改了eq()中的内容,可以和Docs中的代码做照应
1 |
|
第一个值 'id'
:
- 这个值是数据库表中的列名,表示你要匹配的字段。在这里,它指的是小屋表中的
id
列。
第二个值 id
:
- 这个值是你想删除的具体记录的唯一标识符。它是从传入的对象中解构出来的
id
值,代表要删除的小屋的 ID。
(2)连接删除组件
要点解析在代码注释中:
1 |
|
(3)装饰通知(React Toast)
⭕npm i react-hot-toast
https://react-hot-toast.com/(好可爱的界面hhh)
1、首先:(先去查看官方文档)在App.js的架构中,在子组件的最后写上:
(烤面包机当通知也太好笑了吧hhhh)
1 |
|
2、使用
1 |
|
四、React Hook Form
⭕npm i react-hook-form@7
(1)入门
非常好的库,使提交表格方便。
1、const { register, handleSubmit } = useForm();
- **
register
**:这是一个函数,用于将表单字段注册到 React Hook Form 中,以便它能够跟踪其状态和验证。 - **
handleSubmit
**:这是一个函数,用于处理表单提交事件。当用户提交表单时,它会调用你提供的提交处理函数。
2、在input字段中加入register:
1 |
|
3、添加提交函数
这是我们自己创建的Form组件,添加onSubmit函数:
1 |
|
这里是onSubmit函数:(然后表格里的数据就完美出现了)
1 |
|
下面是被register后的input字段的prop,多了不一样的东西。
4、使用Form创建新的Cabin
1 |
|
其实没啥特别的,注意表单的内容和cabin的结构一致就行。
(2)表单的错误处理
”React Hook Form最闪亮耀眼的地方是表单错误验证“
1、
先看看我们通过useForm获得了什么:
⭐const { register, handleSubmit, reset, getValues, formState } = useForm();
- **
reset
**:用于重置表单的值到初始状态,通常在提交成功后调用。 - **
getValues
**:获取当前表单字段的值,可以在需要时获取字段的最新值。 - **
formState
**:包含表单的状态信息,如isSubmitting
、isValid
和errors
等,方便进行表单状态的管理和展示。
于是……
- 可以在onSuccess的回调函数中添加 reset(),清空表单
- const { errors } = formState;来获得错误信息以显示在ui界面,Form的onSubmit函数中添加onError:
<Form onSubmit={handleSubmit(onSubmit, onError)}>
,如果表单提交不成功就会调用onError函数,它的参数是errors
2、表单验证
验证内容在register中添加,例如
- maxCapacity表单:
1 |
|
- discount表单:(validate关键字:自定义验证)✅应用getValues函数访问表内数据
1 |
|
五、上传图片到supabase
⭕https://supabase.com/docs/reference/javascript/storage-from-upload访问此网站找寻详细信息
首先需要设置rlp(Policy)
(1)建立函数
1、得到图片名和图片路径
1 |
|
使用 Math.random()
生成随机数,以防止图片名称冲突,并将其与原图片名称拼接。replaceAll
用于去除 /
字符。
1 |
|
supabaseUrl
是在supabase.js
中定义的。至于路径,我们在前面创建了两个Buket,其中一个是cabin-images,存储cabin图像,从那里复制而来。
2、create cabin
1 |
|
❓❓在这段代码中,将 image
单独列出来的原因主要有以下几点:
存储路径的动态性:
- 图片的路径是通过生成随机名称和构造完整 URL 动态创建的。在插入到数据库之前,路径需要先确定。因此,在插入数据时,使用生成的
imagePath
作为字段的一部分。
- 图片的路径是通过生成随机名称和构造完整 URL 动态创建的。在插入到数据库之前,路径需要先确定。因此,在插入数据时,使用生成的
解耦数据逻辑:
- 将
image
单独列出可以清晰地分隔小屋的基本信息和图片信息,使数据结构更清晰。在数据表中,通常不直接存储文件的内容,而是存储文件的 URL 或路径。
- 将
确保数据完整性:
- 在创建小屋的过程中,首先插入小屋信息,再上传图片。如果上传失败,可以根据存储的
id
删除对应的小屋记录。这种分步骤处理的方式使得在发生错误时能够更容易地回滚操作。
- 在创建小屋的过程中,首先插入小屋信息,再上传图片。如果上传失败,可以根据存储的
方便后续处理:
- 如果将
image
与其他小屋信息合并在一起,后续处理时可能会变得复杂。例如,如果需要仅更新或删除图片,单独存储可以更加方便。
- 如果将
3、upload image
1 |
|
4、Delete the cabin if there was an error uploading image
1 |
|
(2)修改submit函数
1 |
|
六、修改cabin信息
改bug改的想鼠。。。💀💀💀💀💀💀(把Jonas没找到的bug找出来了,我真棒)
这一部分有点儿难理解,让我仔细地捋一下。
(1)传递表单原有信息
在修改信息的时候,很明显我们是需要原有的信息的,然后在此基础上加以修改。由于这个修改信息的表单应该和创建信息的表单一样,所以我们可以偷懒直接借用。于是在CabinRow这个组件里,我们可以添加一个按钮:edit
,onClick函数控制showForm状态变量,然后就可以在cabinRow的要修改的那一行下面显示CreateCabinForm组件了。(传递给它cabin的所有信息)
1 |
|
(2)改变CreateCabinForm
1、接收原有的cabin信息
组件函数接收object prop:{ cabinToEdit = {} }
={}
是给它一个default值,因为创建cabin的时候没有传递cabinToEdit,所以就是空。
2、运用原有的cabin信息
- 首先进行拆解:
const { id: editId, ...editValues } = cabinToEdit;
这里把id重命名为editId是为了下面传递参数更加方便。
- 然后定义变量:
const isEditSession = Boolean(editId);
没有editId说明是创建cabin,这个变量决定了在mutate数据的时候究竟是创建cabin还是修改cabin,因为两个动作接受的参数也不愿意。
- 接着给予表单defaultValues:
const { register, handleSubmit, reset, getValues, formState } = useForm({defaultValues: isEditSession ? editValues : {}, });
这里也比较好理解,如果不是编辑默认值就为空。
3、创建mutate
这里跟createCabin的逻辑很像,只是改变了名字和接收的参数而已。
mutationFn: ({ newCabinData, id }) => createEditCabin(newCabinData, id)
其中这一行着重看。
(⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️请注意请注意这里onSuccess后不能reset()!!!否则image就变成空(undefined)的了,不知道为什么,其他数据都没问题。。。)
判断失误,汗流浃背了,原来只是因为api里那个函数写错了,有个小bug,startsWith而不是startWith。。。真的汗流浃背了,我就说Jonas那里怎么没问题。。但是我真的很奇怪为什么在onSubmit函数里console.log(image)为什么是undifined。。
1 |
|
4、修改表单disabled逻辑
const isWorking = isCreating || isEditing;
用新变量处理
5、修改onSubmit函数
如果图片是新上传的,那么它应该是一个对象,我们需要得到得到的是它的名字来给它建立url,如果是旧的信息,那么它应该是string,直接返回它本身就好。
这里两个函数的参数不太一样,可以观察一下。
1 |
|
(3)创建、修改Cabin函数合二为一
从上到下讲一下要点:
1、通过一个很巧妙的函数来判定图片需不需要重新创建Url
接收到传过来的数据时,如果image是旧的,那么它的开头应该是supabaseUrl,所以通过这个来判断是沿用这个image还是通过它新建Url。
2、if()条件语句下,需要换一种方式接收数据
await supabase.from('cabins')
本身是一个查询操作,返回的是一个查询对象。如果直接在 if
语句中使用它,可能导致控制流不清晰。你需要先构建查询,再执行它。所以我们在这里先创建了query来接收数据
1 |
|
七、复制cabins
(1)创建custom hook
一共创建了四个:
- useCabins:得到cabin数据
- useCreateCabin:得到创建cabin的函数
- useDeleteCabin:得到删除cabin的函数
- useEditCabin:得到修改cabin的函数
reset()函数不能放在这些文件里,但是可以放在onSubmit中,在调用mutate函数时通过onSuccess当作参数传递
(2)复制Cabin
只需要创建一个新的按钮,把onClick函数写为creatCabin,传递的参数就是cabinRow中的cabin信息,把名字更改为copy of ${name}
即可。
八、修改设置
由于只有一行设置,因此对它的改变比较简单。下面只讲一下要点。
下面是其中一个修改框:
1 |
|
在 onBlur
(失去焦点)时触发 handleUpdate
,并将用户输入的值与字段名 'minBookingLength'
一起传递给 handleUpdate
函数。
下面是handleUpdate函数:
1 |
|
九、零碎
(1)滚动条
1、使得sidebar固定,只有main的部分滑动
在Main的样式中加入: overflow: scroll;
2、隐藏全局滚动条(真的很丑)
在GlobalStyles.js
中找到/新建一个body元素,写入: overflow: hidden;
(2)children的prop也可以访问
例如我们会创建cabin表新建的可重用组件:
1 |
|
(3)连接起来的两个表单不能直接删除元素哦
{ “code”: “23503”, “details”: “Key is still referenced from table "booking".”, “hint”: null, “message”: “update or delete on table "cabins" violates foreign key constraint "booking_cabinId_fkey" on table "booking"“ }
想要删除一个cabin的时候报错了,因为它和另一张表单(booking)的内容连接起来了。
9/24:终于完成这一节了呜呜呜呜呜,真不容易😭😭😭😭😭😭😭😭😭😭😭😭😭😭