缺少必要的请求参数: id_React Hooks 第二期:发请求这件小事
距离上一篇讨论 hooks 的文章已经过了半年了,因为越使用就越发现这个坑不浅。“这段代码写得不好,为什么要放 hooks 很难让人理解。”是我主管在 review 了我使用 hooks 的代码之后说的第一句话。就连发请求这件最简单的事情,我都很难讲清楚为什么要使用 hooks。关于 React Hooks 也看了不少文章,但是始终没找到关于"最简单"的 CRUD 日常实践。所以这期我就抛砖迎玉,说说我目前的粗浅理解。
问题
假设有个最简单的需求,MovieList
页面需要从服务端获取数据并展示。这里先抛出几个问题:
- 请求的方法写在哪里?请求来的数据放在哪里?
- 如果请求还未完成,组件已被 Unmount,有没有做特殊处理?
- 请求的参数来自哪里,参数变化会触发重新请求吗?调用写在哪里?
- 请求对应的几个状态: loading,refreshing,error 在组件上处理了么?
- 如果请求需要轮询,怎么做?有没有处理页面 visibilitychange 的情况?
- 如果需要在参数变化后重新请求,如果参数频繁更新,会出现竞态(旧的请求因为慢,晚于后发的请求 resolve)的问题吗?
这些都是我觉得在 Class Component 里写“过程式”代码较难解决的问题。有的问题即使使用了 redux 依然没法很方便的处理。所以之前我很喜欢 Apollo 这种把异步数据放在 HOC 自动处理的解决方法。顺便说一下,Apollo 新版 API 推荐的 renderProps 明显更加难用了... 当然 hooks 的版本也已经 beta 了。但当把逻辑彻底放在 HOC 或者 renderProps 里时,如果一个组件依赖于多个请求并且它们之间又有纠缠时,不是特别方便处理。这是因为 HOC 或 renderProps 莫名的建立起了一个本不存在的层级和作用域关系。
那么在 Hooks 里是怎么样的呢?
useData ?
function useData(dataLoader, params) {const [loading, setLoading] = useState(false);const [error, setError] = useState(undefined);const [data, setData] = useState(undefined);useEffect(() => {dataLoader().then((responseData) => {// ...setData(responseData);});}, [dataLoader, ...params]);return { data, loading, error };
}function MovieList({ page, size }) {const {data: movieListData,loading,error} = useData(() => api.queryMovieList({ page, size }),[page, size]);// ...
}
第一个想到的是这样一个 Hook,类似于 useEffect,传入获取请求数据的 dataLoader
,以及这个数据源依的请求参数 page
和 size
。这样看似没问题,但实际上 useEffect 的 dependencies [dataLoader, ...params]
无法满足 hook 要求的静态依赖,因为 useData
内部无法判断 ...params
里具体有哪些值。上一章我说 useCallback 一般用于性能,没有必要使用。这里就可以看出来其实说错了...
function useData(dataLoader) {// ...useEffect(() => {dataLoader().then((responseData) => {// ...setData(responseData);});}, [dataLoader);// ...
}function MovieList({ page, size }}) {const queryMovieList = useCallback((page, size) => api.queryMovieList({ page, size }), [page, size]);const { data: movieListData, loading, error } = useData(queryMovieList);// ...
}
这里我们利用 useCallback 将 queryMovieList 和它的参数 page,size 绑定起来。只有在 page 或 size 变化时,queryMovieList 才会更新,然后自动触发 useData 内部的请求逻辑。这样的写法,虽然在组件里多了一次 useCallback 的调用,但是更利于 hook 的静态分析。
有了 useData
这样的封装,得益于 hooks 拥有的完整组件生命周期控制,我们可以很容易的将之前提到的组件卸载处理,请求竞态等等的问题,封装在 hooks 中。这里以竞态的问题为例,大致介绍一下可能的方法:
function useData(dataLoader) {const currentDataLoader = useRef(null);// ...useEffect(() => {currentDataGetter.current = dataGetter;dataLoader().then((responseData) => {// ...// 如果有更新的请求,放弃之前的if (currentDataGetter.current !== dataGetter) {return;}setData(responseData);});}, [dataLoader);// ...
}
只需要利用 ref 记录下最新的一个 dataLoader,那么在请求 resolve 时就可以判断出是否是过时的数据了。当然以上的代码只是一个示例,还有更多可以调整优化的地方。
处理异步状态
以上的 useData 只能解决关于数据获取的那一部分问题,为了处理我们得到的几个状态,不免需要写出这样的代码:
function MovieList({ page, size }}) {const queryMovieList = useCallback((page, size) => api.queryMovieList({ page, size }), [page, size]);const { data: movieListData, loading, error } = useData(queryMovieList);if (loading && data == null) {return <Spin />}if (error) {return <Exception />}return (<Spin loading={loading}>{renderMovieList(movieListData)}</Spin>);function renderMovieList(movieList) {return movieList.map(item => <MovieItem key={item.id} data={item} />)}
}
久而久之我们会发现在所有使用这个 useData 的组件中,我们都避免不了手写这两个 if
。但是 hooks 只能解决生命周期的问题,没法封装一些 render 的逻辑。其实这里最有效的解决方法就是 Suspense,但是因为还没有发布,所以我们想到了 renderProps:
function DataBoundary({ data, loading, error, children }) {if (loading) {return <Spin />;}// ...return <Spin loading={loading}>{children(data)}</Spin>;
}function MovieList({ page, size }}) {const queryMovieList = useCallback((page, size) => api.queryMovieList({ page, size }), [page, size]);const movieListResult = useData(queryMovieList);// const { data: movieListData, loading, error } = useData(queryMovieList);return (<DataBoundary {...movieListResult}>// <DataBoundary data={movieListData} loading={loading} error={error}>{(data) => renderMovieList(data)}</DataBoundary>);function renderMovieList(movieList) {return movieList.map(item => <MovieItem key={item.id} data={item} />)}
}
在 DataBoundary
里,我们封装了关于异步状态处理的渲染流程,还可以提供出类似 fallback 的钩子,满足需要定制化的组件场景。用它做到了类似于 Suspense 的事情。
All in Hooks?
但是如果我们真的想在 hooks 里完成所有的事情呢?这里再抛出一个彩蛋:
function useDataBoundary(dataLoader) {// ...function boundary(renderChildren) {if (loading && data == null) {return <Spin />;}// ...return renderChildren(data);}return boundary;
}function MovieList({ page, size }}) {const queryMovieList = useCallback((page, size) => api.queryMovieList({ page, size }), [page, size]);const boundary = useDataBoundary(queryMovieList);return (<div>{boundary((data) => renderMovieList(data))}</div>);function renderMovieList(movieList) {return movieList.map(item => <MovieItem key={item.id} data={item} />)}
}
如果我们可以在 hooks 中返回一个 "renderProp",我们就可以完整的将 render 相关的逻辑也封装在 hooks 里了,但是同时这样也会使得一个 hook 里的代码变得复杂,这里只是提供一种思路。那么这两种写法你更喜欢那个呢?
尾巴
这篇文章大致说明了在使用 hooks 写业务逻辑时的一点思考。很多地方没有详细展开,大家如果有兴趣的话可以再深入讨论。至少在最开始提出的几个问题上,hooks 帮我解决了这些原本没有地方安置的抽象逻辑。所以虽然我还是经常遇到 deps 没考虑清楚导致死循环的请求,我依然觉得 hooks 值得一试~
缺少必要的请求参数: id_React Hooks 第二期:发请求这件小事相关推荐
- Android添加Header请求参数实例,java响应header请求实现demo
1.首先添加AsyncHttpClient.jar包到libs文件夹下 2.初始化请求类以及响应回调类 private AsyncHttpClient client; private AsyncHtt ...
- python编写请求参数带文件_python requests 库请求带有文件参数的接口实例
有些接口参数是一个文件格式,比如fiddler 抓包参数如下显示 这个接口的 form-data fiddler 显示的和不带文件参数的接口有明显区别,显示的不是简单的键值对,所以我们也不能只通过 d ...
- 获取请求参数通用方式|| 中文乱码问题||请求转发
1. 获取请求参数通用方式:不论get还是post请求方式都可以使用下列方法来获取请求参数 1. String getParameter(String name):根据参数名称获取参数值 use ...
- gin ajax 获取请求参数,go的gin框架从请求中获取参数的方法
前言: go语言的gin框架go里面比较好的一个web框架, github的start数超过了18000.可见此框架的可信度 如何获取请求中的参数 假如有这么一个请求: POST /post/te ...
- Java请求参数检查,Java如何检查servlet请求中是否存在参数?
ServletRequest或HttpServletRequest对象具有用于映射参数名称及其值的映射对象.通过访问此映射,我们可以检查servlet请求中是否传递了参数.让我们看下面的例子.pack ...
- android volley 请求参数,android – Volley – 如何发送DELETE请求参数?
同样的问题在这里,但我找到了解决方案 问题是在com.android.volley.toolbox.HttpClientStack.java中实现createHttpRequest方法,只有在请求方法 ...
- 获取referer中的请求参数_javaweb之request获取referer请求头实现防盗链
在开发web程序的时候,有时我们需要得到用户是从什么页面连过来的,这就用到了referer. 它是http协议,所以任何能开发web程序的语言都可以实现,比如jsp中是: request.getHea ...
- php接收不到ajax请求参数,我是否需要在ajax请求和接收该请求的php之间编码/解码查询参数?...
人们,如果你不是绝对确定你在做什么,请不要使用这个代码 此代码受到灾难性安全漏洞的影响,因此除非您绝对确定没有邪恶的人会访问它,否则请不要使用它. 发送: jQuery.ajax({ type: 'p ...
- html action get post请求参数乱码,input type=text 发送请求参数,中文乱码问题
1.不废话,直接代码如下: teacher.jsp pageEncoding="UTF-8"%> 教师管理页面 课程资源目录: --------------- 作业提交目录: ...
最新文章
- 蚂蚁金服蓝绿发布实践
- LibTorch NMS
- ajax可以在localhost上用吗_你还不知道跨域问题是怎样造成的吗?
- [转载]Office Visio快捷键
- EOS账户系统(8)密钥被盗恢复
- 软件项目管理0718:读一页项目管理读书笔记
- python类及其方法
- 云上自动化:云上编排让上云更简单
- 如何提升你的javascript代码逼格之简写篇
- 电流电压曲线 vc源码_电瓶修复—充电曲线你知道多少?
- C++的类型转换操作符
- 诺基亚n1系统更新显示无网络_曾经世界第一大手机系统,诺基亚塞班系统竟还活着!你用过吗?...
- java red5 流媒体服务_[Red5]Red5之Flash流媒体服务器的安装与使用教程完整版(组图)...
- java如何获取全部省市_纯java获取省市区
- 用PS将照片背景变成白色
- 最新个人所得税计算方法
- 微信小程序/小游戏运行环境小结
- 泼冷水!为什么说机器学习在很多方面被高估了? | 精选
- 微信公众平台开发——引言
- UE Gameplay实例49(高级蒙太奇动画连招)