如何操作上下文 - 将函数附加到上下文或在钩子中包装调度? [英] How to manipulate context - attach function to context or wrap dispatch in hook?

查看:47
本文介绍了如何操作上下文 - 将函数附加到上下文或在钩子中包装调度?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

export const TodosProvider: React.FC= ({儿童}) =>{const [state, dispatch] = useReducer(reducer, null, init);返回 (<Context.Provider价值={{待办事项:state.todos,fetchTodos: 异步 ID =>{const todos = await getTodos(id);控制台日志(ID);dispatch({ type: "SET_TODOS", payload: todos });}}}>{孩子们}</Context.Provider>);};导出 const Todos = id =>{const { todos, fetchTodos } = useContext(Context);useEffect(() => {如果 (fetchTodos) fetchTodos(id);}, [fetchTodos]);返回 (<div><pre>{JSON.stringify(todos)}</pre>

);};

然而,有人告诉我直接暴露和使用 react 上下文对象可能不是一个好主意,并被告知将其包装在一个钩子中.

export const TodosProvider: React.FC= ({儿童}) =>{const [state, dispatch] = useReducer(reducer, null, init);返回 (<Context.Provider价值={{派遣,状态}}>{孩子们}</Context.Provider>);};const useTodos = () =>{const { state, dispatch } = useContext(Context);const [actionCreators, setActionCreators] = useState(null);useEffect(() => {setActionCreators({fetchTodos: 异步 ID =>{const todos = await getTodos(id);控制台日志(ID);dispatch({ type: "SET_TODOS", payload: todos });}});}, []);返回 {...状态,...动作创作者};};导出 const Todos = ({ id }) =>{const { todos, fetchTodos } = useTodos();useEffect(() => {if (fetchTodos && id) fetchTodos(id);}, [fetchTodos]);返回 (<div><pre>{JSON.stringify(todos)}</pre>

);};

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

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

解决方案

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

如果你在应用中有多个组件想要使用 TodoProvider 上下文,或者你的应用中有多个上下文,你可以使用自定义钩子稍微简化它

在使用上下文时还必须考虑的另一件事是,您不应该在每次渲染时创建一个新对象,否则所有使用 context 的组件都会重新渲染,即使什么都不会已经改变.为此,您可以使用 useMemo 钩子

const Context = React.createContext<{ todos: any;fetchTodos: any }>(undefined);export const TodosProvider: React.FC= ({儿童}) =>{const [state, dispatch] = useReducer(reducer, null, init);const context = useMemo(() => {返回 {待办事项:state.todos,fetchTodos: 异步 ID =>{const todos = await getTodos(id);控制台日志(ID);dispatch({ type: "SET_TODOS", payload: todos });}};}, [state.todos, getTodos]);return <Context.Provider value={context}>{children}</Context.Provider>;};const getTodos = 异步 ID =>{控制台日志(ID);const 响应 = 等待获取("https://jsonplaceholder.typicode.com/todos/" + id);返回等待 response.json();};export const useTodos = () =>{const todoContext = useContext(Context);返回 todoContext;};导出 const Todos = ({ id }) =>{const { todos, fetchTodos } = useTodos();useEffect(() => {如果 (fetchTodos) fetchTodos(id);}, [ID]);返回 (<div><pre>{JSON.stringify(todos)}</pre>

);};

工作演示

<块引用>

既然 getTodos 只是一个不能改变的函数,那么它是否使在 useMemo 中使用它作为更新参数有意义吗?

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

但是在您的情况下,您可以省略它.

<块引用>

还有你将如何处理初始效果.说如果你要打电话提供程序挂载时 useEffect 挂钩中的getTodos"?你能记住吗那个电话也是?

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

export const TodosProvider: React.FC= ({儿童}) =>{const [state, dispatch] = useReducer(reducer, null, init);const context = useMemo(() => {返回 {待办事项:state.todos,fetchTodos: 异步 ID =>{const todos = await getTodos(id);控制台日志(ID);dispatch({ type: "SET_TODOS", payload: todos });}};}, [state.todos]);useEffect(() => {getTodos();}, [])return <Context.Provider value={context}>{children}</Context.Provider>;};

I'm wondering what the recommended best practice is for manipulating and exposing the new React Context.

The easiest way to manipulate context state seems to be to just attach a function to the context that either dispatches (usereducer) or setstate (useState) to change its internal value once called.

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

I was however told exposing and using the react context object directly is probably not a good idea, and was told to wrap it inside a hook instead.

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

I have made running code examples for both variants here: https://codesandbox.io/s/mzxrjz0v78?fontsize=14

So now I'm a little confused as to which of the 2 ways is the right way to do it?

解决方案

There is absolute no problem with using useContext directly in a component. It however forces the component which has to use the context value to know what context to use.

If you have multiple components in the App where you want to make use of TodoProvider context or you have multiple Contexts within your app , you simplify it a little with a custom hook

Also one more thing that you must consider when using context is that you shouldn't be creating a new object on each render otherwise all components that are using context will re-render even though nothing would have changed. To do that you can make use of 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

EDIT:

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

It makes sense to pass getTodos to dependency array in useMemo if getTodos method is changing and is called within the functional component. Often you would memoize the method using useCallback so that its not created on every render but only if any of its dependency from enclosing scope changes to update the dependency within its lexical scope. Now in such a case you would need to pass it as a parameter to the dependency array.

However in your case, you can omit it.

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?

You would simply have an effect within Provider that is called on initial mount

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

这篇关于如何操作上下文 - 将函数附加到上下文或在钩子中包装调度?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