如何優雅的在react-hook中進行網絡請求


此文轉載自:https://my.oschina.net/wayne90214/blog/4651993

本文將介紹如何在使用React Hook進行網絡請求及注意事項。

前言

Hook是在React 16.8.0版本中新加入的特性,同時在React-Native的0.59.0版本及以上進行了支持,使用hook可以不用class的方式的方式使用state,及類似的生命周期特性。 本片文章通過簡單的網絡請求數據的demo,來一起進一步認識react-hook這一特性,增加理解,涉及到的hook有useState, useEffect, useReducer等。

使用useState創建js頁面

首先創建一個hook的功能頁面demoHooks.js, 功能比較簡單使用flatlist展示一個文本列表頁面

const demoHooks = () => {
    // 初始值
    const [data, setData] = useState({hits: []});
    _renderItem = ({item}) => {
        console.log('rowData', item);
        return(
            <View style={{height: 50, backgroundColor: '#ff0', borderBottomColor: '#f0f', borderBottomWidth: 1, justifyContent: 'center'}}>
                    <Text style={{height: 20, width: 300}}>{item.title}</Text>
            </View>
        )
    };
    return (
        <View style={{backgroundColor: '#f5f5f5', marginTop: 20}}>
            <FlatList
                data={data.hits}
                renderItem={this._renderItem}
            />
        </View>
    );
};
export default demoHooks;

使用useEffect請求數據

import React, {useState, useEffect} from 'react';
import {
    Text,
    View,
    FlatList,
} from 'react-native';
import axios from 'axios'

// import CardView from 'react-native-cardview-wayne'

const demoHooks = () => {
    // 初始值
    const [data, setData] = useState({hits: []});
    // 副作用
    useEffect(async () => {
        const result = await axios('https://hn.algolia.com/api/v1/search?query=redux');
        setData(result.data);
        console.log('執行了')
    });
    _renderItem = ({item}) => {
        console.log('rowData', item);
        return(
            <View style={{height: 50, backgroundColor: '#ff0', borderBottomColor: '#f0f', borderBottomWidth: 1, justifyContent: 'center'}}>
                    <Text style={{height: 20, width: 300}}>{item.title}</Text>
            </View>
        )
    };
    return (
        <View style={{backgroundColor: '#f5f5f5', marginTop: 20}}>
            <FlatList
                data={data.hits}
                renderItem={this._renderItem}
            />
        </View>
    );
};
export default demoHooks;

我們使用effect hook函數獲取數據,這里我們用到了一個axios網絡請求框架。運行上述代碼后,會發現其中的console會一直循環打印,我們知道useEffect函數會在render更新后也就是原來的(componentDidUpdate)進行調用。這里我們在函數中調用了setData設置接口返回數據,觸發頁面的更新機制,就造成了死循環。 其實我們只是需要再頁面加載后執行一次即可,也就是在class寫法中componentDidMount()進行數據請求。 useEffect提供了第二參數,用於解決此類問題。這里傳入一個空數組[],來讓effect hook只在component mount后執行,避免在component update后繼續執行。

// 副作用
    useEffect(async () => {
        const result = await axios('https://hn.algolia.com/api/v1/search?query=redux');
        setData(result.data);
        console.log('執行了')
    },[]);

第二個參數是effect hook的依賴項列表,依賴項中數據發生變化的時候,hook就會重新執行,如果依賴項為空,hook認為沒有數據發生變更,在組件更新的時候就不會在此執行。

你會遇到的問題

An effect function must not return anything besides a function, which is used for clean-up.

在這里插入圖片描述 報錯提示不能直接在useEffect中使用async,切實報錯中也給出了解決方式,就是把async放在useEffect里面,修改如下,重新運行這個警告就消失了。

useEffect(() => {
        const fetchData = async () => {
            const result =  await axios('https://hn.algolia.com/api/v1/search?query=redux');
            setData(result.data);
        }
        fetchData();
        console.log('執行了')
    },[]);

效果頁面如下 在這里插入圖片描述

手動觸發hook請求

現在我們實現手動觸發hook網絡請求,修改代碼如下,加一個按鈕,點擊按鈕后獲取以“redux”為關鍵詞的列表數據

import React, {useState, useEffect} from 'react';
import {
    Text,
    View,
    FlatList,
} from 'react-native';
import axios from 'axios'
import { TouchableOpacity } from 'react-native-gesture-handler';

const demoHooks = () => {
    // 初始值
    const [data, setData] = useState({hits: []});
    const [search, setSearch] = useState('')
    // 副作用
    useEffect(() => {
        const fetchData = async () => {
            const result =  await axios(`https://hn.algolia.com/api/v1/search?query=${search}`);
            setData(result.data);
        }
        fetchData();
        console.log('執行了')
    },[]);
    _renderItem = ({item}) => {
        console.log('rowData', item);
        return(
            <View style={{height: 50, backgroundColor: '#ff0', borderBottomColor: '#f0f', borderBottomWidth: 1, justifyContent: 'center'}}>
                    <Text style={{height: 20, width: 300}}>{item.title}</Text>
            </View>
        )
    };

    _search = () => {
        setSearch('redux')
    }
    return (
        <View style={{backgroundColor: '#f5f5f5', marginTop: 20}}>
            <TouchableOpacity onPress={this._search}>
                <View style={{backgroundColor: '#f00', paddingHorizontal: 10, paddingVertical: 5}}>
                    <Text>Search</Text>
                </View>
            </TouchableOpacity>
            <FlatList
                data={data.hits}
                renderItem={this._renderItem}
            />
        </View>
    );
};
export default demoHooks;

運行上述代碼會發現,點擊按鈕后沒有發生任何變化,細心的讀者想必已經想到了,在代碼中,useEffect hook的第二個參數是空數組,所以沒有觸發effect運行,重新獲取數據,我們添加一下依賴項"search"到數組中,重新運行代碼后,點擊按鈕就可看到我們的數據已經正確更新了。

// 副作用
    useEffect(() => {
        const fetchData = async () => {
            const result =  await axios(`https://hn.algolia.com/api/v1/search?query=${search}`);
            setData(result.data);
        }
        fetchData();
        console.log('執行了')
    },[search]);

添加一個加載框

數據請求是一個過程,通常在頁面請求網絡數據的時候會有一個友好的提示加載框,我們添加一個loading的state來實現一下。

import React, {useState, useEffect} from 'react';
import {
    Text,
    View,
    FlatList,
} from 'react-native';
import axios from 'axios'
import { TouchableOpacity } from 'react-native-gesture-handler';

const demoHooks = () => {
    // 初始值
    const [data, setData] = useState({hits: []});
    const [search, setSearch] = useState('')
    const [isLoading, setIsLoading] = useState(false)
    // 副作用
    useEffect(() => {
        const fetchData = async () => {
            setIsLoading(true);
            const result =  await axios(`https://hn.algolia.com/api/v1/search?query=${search}`);
            setData(result.data);
            setIsLoading(false);
        }
        fetchData();
        console.log('執行了', isLoading)
    },[search]);
    _renderItem = ({item}) => {
        // console.log('rowData', item);
        return(
            <View style={{height: 50, backgroundColor: '#ff0', borderBottomColor: '#f0f', borderBottomWidth: 1, justifyContent: 'center'}}>
                    <Text style={{height: 20, width: 300}}>{item.title}</Text>
            </View>
        )
    };

    _search = () => {
        setSearch('redux')
    }
    return (
        <View style={{backgroundColor: '#f5f5f5', marginTop: 20, flex: 1}}>
            <TouchableOpacity onPress={this._search}>
                <View style={{backgroundColor: '#f00', paddingHorizontal: 10, paddingVertical: 5}}>
                    <Text>Search</Text>
                </View>
            </TouchableOpacity>
            {
                isLoading ? <View style={{backgroundColor: 'pink', flex:1, alignItems: 'center', justifyContent: 'center'}}>
                <Text style={{color: '#f00', fontSize: 30}}>The Data is Loading ...</Text>
                </View> : <FlatList
                data={data.hits}
                renderItem={this._renderItem}
            />
            }
        </View>
    );
};
export default demoHooks;

