React Hooks总结

318 阅读7分钟

什么是Hook

Hook是一些可以让你在函数组件里钩入React state及生命周期等特性的函数。

useState的用法

const [state, setState] = useState(initialValue);

其中state就是一个状态变量,setState是一个用于修改状态的Setter函数,而initialValue则是状态的初始值。

使用useEffect调度的effect不会阻塞浏览器更新屏幕,这样你的应用看起来响应更快。大多数情况下effect不需要同步执行,如果需要同步执行,可以使用useLayoutEffect。

将useEffect放在组件内部让我们可以在effect中直接访问组件内部的state和props,不需要其他API来读取它--它已经保存在函数作用域中。Hook使用了Javascript的闭包机制

每个effect都可以返回一个清除函数,如此可以把添加和移除的逻辑放在一起,你可以使用多个effect,React将按照effect声明的顺序依次调用组件中的每一个effect

清除函数在每次重新渲染时都会执行,而不是只在卸载组件时执行一次

**提示**
1.每调用一次useState,都会在组件之外生成一条Hook记录,同时包括状态值(用useState给定的初始值初始化)和修改状态的Setter函数
2.多次调用useState生成的Hook记录形成了一条链表
3.调用Setter函数不仅会修改状态值还会触发重新渲染
4. 状态和修改状态的Setter函数两两配对,并且后者一定影响前者,前者只被后者影响,作为一个整体他们完全不受外界的影响
5. 鼓励细粒度和扁平化的状态定义和控制,对于代码行为的可预测性和可测试性大有帮助
6. 除了useState,函数组件依然是实现渲染逻辑的“纯组件”,对状态的管理被Hooks所封装了起来

useEffect的用法

useEffect(effectFn, deps)

effectFn是一个执行某些可能具有副作用的Effect函数(例如数据获取、设置/销毁定时器等),它可以返回一个清理函数(Cleanup),例如setInterval和clearInterval

useEffect(() => {
    const intervalId = setInterval(doSomthing(), 1000);
    return () => clearInterval(intervalId)
})
  • 每个Effect必然在渲染之后执行,因此不会阻塞渲染,提高了性能
  • 在运行每个Effect之前,运行前一次渲染的Effect Cleanup函数(如果有的话)
  • 在组件销毁时,运行最后一次Effect的Cleanup函数
**提示**
将Effect推迟到渲染完成之后执行是出于性能的考虑,如果你想在渲染之前执行某些逻辑,俺么可以使用useLayoutEffect函数,使用方法域useEffect完全一致

useEffect的第二个参数:deps(依赖数组)。React会在每次渲染后都运行Effect。而依赖数组就是用来控制是否应该触发Effect,从而减少不必要的计算。具体而言,只要依赖数组中的每一项与上一次渲染相比都没有改变,那么就跳过本次Effect的执行

useEffect钩子与类组件的生命周期相比有两个特点:

  1. 将初次渲染(componentDidMount)、重渲染(componentDidUpdate)和销毁(componentDidUnmount)三个阶段的逻辑用一个统一的API去解决
  2. 将相关的逻辑都放在一个Effect里面,更突出逻辑的内聚性

指定deps为空数组,这样就可以确保Effect只会在组件的初次渲染后执行,在组件销毁时,运行Effet Cleanup函数。

细节

  • useState和useEffect在每次调用时都被添加到Hook链表中;
  • useEffect还会额外地在一个队列中添加一个等待执行地Effect函数;
  • 在渲染完成后,一次调用Effect队列中的每个Effect函数。

Hook规则

只在最顶层使用Hook

不要在循环,条件或嵌套函数中调用Hook确保总是在你的React函数的最顶层调用他们,遵循这条规则,你就能确保Hook在每一次渲染中按照同样的顺序被调用。

只在React函数中调用Hook

不要在普通的JavaScript函数中调用Hook,你可以在React的函数组件中或者自定义Hook中调用其他Hook

自定义Hook必须以use开头,不遵循的话,由于无法判断某个函数是否包含对其内部Hook的调用,React将无法自动检查你的Hook是否违反了Hook的规则

自定义HOOK

自定义Hook本质上只是把调用内置Hook的过程封装成一个个可复用的函数,并不影响Hook链表的生成和读取

注意:不能在条件语句中写hook,因为hook在每次渲染时的查找是根据一个“全局”的下标对链表进行查找的,如果放在条件语句中使用,有一定的机率造成拿到的状态出现错乱。

React性能优化的8种方式

使用React.Memo来缓存组件

提升应用程序性能的一种方法是实现memoization。Memoization是一种优化技术,主要通过存储昂贵的函数调用结果,并在再次发生相同的输入时返回缓存的结果,一次来加速程序。

父组件的每次状态更新,都会导致子组件重新渲染,即使传入子组件的状态没有变化,为了减少重复渲染,我们可以使用React.memo来缓存组件,这样只有当传入组件的状态值发生变化时才会重新渲染,如果传入相同的值,则返回缓存的组件。

示例

export default React.memo((props) => {
    return (
        <div>{props.value}</div>
    )
})

使用useMemo缓存大量的计算

有时渲染是不可避免的,如果您的组件是一个功能组件,重新渲染会导致每次调用大型计算函数,这是非常消耗性能的,我们可以使用新的useMemo钩子来“记忆”这个计算函数的结果,这样只有传入的参数发生变化后,该计算函数才会重新调用计算新的结果。

通过这种方式,您可以使用从先前渲染计算的结果来挽救昂贵的计算耗时,总体目标是减少javascript在呈现组件期间必须执行的工作量,以便主线程被阻塞的时间更短。

// 避免这样做
function Component(props) {
    const someProp = heavyCalculation(props.item);
    return <AnotherComponent someProp={someProp} />
}
​
// 只有props.item改变时someProp的值才会被重新计算
function Component(props) {
    const someProp = useMemo(() => heavyCalculation(props.item), [props.item]);
    return <AnotherComponent someProp={someProp}>
}

使用React.PureComponent,shouldComponentUpdate

父组件状态的每次更新,都会导致子组件的重新渲染,即使是传入相同props,但是这里的重新渲染不是说会更新DOM,而是每次都会调用diff算法来判断是否需要更新DOM,这对于大型组件例如组件树来说是非常消耗性能的。

在这里我们可以使用React.PureComponent,shouldComponentUpdate生命周期来确保只有当组件props状态改变时才会重新渲染。

示例:

export default function ParentComponent(props) {
  return (
    <div>
      <SomeComponent someProp={props.somePropValue}
    <div>
      <AnotherComponent someOtherProp={props.someOtherPropValue} />
    </div>
   </div>
 )
}
​
export default function SomeComponent(props) {
  return (
    <div>{props.someProp}</div>  
  )
}
​
// 只要props.somePropValue 发生变化,不论props.someOtherPropValue是否发生变化该组件都会发生变化
export default function AnotherComponent(props) {
  return (
    <div>{props.someOtherProp}</div>  
  )
}
​
// 我们使用React.PureComponent 或shouldComponentUpdate 进行如下优化// 第一种PureComponent 
class AnotherComponent extends React.PureComponent {
    render() {
        return <div>{this.props.someOtherProp}</div>
    }
}
// 第二种shouldComponentUpdate
class AnotherComponent extends Component {
    shouldComponentUpdate(nextProps) {
        return this.props !== nextProps
    }
    render() {
        return <div>{this.props.someOtherProp}</div>
    }
}

PureComponent会进行浅比较来判断组件是否应该重新渲染,对于传入的基本类型props,只要值相同,浅比较就会认为相同,对于传入的引用类型props,浅比较只会认为传入的props是不是同一个引用,如果不是,哪怕这两个对象中的内容完全一样,也会被认为是不同的props。 需要注意的是在对于那些可以忽略渲染时间的组件或者是状态一直变化的组件则要谨慎使用PureComponent,因为进行浅比较也会花费时间,这种优化更适用于大型的展示组件上。大型组件也可以拆分成多个小组件,并使用memo来包裹小组件,也可以提升性能。

避免使用内联对象