Javascript 如何使用react钩子在组件之间共享资产请求?
我正在寻找一种很好的方法来在使用挂钩的任意数量的消费者之间共享单个资产请求/响应 在提供的代码片段中,我使用URL跟踪请求相同资产的组件数量。我存储请求,如果组件2在组件1卸载之前发出相同的请求,那么组件2将使用缓存的请求。如果卸载了所有组件,我们可以中止请求并将其从缓存中删除Javascript 如何使用react钩子在组件之间共享资产请求?,javascript,reactjs,react-hooks,Javascript,Reactjs,React Hooks,我正在寻找一种很好的方法来在使用挂钩的任意数量的消费者之间共享单个资产请求/响应 在提供的代码片段中,我使用URL跟踪请求相同资产的组件数量。我存储请求,如果组件2在组件1卸载之前发出相同的请求,那么组件2将使用缓存的请求。如果卸载了所有组件,我们可以中止请求并将其从缓存中删除 /* * Share asset requests between components. * const { data, loaded, total, error } = useAssetLoader('large
/*
* Share asset requests between components.
* const { data, loaded, total, error } = useAssetLoader('largeFile.tiff', 'arraybuffer');
*/
import { useEffect, useState } from 'react';
import request from 'superagent';
// Keep track of how many instances are using a each request
// when all instances have been unmounted, we can abort the request
const instances = {};
// Keep track of requests to share between instances
const requests = {};
export default function useAssetLoader(url, responseType) {
const [data, setData] = useState();
const [loaded, setLoaded] = useState();
const [total, setTotal] = useState();
const [error, setError] = useState();
useEffect(() => {
if (!url) {
return () => {};
}
const key = url + responseType;
instances[key] = (instances[key] || 0) + 1;
if (!requests[key]) {
requests[key] = request(url);
if (responseType) {
requests[key].responseType(responseType);
}
}
requests[key].on('progress', (event) => {
if (event.direction === 'download') {
setLoaded(event.loaded);
setTotal(event.total);
}
});
requests[key].on('error', setError);
req.then(response => setData(response.body || response.text));
return () => {
instances[key] -= 1;
// When all components are unmounted we can abort request
if (instances[key] === 0) {
delete instances[key];
if (requests[key]) {
requests[key].abort();
delete requests[key];
}
}
};
}, [url, responseType]);
return { data, loaded, total, error };
}
有更好的方法吗?我也希望将此模式用于其他异步任务。您可以使用上下文共享所有组件的“全局”数据。
AssetLoaderContext.js
import React, { Component } from 'react'
//Add all states that you want to share over here.
const defaultState = { data: {}, instances: {}, requests: {} }
const AssetLoaderContext = React.createContext(defaultState)
class AssetLoaderProvider extends Component {
state = {
data: {},
instances: {},
requests: {}
}
setContextState = args => {
this.setState(args)
}
render() {
return (
<AssetLoaderContext
value={{
setContextState: this.setContextState,
...this.state
}}>
{children}
</AssetLoaderContext>
)
}
}
//We're making a different file so the index.js/App.js doesn't get cluttered.
//You can also put all your context files in one folder and name it "context" so all
//the "global" and shared state is in one place.
export default AssetLoaderContext
export { AssetLoaderProvider }
import AssetLoaderContext from './context/AssetLoaderContext'
//other imports
class SomeComponent extends React {
static contextType = AssetLoaderContext
state = {
date: {}
// ....etc
}
componentDidMount() {
console.log(this.context)
}
componentDidUpdate() {
const assetLoader = this.context
// do stuff with this.props.url, this.props.responseType.
//Then set the AssetLoaderContext state.
const data = this.state.data
assetLoader.setContextState({
data
// etc.
})
}
render() {
//you can just get the context in OtherComponent without passing it here.
return <OtherComponent />
}
}
import AssetLoaderProvider from './context/AssetLoaderContext'
//At the top level wrap your components in AssetLoaderProvider so each
//component can be a "consumer" of the data provded by the context.
ReactDOM.render(
<Router>
<AssetLoaderProvider>
<App />
</AssetLoaderProvider>
</Router>,
document.getElementById('root')
)
App.js
import React, { Component } from 'react'
//Add all states that you want to share over here.
const defaultState = { data: {}, instances: {}, requests: {} }
const AssetLoaderContext = React.createContext(defaultState)
class AssetLoaderProvider extends Component {
state = {
data: {},
instances: {},
requests: {}
}
setContextState = args => {
this.setState(args)
}
render() {
return (
<AssetLoaderContext
value={{
setContextState: this.setContextState,
...this.state
}}>
{children}
</AssetLoaderContext>
)
}
}
//We're making a different file so the index.js/App.js doesn't get cluttered.
//You can also put all your context files in one folder and name it "context" so all
//the "global" and shared state is in one place.
export default AssetLoaderContext
export { AssetLoaderProvider }
import AssetLoaderContext from './context/AssetLoaderContext'
//other imports
class SomeComponent extends React {
static contextType = AssetLoaderContext
state = {
date: {}
// ....etc
}
componentDidMount() {
console.log(this.context)
}
componentDidUpdate() {
const assetLoader = this.context
// do stuff with this.props.url, this.props.responseType.
//Then set the AssetLoaderContext state.
const data = this.state.data
assetLoader.setContextState({
data
// etc.
})
}
render() {
//you can just get the context in OtherComponent without passing it here.
return <OtherComponent />
}
}
import AssetLoaderProvider from './context/AssetLoaderContext'
//At the top level wrap your components in AssetLoaderProvider so each
//component can be a "consumer" of the data provded by the context.
ReactDOM.render(
<Router>
<AssetLoaderProvider>
<App />
</AssetLoaderProvider>
</Router>,
document.getElementById('root')
)
从“./context/AssetLoaderContext”导入AssetLoaderProvider
//在顶层,将组件包装在AssetLoaderProvider中,以便
//组件可以是上下文提供的数据的“使用者”。
ReactDOM.render(
,
document.getElementById('root'))
)
要清除上下文,只需调用
setContextState({data:{},instances:{},requests:{})
或编写一个指定的clearContextData
方法。您的思路肯定是正确的,但是您需要使用useContext
钩子,以及useState
和useEffect
钩子。让我们从一个助手钩子开始,它接受一个上下文和一个请求,并返回一个最终将返回的上下文提供程序y提供以下数据:
export const useFetchContext = (
Ctx,
request,
initialData = null,
) => {
const [data, setData] = useState(initialData);
useEffect(() => {
const getData = async () => {
try {
const response = await fetch(request);
const json = await response.json();
setData(json);
} catch (err) {
// handle error
}
};
if (!data) getData();
// currently only runs once. Add request and data as dependencies
// in the array below if you'll be feeding it a stateful request.
}, []);
const CtxProvider = ({ children }) => {
return <Ctx.Provider value={data}>{children}</Ctx.Provider>;
};
return CtxProvider;
};
清洗并重复每个文件,以获取您想要的全局可用内容。您甚至可以升级到index.js,并将
组件包装到提供程序中,这样它们就不会阻塞您的JSX
现在,您有了一个助手,它获取数据并在向其提供上下文时返回一个提供程序,一个使用它的钩子(useDataContextProvider)和一个“打开”全局上下文数据的钩子(useDataContext)
因此,在App.js中:
import { useDataContextProvider } from './path/to/file';
export default () => {
const CtxProvider = useDataContextProvider();
return (
<CtxProvider>
<YourComponentsHere />
</CtxProvider>
);
};
将{useDataContextProvider}从“./path/to/file”导入;
导出默认值()=>{
const CtxProvider=useDataContextProvider();
返回(
);
};
提供程序的子级现在可以通过另一个钩子访问数据(一旦获取完成)。因此,在要使用上下文的组件中:
import { useDataContext } from './path/to/file';
export default ({
someProp
}) => {
// This will be undefined, or whatever you set as the default,
// until the fetch completes. Don't try to e.g. destructure it.
const contextData = useDataContext();
return <SomeJSX />;
};
import{useDataContext}from./path/to/file';
导出默认值({
某物
}) => {
//这将是未定义的,或者您设置为默认值的任何内容,
//在提取完成之前,不要尝试(例如)对其进行分解。
const contextData=useDataContext();
返回;
};
这并没有回答关于如何使用钩子实现的问题……这很有趣,但不会在组件之间共享请求。如果ComponentA
和ComponentB
都使用相同的URL使用useDataContextProvider
,将发出两个请求。使用键将数据转换为对象作为url。如果(!data[url])
,则将条件更改为。因此,仅当url键不存在时才会发出请求。在组件中,使用useEffect with data作为获取数据后的依赖项。