react-hook中setTimeout、useEffect执行顺序与数据矛盾

#1

react-hook中setTimeout、useEffect执行顺序与数据矛盾

setTimeout、useEffect执行顺序

情况1:

const App = () => {
    const [count, setCount] = useState(0)
    const [total, setTotal] = useState(0)

    const sayVar = useCallback((tag = '') => {
        console.log('sayVar',tag,  count, total)
    }, [count, total])


    const reset = useCallback(() => {
        setCount(1)
        setTotal(1)//情况1:
        setTimeout(() => {
            console.log('setTimeout', count, total) //2 0 0
            sayVar('setTimeout') //3 0 0 
        }, 0)
        sayVar()// 1 0 0
    }, [count, total, sayVar])
    useEffect(() => {
        console.log('useEffect', count, total) //4 1 1
    }, [count, total])
    return (
        <div>
            <button onClick={reset}>reset</button>{count}{total}
        </div>
    )
}
执行结果顺序
1. sayVar  0 0
2. setTimeout 0 0
3. sayVar setTimeout 0 0
4. useEffect 1 1
结论:执行顺序setTimeout > useEffect

情况2:

const App = () => {
    const [count, setCount] = useState(0)
    const [total, setTotal] = useState(0)

    const sayVar = useCallback((tag = '') => {
        console.log('sayVar',tag,  count, total)
    }, [count, total])


    const reset = useCallback(() => {
        setCount(1)
        setTimeout(() => {
            setTotal(1)//情况2:
            console.log('setTimeout', count, total) //3 0 0
            sayVar('setTimeout') //4 0 0
        }, 0)
        sayVar() //1 0 0
    }, [count, total, sayVar])
    useEffect(() => {
        console.log('useEffect', count, total) //2 1 0 //5 1 1
    }, [count, total])
    return (
        <div>
            <button onClick={reset}>reset</button>{count}{total}
        </div>
    )
}
执行结果顺序
1. sayVar  0 0
2. useEffect 1 0
3. setTimeout 0 0
4. sayVar setTimeout 0 0
5. useEffect 1 1
结论:执行顺序setTimeout < useEffect 或者 setTimeout < useEffect

问题:为什么两端代码到处了一个很矛盾的结论: 执行顺序setTimeout < useEffect 或者 setTimeout < useEffect;这个结论该如何解释?

数据矛盾

情况:

需求:
    列表展示页:分两部分 1.上面过滤(tab,搜索)2.下面数据(表格,翻页器)
    tab、搜索、翻页器都能拿到对应的数据,点击搜索时将tab、翻页器重置为初始
const App = () => {
    const [type, setType] = useState(0)
    const [currentPage, setCurrentPage] = useState(0)
    const [searchValue, setSearchValue] = useState('')

    const doRequest = useCallback(() => {
        //接口设计如此
        api.post('/api/xxx', {
            type: type,
            currentPage: currentPage,
            searchValue: searchValue 
        }).then(res => { /*...*/ })
    }, [type, currentPage, searchValue])

    //切换类型
    const tabChange = useCallback((v) => {
        setTypev)
    }, [])
    useEffect(() => {
        doRequest()
    }, [type])

    // 为什么不用这种useEffect了?
    // useEffect(() => {
    //     doRequest()
    // }, [doRequest])

    //翻页
    const pageChange = useCallback((v) => {
        setCurrentPage(v)
    }, [])
    useEffect(() => {
        doRequest()
    }, [currentPage])

    
    //搜索
    const doSearch = useCallback((v) => {
        //请求之前将type、currentPage重置
        setType(0)
        setCurrentPage(0)
        doRequest()//问题:doRequest中如何能拿到type=0、currentPage=0,或者说这段代码本身设计上存在问题如何写一个语义更清晰的结构了?
        
    }, [doRequest])
    return (
        <div>
            <input value={searchValue} onChange={setSearchValue} onClick={doSearch}/>
            <Pagination currentPage={currentPage} onChange={pageChange}/>
            <Tab type={type} onChange={tabChange}>
        </div>
    )
}

问题:doRequest依赖好几个变量,其中一部分变量的改变必须导致其他变量重置,场景就是:两tab切换的无限加载,tab切换(type)必然会导致当前页(currentPage)重置,在我司接口设计上他们是同一个接口的不同字段理应只用一个doRequest,

    const doRequest = useCallback(() => {
        request.post({
            type,
            currentPage
        })
    }, [type, currentPage])

    useEffect(() => {
        setCurrentPage(0)
    }, [type])
    useEffect(() => {
        doRequest()
    }, [doRequest])
但存在一个问题以上代码可能会有两个或者两个以上的请求发出(你并不能保证请求返回的先后顺序)
那把代码改成下面这样试试:
    const doRequest = useCallback(() => {
        request.post({
            type,
            currentPage
        })
    }, [type, currentPage])
    useEffect(() => {
        setCurrentPage(0)
        doRequest()
    }, [type])
以上代码虽然只发一个请求出去,但不能做到doRequest中的currentPage=0
如果用Class component;问题很好解决
    doRequest = () => {
        request.post({
            type: this.state.type,
            currentPage: this.state.currentPage
        })
    }
    changeType = (v) => {
        this.setState({
            currentPage: 0,
            type: v
        }, () => {
            this.doRequest()
        })
    }
在react-hook中type、currentPage在接口上是统一的但是在逻辑上是对立的,那如何用hook解决上面所描述的问题了?

最后求解答 特别是第二个问题 谢谢了!!!

#2

第一个问题不太清楚你要表达啥,useEffect用的是Filber, 在执行一批修改后才调用

const [val, setVal] = useState(0)
useEffect(()=> {
  setVal(1);
  setTimeout(()=> setVal(4),0);
  Promise.resolve(3).then(setVal);
  setVal(2);
}, []);
useEffect(()=> {
   console.log("val", val);
}, [val]);
// console 最后显示
// val0
// val2
// val3
// val4

第二个,

  1. 用useEffect来解决, 这个方法的主要思路就是用一个useEffect来监听和请求相关的所有变量,一旦和请求有关的参数发生变化就自动生成一个新的请求,并让之前的请求失效。
const [type, setType] = useState(0);
const [currentPage, setCurrentPage] = useState(0);
const [data, setData] = useState(null);
const useEffect(()=> {
    // 没有doRequest这个事件, 用 useEffect来监听参数变化达到自动请求
    // 如果 cancel = true, 就表示已经被取消了, 事实一些ajax的库提供了终端请求的借口, 可以直接 xhr.abort() 来中断请求
    let canceled = false;
    request.post({ type, currentPage }).then((v)=> {
       if (!canceled) {
           setData(v);
       }
    })
   return ()=> {
     canceled = true;
   }
}, [type, currentPage]);
const onClickTab = useCallback((tab)=> {
    setType(tab);
    setCurrentPage(0);
}, []);
  1. 用 useRef来解决,在 ref中保存一个请求的计数器, 在返回的时候看看计数器是否被更新
const ref = useRef(0);
const [data, setData] = useState(null);
const doRequest = useCallback(()=> {
    const index = ++ref.current;
    request({}).then(()=> {
       if (index === ref.current){
           // 如果是最后一次请求, index === ref.current
       }
    })
}, [])