如何操作上下文-将函数附加到上下文或在钩子中包装分派?

时间:2019-04-21 21:06:20

标签: 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上下文对象可能不是一个好主意,并被告知将其包装在一个钩子中。

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

因此,现在我对这两种方法中的哪一种是正确的方法感到有些困惑?

2 个答案:

答案 0 :(得分:6)

直接在组件中使用useContext绝对没有问题。但是,它迫使必须使用上下文值的组件知道要使用的上下文。

如果您在应用程序中有多个要使用TodoProvider上下文的组件,或者您的应用程序中有多个上下文,则可以使用自定义钩子对其进行简化

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

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

编辑:

  

由于getTodos只是一个无法更改的函数,它会   可以在useMemo中使用它作为更新参数吗?

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

但是,您可以忽略它。

  

您还将如何处理初始效果。假设您要打电话   提供程序挂载时,useEffect挂钩中的`getTodos'?你能记住吗   那个电话吗?

您将在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>;
};

答案 1 :(得分:1)

我认为没有官方的答案,因此让我们尝试在此处使用一些常识。我发现直接使用#include <iostream> #include "mbed.h" DigitalIn columns[3] = {PB_6, PB_7, PD_0}; // Columns for digital input DigitalOut rows[4] = {PA_5, PA_1, PA_2, PA_3}; // rows for digital output DigitalIn startButton(USER_BUTTON); DigitalOut led1(LED1); // reference LED int numpad[4][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {-2, 0, -1}}; // keypad int Total(); int Keypad(); int c = 0; int Read; int Num1 = 0; int SelectOp(); int Oper; int main() { while (1) { if (startButton == 1) { printf("%s\n\rInput First Number\n\r"); wait(.5); Keypad(); int First = Num1; Num1 = 0; printf("%s\n\r Your first number is "); printf("%i", First); printf("%s\n\r Input your second number\n\r"); wait(.5); Keypad(); // this seems to be getting skipped int Second = Num1; Num1 = 0; printf("%s\n\r Your Second number is "); printf("%i", Second); printf("%s\n\rSelect Operator: 1(+), 2(-), 3(*), 4(/)"); Keypad(); Oper = Num1; } } } int Keypad() { columns[0].mode(PullUp); columns[1].mode(PullUp); columns[2].mode(PullUp); while (1) { if (Read == -1) { return Num1; } for (int i = 0; i < 4; i++) { rows[0] = 1; rows[1] = 1; rows[2] = 1; rows[3] = 1; rows[i] = 0; wait(0.01); for (int j = 0; j < 3; j++) { if (columns[j] == 0) { Read = numpad[i][j]; Total(); c++; if (c == 5) { c = 0; } wait(0.005); while (columns[j] == 0) ; } } } } } int Total() { if (Read >= 0) { Num1 *= 10; Num1 += Read; printf("%i\n\r", Num1); } else { return Num1; } return Num1; } 很好,我不知道谁告诉过你,也许HE / SHE应该为官方文档提供帮助。如果不应该使用它,为什么React团队会创建该挂钩? :)

但是,我可以理解,试图避免在useContext中创建像value那样的巨大对象,该对象将状态与操纵状态的函数混合在一起,可能会产生示例所示的异步效果。 / p>

但是,在重构中,您为操作创建者引入了一个非常奇怪且绝对不必要的Context.Provider,而您在第一种方法中只是简单地定义了内联。在我看来,您似乎正在寻找useState。那么,为什么不这样混合使用呢?

useCallback

您的调用代码不需要进行怪异的检查来确认 const useTodos = () => { const { state, dispatch } = useContext(Context); const fetchTodos = useCallback(async id => { const todos = await getTodos(id) dispatch({ type: 'SAVE_TODOS', payload: todos }) }, [dispatch]) return { ...state, fetchTodos }; } 确实存在。

fetchTodos

最后,除非您确实需要使用export const Todos = id => { const { todos, fetchTodos } = useContext(Context); useEffect(() => { fetchTodos() }, []); return ( <div> <pre>{JSON.stringify(todos)}</pre> </div> ); }; 下方树中更多组件的todos + fetchTodos组合,否则您没有在问题中明确指出当不需要它们时,使用上下文会使事情变得复杂。删除额外的间接层,并在您的Todos中直接调用useReducer

情况可能并非如此,但我发现人们正在脑海中混合很多东西,并将简单的东西变成复杂的东西(例如Redux = Context + useReducer)。

希望有帮助!