在函数组件主体之外访问 React Context 值

Access React Context value outside of the body of a function component

案例

我想使 isLoading(使用 React Context 的全局状态)值和 changeIsLoading 函数(它从 IsLoadingContext.js 文件更改函数)对所有文件(函数组件和简单的 javascript 函数)。

我知道 React Hooks 只能在函数组件的内部调用。

问题:所以在我这里的例子中,我怎么能在 util 文件(非函数组件或只是一个简单的 javascript函数)?

我应该从代码中更改什么?

代码流

  1. (位置:SummariesPage.js)点击SummariesPage组件里面的button
  2. (location:SummariesPage.js)调用SummariesPage组件中的onApplyButtonIsClicked函数
  3. (location: SummariesPage.js) 将 isLoading 全局状态更改为 true 然后调用 fetchAPISummaries 函数
  4. (位置:fetchAPISummaries.js)调用fetchAPICycles函数
  5. (位置:fetchAPICycles.js)调用exportJSONToExcel函数
  6. (location: exportJSONToExcel.js) 将 JSON 导出到 Excel 文件中,然后将 isLoading 全局状态更改为 false
  7. IsLoadingContextProvider 组件将被重新渲染,SummariesPage 中的 isLoading 值将是 true

错误日志

Uncaught (in promise) Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app

代码

IsLoadingContext.js:

import React, { useState } from 'react'

const IsLoadingContext = React.createContext()

const IsLoadingContextProvider = (props) => {
    const [isLoading, setIsLoading] = useState(false)

    const changeIsLoading = (inputState) => {
        setIsLoading(inputState)
    }

    return(
        <IsLoadingContext.Provider
            value={{
                isLoading,
                changeIsLoading
            }}
        >
            {props.children}
        </IsLoadingContext.Provider>
    )
}

export { IsLoadingContextProvider, IsLoadingContext }

SummariesPage.js:

import React, { useContext } from 'react'

// CONTEXTS
import { IsLoadingContext } from '../../contexts/IsLoadingContext'

// COMPONENTS
import Button from '@material-ui/core/Button';

// UTILS
import fetchAPISummaries from '../../utils/export/fetchAPISummaries'

const SummariesPage = () => {
    const { isLoading, changeIsLoading } = useContext(IsLoadingContext)

    const onApplyButtonIsClicked = () => {
        changeIsLoading(true)
        fetchAPISummaries(BEGINTIME, ENDTIME)
    }

    console.log('isLoading', isLoading)

    return(
        <Button
            onClick={onApplyButtonIsClicked}
        >
            Apply
        </Button>
    )
}

export default SummariesPage

fetchAPISummaries.js:

// UTILS
import fetchAPICycles from './fetchAPICycles'

const fetchAPISummaries = (inputBeginTime, inputEndTime) => {
    const COMPLETESUMMARIESURL = .....
    
    fetch(COMPLETESUMMARIESURL, {
        method: "GET"
    })
    .then(response => {
        return response.json()
    })
    .then(responseJson => {
        fetchAPICycles(inputBeginTime, inputEndTime, formatResponseJSON(responseJson))
    })
}

const formatResponseJSON = (inputResponseJSON) => {
    const output = inputResponseJSON.map(item => {
        .....
        return {...item}
    })
    return output
}

export default fetchAPISummaries

fetchAPICycles.js

// UTILS
import exportJSONToExcel from './exportJSONToExcel'

const fetchAPICycles = (inputBeginTime, inputEndTime, inputSummariesData) => {
    const COMPLETDEVICETRIPSURL = .....

    fetch(COMPLETDEVICETRIPSURL, {
        method: "GET"
    })
    .then(response => {
        return response.json()
    })
    .then(responseJson => {
        exportJSONToExcel(inputSummariesData, formatResponseJSON(responseJson))
    })
}

const formatResponseJSON = (inputResponseJSON) => {
    const output = inputResponseJSON.map(item => {
        .....
        return {...item}
    })
    return output
}

export default fetchAPICycles

exportJSONToExcel.js

import { useContext } from 'react'

import XLSX from 'xlsx'

// CONTEXTS
import { IsLoadingContext } from '../../contexts/IsLoadingContext'

const ExportJSONToExcel = (inputSummariesData, inputCyclesData) => {
    const { changeIsLoading } = useContext(IsLoadingContext)

    const sheetSummariesData = inputSummariesData.map((item, index) => {
        let newItem = {}
        .....
        return {...newItem}
    })

    const sheetSummaries = XLSX.utils.json_to_sheet(sheetSummariesData)
    
    const workBook = XLSX.utils.book_new()

    XLSX.utils.book_append_sheet(workBook, sheetSummaries, 'Summaries')
    
    inputCyclesData.forEach(item => {
        const formattedCycles = item['cycles'].map((cycleItem, index) => {
            .....
            return {...newItem}
        })

        const sheetCycles = XLSX.utils.json_to_sheet(formattedCycles)
        XLSX.utils.book_append_sheet(workBook, sheetCycles, item['deviceName'])
    })

    XLSX.writeFile(workBook, `......xlsx`)

    changeIsLoading(false)
}

export default ExportJSONToExcel

我相信您面临的真正问题是管理异步调用。如果您使用 async/await 关键字,它的可读性会更高。

    const onApplyButtonIsClicked = async () => {
        changeIsLoading(true)
        await fetchAPISummaries(BEGINTIME, ENDTIME)
        changeIsLoading(false)
    }

您需要重写 fetchAPICycles 以使用 async/await 关键字而不是承诺。

const fetchAPICycles = async (
  inputBeginTime,
  inputEndTime,
  inputSummariesData
) => {
  const COMPLETDEVICETRIPSURL = ...;

  const response = await fetch(COMPLETDEVICETRIPSURL, {
    method: "GET",
  });
  const responseJson = await response.json();
  exportJSONToExcel(inputSummariesData, formatResponseJSON(responseJson));
};