React Hook 使用详解

一:简介

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

hook 引入时间 (react 版本更新大事记)
- 20130529日     v0.3.0        Facebook 开源了React 
	…..
- 20141028日     v0.12.0      使用BSD协议+附加专利授权协议- 20160330日     v0.14.0      拆分成React 和 ReactDOM
- 20160409日     v15.0.0	     挂载组件方式改动、SVG兼容等
- 20170925日     v15.6.2	     开源协议改为MIT
- 20170926日     v16.0.0      引入Fiber
- 20190206日     v16.8.0      Hook 引入
- 20201020日     v17.0.0      Concurrent Mode、底层技术改造、解决一些历史包袱
在Hook之前

函数组件

function Welcome(props) {
  return <h1>hello, {props.name}</h1>
}

类组件
在这里插入图片描述

有了Hook后

函数组件能够完成和类组件一样的功能,有自己的状态,生命周期以及状态控制能力

let timer = null
export default function Clock() {

  const [date, setDate] = useState(new Date())

  useEffect(() => {
    timer = setInterval(() => {
      setDate(new Date())
    }, 1000)
    return () => {
      clearTimeout(timer)
    };
  }, [])

  return <div>
    <h1>Hello,world!</h1>
    <h2>It is {date.toLocaleTimeString()}.</h2>
  </div>
}

二:React 提供的内置Hook API清单

useState
useEffect

useContext
useRe
useCallback
useMemo

useReducer
useLayoutEffect

useImperativeHandle
useDebugValue
useTransition
useDeferredValue

三:Hook API详解

  1. useState
    源码定义
export function useState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

使用示例

const [a, setA] = useState(0)
 const [data, setData] = useState(function () {
    return JSON.parse(bigData)
  })

如果传入函数,函数只在第一此初始化组件的时候执行,后面组件update的时候不会重复计算,适合对初始值需要进行大量计算的场景的优化的时候使用

  1. useEffect

源码定义

export function useEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  const dispatcher = resolveDispatcher();
  return dispatcher.useEffect(create, deps);
}

功能:

  • 处理副作用
  • 实现生命周期
  • 粒度可控

使用示例
在这里插入图片描述
useEffect 作为副作用处理函数,可以模拟 componentDidMount 已经componentDidUpdate等生命周期,

  1. useLayoutEffect

源码定义

export function useLayoutEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  const dispatcher = resolveDispatcher();
  return dispatcher.useLayoutEffect(create, deps);
}

功能:

  • 处理副作用
  • 实现生命周期
  • 粒度可控

使用方法和useEffect一致
区别:会阻塞浏览器渲染,比useEffect先执行

使用场景:
不想让用户看到dom变化过程,副作用回调函数里有dom更新操作适合使用

示例代码
对比下面两组代码,一个是使用的useEffect ,一个是使用的useLayoutEffect

import { React } from '../adaptation'
// import { useEffect } from './react/packages/react'

const { useLayoutEffect, useEffect, useState } = React

export default function TestUseLayoutEffect(props) {
  const [count, setCount] = useState(0)
  
  const handleClick = () => {
    setCount(0)
  }

  useLayoutEffect(() => {
    if (count === 0) {
      // 耗时操作 start
      const arr = []
      for (let i = 0; i < 100000000; i++) {
        arr.push(i)
      }
      // 耗时操作 end
      setCount(Math.random())
    }
  }, [count])

  return <div >
    {count}
    <button onClick={handleClick}>改变</button>
  </div>
}
import { React } from '../adaptation'
// import { useEffect } from './react/packages/react'

const { useEffect, useState } = React

export default function TestUseEffect(props) {

  const [count, setCount] = useState(0)
  const handleClick = () => {
    setCount(0)
  }

  useEffect(() => {
    if (count === 0) {
      // 耗时操作 start
      const arr = []
      for (let i = 0; i < 100000000; i++) {
        arr.push(i)
      }
      // 耗时操作end
      setCount(Math.random())
    }
  }, [count])

  useEffect(() => {
    console.log('xxx123')
  })
  return <div >
    {count}
    <button onClick={handleClick}>改变</button>
  </div>
}
  1. useRef

