Hook 规则与自定义 Hook
本文由 AI 协助更新
本文介绍 Hook 的使用规则,以及如何用自定义 Hook 抽离逻辑、让组件专注渲染。
Hook 规则
在使用 Hook 时,需要遵循若干规则。
总是在 React 函数组件的顶层调用 React Hook,不要在循环语句,条件语句,以及内部函数中调用 React Hook。
总是在 React 函数组件或自定义 Hook 中调用 Hook,不要在普通函数中调用 Hook。
Facebook 开发了 eslint-plugin-react-hooks EsLint 插件来帮助我们遵守以上规则。通过 React Native CLI 创建的项目已经配置好该插件。
自定义 Hook 必须以 use 作为前缀,这是一种约定,就像高阶函数或高阶组件以 with 作为前缀一样。
自定义 Hook
在日常开发中,我们经常自定义 Hook,遵循单一职责原则,将不同业务隔离到不同的自定义 Hook 中,方便维护和复用。
function App() {
const [todos, setTodos] = useState<string[]>([])
const [inputText, setInputText] = useState('')
const [lastUpdated, setLastUpdated] = useState('')
function handleAddTodo() {
if (inputText.trim()) {
setTodos((prev) => [...prev, inputText])
setInputText('')
}
}
useEffect(() => {
const now = new Date().toLocaleTimeString()
setLastUpdated(now)
console.log(`待办列表更新于 ${now},共 ${todos.length} 项`)
}, [todos])
return (
<View style={styles.container}>
<Text style={styles.text}>待办事项: {todos.length}</Text>
<Text style={styles.subtext}>最后更新: {lastUpdated}</Text>
<TextInput
value={inputText}
onChangeText={setInputText}
placeholder="输入新的待办事项"
style={styles.input}
/>
<Button title="添加" onPress={handleAddTodo} />
{todos.map((todo, index) => (
<Text key={index}>{todo}</Text>
))}
</View>
)
}
可以抽成自定义 Hook,把待办列表的数据和行为封装进去:
// useTodos.ts
import { useState, useEffect, useEffectEvent } from 'react'
function useTodos() {
const [todos, setTodos] = useState<string[]>([])
const [lastUpdated, setLastUpdated] = useState('')
function addTodo(title: string) {
if (title.trim()) {
setTodos((prev) => [...prev, title])
}
}
function removeTodo(index: number) {
setTodos((prev) => prev.filter((_, i) => i !== index))
}
const onUpdate = useEffectEvent(() => {
const now = new Date().toLocaleTimeString()
setLastUpdated(now)
console.log(`待办列表更新于 ${now},共 ${todos.length} 项`)
})
useEffect(() => {
onUpdate()
}, [todos])
return { todos, lastUpdated, addTodo, removeTodo }
}
// App.tsx
function App() {
const { todos, lastUpdated, addTodo, removeTodo } = useTodos()
const [inputText, setInputText] = useState('')
function handleAddTodo() {
addTodo(inputText)
setInputText('')
}
return (
<View style={styles.container}>
<Text style={styles.text}>待办事项: {todos.length}</Text>
<Text style={styles.subtext}>最后更新: {lastUpdated}</Text>
<TextInput
value={inputText}
onChangeText={setInputText}
placeholder="输入新的待办事项"
style={styles.input}
/>
<Button title="添加" onPress={handleAddTodo} />
{todos.map((todo, index) => (
<View key={index} style={styles.todoRow}>
<Text>{todo}</Text>
<Button title="删除" onPress={() => removeTodo(index)} />
</View>
))}
</View>
)
}
App 组件是不是清晰了许多?它不需要关心待办列表是如何管理的,有哪些副作用,只需要专注渲染即可。