如何使用React Hooks优雅的获取数据?

原文链接:https://www.robinwieruch.de/react-hooks-fetch-data
作者简介:https://overreacted.io/zh-hans/my-decade-in-review/

如果我们要请求一个接口获得数据,并要遍历到当前页面中渲染出来,可能会这么写

import React, { useState, useEffect } from 'react';
import axios from 'axios';
 
function App() {
  const [data, setData] = useState({ hits: [] });
  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        'https://hn.algolia.com/api/v1/search?query=redux',
      );
      setData(result.data);
    };
    fetchData();
  }, []);
  
   return (
       // 数据展示区
       {data.hits}
    );
}
 
export default App;

如果我们需要修改地址中的查询参数,用一个输入框和一个提交按钮实现。点击按钮时改变url,从而使副作用拉取新的数据,代码如下

export default function BestFetchData() {
    const [data, setData] = useState(0)
    const [query, setQuery] = useState('')
    const [url, setUrl] = useState('http://wthrcdn.etouch.cn/weather_mini?city=青岛')
    useEffect(() => {
        const fetchData = async () => {
            const ret = await axios(url)
            setData(ret.data.data)
        }
        fetchData()
    }, [url])
    return (
        <div>
            <h3>可输入内容再搜索</h3>
            <input value={query} onChange={e => setQuery(e.target.value)} />
            <button type='button' onClick={() => setUrl('http://wthrcdn.etouch.cn/weather_mini?city=' + query)}>搜索</button>

            {data && <>
                // 数据展示区
            </>}

        </div>
    )
}

下一步,我们加入loading和错误捕获功能,并通过表单提交事件来实现搜索

export default function () {
    const [data, setData] = useState('')
    const [query, setQuery] = useState('')
    const [url, setUrl] = useState('http://wthrcdn.etouch.cn/weather_mini?city=青岛')
    const [isLoading, setIsLoading] = useState(false)
    const [isError, setIsError] = useState(false)

    useEffect(() => {
        const fetchData = async () => {
            setIsLoading(true)
            setIsError(false)
            try{
                const ret = await axios(url)
                setData(ret.data.data)
            }catch(e){
                setIsError(true)
            }
            setIsLoading(false)
        }
        fetchData()
    }, [url])

    return (
        <div>
            <h3>loading+错误捕捉+表单提交</h3>
            <p>输入内容后可直接按回车</p>
            <form onSubmit={(e) =>{e.preventDefault(); setUrl('http://wthrcdn.etouch.cnn/weather_mini?city2==' + query)}}>
                <input value={query} onChange={e => setQuery(e.target.value)} />
                <button type='submit'>搜索</button>
            </form>

            {isError&&<div style={{background:'pink',padding:'1rem',margin:'1rem'}}>出错啦~~</div>}

            {data && (isLoading
                ? <div> loading...</div>
                : <>
                    // 数据展示区
                </>)}
        </div>
    )
}

可以使用自定义hooks来把请求相关逻辑进行封装,以便复用。useFetchData 只在乎逻辑处理而不关心数据。

const useFetchData = (initialUrl, initialData) => {
    const [url, setUrl] = useState(initialUrl)
    const [data, setData] = useState(initialData)
    const [isLoading, setIsLoading] = useState(false)
    const [isError, setIsError] = useState(false)
    useEffect(() => {
        const fetchData = async () => {
            setIsLoading(true)
            setIsError(false)
            try {
                const ret = await axios(url)
                setData(ret.data.data)
            } catch (e) {
                setIsError(true)
            }
            setIsLoading(false)
        }
        fetchData()
    }, [url])
    return [{ data, isLoading, isError }, setUrl]
}

export default function () {
    const [query, setQuery] = useState('')
    const [{ data, isLoading, isError }, doFetch] = useFetchData('http://wthrcdn.etouch.cn/weather_mini?city=青岛', null)
    return (
        <div>
            <h3>抽取自定义hooks为公用</h3>
            <p>输入内容后可直接按回车</p>
            <form onSubmit={(e) => { e.preventDefault(); doFetch('http://wthrcdn.etouch.cn/weather_mini?city=' + query) }}>
                <input value={query} onChange={e => setQuery(e.target.value)} />
                <button type='submit'>搜索</button>
            </form>

            {isError && <div style={{ background: 'pink', padding: '1rem', margin: '1rem' }}>出错啦~~</div>}

            {data && (isLoading
                ? <div> loading...</div>
                : <>
                    // 数据展示区
                </>)}
        </div>
    )
}

可以使用reducer把自定义hook里面比较独立的逻辑再次封装

const dataFetchReducer = (state, action) => {
    switch (action.type) {
        case 'FETCH_INIT':
            return { ...state, isLoading: true, isError: false }
        case 'FETCH_SCUESS':
            return { ...state, isError: false, isLoading: false, data: action.payload }
        case 'FETCH_FAILURE':
            return { ...state, isLoading: false, isError: true }
        default:
            throw new error();
    }
}

const useDataApi = (initialUrl, initialData) => {
    const [state, dispatch] = useReducer(dataFetchReducer, {
        data: initialData,
        isLoading: false,
        isError: false
    })
    const { data, isLoading, isError } = state
    const [url, setUrl] = useState(initialUrl)
    useEffect(() => {
        const fetchData = async () => {
            dispatch({ type: 'FETCH_INIT' })
            try {
                const ret = await axios(url)
                dispatch({ type: 'FETCH_SCUESS', payload: ret.data.data })
            } catch (e) {
                dispatch({ type: 'FETCH_FAILURE' })
            }
        }
        fetchData()
    }, [url])
    return [state, setUrl]
}

export default function () {
    const [query, setQuery] = useState('')
    const commonUrl = 'http://wthrcdn.etouch.cn/weather_mini?city='
    const [{ data, isLoading, isError }, doFetch] = useDataApi(commonUrl + '青岛', null)
    return (
        <div>
            <h3>使用reducer</h3>
            <p>输入内容后可直接按回车</p>
            <form onSubmit={(e) => {
                e.preventDefault()
                doFetch(commonUrl + query)
            }}>
                <input value={query} onChange={e => setQuery(e.target.value)} />
                <button type='submit'>搜索</button>
            </form>

            {isError && <div style={{ background: 'pink', padding: '1rem', margin: '1rem' }}>出错啦~~</div>}

            {data && (isLoading
                ? <div> loading...</div>
                : <>
                    // 数据展示区
                </>)}
        </div>
    )
}

结束

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值