使用`useCallback`仅与`useReducer`一起使用?

时间:2020-06-11 19:15:57

标签: javascript reactjs react-hooks

我有一个使用React构建的非常简单的待办事项应用程序。

App.js看起来像这样

const App = () => {
  const [todos, setTodos] = useState(initialState)

  const addTodo = (todo) => {
    todo.id = id()
    todo.done = false
    setTodos([...todos, todo])
  }

  const toggleDone = (id) => {
    setTodos(
      todos.map((todo) => {
        if (todo.id !== id) return todo

        return { ...todo, done: !todo.done }
      })
    )
  }

  return (
    <div className="App">
      <NewTodo onSubmit={addTodo} />
      <Todos todos={todos} onStatusChange={toggleDone} />
    </div>
  )
}

export default App

其中<NewTodo>是呈现输入表单以提交新待办事项的组件,而<Todos />是呈现待办事项列表的组件。

现在的问题是,当我切换/更改现有的待办事项时,<NewTodo>将被重新渲染,因为<App />被重新渲染并将其传递给{{1} },即<NewTodo>也将更改。由于它是新的addTodo,因此每个渲染其中定义的函数也将是新函数。

为解决此问题,我首先将<App />包裹在<NewTodo>中,以便在道具没有变化时跳过重新渲染。而且我想使用React.memo来获得记忆化的useCallback,以便addTodo不会得到不必要的重新渲染。

<NewTodo>

但是我意识到显然 const addTodo = useCallback( (todo) => { todo.id = id() todo.done = false setTodos([…todos, todo]) }, [todos] ) 依赖于addTodo,它是保存现有待办事项的状态,并且在切换/更改现有待办事项时会发生变化。因此,此记忆功能也会更改。

然后,我将应用程序从使用todos切换为useState,发现突然useReducer不再依赖状态,至少对我来说是这样。< / p>

addTodo

正如您在此处看到的, const reducer = (state = [], action) => { if (action.type === TODO_ADD) { return [...state, action.payload] } if (action.type === TODO_COMPLETE) { return state.map((todo) => { if (todo.id !== action.payload.id) return todo return { ...todo, done: !todo.done } }) } return state } const App = () => { const [todos, dispatch] = useReducer(reducer, initialState) const addTodo = useCallback( (todo) => { dispatch({ type: TODO_ADD, payload: { id: id(), done: false, ...todo, }, }) }, [dispatch] ) const toggleDone = (id) => { dispatch({ type: TODO_COMPLETE, payload: { id, }, }) } return ( <div className="App"> <NewTodo onSubmit={addTodo} /> <Todos todos={todos} onStatusChange={toggleDone} /> </div> ) } export default App 仅宣布发生在状态上的操作,而不是执行与状态直接相关的操作。这样就可以了

addTodo

我的问题是,这是否意味着 const addTodo = useCallback( (todo) => { dispatch({ type: TODO_ADD, payload: { id: id(), done: false, ...todo, }, }) }, [dispatch] ) 永远无法与包含useCallback的函数很好地配合?使用useState来记住功能的这种功能是否被视为从useCallback切换到useState的好处?如果我不想切换到useReducer,在这种情况下是否可以将useReduceruseCallback一起使用?

3 个答案:

答案 0 :(得分:4)

是的。

您需要使用setTodos

的更新函数语法
const addTodo = useCallback(
    (todo) => {
      todo.id = id()
      todo.done = false
      setTodos((todos) => […todos, todo])
    },
    []
  )

答案 1 :(得分:1)

您已经钻了一个兔子洞!您最初的问题是您的addTodo()函数依赖于状态todos,因此,每更改todos时,您都需要创建一个新的addTodo函数并将其传递给{{1} },导致重新渲染。

您发现了NewTodo,这可以帮助解决此问题,因为reducer传递了当前状态,因此不需要在闭包中捕获它,因此它可以在useReducer的更改中保持稳定。但是,React作者已经考虑到了这种情况,您不需要todos(这实际上是对喜欢Redux风格的状态更新的人们的让步!)。正如Gabriele Petrioli指出的那样,您可以只使用状态设置程序的更新用法。参见the docs

这使您可以编写Gabriele提供的回调函数。

因此,请回答您的最终问题:

这是否意味着useCallback总是不能很好地与包含useState的函数一起播放?

useReducer可以很好地发挥效果,但是您需要了解传递给useCallback的闭包中捕获的内容,并且如果您使用的是useCallback中的变量您的回调函数需要在useState列表中传递该变量,以确保刷新您的闭包,并且不会以过期状态对其进行调用。

然后您必须意识到回调将是一个新函数,从而导致重新渲染以该回调为参数的组件。

使用useCallback来记住该功能是否被认为是从useState切换到useReducer的好处?

不,不是。 deps不喜欢useCallbackuseState。就像我说的,useReducer确实是在支持另一种编程风格,不是因为它提供了其他方式无法提供的功能。

如果我不想切换到useReducer,在这种情况下是否可以将useCallback与useState一起使用?

是的,如上所述。

答案 2 :(得分:1)

Gabriele Petrioli所述,您可以使用回调语法,也可以将回调的依赖项值保留在ref中,并在您的回调中使用该ref而不是状态as mentioned here。 / p>

在您的示例中,这种方法如下所示:

const [todos, setTodos] = useState([]);
const todosRef = useRef(todos);
useEffect(() => {
  todosRef.current = todos;
},[todos]);

const addTodo = useCallback(
  todo => {
    todo.id = id()
    todo.done = false
    setTodos([…todosRef.current, todo])
  },
  [todosRef]
)