React革命性新特性Hooks

1,160 阅读5分钟

Hooks优势

  • 函数组件无this问题
  • 副作用(绑定事件、网络请求、访问DOM)的关注点分离
  • 复用状态逻辑 Custom Hooks

1. State Hooks

1.1 一般写在函数顶部,这里通过setCount更新count

const [count, setCount]

1.2 延迟初始化,只会执行一次
应用场景:值只需要在第一次渲染时使用

const [count, setCount] = useState(() => {
  console.log('initial count', props.defaultCount)
  return props.defaultCount || 0
})

2. Effect Hooks

使用时机:第一次渲染之后和每次更新之后执行。
优势:

  • 提高代码复用 - 代码无需在componentDidMount、componentDidUpdate、componentWillUnmount重复写
  • 关注点分离(相关逻辑分离,不相关逻辑包含) - 多个逻辑多个useEffect互不干扰

const [count, setCount] = useState(0)

useEffect(() => {
  document.title = count
})

/*
用[]effect 内部的 props 和 state 就会一直拥有其初始值表示,
addEventListener和removeEventListener就只会发生在第一次渲染后了。
如果第二个参有值,React会通过对比值是否相等来判断是否重新执行effect。
*/
useEffect(() => {
  document.querySelector('#size').addEventListener('click', onClick, false)

  return () => {
    document.querySelector('#size').removeEventListener('click', onClick, false)
  }
}, [])

return (
  <div>
    {
      count % 2
      ? <span id="size">size: {size.width}x{size.height}</span>
      : <p id="size">size: {size.width}x{size.height}</p>
    }
  </div>
)

3. Context Hooks

优势

  • 让数据在组件树中传递( Consumer替代方案 )

缺点

  • 破坏组件独立性
import React, { useState, createContext, useContext } from 'react';
const CountContext = createContext()

function Counter() {
  const count = useContext(CountContext)
  return (
      <h1>{count}</h1>
  )
}

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

  return (
    <div>
      <button
          type="button"
          onClick={() => {setCount(count + 1)}}
      >
        Click ({count})
      </button>
      <CountContext.Provider value={count}>
        <Counter/>
      </CountContext.Provider>
    </div>
  )
}

4. 使用Memo&Callback Hooks

使用useMemo和useCallback优势

  • 两者区别:useCallback省略了顶层函数。参考
  • 减少不必要的重复计算,避免资源浪费( 此处通过依赖的参数减少不必要的计算,这里是count )
  • useMemo可以依赖另一个useMemo

useMemo和useEffect区别

  • 两者区别:参考
  • useEffect执行的是副作用,在渲染后执行。 useMemo有返回值,返回值直接参与渲染,所以useMemo是在渲染期间完成。
  • useMemo能解决DOM发生变化时,不相干的函数不触发
  • 注:所有副作用都放useEffect里,所有渲染放useMemo里

(1) Memo、useMemo

Memo<Foo/>,针对一个组件渲染是否可以重复执行。 参考

useMemo() => {},针对一段函数逻辑是否重复执行

注意:循环依赖易致浏览器崩溃

import React, { useState, userMemo } from 'react';
function App() {
  const [count, setCount] = useState(0)
 /*
 useMemo、useEffect、useCallback 是否重复执行仅仅判断当前的依赖值与上一次是否一样,
 至于其值是数字,是对象,是true,是false都不重要。
 显然,当count计算到3的时候,useMemo的第二个参数是[true],当count变成4的时候,
 就是 [false]。显然,值发生了改变,所以当count等于3和4的时候,都会触发useMemo重新计算。
 */
  const double = useMemo(() => {
    return count * 2
  }, [count === 3])
  
  const half = userMemo(() => {
    return double / 4
  }, [double])

  return (
    <div>
      <button
          type="button"
          onClick={() => {setCount(count + 1)}}
      >
        Click ({count}), double: ({double})
      </button>
      <Counter count={count}/>
    </div>
  )
}

(2) useCallback

作用:相对于useMemo来说省略了顶层函数。useMemo(() => fn) 等价于 useCallback(fn)

const Counter = memo(function Counter(props) {
  console.log('Counter render')
  return (
      <h1 onClick={props.onClick}>{props.count}</h1>
  )
})