網絡請求錯誤的處理

錯誤處理是在網絡請求中是非常必要的,添加一個error狀態,使用try/catch來進行捕獲處理。

const [isError, setIsError] = useState(false)
    // 副作用
    useEffect(() => {
        const fetchData = async () => {
            setIsError(false)
            setIsLoading(true);
            try{
                const result =  await axios(`https://hn.algolia.com/api/v1/search?query=${search}`);
                setData(result.data);
            }catch(error){
                setIsError(true);
            }
            setIsLoading(false);
        }
        fetchData();
        console.log('執行了', isLoading)
    },[search]);

CommonFetchApi

我們將上述代碼提取出一個通用的網絡請求hook也就是自定義一個hook,包含initialData,error,initialState等;自定義hook也是一個函數,在其內部可以調用其他hook函數,使用“use”開頭。

import React, {useState, useEffect} from 'react';
import {
    Text,
    View,
    FlatList,
} from 'react-native';
import axios from 'axios'
import { TouchableOpacity } from 'react-native-gesture-handler';

const useDataApi = (initUrl, initData) => {
    const [data, setData] = useState(initData);
    const [url, setUrl] = useState(initUrl);
    const [isLoading, setIsLoading] = useState(false);
    const [isError, setIsError] = useState(false);
    // 副作用
    useEffect(() => {
        const fetchData = async () => {
            setIsError(false)
            setIsLoading(true);
            try{
                const result =  await axios(url);
                setData(result.data);
            }catch(error){
                setIsError(true);
            }
            setIsLoading(false);
        }
        fetchData();
    },[url]);

    return [{data, isLoading, isError}, setUrl];
}


const demoHooks = () => {
    const [search, setSearch] = useState('react')
    // 初始值
    const [{data, isLoading,isError}, fetchData ] = useDataApi(
        'https://hn.algolia.com/api/v1/search?query=redux',
        {hits: []});
    _renderItem = ({item}) => {
        return(
            <View style={{height: 50, backgroundColor: '#ff0', borderBottomColor: '#f0f', borderBottomWidth: 1, justifyContent: 'center'}}>
                    <Text style={{height: 20, width: 300}}>{item.title}</Text>
            </View>
        )
    };
    _search = () => {
        fetchData(`https://hn.algolia.com/api/v1/search?query=${search}`)
    }
    return (
        <View style={{backgroundColor: '#f5f5f5', marginTop: 20, flex: 1}}>
            <TouchableOpacity onPress={this._search}>
                <View style={{backgroundColor: '#f00', paddingHorizontal: 10, paddingVertical: 5}}>
                    <Text>Search</Text>
                </View>
            </TouchableOpacity>
            {
                isError && <View style={{backgroundColor: 'pink', flex:1, alignItems: 'center', justifyContent: 'center'}}>
                <Text style={{color: '#f00', fontSize: 30}}>網絡請求出錯了...</Text>
                </View>
            }
            {
                isLoading ? <View style={{backgroundColor: 'pink', flex:1, alignItems: 'center', justifyContent: 'center'}}>
                <Text style={{color: '#f00', fontSize: 30}}>The Data is Loading ...</Text>
                </View> : <FlatList
                data={data.hits}
                renderItem={this._renderItem}
            />
            }
        </View>
    );
};
export default demoHooks;

使用useReducer進行網絡請求

以上通過綜合使用useState 和 useEffect的方式實現了網絡請求的loading,error,initstate的處理,可以看到我們在其中使用了4個useState處理響應的狀態,其實我們也可以通過useReducer這個hook函數,來做統一管理,這里就類似於在class模式下,我們通常使用的react-redux進行數據流管理一樣。 useReducer在很多時候可以用來替換useState, 接受兩個參數(state, dispatch)返回一個計算后的新state,已達到更新頁面的效果。

import React, {useState, useEffect, useReducer} from 'react';
import {
    Text,
    View,
    FlatList,
} from 'react-native';
import axios from 'axios'
import { TouchableOpacity } from 'react-native-gesture-handler';

