转译:使用react hooks优化回调函数在组件间的传递,useState,useReducer?

栏目: IT技术 · 发布时间: 3年前

内容简介:我们先看一下使用为了避免遍历层次结构中的每个组件,我们可以通过带有useContext Hook的React Context传递“ API对象”。

我们先看一下使用 useState hooks写的todoList组件,里面我们需要层层传递回调函数。

import React, { useState } from "react";

const AddTodoBtn = ({ onAddTodo }) => (
  <div className="action-add">
    <button onClick={onAddTodo}>Add new todo</button>
  </div>
);

const RemoveAllBtn = ({ onRemoveAll }) => (
  <div className="action-remove-all">
    <button onClick={onRemoveAll}>Remove all todos</button>
  </div>
);

// This component and every one placed between the TodoList
// and the final component which will use a TodoList callback
// should pass it down.
const Actions = ({ onAddTodo, onRemoveAll }) => (
  <div className="actions-container">
    <AddTodoBtn onAddTodo={onAddTodo} />
    <RemoveAllBtn onRemoveAll={onRemoveAll} />
    //...more actions
  </div>
);

const MenuOnTop = ({ onAddTodo, onRemoveAll }) => (
  <div className="menu-on-top">
    <Actions onAddTodo={onAddTodo} onRemoveAll={onRemoveAll} />
    <Img url="some-path" />
  </div>
);

const TodoList = () => {
  const [todos, setTodos] = useState([]);
  const addTodo = () => setTodos([...todos, {}]);
  const removeAll = () => setTodos([]);

  return (
    <div className="todo-list">
      <MenuOnTop onAddTodo={addTodo} onRemoveAll={removeAll}/>
      //...todos
    </div>
  );
};

转译:使用react hooks优化回调函数在组件间的传递,useState,useReducer? 我们可以看到在父组件 TodoList 里面定义的两个方法 addTodoremoveAll ,它们被一层一层传递到子组件 AddTodoBtnRemoveAllBtn ,也就是真正执行该方法的组件。

TodoList -> MenuOnTop -> Actions -> AddTodoBtn/RemoveAllBtn

为了避免遍历层次结构中的每个组件,我们可以通过带有useContext Hook的React Context传递“ API对象”。

import React, { useState, createContext, useContext } from "react";

const AddTodoBtn = () => {
  const api = useContext(TodosApi);

  return (
    <div className="action-add">
      <button onClick={api.addTodo}>Add new todo</button>
    </div>
  );
};

const RemoveAllBtn = () => {
  const api = useContext(TodosApi);

  return (
    <div className="action-remove-all">
      <button onClick={api.removeAll}>Remove all todos</button>
    </div>
  );
};

const Actions = () => (
  <div className="actions-container">
    <AddTodoBtn />
    <RemoveAllBtn />
    //...more actions
  </div>
);

const MenuOnTop = () => (
  <div className="menu-on-top">
    <Actions />
    <Img url="some-path" />
  </div>
);

const TodosApi = createContext(null);

const TodoList = () => {
  const [todos, setTodos] = useState([]);
  const addTodo = () => setTodos([...todos, {}]);
  const removeAll = () => setTodos([]);

  const api = { addTodo, removeAll };

  return (
    <div className="todo-list">
      <TodosApi.Provider value={api}>
        <MenuOnTop />
        //...state.todos
      </TodosApi.Provider>
    </div>
  );
};

这样我们可以在子组件 AddTodoBtnRemoveAllBtn 里面直接获取到最外层父组件 TodoList 里面定义的 addTodoremoveAll 方法。

但是仍然有些问题,问题在于API对象在每次重新渲染时都会更改,因此从上下文读取它的所有组件也会被重新渲染。

官方团队推荐我们使用 useReducer 来替代 useState , 使用useReducer管理state可以使我们仅向下传递,并且由于传递过程在渲染之间不会更改,因此不会重新渲染从上下文中读取状态的组件。

我们的代码就应该这样写了。

import React, { useReducer, createContext, useContext } from "react";

const AddTodoBtn = () => {
  const dispatch = useContext(TodosDispatch);

  return (
    <div className="action-add">
      <button onClick={() => dispatch({ type: "add" })}>Add new todo</button>
    </div>
  );
};

const RemoveAllBtn = () => {
  const dispatch = useContext(TodosDispatch);

  return (
    <div className="action-remove-all">
      <button onClick={() => dispatch({ type: "removeAll" })}>Remove all todos</button>
    </div>
  );
};

const Actions = () => (
  <div className="actions-container">
    <AddTodoBtn />
    <RemoveAllBtn />
    //...more actions
  </div>
);

const MenuOnTop = () => (
  <div className="menu-on-top">
    <Actions />
    <Img url="some-path" />
  </div>
);

const TodosDispatch = createContext(null);

const TodoList = () => {

  const reducer = (state, action) => {
    switch (action.type) {
      case "add":
        return { todos: [...state.todos, {}] };
      case "removeAll":
        return { todos: [] };
      default:
        return state;
    }
  }; 

  const [state, dispatch] = useReducer(reducer);

  return (
    <div className="todo-list">
      <TodosDispatch.Provider value={dispatch}>
        <MenuOnTop />
        //...state.todos
      </TodosDispatch.Provider>
    </div>
  );
};

通过这个改动,我们发现有点小问题:

  1. 我们不得不把无状态组件 AddTodoBtn 和I RemoveAllBtn 变成了“有状态组件”了,它们其实只是想要点击的回调而已。
  2. 我们不得不用 useReducer hook了,在上面的简单示例里面确实没有问题,但是如果复杂场景的话,之前依据 useState 做的工作可能无法再复用了。

