使用 React Hooks 在不同页面中获取请求

Posted

技术标签:

【中文标题】使用 React Hooks 在不同页面中获取请求【英文标题】:Fetch Requests in different pages with React Hooks 【发布时间】:2020-02-10 12:20:15 【问题描述】:

我正在使用 React Hooks 构建一个网站,我有两个不同的页面(Workshops.js 和 Shows.js)从同一个 API 获取数据,只是使用不同的参数(?type=0 和 ?type=1 )。 获取数据后,我将映射结果(那里有一个可重用的组件会很好......请参阅下面代码中的 cmets)。当用户点击节目或研讨会时,他将被重定向到同一页面。

现在代码可以正常工作了。 有没有更优雅的方法来避免重复相同的代码? ...类似于 Angular 中的服务?

谢谢!

这里是 Workshop.js。

import React,  useState, useEffect  from 'react';
import  Link  from 'react-router-dom'
import api from '../../maps/Api' 

const Workshops = () => 
    const [ workshops, setWorkshop ] = useState([])
    const [ isLoading, setIsLoading ] = useState(false)
    const [ error, setError ] = useState(null)
    const GET_URL = api.get.workshops /* http://someapi/workshops?type=0 */

    useEffect(() => 
        setIsLoading(true)
        fetch(GET_URL, headers: 
            "Accept": "application/json",
            "Access-Control-Allow-Origin": "*"
        )
        .then(res => 
            return (res.ok) ?  res.json() : new Error("Mistake!")
        )
        .then(workshops => 
            if(workshops.upcoming) 
                setWorkshop(workshops.upcoming);
            
            setIsLoading(false);
        )
        .catch(error => 
            setError(error)
        )
    , [GET_URL])

    if ( error ) return <p> error.message </p> 
    if ( isLoading )
        return <p>Loading workshops...</p>
    

    return(

        <main>
            <div className='content'>
                <div className='contentCol'>
                    <ul id='workshopBox'>
                    
                        workshops.map( (workshop, i) => (
                            <li> // FROM HERE...
                                <div
                                    className='workshop-active'>
                                    <h2> workshop.title </h2>
                                    <p> workshop.description </p>
                                    <p> workshop.place </p>
                                    <p> (new Date(workshop.date).toLocaleDateString("it-IT", 
                                            weekday: 'long',
                                            year: 'numeric',
                                            month: 'long',
                                            day: 'numeric'
                                          ))</p>
                                    <p> (new Date(workshop.date).toLocaleTimeString("it-IT", 
                                            hour: '2-digit',
                                            minute: '2-digit',
                                            hour12: true
                                          ))</p>
                                    <p> Full price  workshop.price_full + ', 00' &euro; </p>
                                    <p> Early bird price  workshop.price_earlybirds + ', 00' &euro; </p>
                                    <p>
                                    <Link to=`/workshops/$ workshop.id`>
                                        <button>Details</button>
                                    </Link>
                                    </p>
                                    <br/>
                                </div>
                            </li> //..to HERE I WOULD LIKE TO USE A REUSABLE COMPONENT
                            ))
                    
                    </ul>
                </div>
            </div>
        </main>
        )
    

export default Workshops

这里是 Shows.js

import React,  useState, useEffect  from 'react';
//import  Link  from 'react-router-dom'
import api from '../maps/Api'

const Spettacoli = () => 
    const [ shows, setShows ] = useState([])
    const [ isLoading, setIsLoading ] = useState(false)
    const [ error, setError ] = useState(null)
    const GET_URL = api.get.shows /* http://someapi/workshops?type=1 */

    useEffect(() => 
        setIsLoading(true)
        fetch(GET_URL, headers: 
            "Accept": "application/json",
            "Access-Control-Allow-Origin": "*"
        )
        .then(res => 
            return (res.ok) ?  res.json() : new Error("Mistake!")
        )
        .then(shows => 
            setShows(shows)
            setIsLoading(false)
        )
        .catch(error => 
            setError(error)
        )
    , [GET_URL])


    return(
        <main>
            <div className='content'>
                <div className='contentCol'>
                    /* SAME INTERFACE AS WORKSHOP */

                </div>
            </div>
        </main>
        )


export default Shows

【问题讨论】:

节目的界面和工作坊完全一样吗? 是的!刚刚编辑了我的帖子。 只需将类型作为道具传递给您的组件并使用道具值设置您的api。 【参考方案1】:

所以你可以创建你的custom hook:

function useMyDataFetch(GET_URL) 
    const [ data, setData ] = useState([])
    const [ isLoading, setIsLoading ] = useState(true)
    const [ error, setError ] = useState(null)

    useEffect(() => 
        let hasBeenAborted = false; // added
        setIsLoading(true)
        fetch(GET_URL, headers: 
            "Accept": "application/json",
            "Access-Control-Allow-Origin": "*"
        )
        .then(res => 
            return (res.ok) ?  res.json() : new Error("Mistake!")
        )
        .then(data => 
            if (hasBeenAborted) return; // added
            if(data.upcoming) 
                setData(data.upcoming);
            
            setIsLoading(false);
        )
        .catch(error => 
            if (hasBeenAborted) return; // added
            setIsLoading(false); // added
            setError(error)
        );
        return () =>  hasBeenAborted = true;  // added
    , [GET_URL]);

    return  data, error, isLoading ;

并在您的组件中使用它。

我用// added 标记的注意线。 hasBeenAborted 允许我们在 GET_URL 因任何原因对同一组件进行更新时做出反应。 Cleanup in useEffect 非常重要,因此我们避免了竞争条件。

我们可以使用AbortController 代替hasBeenAborted 标志,但这样我们仍然会落入catch 分支,并且需要额外的if 来区分请求是否已被取消或实际失败。所以对我来说只是品味问题。

至于你的组件,他们会像这样使用钩子:

const Workshops = () => 
    const isLoading, error, data: workshops = useMyDataFetch(api.get.workshops);

    if ( error ) return <p> error.message </p> 
    if ( isLoading )
        return <p>Loading workshops...</p>
    

    return(
      // the same here
    );



export default Workshops

【讨论】:

一切正常,非常感谢!有没有办法在一个共享组件中隔离 Workshops 和 Shows 中的 return 语句中的内容?由于代码是相同的......我尝试使用 Context 但它只适用于二选一。 @MarcoMazzai 你的意思是JSX 部分吗?你可以只用一个组件来做这个,它需要一个带有api.get.workshopsapi.get.shows 作为值的道具。 或者它可能宁愿让责任不明确。可能您可能只是将 .map() 中的所有内容提取到 &lt;Card /&gt; 之类的东西中,保持加载数据逻辑仍然在 WorkshopsShows 之间重复 是的,我指的是 JSX 部分。我尝试使用道具方式,但到目前为止我没有运气。

以上是关于使用 React Hooks 在不同页面中获取请求的主要内容,如果未能解决你的问题,请参考以下文章

翻译在 React Hooks 中如何请求数据?

React hooks之useEffect

React hooks解决搜索时序问题(竞态)

使用 React Hooks useEffect 发送 ajax 请求获取数据全攻略

React Custom Hooks 全局获取数据并跨组件共享?

使用 React Hooks 跟踪 URL 更改