源码定义

export function useRef<T>(initialValue: T): {| current: T |} {
  const dispatcher = resolveDispatcher();
  return dispatcher.useRef(initialValue);
}

示例

const { useRef } = React

export default function TestRef() {
  const myRef = useRef(null)
  const handleClick = () => {
    console.log(myRef.current)
  }
  return <div>
    <button ref={myRef}  onClick={handleClick}>click</button>
  </div>
}

在这里插入图片描述
useRef vs createRef()

功能类似
useRef 会在整个生命周期中保持引用修改useRef current的值不会触发重新渲染
createRef 每次重新渲染值都会变化

  1. useMemo 和 useCallback

源码定义

export function useCallback<T>(
  callback: T,
  deps: Array<mixed> | void | null,
): T {
  const dispatcher = resolveDispatcher();
  return dispatcher.useCallback(callback, deps);
}

export function useMemo<T>(
  create: () => T,
  deps: Array<mixed> | void | null,
): T {
  const dispatcher = resolveDispatcher();
  return dispatcher.useMemo(create, deps);
}

useMemo 避免更新时重复计算值
useCallback 避免重新定义函数

示例:
下面这个例子,无论更新多少次,只要name不变,就不会执行genWelcomeStr方法

import { React } from '../adaptation'

const { useMemo } = React

export default function TestMemo(props) {
  const { name, count } = props

  const genWelcomeStr = () => {
    console.log('genWelcomeStr called')
    return 'Hello,' + name
  }
  const welcomeStr = useMemo(genWelcomeStr, [name])
  // const welcomeStr = genWelcomeStr()

  return <div>
    {count}
    {welcomeStr}
  </div>
}

下面两组代码,通过父子组件 useCallback 和useMemo的配合,只要name不变,useCallback包裹的函数指向就不会变,子组件中通过useMemo来监听传入的方法,只要方法指向不变就不会重复计算

import { React } from '../adaptation';
// import TestMemo from './TestComponents/TestMemo'
import TestUseCallback from './TestUseCallback';

const { useState, useCallback } = React

function TestUseCallbackInApp() {
  const [count, setCount] = useState(0)
  const [name, setName] = useState('bob')
  
  const handleClick = (count) => {
    setCount(++count)
  }

  const handleGenStr = useCallback(() => {
    return 'name: ' + name
  }, [name])

  return <div className="App">
    <TestUseCallback handleGenStr={handleGenStr} />
    {count}
    <button onClick={() => handleClick(count)}>add</button>
    <button onClick={() => setName('jack' + Date.now())}>changeName</button>
  </div>
}

export default TestUseCallbackInApp;

import { React } from '../adaptation'

const { useMemo } = React

export default function TestUseCallback(props) {
  const { handleGenStr } = props

  const renderStr = useMemo(() => {
    console.log('handleGenStr called')
    return handleGenStr()
  }, [handleGenStr])

  console.log('child comp render')
  return <div >
    {renderStr}
  </div>
}

useMemo和useCallback 适合在大数据量等极端情况来做优化,否则普通数据量下,优化效果不大,甚至由于多了很多监听逻辑,反而使代码体积和性能变得更糟,甚至引起bug,底层引擎的速度可以抵消这一点性能差异。

  1. useReducer
    源码定义
export function useReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useReducer(reducer, initialArg, init);
}

示例
在这里插入图片描述

  1. useContext

源码定义

