[英]In React, how to have a single instance of a custom hook in all components?
我有几个组件需要访问相同的玩家列表。 这个玩家列表不会改变,但我不知道哪个组件首先需要它。 我的想法是,无论谁组件首先需要它(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 状态)。 由于这导致我出错(假设这个 state 对每个组件来说都是独立的),我将它们发送到商店。
最好的问候,费利佩。
TL;DR 使用上下文
解释:
每个组件都获取其自定义挂钩的实例。
在下面的示例中,我们可以看到调用setState
会更改Bla
组件中的 state 而不会更改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>
可以使用上下文和自定义挂钩的组合来解决上述问题:
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 中维护 state。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.