React16.8 useHook 特性

Duang2020-01-21 18:19:28前端基础JavaScriptReact

提示

React16.8 迎来了大众期盼已久的函数式编程利器:useHook特性。它能大大增强函数式组件的能力,使得立即执行的、没有生命周期与 state 等等类组件特性的函数式组件拥有相同的能力。

useHook的原理实际上是利用了 JavaScript 的闭包机制,因此在多次执行函数的同时记住一些状态。

useHook拥有多种函数,我们也可以自定义自己的 hook。在这里主要来说说其中最常用的几种,简述他们的大致原理,以及使用时的注意事项。

useState

useState就是为了给纯函数组件加入 class 组件中的 state 能力。它返回一个有两个元素的数组,第一个元素是需要设置的 state 变量,第二个是改变这个变量的 setter 函数。useState的入参决定了返回变量的初始值。

const [flag, setFlag] = useState(false)

在每一次执行 setter 之后,使用了useState的整个函数组件都会被重新执行一次。但是此时useState函数本身并不会被再次执行。这是因为实际上,useState利用了闭包的特性,在闭包内设置了一个私有变量。事实上 setter 改变的值是这个私有变量,我们能取出的变量是这个私有变量的 getter 返回值。

注意,只能在函数的最外层调用 Hook,不能在循环、条件判断或子函数中使用。

这是因为,为了支持在同一个函数组件中使用多次useState,在闭包中,被useState的赋值的私有变量本质上是一个数组类型,通过函数首次被调用的useState的顺序来决定被赋值变量的索引位置,最后 getter 通过索引顺序找到希望取得的变量值。若在循环、条件判断或子函数中使用,则有可能造成函数组件重新执行时顺序与首次执行不一致,这将导致useState的取值混乱。

useEffect

useEffect 为纯函数组件提供了 class 组件中的componentDidMountcomponentDidUpdatecomponentWillUnMountshouldComponentUpdate这些生命周期能力。

useEffect 接收两个参数,第一个参数是一个函数,在函数中执行的动作相当于在类组件中生命周期中的所需要进行的工作。该函数的返回值也是一个函数,若不为空的话它执行的时机等同于componentWillUnMount

第二个参数是一个数组,传入的是需要监听变量列表,若填写该值,只有当监听范围内的变量更新时,才执行第一个参数函数中的动作。起到了类似shouldComponentUpdate的作用。当传入空数组时相当于不监听任何变量,只在组件生成时执行一遍,等同于componentDidMount。当传入为空值时相当于监听所有变量,等同于componentDidUpdate

function fetchSth() {
  /* 执行异步操作 */
}
function destroySth() {
  /* 执行 unmount 操作 */
}

useEffect(() => {
  fetchSth()
  return detroySth()
},[])

useReducer

useReducer事实上与 redux 概念中的reducer十分类似,如果使用过 redux 应该对reducer的概念和定义不会陌生。

它与useState作用场景相似,也能赋予函数组件设定和处理 state 的能力。通常情况下,它们是可以互换的。

但是,对于在组件中存在着多个 state 的定义,或者是操作逻辑复杂需要同时更改多个 state,又或者定义的 state 是一个复杂类型,如数组、对象或存在嵌套,使用useReducer会使得 state 声明更加集中,同时操作逻辑更为清晰,代码也能有更好的可读性。

// 第一个参数:应用的初始化
const initialState = {count: 0, isDone: false};

// 第二个参数:state 的 reducer 处理函数
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {...initialState, count: state.count + 1, isDone: true};
    case 'decrement':
      return {...initialState, count: state.count - 1, isDone: true};
    default:
      return initialState
  }
}

const [state, dispatch] = useReducer(reducer, initialState);

// 用法
function onIncrementClick() {
  dispatch({type: 'increment'})
}

useMemo 和 useCallback

这两个 API 我拿在一起来说,原因很简单,他们的目标都是一致的,都是缓存结果,只不过useCallback是缓存函数本身,useMemo是缓存函数返回的结果。从这里也可以看出,实际上,useCallback是可以被useMemo通过多包裹一层函数实现的。

这两个 API 的函数签名与 useEffect 基本一致,因此调用方法区别不大。在用法功能上,他们之间区别最大的一点是,useEffect 是处理副作用的,是在 render 函数执行完后执行的,相当于 class 组件的didMount/didUpdate。而这两个 API 不能处理副作用,必须同步调用,即在哪里调用就在哪里立即执行函数内容,在下一行就能直接使用返回的结果。

那么,这两个 API 的使用情景是什么呢?答案是性能优化。我们先来看看需要使用useMemo的情况:

function keywordChangeCompute(keyword) {
  /* 执行一些非常消耗资源的同步操作 */
}
const [keyword, setKeyword] = useState('')
useMemo(() => {
  keywordChangeCompute(keyword)
},[keyword])

若不使用useMemo而直接将函数调用写在 render 中的话,当外部组件或者该组件的其他 state 进行频繁的更新时,该组件会不断地多次触发keywordChangeCompute函数,造成不必要的性能损失。而对于使用了useMemo来说,只在keyword变化的时候才会触发一次函数调用,这与useEffect的触发机制是一致的。

对于useCallback的使用,有一种情况是非常实用的,即该方法会被传给子组件的情况:

const [status, setStatus] = useState(false)
const onChange = (value) => {
  fetch(`/api/get/${value}/${status}`)
}

return (
  <>
    <ChildComponent onChange={useCallback(onChange,[status])}>
  </>
)

因为父组件在每一次更新的时候都会生成一个全新的onChange函数,而由于status状态不变,新的onChange函数并没有改变,这样会导致子组件进行一次没必要的更新,造成性能损失。而使用了useCallback后,只会在status状态更新时生成新的函数传给子组件,从而减少子组件渲染次数。

最后更新时间 2/7/2025, 12:13:03 PM
Comments
  • Latest
  • Oldest
  • Hottest
Powered by Waline v2.15.8