export function useContext<T>(
  Context: ReactContext<T>,
  unstable_observedBits: number | boolean | void,
) {
  const dispatcher = resolveDispatcher();
  if (__DEV__) {
    if (unstable_observedBits !== undefined) {
      console.error(
        'useContext() second argument is reserved for future ' +
        'use in React. Passing it is not supported. ' +
        'You passed: %s.%s',
        unstable_observedBits,
        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])
          ? '\n\nDid you call array.map(useContext)? ' +
          'Calling Hooks inside a loop is not supported. ' +
          'Learn more at https://fb.me/rules-of-hooks'
          : '',
      );
    }

    // TODO: add a more generic warning for invalid values.
    if ((Context: any)._context !== undefined) {
      const realContext = (Context: any)._context;
      // Don't deduplicate because this legitimately causes bugs
      // and nobody should be using this in existing code.
      if (realContext.Consumer === Context) {
        console.error(
          'Calling useContext(Context.Consumer) is not supported, may cause bugs, and will be ' +
          'removed in a future major release. Did you mean to call useContext(Context) instead?',
        );
      } else if (realContext.Provider === Context) {
        console.error(
          'Calling useContext(Context.Provider) is not supported. ' +
          'Did you mean to call useContext(Context) instead?',
        );
      }
    }
  }
  return dispatcher.useContext(Context, unstable_observedBits);
}

示例

在这里插入图片描述
解决函数组件状态共享问题
避免了层层传递

  1. 其他
  • useDebugValue 给hook 加一个调试专用的名字
function useMyCount(num) {
  const [ count, setCount ] = useState(0);

  useDebugValue('myHook');

  const myCount = () => {
    setCount(count + 2);
  }

  return [ count, myCount ];
}

在这里插入图片描述

  • useImperativeHandle 限制ref对应dom实例对外暴露的内容
import { React } from '../adaptation'

const { useRef,useImperativeHandle,forwardRef } = React

const TestImperative =  forwardRef((props, ref) => {
  const myRef = useRef(null)

  useImperativeHandle(ref, () => ({
    focus: () => {
      myRef.current.focus()
    }
  }))

  return <div>
    <input ref={myRef}/>
  </div>
})

export default TestImperative
import { React } from '../adaptation';
// import TestMemo from './TestComponents/TestMemo'
import TestImperative from './TestUseImperative';

const { useEffect, useRef } = React

function TestUseImperativeInApp() {
  const myRef = useRef(null)

  useEffect(() => {
    console.log(myRef)
    myRef.current.focus()
  }, [])

  return <div className="App">
    <TestImperative ref={myRef}/>
  </div>
}

export default TestUseImperativeInApp;

运行结果
在这里插入图片描述

  • useTransition 组件延迟过渡钩子
  • useDeferredValue 监听状态,延迟过度更新

四:常见问题

  1. 为什么不能在条件、嵌套或者循环语句中定义hook

这是hook链表的基础结构:

type Hooks = {
  memoizedState: any, 
  baseState: any, 
  baseUpdate: Update<any> | null
  queue: UpdateQueue<any> | null
  next: Hook | null, // link 到下一个 hooks,通过 next 串联每一 hooks
}


伪代码描述代码和hook数据结构的对应关系:
在这里插入图片描述
hook是初始化的时候按照链表的方式逐行设置next,如果中途插入了条件语句,第一次初始化生成的链表顺序 跟更新时候的实际useState定义的顺序可能会不一致,导致bug出现。

  1. 拥有Hook的函数组件是否可以完全替代Class组件?
    “官方推荐使用hook来编写组件”

  2. hook 能模拟class 组件的所有生命周期吗?参考下图:

class组件Hooks组件
constructoruseState的时候
getDerivedStateFromPropsuseEffect 配置依赖的变量,会自定按照依赖的新旧对比控制更新
shouldComponentUpdateReact.memo 缓存render的值,配置依赖
render函数返回的内容
componentDidMountuseEffect 依赖配置成空数组
componentDidUpdateuseEffect 通过useRect 缓存一个记录第一次更新的变量来控制只在更新的时候执行
componentWillUnmountuseEffect第一个参数返回一个函数,这个函数将在unmount的时候执行
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

森哥的歌

一杯咖啡半包烟,助我熬过那长夜

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值