reactjs - 如何操作上下文 - 将函数附加到上下文或将调度包装在钩子(Hook)中?

标签 reactjs react-hooks react-context

我想知道操作和公开新的 React Context 的推荐最佳实践是什么。

操纵上下文状态的最简单方法似乎是将一个函数附加到上下文,该函数可以调度 (usereducer) 或 setstate (useState) 来更改其内部调用一次的值。

export const TodosProvider: React.FC<any> = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, null, init);

  return (
    <Context.Provider
      value={{
        todos: state.todos,
        fetchTodos: async id => {
          const todos = await getTodos(id);
          console.log(id);
          dispatch({ type: "SET_TODOS", payload: todos });
        }
      }}
    >
      {children}
    </Context.Provider>
  );
};

export const Todos = id => {
  const { todos, fetchTodos } = useContext(Context);
  useEffect(() => {
    if (fetchTodos) fetchTodos(id);
  }, [fetchTodos]);
  return (
    <div>
      <pre>{JSON.stringify(todos)}</pre>
    </div>
  );
};

然而,我被告知直接公开和使用 react 上下文对象可能不是一个好主意,并被告知将其包装在一个钩子(Hook)中。

export const TodosProvider: React.FC<any> = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, null, init);

  return (
    <Context.Provider
      value={{
        dispatch,
        state
      }}
    >
      {children}
    </Context.Provider>
  );
};

const useTodos = () => {
  const { state, dispatch } = useContext(Context);
  const [actionCreators, setActionCreators] = useState(null);

  useEffect(() => {
    setActionCreators({
      fetchTodos: async id => {
        const todos = await getTodos(id);
        console.log(id);
        dispatch({ type: "SET_TODOS", payload: todos });
      }
    });
  }, []);

  return {
    ...state,
    ...actionCreators
  };
};

export const Todos = ({ id }) => {
  const { todos, fetchTodos } = useTodos();
  useEffect(() => {
    if (fetchTodos && id) fetchTodos(id);
  }, [fetchTodos]);

  return (
    <div>
      <pre>{JSON.stringify(todos)}</pre>
    </div>
  );
};

我在这里为这两个变体制作了运行代码示例:https://codesandbox.io/s/mzxrjz0v78?fontsize=14

所以现在我有点困惑这两种方法中哪一种是正确的方法?

最佳答案

直接在组件中使用useContext绝对没有问题。然而,它强制必须使用上下文值的组件知道要使用什么上下文。

如果您希望在应用程序中有多个组件使用 TodoProvider 上下文,或者您的应用程序中有多个上下文,则可以使用自定义 Hook 来简化它

使用上下文时还必须考虑的另一件事是,您不应该在每次渲染时创建新对象,否则所有使用 context 的组件都将重新渲染,即使没有任何内容已改变。为此,您可以使用 useMemo 钩子(Hook)

const Context = React.createContext<{ todos: any; fetchTodos: any }>(undefined);

export const TodosProvider: React.FC<any> = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, null, init);
  const context = useMemo(() => {
    return {
      todos: state.todos,
      fetchTodos: async id => {
        const todos = await getTodos(id);
        console.log(id);
        dispatch({ type: "SET_TODOS", payload: todos });
      }
    };
  }, [state.todos, getTodos]);
  return <Context.Provider value={context}>{children}</Context.Provider>;
};

const getTodos = async id => {
  console.log(id);
  const response = await fetch(
    "https://jsonplaceholder.typicode.com/todos/" + id
  );
  return await response.json();
};
export const useTodos = () => {
  const todoContext = useContext(Context);
  return todoContext;
};
export const Todos = ({ id }) => {
  const { todos, fetchTodos } = useTodos();
  useEffect(() => {
    if (fetchTodos) fetchTodos(id);
  }, [id]);
  return (
    <div>
      <pre>{JSON.stringify(todos)}</pre>
    </div>
  );
};

<强> Working demo

编辑:

Since getTodos is just a function that cannot change, does it make sense to use that as update argument in useMemo?

如果 getTodos 方法正在更改并在功能组件内调用,则将 getTodos 传递到 useMemo 中的依赖项数组是有意义的。通常,您会使用 useCallback 来内存该方法,以便它不会在每次渲染时创建,但仅当其封闭范围的任何依赖项发生更改以更新其词法范围内的依赖项时才创建。现在,在这种情况下,您需要将其作为参数传递给依赖项数组。

但是,就您的情况而言,您可以省略它。

Also how would you handle an initial effect. Say if you were to call `getTodos´ in useEffect hook when provider mounts? Could you memorize that call as well?

您只需在初始安装时调用的 Provider 中产生一个效果

export const TodosProvider: React.FC<any> = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, null, init);
  const context = useMemo(() => {
    return {
      todos: state.todos,
      fetchTodos: async id => {
        const todos = await getTodos(id);
        console.log(id);
        dispatch({ type: "SET_TODOS", payload: todos });
      }
    };
  }, [state.todos]);
  useEffect(() => {
      getTodos();
  }, [])
  return <Context.Provider value={context}>{children}</Context.Provider>;
};

关于reactjs - 如何操作上下文 - 将函数附加到上下文或将调度包装在钩子(Hook)中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55786752/

相关文章:

javascript - 从图像 URL 转换时 Base64 图像未打开

css - div占body高和宽的一半

javascript - 如何重置 JavaScript 数组中的数组索引

javascript - 将 Hooks 与 Redux 一起使用——不好的做法?

reactjs - 如何在 react 中定义上下文数组中的类型

reactjs - 从 React 的 Context API 组件重定向

javascript - 如何在ReactJS中显示模式对话框?

javascript - 如何从获取的 json 数据中仅返回 set-State?

reactjs - 刷新页面时 react 钩子(Hook)清理

reactjs - 使用 testing-library 和 jest 的 React 组件抛出的测试错误