我们怎么解决这些问题呢?

结合上面的两个示例我们准备这样做。

  1. 将需要的变量通过上下文传递给组件
  2. 用一个包含我们需要的回调函数的“API对象”,这些回调函数可以用 useState 或这 useReducer 都行
  3. 缓存这个“API对象”,确保在组件重新渲染也不会改变
  4. 确保“API对象”里面的回调不依赖当前作用域

怎么缓存

缓存“API对象”是我们需要除了用 useMemo 缓存这个对象外,还要 useCallback 缓存它里面包含的回调

import React, { useState, createContext, useMemo, useCallback } from "react";

const TodosApi = createContext(null);

const TodoList = () => {
  const [todos, setTodos] = useState([]);
  //  THIS IS WRONG: The callback memoization depends on the scope
  const addTodo = useCallback(() => setTodos([...todos, {}]));
  const removeAll = useCallback(() => setTodos([]));

  const getApi = useMemo(() => ({ addTodo, removeAll }));

  return (
    <div className="todo-list">
      <TodosApi.Provider value={getApi()}>
        <MenuOnTop />
        //...state.todos
      </TodosApi.Provider>
    </div>
  );
};

好的,我们已经缓存了API对象,因此它在重渲染时也不会改变,但是取决于作用域的操作(比如 addTodo 取决于待办事项状态值)在重渲染上将会不一致,因为状态变量的值不会改变。

我们可以将 todos 作为 useMemouseCallback 的依赖传给第二个参数,这样 todos 变化时“API对象”和回调就会随之改变了。

import React, { useState, createContext, useMemo, useCallback } from "react";

const TodosApi = createContext(null);

const TodoList = () => {
  const [todos, setTodos] = useState([]);
  //  It works but still it's not the solution we wanted 
  const addTodo = useCallback(() => setTodos([...todos, {}]), [todos]);
  const removeAll = useCallback(() => setTodos([]), []);

  // We have to add all scope variables used by the callbacks
  // to the array of dependencies
  const getApi = useMemo(() => ({ addTodo, removeAll }), [todos]);

  return (
    <div className="todo-list">
      <TodosApi.Provider value={getApi()}>
        <MenuOnTop />
        //...state.todos
      </TodosApi.Provider>
    </div>
  );
};

如果“API对象”只依赖它定义时的作用域的话,我们只是局部缓存。

If we want our API object to never change between rerenders, we need our callbacks to be independent of the scope. We can do this by updating the state with functional updates: We can pass a function to setState, the function will receive the previous value of the state, and return the updated value.

如果我们希望“API对象”在重渲染的永远不改变的话,我们需要让回调独立于作用域。 我们将更新的state改用函数更新的方式 :我们传递一个函数给 setState (而不再是直接传入新的state),这个函数会收到旧的state,它需要返回新的state。

import React, { useState, createContext, useMemo, useCallback } from "react";

const TodosApi = createContext(null);

const TodoList = () => {
  const [todos, setTodos] = useState([]);
  // Independent from its scope
  const addTodo = useCallback(() => setTodos(prevTodos => [...prevTodos, {}]));
  const removeAll = useCallback(() => setTodos([]), []);

  // Now there isn't any dependency with the scope, 
  // the API object won't change!
  const getApi = useMemo(() => ({ addTodo, removeAll }), []);

  return (
    <div className="todo-list">
      <TodosApi.Provider value={getApi()}>
        <MenuOnTop />
        //...state.todos
      </TodosApi.Provider>
    </div>
  );
};

现在我们的“API对象”彻底独立于作用域了,它就是做dispatch的,重渲染时它再也不会改变了。

总结下:

To avoid passing callbacks down through the component hierarchy, we can follow the suggested pattern of passing the dispatch function of the useReducer using the Context, but we end up being forced to manage the state via a reducer, and we have to spread some knowledge about the state through the components that use this callbacks. We suggest to do this by passing a memoized API object using the context. To achieve the complete invariability of the API object, we use functional updates so we don’t depend on the scope of the callbacks.

为了避免回调函数在组件中层层传递,我们先尝试了官方推荐的解决方案—— useReducer ,但是因为鉴于要强制使用reducer的方式管理state,我们放弃了这种方案,我们不得不继续使用回调来传递state。 后来我们建议通过context传递一个缓存的“API对象”,为了确保“API对象”的不变性,我们改用函数更新来解除了对回调作用域的依赖。

本文绝大部分内容转自 Passing callbacks down with React Hooks


以上所述就是小编给大家介绍的《转译:使用react hooks优化回调函数在组件间的传递,useState,useReducer?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

腾讯之道

腾讯之道

艾永亮、刘官华、梁璐 / 机械工业出版社 / 2016-7-19 / 59

放眼整个中国乃至全球,腾讯都是一家成功的互联网企业,它代表中国企业在世界互联网版图中竖起了一面高高的旗帜。腾讯为何能取得如此大的成就,它的成功方法和商业逻辑是什么?你是不是和无数中国企业和商界人士一样,都想向腾讯取取经,但是又不得其门而入? 腾讯一直以低调、务实著称,所 以腾讯及其内部员工都极少对外界分享他们的经验;加之腾讯的商业模式多元、业务繁多且交叉、体量又极其庞大,使得从外部来系统研究......一起来看看 《腾讯之道》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

在线进制转换器
在线进制转换器

各进制数互转换器

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码