web前端 - React hooks

72 阅读3分钟

React hooks是什么?

hooks让函数组件能实现和类组件一样的功能(例如状态管理、副作用、ref),同时相比state和生命周期函数,hooks更利于逻辑的拆分与复用。

why hooks

React官方一致提倡函数式编程,但是函数组件的能力不足以支持、相对而言类组件的功能更为强大。hooks解决了函数组件无法做内部的状态管理、产生副作用等问题,让函数组件的能力边界几乎和类组件一致,目的是让开发者在使用React时更好的实践FP的编程思想。

内置hooks

状态hooks

状态hooks的功能是给函数组件内部添加一个状态值,内置的状态hooks有useState和useReducer。

两者的底层实现一致,useState可以理解为useReducer的一个特例。

function Counter() {
  const [count, setCount] = useState(0)
  const handleClick = () => setCount(count => count + 1)
  return (
    <>
      <div>{{ count }}</div>
      <button onClick={handleClick}>increase</button>
    </>
  )
}

ref hooks

当你需要在函数中持久化某些信息,而这些信息又不会触发rerender(状态会触发再渲染),此时需要使用ref, useRef这个hooks可以帮助开发者在函数组件中定义一个ref。

ref和状态区别就在于:ref的更新不会触发渲染,可以用来存储dom节点或者计时器ID。

ref解决了在函数组件中需要存储一些值,但这些值不影响渲染逻辑的需求。

ref.current存储dom节点

function Test() {
  const ref = useRef(null)
  return <div ref={ref}>test</div>
}

ref.current存储某个和ui无关的变量

function Counter() {
  const ref = useRef(0);
  const handleClick = () => {
    ref.current = ref.current + 1;
    alert('你点击了 ' + ref.current + ' 次!');
  }

  return (
    <button onClick={handleClick}>
      点击我!
    </button>
  );
}

effect hooks

effect hooks用来在特定的时机执行一些副作用,这些副作用是与组件外部的系统交互的(如DOM API, 网络请、BOM API等)

常用的effect hooks有:useEffect和useLayoutEffect

// 在组件第一次挂载后请求网络数据
useEffect(() => {
  fetch(url)
  .then(res => res.json)
  .then(...)
}, [])

// 监听事件,需要返回一个函数用于取消监听(计时器同理)
useEffect(() => {
  window.addEventListener('resize', fn)
  return () => window.removeEventListener('resize', fn)
})

useEffect在dev模式下执行两次的问题

这是React为了验证useEffect的编写是否违反规则,特别是是否设置了正确的cleanup函数,同时useEffect中如果有数据请求,应当避免其会修改服务端数据。

useEffect与useLayoutEffect的区别

useEffect不会阻塞浏览器渲染,它是一个异步回调,如果一定要在浏览器更新前执行,则需要使useLayoutEffect(componentDidMount、componentDidUpdate也是在浏览器更新前执行)。这样的好处是避免2次浏览器绘制导致的“闪烁”,但同时阻塞渲染也可能带来性能问题。

useContext

context是组件树跨层级传递数据的一种方式,使用useContext可以向上查找最近的Provider提供的context值。

性能hooks

useMemo和useCallback这2个hooks用于性能优化,如果页面无性能问题没必要使用。

useMemo用于缓存一个计算代价非常高的结果,甚至可以是jsx

const jsx = useMemo(() => data.map(item => <ListItem {...item}/>), [data])

useCallback用于缓存一个函数值,这样传递给子组件的函数是用一个引用,子组件使用优化(如memo)时就可以跳过更新。

// count状态的更新时,Button接受到的handleClick和上一次渲染是同一个,可以跳过更新
function Counter() {
  const [count, setCount] = useState(0)
  const handleClick = useCallback(() => setCount(count => count + 1), [])
  return (
    <>
      <div>{{ count }}</div>
      <Button handleClick={handleClick}/>
    </>
  )
}

const Button = React.memo(function(props) { 
  return <div onClick={props.handleClick}>increase</div>
})