zoukankan      html  css  js  c++  java
  • 如何优雅的在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,  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,  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,  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,  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,  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,  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(备注技术交流)邀你入群,抱团学习共进步

       

    更多内容详见微信公众号:Python测试和开发

    Python测试和开发

  • 相关阅读:
    SQL 高级查询(层次化查询,递归)
    IntelliJ IDEA添加注释常用的快捷键
    java配置环境变量
    Python单例模式的4种实现方法 ++ redis pool的一种单例实现方式
    MYSQL安装配置文件my-small.ini、my-medium.ini、my-large.ini、my-huge.ini文件的作用
    flask中使用flask-sqlalchemy
    Python向Mysql写入时间类型数据
    [慢查优化]慎用MySQL子查询,尤其是看到DEPENDENT SUBQUERY标记时
    Cocos2d-x 3.2 创建新应用
    In-App Purchase Programming Guide----(六) ----Working with Subscriptions
  • 原文地址:https://www.cnblogs.com/phyger/p/14190316.html
Copyright © 2011-2022 走看看