const fetchDataReducer = (state, action) => {
    switch(action.type){
        case 'FETCH_INIT':
            return{
                ...state,
                isLoading: true,
                isError: false
            }
        case 'FETCH_SUCCESS':
            return {
                ...state,
                isLoading: false,
                isErroe: false,
                data: action.payload,
            }
        case 'FETCH_ERROR':
            return {
                ...state,
                isLoading: false,
                isErroe: false,
                data: action.payload,
            }
            break;
        default:
            return state;
    }
}

const useDataApi = (initUrl, initData) => {
    const [url, setUrl] = useState(initUrl);
    const [state, dispatch] = useReducer(fetchDataReducer,{
        data: initData,
        isLoading: false,
        isErroe: false
    })
    // 副作用
    useEffect(() => {
        const fetchData = async () => {
            dispatch({type: 'FETCH_INIT'})
            try{
                const result =  await axios(url);
                dispatch({type: 'FETCH_SUCCESS', payload: result.data})
            }catch(error){
                dispatch({type: 'FETCH_ERROR'})
            }
        }
        fetchData();
    },[url]);

    return [state, setUrl];
}


const demoHooks = () => {
    const [search, setSearch] = useState('react')
    // 初始值
    const [{data, isLoading,isError}, fetchData ] = useDataApi(
        'https://hn.algolia.com/api/v1/search?query=redux',
        {hits: []});
    _renderItem = ({item}) => {
        return(
            <View style={{height: 50, backgroundColor: '#ff0', borderBottomColor: '#f0f', borderBottomWidth: 1, justifyContent: 'center'}}>
                    <Text style={{height: 20, width: 300}}>{item.title}</Text>
            </View>
        )
    };
    _search = () => {
        fetchData(`https://hn.algolia.com/api/v1/search?query=${search}`)
    }
    return (
        <View style={{backgroundColor: '#f5f5f5', marginTop: 20, flex: 1}}>
            <TouchableOpacity onPress={this._search}>
                <View style={{backgroundColor: '#f00', paddingHorizontal: 10, paddingVertical: 5}}>
                    <Text>Search</Text>
                </View>
            </TouchableOpacity>
            {
                isError && <View style={{backgroundColor: 'pink', flex:1, alignItems: 'center', justifyContent: 'center'}}>
                <Text style={{color: '#f00', fontSize: 30}}>網絡請求出錯了...</Text>
                </View>
            }
            {
                isLoading ? <View style={{backgroundColor: 'pink', flex:1, alignItems: 'center', justifyContent: 'center'}}>
                <Text style={{color: '#f00', fontSize: 30}}>The Data is Loading ...</Text>
                </View> : <FlatList
                data={data.hits}
                renderItem={this._renderItem}
            />
            }
        </View>
    );
};
export default demoHooks;

頁面銷毀時中斷網絡請求

每個effect函數中都會返回一個函數用於清除操作,類似於class模式中的componentWillUnmount()進行移除監聽操作,這個動作很重要,防止發生內存泄露及其他意想不到的情況,這里我們簡單提供一個boolean值來在組件銷毀時清除網絡請求操作。

// 副作用
    useEffect(() => {
        let doCancel = false;
        const fetchData = async () => {
            dispatch({type: 'FETCH_INIT'})
            try{
                const result =  await axios(url);
                if(!doCancel){
                    dispatch({type: 'FETCH_SUCCESS', payload: result.data})
                }
            }catch(error){
                if(!doCancel){
                    dispatch({type: 'FETCH_ERROR'})
                }
            }
        }
        fetchData();
        return ()=>{
            doCancel = true;
        }
    },[url]);

總結

本文通過一個網絡請求的demo講述了react hooks部分API的使用及注意事項,這幾個api也是平時開發工作中常見的,因此通過閱讀本文,你應該可以收獲如下內容:

  • useState的使用
  • useEffect的使用及注意事項
  • useReducer的使用
  • 自定義Hook的實現

覺得文章不錯的,給我點個贊哇,關注一下唄! 技術交流可關注公眾號【君偉說】,加我好友一起探討 交流群:wayne214(備注技術交流)邀你入群,抱團學習共進步


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM