React hooks 深度使用总结

472 阅读5分钟

这半年来重度使用了react,有的项目中完全使用了hooks,还记得使用react从刚开始的懵懵懂懂,跌跌撞撞,到现在的游刃有余,一路走来真是觉得万分煎熬啊,因为我以前工作中更多的是用vue,有vue的使用经验,所以拾起react也没那么难,但也没并不轻松。

工作中,有的项目react版本较低,只能使用class组件,当我习惯了写hooks,切换到使用class组件的项目中时,总是觉得很难受,感觉写起来很繁琐,啰嗦,因为总是要写this,this,this,this.state,this.props,还有很多个生命周期方法,还有让我很痛苦的是当我变更状态使用setState时,state是个对象的时候,我还得复制一份这个对象,生成新的对象,再setState。

现在,只要是在支持hooks的项目中,我都用hooks来写组件,它解决了我以上的所有痛点,让开发组件的体验焕然一新,在一些简单的项目中我发现竟然用两个hooks api就完全足够了,比如useState和useEffect,真是太神奇了!

好了,现在进入主题吧。

useState

一个鲜活的应用程序都有大量状态,内容无法变化的话,我们是无法使用的,所以useState在react中是非常重要的。

更多介绍可以看官方文档哈,下面来看看它的一个有趣应用。

实现组件的强制刷新。

function App() {
//使用useForceUpdate
const forceUpdate = useForceUpdate()

  return (
    <div>
      <div>Time: {Date.now()}</div>
      <button onClick={forceUpdate}>强制更新</button>
    </div>
  )
}

const useForceUpdate = () => {
  const [, setState] = useState({})

  return () => setState({})
}

我们可以发现每次点击强制更新button的时候,time的内容也跟着变化。

useEffect

每当组件渲染完成时,会调用这个effect,那么我们可以在这里做数据获取、操作DOM和BOM等工作,你还可以返回一个函数,这个函数会在组件卸载前被调用。

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

  const handleClick = () => {
    setCount(count + 1)
  }

  useEffect(() => {
    console.log('组件已挂载')

    return () => {
      //可以在这做一些组件的清除工作
      console.log('组件已卸载')
    }
  })

  return (
    <div>
      <button onClick={handleClick}>Increment</button>
    </div>
  )
}

//首次渲染和点击2次Increment按钮,控制台依次打印:
//"组件已挂载"
//"组件已卸载"
//"组件已挂载"
//"组件已卸载"
//"组件已挂载"

由此发现,组件挂载完成时,会调用useEffect,当点击Increment按钮,组件的state发生变更时,组件需要重新渲染,然后组件经历了被卸载和被挂载的过程。

useEffect如何只在组件挂载时执行

很简单,useEffect的第2个参数是个依赖数组,当我们指定了依赖数组,就只会在依赖发生改变时执行useEffect,它还可以接收一个空数组,这样就只会在组件挂载时执行了。

useEffect(() => {
    console.log('管你state如何变,我永远只会在组件挂载时执行一次')
}, [])

useRef和useEffect结合忽略首次渲染(执行)

useRef不仅仅可以获取dom,它还可以存储任何值,而不随状态改变导致组件重新渲染而改变。

const isMounted = useRef(false)

useEffect(() => {
  if (!isMounted.current) {
    isMounted.current = true
  } else {
    //do something
  }
})

useCallback

useCallback 返回一个经过缓存的回调函数 , 它接收2个参数,一个是回调函数,一个是依赖数据,当依赖项变化的时候,才会重新更新回调函数。它主要是用来做性能优化的。

比如下面的例子,假如我们的useForceUpdate经常被使用的话,可以使用useCallback包裹,这样每次调用和组件重渲染使用的都是缓存过的函数,减少重新初始化函数的性能损耗。

function App() {

  const forceUpdate = useForceUpdate()

  return (
    <div>
      <div>time:{Date.now()}</div>
      <button onClick={forceUpdate}>强制更新</button>
    </div>
  )
}

const useForceUpdate = () => {
  const [, setState] = useState({})

  return useCallback(() => setState({}), [])
}

useMemo

useMemo 可以缓存一个值,一般是用来做性能优化的,它接受2个参数,一个是返回计算值的回调函数,一个是依赖数组,当依赖发生变化时,才会重新更新函数,下面来看代码掌握它。

function App() {

  const forceUpdate = useForceUpdate() 
  
  const count = useMemo(() => {
    console.log('called')
    return 1 + 1
  }, [])

  return (
    <>
      <div>count:{count}</div>
      <button onClick={forceUpdate}>强制更新</button>
    </>
  )
}

运行程序首次打印called,当我们点击强制更新button时,控制台没有再次打印called,由此发现,useMemo具有缓存计算的能力,所以在一些耗时的计算中可以使用它。

useImperativeHandle 和 forwardRef结合

父组件通过ref访问子组件的时候,可以在子组件中使用useImperativeHandle向父组件暴露接口(实例方法),useImperativeHandle通常和forwardRef结合使用。

function App() {
  const inputModal = useRef()

  const showInputModel = () => {
    inputModal.current.show()
  }

  const hideInputModel = () => {
    inputModal.current.hide()
  }

  return (
    <div>
      <button onClick={showInputModel}>show modal</button>
      <button onClick={hideInputModel}>hide modal</button>
      <InputModal ref={inputModal} />
    </div>
  )
}

const InputModal = forwardRef((props, ref) => {
  const inputRef = useRef()
  const [show, setShow] = useState(false)

  useImperativeHandle(ref, () => ({
    show: () => setShow(true),
    hide: () => setShow(false),
  }))

  useEffect(() => {
    if (show) inputRef.current.focus()
  })

  return (
    show && (
      <div>
        <form>
          <div>
            <label>What's your name?</label>
            <input ref={inputRef} />
          </div>
        </form>
      </div>
    )
  )
})

代码中,使用了useImperativeHandle向父组件暴露show和hide方法,这何尝不是实现控制model显示和隐藏的方法?

useLayoutEffect

useLayoutEffect 和 useEffect 功能相同,但不同的地方在于渲染机制,useLayoutEffect会跟dom同步渲染,在里面更新dom布局会同步触发重渲染。

function App() {
  const app = useRef()

  // useEffect(() => {
  //   app.current.style.left = '100px'
  // })

  useLayoutEffect(() => {
    app.current.style.left = '100px'
  })

  return (
    <div
      ref={app}
      style={{
        position: 'absolute',
        width: '80px',
        height: '80px',
        backgroundColor: 'tomato',
      }}
    />
  )
}

运行上面代码,不断刷新浏览器你可以发现useEffect里的色块你能看到它的布局过程,但在useLayoutEffect中是看不到的,当你遇到这种情况的时候,可以尝试用它来解决。

最后:

上面是我在项目中使用hooks的一些发现和经验,分享给大家,hooks的开发体验非常棒,使用起来简单,轻松,几个hooks即可满足开发需求,极大的降低了开发者的学习成本,大大的提高了开发效率。

除去吃喝拉撒睡,可见我使用react和hooks的时间也不算太长,如有错误的地方,欢迎留言与我交流。