在 React 中,如何在所有组件中拥有一个自定义钩子实例?
Posted
技术标签:
【中文标题】在 React 中,如何在所有组件中拥有一个自定义钩子实例?【英文标题】:In React, how to have a single instance of a custom hook in all components? 【发布时间】:2021-04-02 23:19:27 【问题描述】:我有几个组件需要访问相同的玩家列表。 这个球员名单不会改变,但我不知道哪个组件首先需要它。 我的想法是,首先需要它的组件(axios 请求)更新我的 redux 存储,然后其他组件将为此使用相同的自定义钩子。
export default function usePlayersList()
const list, loading, error = useSelector(state => state.accounts.players)
const dispatch = useDispatch()
useEffect(() =>
try
const response = authAxios.get(endpoints.players.retrieve)
response.then(res =>
dispatch(
type: "SET_PLAYERS_LIST",
payload:
error: false,
loading: false,
list: res.data.payload
)
)
catch (e)
console.log(e)
dispatch(
type: "SET_PLAYERS_LIST",
payload:
error: true,
loading: false,
list: []
)
, [dispatch])
return list, loading, error
这是我的自定义钩子。想知道这样做是否有任何不便,甚至更好的模式。提前致谢。
顺便说一句...更好的是,我会在我的 usePlayersList 中将加载和错误设置为 useState 变量(而不是将它们发送到 redux 状态)。由于这会导致我出错(加载和错误在每个组件上变得不同,假设这种状态对每个组件都是独立的)我将它们发送到存储。
最好的问候, 费利佩。
【问题讨论】:
这看起来不错,但是您在哪里检查列表是否已获取? 好吧,因为 useEffect 只会执行一次,因为没有依赖项(除了调度),我认为该列表将在第一次调用 usePlayersList 时获取? 没错,但是如果这个自定义钩子在三个组件中使用,每个实例都会不同,因此 useEffect 中的代码将被执行 3 次。请参阅我创建的example,注意控制台中有两个日志,因为挂钩用于两个组件。这就是我在回答中建议使用上下文 API 的原因。 谢谢,拉梅什!因此,我将使用 Context API。如果我创建一个这样的自定义钩子,我总是必须检查在我的 axios 请求之前是否已经获取了东西。干杯! 【参考方案1】:TL;DR 使用Context
说明:
每个组件都有它的自定义钩子实例。
在下面的示例中,我们可以看到调用setState
会更改Bla
组件中的状态,但不会更改Foo
组件中的状态:
const Fragment, useState, useEffect = React;
const useCustomHook = () =>
const [state, setState] = useState('old');
return [state, setState];
;
const Foo = () =>
const [state] = useCustomHook();
return <div>state in Foo component: state</div>;
;
const Bla = () =>
const [state, setState] = useCustomHook();
return (
<div>
<div>state in Bla component: state</div>
<button onClick=() => setState("new")>setState</button>
</div>
);
;
function App()
return (
<Fragment>
<Foo />
<Bla />
</Fragment>
);
ReactDOM.render(<App />, document.querySelector('#root'));
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<div id="root"></div>
Context 和自定义钩子的组合可以用来解决上述问题:
const createContext, useContext, useState, useEffect = React;
const Context = createContext();
const ContextProvider = ( children ) =>
const value = useState("old");
return <Context.Provider value=value children=children />;
;
const useCustomHook = () =>
const [state, setState] = useContext(Context);
return [state, setState];
;
const Foo = () =>
const [state] = useCustomHook();
return <div>state in Foo component: state</div>;
;
const Bla = () =>
const [state, setState] = useCustomHook();
return (
<div>
<div>state in Bla component: state</div>
<button onClick=() => setState("new")>setState</button>
</div>
);
;
function App()
return (
<ContextProvider>
<Foo />
<Bla />
</ContextProvider>
);
ReactDOM.render(<App />, document.querySelector('#root'));
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<div id="root"></div>
针对 OP 想要的原始答案:
定义上下文:
import React from 'react';
const PlayersContext = React.createContext();
const PlayersProvider = PlayersContext.Provider;
export const usePlayersContext = () => React.useContext(PlayersContext);
export default function PlayersProvider( children )
// add all the logic, side effects here and pass them to value
const list, loading, error = useSelector(
(state) => state.accounts.players
);
const dispatch = useDispatch();
useEffect(() =>
try
const response = authAxios.get(endpoints.players.retrieve);
response.then((res) =>
dispatch(
type: 'SET_PLAYERS_LIST',
payload:
error: false,
loading: false,
list: res.data.payload,
,
);
);
catch (e)
console.log(e);
dispatch(
type: 'SET_PLAYERS_LIST',
payload:
error: true,
loading: false,
list: [],
,
);
, [dispatch]);
return <PlayersProvider value= list, loading, error >children</PlayersProvider>;
将PlayersProvider
作为父组件添加到需要访问播放器的组件中。
在那些子组件内部:
import usePlayersContext from 'contextfilepath'
function Child()
const list, loading, error = usePlayersContext()
return ( ... )
您还可以在 Provider
本身而不是在 redux 中维护状态。
【讨论】:
以上是关于在 React 中,如何在所有组件中拥有一个自定义钩子实例?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 TypeScript v2.x 中从自定义 React 组件公开“通用”事件