天天看点

React Hook 理解使用 useState、useEffect、useContext、useRef、自定义hookReact Hook

React Hook

react hooks

的出现,是对

react

中无状态组件的一种升级,使得函数组件也能state 和 生命周期

React Hooks 要解决的问题是状态共享,是继 render-props(渲染属性) 和 higher-order components(HOC;高阶组件) 之后的第三种状态逻辑复用方案,不会产生 JSX 嵌套地狱问题。

1. useState

hooks

的能力,就是让我们在函数组件中使用

state

, 就是通过

useState

来实现的

UseState默认值

  • useState

    支持我们在调用的时候直接传入一个值,来指定

    state

    的默认值,比如这样

    useState(0)

    ,

    useState({ a: 1 })

    ,

    useState([ 1, 2 ])

  • 支持我们传入一个函数,

    useState

    中的函数**只会执行一次*

setUseState时

  • 获取上一轮的值:使用

    useState

    的第二个参数时,我们想要获取上一轮该

    state

    的值的话,只需要在

    useState

    返回的第二个参数,也就是我们上面的例子中的

    setCount

    使用时,传入一个参数,该函数的参数就是上一轮的

    state

    的值
  • 更新值:setUseState必须传入一个新的值与上次值不相等,否则将不会重新渲染(函数会继续执行)
    setCount([...Array])
               

多个 useState 的情况

React依靠useState执行顺序来识别useState,不可以打乱其执行顺序即不能写在判断语句中

2. useEffect

Effect Hook

可以让你在函数组件中执行副作用操作,这里提到副作用,什么是副作用呢,就是除了状态相关的逻辑,比如网络请求,监听事件,查找

dom

useEffect

的第二个参数,有三种情况

  1. 什么都不传,组件每次 render 之后 useEffect 都会调用,相当于 componentDidMount 和 componentDidUpdate
  2. 传入一个空数组 [], 只会调用一次,相当于 componentDidMount 和 componentWillUnmount
  3. 传入一个数组,其中包括变量,只有这些变量任意一个变动时,useEffect 才会执行

3. useContext

Context

提供了一种方式,能够让数据在组件树中传递时不必一级一级的手动传递

context

中的

Provider

Consumer

,在类组件和函数组件中都能使用,

contextType

只能在类组件中使用,因为它是类的静态属性
import React, {createContext, useState, useContext} from 'react';

const Context = createContext(0)

function Parent () {
  const [ count, setCount ] = useState(0)
  return (
    <div>
      点击次数: { count } 
      <button onClick={() => { setCount(count + 1)}}>点我</button>
      <Context.Provider value={count}>
        <Child></Child>
      </Context.Provider>
    </div>
    )
}

function Child () {
  const count = useContext(Context);
  return (
    <div>{ count }</div>
  )
}

           

4. useMemo

根据依赖的值计算出结果(类似Vue计算属性),当依赖的值未发生改变的时候,不触发状态改变

说白了React中 的

memo

就是函数组件的

PureComponent

useMemo

同理也是
function App () {
  const [ count, setCount ] = useState(0)
  const add = useMemo(() => count + 1 , [count])
  return (
    <div>
      点击次数: { count }<br/>
      次数加一: { add }<br/>
      <button onClick={() => { setCount(count + 1)}}>点我</button>
    </div>
  )
}
           

useMemo

也支持传入第二个参数,用法和

useEffect

类似。需要注意的是,

useMemo

会在渲染的时候执行,而不是渲染之后执行,这一点和

useEffect

有区别,所以

useMemo

不建议有 副作用相关的逻辑
  1. 不传数组,每次更新都会重新计算
  2. 空数组,只会计算一次
  3. 依赖对应的值,当对应的值发生变化时,才会重新计算(可以依赖另外一个

    useMemo

    返回的值)
const expensive = useMemo(() => {
  let sum = 0;
  for (let i = 0; i < count * 100; i++) {
    sum += i;
  }
  return sum;
}, [count]);
           

5. useCallback

可以说是

useMemo

的语法糖,能用

useCallback

实现的,都可以使用

useMemo

, 在 react 中我们经常面临一个子组件渲染优化的问题,

useCallback

的第二个参数和

useMemo

一样,没有区别
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。
           

主要区别是

React.useMemo

将调用

fn

函数并返回其结果,而

React.useCallback

将返回

fn

函数而不调用它。

使用场景是:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。

function Parent() {
  const [count, setCount] = useState(1)
  const [val, setVal] = useState('')
  const callback = useCallback(() => {return count}, [count])
  return (<div>
      <h4>{count}</h4>
      <Child callback={callback}/>
      <div>
          <button onClick={() => setCount(count + 1)}>+</button>
          <input value={val} onChange={event => setVal(event.target.value)}/>
      </div>
  </div>)
}

function Child({ callback }) {
  const [count, setCount] = useState(() => callback())
  useEffect(() => {setCount(callback())}, [callback])
  return <div>{count}</div>
}
           

6. useRef

useRef

的作用,总共有两种用法
  1. 获取子组件的实例(只有类组件可用)
  2. 在函数组件中的一个全局变量,不会因为重复

    render

    重复申明, 类似于类组件的

    this.xxx

对于第2点:

有些情况下,我们需要保证函数组件每次 render 之后,某些变量不会被重复申明,比如说 Dom 节点,定时器的 id 等等,在类组件中,我们完全可以通过给类添加一个自定义属性来保留,比如说 this.xxx, 但是函数组件没有 this,自然无法通过这种方法使用,有的朋友说,我可以使用

useState 来保留变量的值,但是 useState 会触发组件 render,在这里完全是不需要的,我们就需要使用 useRef 来实现了,具体看下面例子

function App () {
  const [ count, setCount ] = useState(0)
  const timer = useRef(null)
  let timer2
  
  useEffect(() => {
    let id = setInterval(() => {setCount(count => count + 1)}, 500)
    timer.current = id
    timer2 = id
    return () => {clearInterval(timer.current)}
  }, [])

  const onClickRef = () => {clearInterval(timer.current)}
  const onClick = () => {clearInterval(timer2)}
  

  return (
    <div>
      点击次数: { count }
      <button onClick={onClick}>普通</button>
      <button onClick={onClickRef}>useRef</button>
    </div>
    )
}
           

当我们们使用普通的按钮去暂停定时器时发现定时器无法清除,因为 App 组件每次 render,都会重新申明一次 timer2, 定时器的 id 在第二次 render 时,就丢失了,所以无法清除定时器,针对这种情况,就需要使用到 useRef,来为我们保留定时器 id,类似于 this.xxx,这就是 useRef 的另外一种用法

7. useReducer

useReducer

是什么呢,它其实就是类似

redux

中的功能,相较于

useState

,它更适合一些逻辑较复杂且包含多个子值,或者下一个

state

依赖于之前的

state

等等的特定场景,

useReducer

总共有三个参数
  1. 一个 reducer,就是一个函数类似 (state, action) => newState 的函数,传入 上一个 state 和本次的 action
  2. 初始 state,也就是默认值,是比较简单的方法
  3. 惰性初始化,这么做可以将用于计算 state 的逻辑提取到 reducer 外部,这也为将来对重置 state 的 action 做处理提供了便利
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function App() {
  const [state, dispatch] = useReducer(reducer, {count: 0});
  return (
    <>
      点击次数: {state.count}
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}
           

8. useEffect和useLayoutEffect的区别

useEffect是在渲染完成后执行,如果在渲染后执行DOM操作就可能出现元素的闪烁、瞬移,useLayoutEffect里面的callback函数会在DOM更新完成后立即执行,但是会在浏览器进行任何绘制之前运行完成,阻塞了浏览器的绘制.

区别就在执行的时间不同:

DOM更新 ⇒ 渲染 ⇒ useEffect

DOM更新 ⇒ useLayoutEffect ⇒ 渲染

9. 自定义Hook

关于自定义hook使用:

当组件的整个逻辑可以抽离时候,适当的封装起来,便于引用使用。打个比方,用React Hook 想做一个输入框的UI组件,我们把逻辑抽离,对于UI组件的样式就可以随便写,有几种样式就能生成几种不同的输入框组件,只需要引入自定义hook实现逻辑复用。