最近在项目中做多情况下的触发接口请求(按钮触发,实时触发等)的最佳代码实践整理时,发现为了处理 清空条件->重新搜索 这个情况,会增加很多代码,让可读性变得很差。那是否有什么办法能对其进行优化呢?
先看问题代码
来一段最常见的列表请求展示代码
import React, { useState, useEffect } form 'react'
const example = () => {
const [ search, setSearch ] = useState('')
const [ list, setList ] = useState([])
useEffect(() => {
getList()
}, [])
const getList = async () => {
const data = await ajax({
search,
}) // 请求方法
setList(data)
}
cosnt clear = () => {
setSearch('')
getList()
}
return (
<div>
<input value={search} (e) => setSearch(e.target.value) />
<button onClick="getList">搜索</button>
<button onClick="clear">清空</button>
</div>
)
}
这里我们可以看到在input内输入内容后点击搜索,请求的方法内的获取的search的参数是最新的。
这时我需要在页面上加个清空条件重新搜索的功能,逻辑就是清空搜索条件setSearch('')然后调用请求方法getList()。
我们知道因为setSearch是异步的且React hook的闭包问题不能直接写
cosnt clear = () => {
setSearch('')
getList()
}
return (
<div>
...
<button onClick="clear">清空</button>
</div>
)
这时请求里的search还是原来的数据。
解决
遇上上述问题,我们当然可以通过useRef先对搜索条件的字段进行储存,每次更新state是同时修改useRef的值,但这肯定会增加代码量,降低代码的可读性。
或者只要能解决异步和闭包问题。
hook中如何判断state已修改, 且解决闭包问题
通过useEffect我们可以知道state的变化
const ready = useRef(false) // 用来标记第一次不执行
/** search更新后会重新渲染,getList方法会重新生成个实例,新的实例内的state将会是最新 */
useEffect(() => {
if (ready.current) {
getList()
} else {
ready.current = true
}
}, [search])
上面的方法就能在setSearch('')然后search更新调用请求方法getList()
然而如果这样写,会有两个问题。
1、会有其地方使用setSearch但并不希望重新getList
2、setSearch('')和getList()代码编写的位置分离,难以阅读。
模拟同步
React中一般条件下(async/await、Promise除外)多个setState放到了一个队列里面,等待事件结束在执行。
所以在一个方法内我们就写多个setState,然后设置一个state作为useEffect绑定监听参数。
这里我们定义个flag作为监听参数
const ready = useRef(false)
const [flag, setFlag] = useState([]); // 设置[],之后再setFlag([]),由于数组的对应指针不同会被判断成新值触发重新渲染。
useEffect(() => {
if (ready.current) {
getList()
} else {
ready.current = true
}
}, [flag])
这样我们就解决上上面的问题1。
接下来的问题2就好解决,我们封装个hook,入参为要执行的方法。
看代码
/**
* @param callbacks:一个返回值为'函数数组'的函数 (...args) => [function]
*/
const useQueueFn = (callbacks) => {
const ready = useRef(false)
const params = useRef([]) // 记录参数
const index = useRef(0); // 执行标记位置
const [flag, setFlag] = useState([]);
useEffect(() => {
if (ready.current) {
execute()
} else {
ready.current = true
}
}, [flag]);
const init = useCallback(
(...args) => {
params.current = args;
index.current = 0;
execute();
},
[],
);
const execute = () => {
const fnArr = callbacks(...params.current); // 每次调用重新获取参数实例
fnArr[index.current]();
if (index.current < fnArr.length - 1) {
index.current++;
setFlag([]);
}
};
return init;
}
useQueueFn使用
const clear = useQueueFn(() => ([
() => {
setSearch('');
... // 其他setState
},
() => {
getList();
},
]));
return (
<div>
...
<button onClick="clear">清空</button>
</div>
)
这样就完美了解决了上面的问题。