使用 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 在不同页面中获取请求的主要内容,如果未能解决你的问题,请参考以下文章