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

Fel*_*res 8 reactjs react-redux react-context react-hooks

我有几个组件需要访问相同的玩家列表。这个玩家列表不会改变,但我不知道哪个组件首先需要它。我的想法是,无论哪个组件首先需要它(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}
}
Run Code Online (Sandbox Code Playgroud)

这是我为此定制的钩子。想知道这样做是否有任何不便,或者是否有更好的模式。提前致谢。

顺便说一句...更好的是,我将加载和错误设置为 usePlayersList 中的 useState 变量(而不是将它们发送到 redux 状态)。由于这导致我出错(每个组件的加载和错误都不同,假设这个状态对于每个组件来说都是单独的),我将它们发送到存储。

最好的问候,费利佩。

Ram*_*ddy 14

TL;DR 使用上下文

解释:

每个组件都会获取其自定义挂钩的实例。

在下面的示例中,我们可以看到调用会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'));
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

Context和自定义 hook的组合可以用来解决上述问题:

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'));
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)


原始答案具体针对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>;
}
Run Code Online (Sandbox Code Playgroud)

PlayersProvider作为父组件添加到需要访问玩家的组件中。

在这些子组件内部:

import {usePlayersContext} from 'contextfilepath'

function Child() {
  
  const { list, loading, error } = usePlayersContext()
  return ( ... )
}
Run Code Online (Sandbox Code Playgroud)

Provider您还可以在其本身而不是在 redux 中维护状态。