/*
* App重新渲染后,onClick导致组件被重新渲染
* onClick作为函数不应该每次都不变化
*/
function App() {
  const [count, setCount] = useState(0)
  const [clickCount, setClickCount] = useState(0)

  const double = useMemo(() => {
    return count * 2
  }, [count === 3])
  // const double = useCallback(count * 2, [count === 3])

  // 省略顶层函数
  // useMemo(() => fn) 等价于 useCallback(fn)
  // const onClick = useMemo(() => {
  //   return () => {
  //     console.log('Click')
  //     setClickCount((clickCount) => clickCount + 1)
  //   }
  // }, [])
  const onClick = useCallback(() => {
    console.log('Click')
    setClickCount((clickCount) => clickCount + 1)
  }, [])

  return (
    <div>
      <button
          type="button"
          onClick={() => {setCount(count + 1)}}
      >
        Click ({count}), double: ({double})
      </button>
      <Counter count={double} onClick={onClick}/>
    </div>
  )
}

5. Ref Hooks

关于useRef更详解,这位博主文章可参考:useRef参考

useRef的作用(可代替老版String Ref, Callback Ref, CreateRef):

(1) 获取子组件或者DOM节点的句柄

import React, { PureComponent, useRef, useCallback } from 'react';

class Counter extends PureComponent {
  speak() {
    console.log(`now counter is: ${this.props.count}`)
  }
  render() {
    const { props } = this
    return (
        <h1 onClick={props.onClick}>{props.count}</h1>
    )
  }
}

function App() {
  const counterRef = useRef()

  const onClick = useCallback(() => {
    counterRef.current.speak()
  }, [counterRef])

  return (
      <Counter ref={counterRef} count="1" onClick={onClick}/>
  )
}

export default App;

(2) 渲染周期之间共享数据的存储

state也可以实现,但是会触发重新渲染。 保存普通变量,访问上一次渲染时的数据甚至是state

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

function App() {
  const [count, setCount] = useState(0)
  let it = useRef()

  useEffect(() => {
    it.current = setInterval(() => {
      setCount(count => count + 1)
    }, 250)
  }, [])

  useEffect(() => {
    if (count >= 10) {
      clearInterval(it.current)
    }
  })

  return (
      <div>
        <button
            type="button"
            onClick={() => {setCount(count + 1)}}
        >
          Click ({count})
        </button>
      </div>
  )
}
...

6. 自定义Hooks

应用场景:共享状态逻辑

hooks特别擅长用来实现状态行为的复用,如果你的li组件里面仅仅用来渲染数据,并没有与其它组件相复用的状态逻辑,那么还是建议使用component,毕竟组件可以直接声明在jsx中,而hooks必须手动调用

hooks和组件的区别在于输入输出,相同之处在于都可以返回jsx参与渲染.
例,useSize(),useCount()为自定义hook,其中useCount分享了关于获取size的状态逻辑,而组件不能这样实现。

import React, { useState, useEffect, PureComponent, useRef, useCallback } from 'react';

function useCounter(count) {
  const size = useSize()
  return (
      <h1>{count} {size.width} x {size.height}</h1>
  )
}


// 书写:自定义hook以use开头
function useCount(defaultCount) {
  const [count, setCount] = useState(defaultCount)
  let it = useRef()

  useEffect(() => {
    it.current = setInterval(() => {
      setCount(count => count + 1)
    }, 500)
  }, [])

  useEffect(() => {
    if (count >= 10) {
      clearInterval(it.current)
    }
  })

  return [count, setCount]
}

function useSize() {
  const [size, setSize] = useState({
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight
  })

  const onResize = useCallback(() => {
    setSize({
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight
    })
  })

  useEffect(() => {
    window.addEventListener('resize', onResize, false)

    return () => {
      window.removeEventListener('resize', onResize, false)
    }
  }, [])

  return size
}

function App() {
  const [count, setCount] = useCount(0)
  const Counter = useCounter(count)
  const size = useSize()

  return (
      <div>
        <button
            type="button"
            onClick={() => {setCount(count + 1)}}
        >
          Click ({count}), {size.width} x {size.height}
        </button>
         {Counter}
      </div>
  )
}
...