使用"useCallback"仅适用于"useReducer"?



我有一个非常简单的待办事项应用程序,使用 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 />被重新渲染并且它传递给<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,我发现突然间我的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的好处吗?如果我不想切换到useReducer,在这种情况下,有没有办法将useCallbackuseState一起使用?

是的,有。

您需要使用setTodos的更新函数语法

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

你已经潜入了一个兔子洞!你最初的问题是你的addTodo()函数依赖于状态todos,因此每当todos发生变化时,你需要创建一个新的addTodo函数并将其传递给NewTodo,导致重新渲染。

您发现了useReducer,这可能有助于解决这个问题,因为化简器通过当前状态,因此不需要在闭包中捕获它,因此它可以在todos的变化中保持稳定。但是,React 的作者已经想到了这种情况,你不需要useReducer(这实际上是作为对那些喜欢 Redux 风格的状态更新的人的让步!正如 Gabriele Petrioli 指出的那样,您可以使用状态设置器的更新用法。请参阅文档。

这允许您编写 Gabriele 提供的回调函数。

所以要回答你的最后一个问题:

这是否意味着 useCallback 总是不能很好地与包含 useState 的函数配合使用?

useCallback可以玩得非常好,但你需要知道你在传递给useCallback的闭包中捕获了什么,如果你在回调中使用来自useState的变量,你需要在deps列表中传递该变量,以确保你的闭包被刷新并且不会被调用过时状态。

然后你必须意识到回调将是一个新函数,从而导致重新呈现到将其作为参数的组件。

这种使用 useCallback 来记忆函数的能力是否被认为是从 useState 切换到 useReducer 的好处?

不,不是真的。useCallback不喜欢useStateuseReducer.正如我所说,useReducer确实是为了支持不同的编程风格,而不是因为它提供了通过其他方式无法获得的功能。

如果我不想切换到useReducer,在这种情况下有没有办法将useCallback与useState一起使用?

是的,如上所述。

正如 Gabriele Petrioli 所提到的,您可以使用回调语法,也可以将回调依赖项的值保留在 ref 中,并在回调中使用该 ref 而不是此处提到的状态。

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

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]
)

相关内容

  • 没有找到相关文章

最新更新