React Hooks 入门

# 常用 hook

# useState

建立一个 state,改变 state 会触发组件重新渲染

import React, { useState } from 'react'

function Example() {
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  )
}

setCount 可传入函数,回调值为当前 state

setCount((prev) => {
  console.log(prev)
  return prev + 1
})

某些闭包情况下可能获取不到准确的 state 值,例如

import React, { useState, useEffect, useRef } from 'react'

// 计时到 30s 停止
function Example() {
  const timerRef = useRef()
  const [count, setCount] = useState(0)

  useEffect(() => {
    timerRef.current = setInterval(() => {
      if (count === 30) {
        clearInterval(timerRef.current)
      }
      setCount(count + 1)
    }, 1000)
  })

  return <div>{count} seconds.</div>
}

由于 setInterval 形成了闭包,setInterval 回调函数里面的两个 count 值永远为 0。此时 setCount 应使用回调函数写法

import React, { useState, useEffect, useRef } from 'react'

// 计时到 30s 停止
function Example() {
  const timerRef = useRef()
  const [count, setCount] = useState(0)

  useEffect(() => {
    timerRef.current = setInterval(() => {
      setCount((prev) => {
        if (count === 30) {
          clearInterval(timerRef.current)
          return prev
        }
        return prev + 1
      })
    }, 1000)
  })

  return <div>{count} seconds.</div>
}

# useEffect

Effect Hook 可以让你在函数组件中执行副作用操作。此时已进入 render commit 阶段,可以获取组件 Dom.

  • 什么是副作用操作?

先要从纯函数说起,纯函数的特性:相同的输入,永远得到相同的输出。受外界影响的、甚至改变外界状态的函数被称为是有副作用的函数。我们常常在 useEffect 里面获取 api 数据,操作 dom,因此说 useEffect 是有副作用的。

可以把 useEffect Hook 看做 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合

  • 模拟 componentDidMount

安装 eslint-plugin-react-hooks,会自动添加 hooks 依赖项。若想避免依赖项被添加到 useEffect 中,可以用 useCallback包裹回调函数。

function Example({ dispatch, state }) {
  const fetchData = useCallback(() => {
    axios.get('http://xxx').then((data) => {
      dispatch({
        type: 'ACTION_NAME',
        payload: data
      })
    })
  }, [dispatch])
  useEffect(fetchData, [])

  return <div>{JSON.stringfy(state)}</div>
}
  • 模拟 componentDidUpdate

还是使用 useCallback 包裹回调函数,useEffect 依赖项视需求添加。相当于 componentDidUpdate 的 if 判断

  • 模拟 componentWillUnmount

useEffect 的回调函数的返回函数会在组件即将取消挂载的时候执行

import React, { useState, useEffect, useRef } from 'react'

// 计时到 30s 停止
function Example() {
  const timerRef = useRef()
  const [count, setCount] = useState(0)

  useEffect(() => {
    timerRef.current = setInterval(() => {
      setCount((prev) => {
        if (count === 30) {
          clearInterval(timerRef.current)
          return prev
        }
        return prev + 1
      })
    }, 1000)
    return () => {
      clearInterval(timerRef.current)
    }
  })

  return <div>{count} seconds.</div>
}

# useMemo

计算并缓存一个值,依赖项改变时重新计算

function Example({ a, b }) {
  const computedValue = useMemo(() => a + b, [a, b])

  return <div>{computedValue}</div>
}

不要试图用 useMemo 创建一个永远不会变化的值,这会引起一些问题

function Example({ a, b }) {
  const computedValue = useMemo(() => a + b, []) // 不要传空依赖项

  return <div>{computedValue}</div>
}

# useCallback

缓存一个方法,useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

# useImperativeHandle

给函数式组件添加实例方法,与 forwardRef 组合使用

import React, { useImperativeHandle, forwardRef } from 'react'

function Form(props, ref) {
  useImperativeHandle(ref, () => ({
    validate: () => {
      // ...
    },
    resetValidate: () => {
      // ...
    }
  }))
  // ...
}

export default forwardRef(Form)