简体   繁体   English

在 React 中,如何在所有组件中拥有自定义钩子的单个实例?

[英]In React, how to have a single instance of a custom hook in all components?

I have several components that require access to the same list of players.我有几个组件需要访问相同的玩家列表。 This list of players won't change, but I do not know what component will required it first.这个玩家列表不会改变,但我不知道哪个组件首先需要它。 My idea here is that whoever component requires it first (axios request) updates my redux store and then the other ones would just use this same custom hook for that.我的想法是,无论谁组件首先需要它(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}
}

This is my custom hook for that.这是我为此定制的钩子。 Wondering if there is any inconvenience with doing that or even better patterns for such.想知道这样做是否有任何不便或更好的模式。 Thanks in advance.提前致谢。

BTW... Even better, I would set loading and error as useState variables inside my usePlayersList (as opposed to sending them to redux state).顺便说一句...更好的是,我会在 usePlayersList 中将加载和错误设置为 useState 变量(而不是将它们发送到 redux 状态)。 Since this lead me to error (loading and error became different on each component, assuming that this state became individual to each component) I sent them to store.由于这导致我出错(假设这个 state 对每个组件来说都是独立的),我将它们发送到商店。

Best regards, Felipe.最好的问候,费利佩。

TL;DR use Context TL;DR 使用上下文

Explanation:解释:

Each component gets its instance of the custom hook.每个组件都获取其自定义挂钩的实例。

In the example below, we can see that calling setState changes the state in the Bla component but not in the Foo component:在下面的示例中,我们可以看到调用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>

A combination of Context and custom hook can be used to fix the above issue:可以使用上下文和自定义挂钩的组合来解决上述问题:

 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>


Original answer that's specific to what OP wants:特定于 OP 需求的原始答案:

Defining the context:定义上下文:

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>;
}

Add the PlayersProvider as a parent to the components that need access to the players.PlayersProvider作为父级添加到需要访问玩家的组件中。

and inside those child components:在这些子组件中:

import {usePlayersContext} from 'contextfilepath'

function Child() {
  
  const { list, loading, error } = usePlayersContext()
  return ( ... )
}

You can also maintain the state in the Provider itself instead of in the redux.您还可以在Provider本身而不是 redux 中维护 state。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM