const getApi = async () => { const res = await fetch('https://api.chucknorris.io/jokes/random') return res.json() }然后和前面的案例一样,我们将每次点击的 api 作为状态存储起来,通过 api 的改变来触发更新的执行。
const [api, setApi] = useState(null)与此同时,我们还需要一个数组作为状态来管理列表。
const [list, setList] = useState([])有了这个数组之后,我们需要遍历这个数组渲染成 UI
<div className="list"> {list.map((item, index) => { return <div className='item' key={item}>{item}</div> })} </div>最后需要 loading 显示的部分,我们使用 Suspense 来完成。
<Suspense fallback={<div>loading...</div>}> <Item api={api} setList={setList} /> </Suspense>需要注意的是,我们这里把 setList 传递进入了子组件。这个细节需要仔细思考我的动因。
const Item = ({api, setList}) => { const [show, setShow] = useState(true) const joke = api ? use(api) : {value: 'nothing'} useEffect(() => { if (!api) return setList((list) => { if (!list.includes(joke.value)) { return list.concat(joke.value) } return list }) setShow(false) }, []) const __cls = show ? '_03_a_value show' : '_03_a_value' return ( <div className={__cls}>{joke.value}</div> ) }状态 show 是为了让最后一条数据在列表中显示,而不在这里显示。这里我们使用了 useEffect 来表示子组件渲染完成时需要执行的逻辑。注意 React 19 虽然通过很多方式大幅度弱化了 useEffect 的存在感,但是偶尔在合适的时候使用也是必要的。
setList((list) => { if (!list.includes(joke.value)) { return list.concat(joke.value) } return list })这个细节在真实项目开发中尤其重要。因为 React 19 严格模式之下,组件会让 useEffect 执行两次,以模拟生产环境的重复请求问题,因此,我这里做了一个判断方式同样的数据连续推送到数组里,从而导致线上 bug 的发生。一个程序员是否经验丰富,是否成熟,都是体现在这些生产环境的细节中。
const getApi = async () => { const res = await fetch('https://api.chucknorris.io/jokes/random') return res.json() } export default function Index() { const [api, setApi] = useState(null) const [list, setList] = useState([]) function __clickToGetMessage() { setApi(getApi()) } return ( <div> <div id='tips'>点击按钮新增一条数据,该数据从接口中获取</div> <button onClick={__clickToGetMessage}>新增数据</button> <div className="content"> <div className="list"> {list.map((item, index) => { return <div className='item' key={item}>{item}</div> })} </div> <Suspense fallback={<div>loading...</div>}> <Item api={api} setList={setList} /> </Suspense> </div> </div> ) } const Item = ({api, setList}) => { const [show, setShow] = useState(true) const joke = api ? use(api) : {value: 'nothing'} useEffect(() => { if (!api) return setList((list) => { if (!list.includes(joke.value)) { return list.concat(joke.value) } return list }) setShow(false) }, []) const __cls = show ? '_03_a_value show' : '_03_a_value' return ( <div className={__cls}>{joke.value}</div> ) }这样之后,我们的目标基本就完成了。接下来,我们需要观察,当我恶意重复点击按钮,会发生什么事情。
const [disabled, setDisabled] = useState(false)并将其传递给按钮 button 组件的 disabled 属性。
<button disabled={disabled} onClick={__clickToGetMessage} >新增数据</button>点击时,我们将其设置为 true,此时一个新的请求会发生
function __clickToGetMessage() { setDisabled(true); setApi(getApi()) }请求成功之后,我们在子组件的 useEffect 中,将其设置为 false。子组件代码调整如下
const Item = ({api, setList, setDisabled}) => { const [show, setShow] = useState(true) const joke = api ? use(api) : {value: 'nothing'} useEffect(() => { if (!api) return + setDisabled(false) setList((list) => { if (!list.includes(joke.value)) { return list.concat(joke.value) } return list }) setShow(false) }, []) //堆代码 duidaima.com const __cls = show ? '_03_a_value show' : '_03_a_value' return ( <div className={__cls}>{joke.value}</div> ) }演示效果如下
这种方式也可以比较合理的解决竞态问题。后续我们通过别的案例,再来演示通过取消上一次的接口请求方式是如何实现的。
(全文完)