Hook 规则与自定义 Hook

本文由 AI 协助更新

本文介绍 Hook 的使用规则,以及如何用自定义 Hook 抽离逻辑、让组件专注渲染。


Hook 规则

在使用 Hook 时,需要遵循若干规则open in new window

  1. 总是在 React 函数组件的顶层调用 React Hook,不要在循环语句,条件语句,以及内部函数中调用 React Hook。

  2. 总是在 React 函数组件或自定义 Hook 中调用 Hook,不要在普通函数中调用 Hook。

Facebook 开发了 eslint-plugin-react-hooksopen in new window 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 组件是不是清晰了许多?它不需要关心待办列表是如何管理的,有哪些副作用,只需要专注渲染即可。


目录

  1. 组件 — 认识组件和元素、Props、State、单向数据流
  2. useState、useReducer、useContext
  3. useEffect、useEffectEvent
  4. 闭包陷阱、useRef
  5. useCallback、useMemo、React.memo
  6. Hook 规则、自定义 Hook
上次更新: