react中常见hook的使用方式与区别

一、什么是hook?
react hook是react 16.8推出的方法,可以让函数式组件像类式组件同样拥有state、ref、生命周期等属性。react

二、为何要出现hook?
函数式组件是全局当中一个普通函数,在非严格模式下this指向window,可是react内部开启了严格模式,此时this指向undefined,没法像类式组件同样使用state、ref,函数式组件定义的变量都是局部的,当组件进行更新时会从新定义,也没法存储,因此在hook出现以前,函数式组件有很大的局限性,一般状况下都会使用类式组件来进行代码的编写。数组

三、有哪些经常使用的hook?
(1) useState
使函数式组件也能保存状态的一个hook,这个hook的入参是状态的初始值,返回值是一个数组,数组里第一个参数为状态的值,第二个参数为修改状态的方法。性能优化

// 初始化
const [ count,  setCount ] = useState(0)
// 更新
setCount(count+1)

(2) useEffect
函数式组件用来模拟生命周期的hook,能够模拟组件挂载完成、更新完成、即将卸载三个阶段,即componentDidMount、componentDidUpdate、componentWillUnmount。dom

useEffect的一个参数为函数,表示组件挂载、更新时执行的内容,在函数里再返回一个函数,表示组件即将卸载时调用的函数。ide

第二个参数为可选项,可传入数组,数组里能够为空,表示不依赖任何状态的变化,即只在组件即将挂载时执行,后续任何状态发生了变化,都不调用此hook。数组里也能够定义一或多个状态,表示每次该状态变化时,都会执行此hook。函数

useEffect(()=>{
  // 这样模拟的是 componentDidMount
}, [])

useEffect(()=>{
  // 这样模拟的是componentDidMount 以及当count发生变化时执行componentDidUpdate
}, [count])

useEffect(()=>{
  return ()=>{
  // 这样模拟的是 componentWillUnmount
  }
}, [])

(3) useContext
在没有hook以前,咱们一般都会经过 xxxContext.Provider 和 xxxContext.Consumer 的方式来传递和获取context的值,使用hook以后,传递context的方式不变,但子元素获取context的方式变得更加的简洁。性能

// 之前的定义方式
const CountContext = React.createContext()
 <CountContext.Provider value={{ count: 10 }}>
     <...自定义的组件>
 </CountContext.Provider>

// 子元素
<CountContext.Consumer>
    { value => { console.log(value.count) }} //10
</CountContext.Consumer>

//使用context的获取方式
const countObj = useContext(CountContext)
console.log(countObj.count) // 10

(4) useRef
useRef和类式组件中createRef用法比较相似,返回一个ref对象,这个对象在函数的整个生命周期都不变,根据这个特性,有两种比较常见的用法。优化

① 用于dom元素或者组件上,经过current属性能够获取到dom元素或者类式组件的实例对象。须要注意的是,不管是useRef仍是createRef或者是回调形式、字符串形式的ref,都是不能直接给函数式组件定义的,由于函数式组件的this指向undefined,没有实例对象,只能经过forwardRef定义到函数式组件中的某个dom元素。this

// 这样就将传递给函数式组件的ref绑定在了函数式组件内部的input标签上
import React, { useRef, forwardRef } from 'react'

// 使用函数表达式的方式定义了一个函数式组件
const InputCom = forwardRef((props, ref) => {  
  return <input ref={ref}/> 
})

export default function refDemo(){
    const comRef = useRef()
    return(<div>
       <InputCom ref={comRef}/>     
    </div>) 
 }

② 保存一个数据,该数据若是不手动修改,它在整个生命周期中都不变spa

const [ count, setCount ] = useState(0)
const prevCount = useState(count)
// 当count发生变化时,组件更新,对count的前一次数据进行保存
useEffect(()=>{
  prevCount.current = count
}, [count])

(5) useReducer
useReducer至关因而useState的升级版,做用与useState相似,都是用来保存状态,但它的不一样点在于能够定义一个reducer的纯函数,来处理复杂数据。

// 定义一个处理数据的reducer纯函数
function reducer(prevState, action){
  switch(action.type){
    case 'increment':
      return {...prevState, count: prevState.count + 1 }
    case 'decrement':
      return {...prevState, count: prevState.count - 1 }
    default:
      return prevState
  }
}

// 初始化状态
const [ count, dispatch ] = useReducer(reducer, { count: 0 })
// 修改状态,此时的修改须要派发一个action,让传入的reducer函数进行处理
dispatch({ type: 'increment' })

(6) useCallback
函数式组件中,每一次更新状态,自定义的函数都要进行从新的声明和定义,若是函数做为props传递给子组件,会形成子组件没必要要的从新渲染,有时候子组件并无使用到父组件发生变化的状态,此时可使用useCallback来进行性能优化,它会为函数返回一个记忆的值,若是依赖的状态没有发生变化,那么则不会从新建立该函数,也就不会形成子组件没必要要的从新渲染。

import React, { useState, useCallback, memo } from 'react'
const AddBtn = memo((props)=>{ // 使用函数表达式的方式定义了一个函数式组件
    return<button onClick={props.increment}>+1</button>
})

export default function CallBackPerformance(){
    const [ count, setCount ] = useState(0)
    const [ show, setShow ] = useState(true)
    const increment1 = () => {
        console.log('increment1被调用了')
        setCount(count+1)
    }

   const increment2 = useCallback(()=>{  // 使用了useCallback来优化的函数
         console.log('increment2被调用了')
         setCount(count+1)
    },[count])

    return(<div>
            <div>当前计数:{count}</div>
            <AddBtn increment={increment1} name="1"/>
            <AddBtn increment={increment2} name="2"/>
            <button onClick={e => setShow(!show)}>切换show</button>
        </div>)
}
// 当show这个状态发生变化时,子组件increment1会从新渲染,increment2不会从新渲染

(7) useMemo
useMemo也是返回一个记忆的值,若是依赖的内容没有发生改变的话,这个值也不会发生变化,useMemo与useCallback的不一样点在于useMemo须要在传入的函数里须要return 一个值,这个值能够是对象、函数,格式以下。

useMemo(()=>{
   return { count }
}, [count])

// 使用useCallback时   
const increment2 = useCallback(()=>{
    setCount(count+1)
},[count])

// 使用useMemo模拟useCallback
const increment2 = useCallback(()=>{
    return ()=>{
        setCount(count+1)
    }
},[count])

// useMemo的应用场景,当要进行一些复杂的计算时,
//计算的值没有发生变化,并不须要每一次更新都从新计算

import React, { useState, useMemo } from 'react'
const calculateNum = (count) => {
    console.log('total从新计算了')
    let total = 0
    for(let i = 0; i <= count; i++){
        total += i
    }
    return total
}

export default function ComplexUseMemo(){
    const [ count, setCount ] = useState(10)
    const [ show, setShow ] = useState(true)
    const total = useMemo(()=>{
        return calculateNum(count)
    }, [count])
    return(<div>
        <div>{total}</div>
        <button onClick={e=>setCount(count+1)}>+1</button>
        <button onClick={e=>setShow(!show)}>切换show</button>
    </div>)
}

(8) useImperativeHandle
这个是与forwardRef配合来使用的,当咱们对函数式组件使用forwardRef将ref指定了dom元素以后,那就父组件就能够任意的操做指定的dom元素,使用useImperativeHandle就是为了控制这样的一种行为,指定父元素可操做的子元素的方法。

import React, {  useRef, useImperativeHandle, forwardRef } from 'react'
const InputComp = forwardRef((props, ref)=>{
  const childInputRef = useRef()
  useImperativeHandle(ref, ()=>({
    focus: ()=>{
      childInputRef.current.focus()
    }
  }), [childInputRef.current])

  return<input ref={childInputRef}></input>
})

export default function ImperativeHookDemo() {
  const inputRef = useRef()
  return(<div>
      <InputComp ref={inputRef}/>
      <button onClick={e=>inputRef.current.focus()}>聚焦</button>
    </div>)
}

(9) useLayoutEffect
这个方法与useEffect相似,只是执行的顺序稍有不一样,useEffect是在组件渲染绘制到屏幕上以后,useLayoutEffect是render和绘制到屏幕之间。
useLayoutEffect与useEffect的区别.png

四、如何自定义hook? hook只能定义在函数式组件中,不能在普通函数中使用,若是咱们想要使用到上面的hook来封装一些方法供不少个组件调用,这时候就须要自定义hook,自定义hook的命名就是在函数名前加 use,函数名由 saveInfo 改成 useSaveInfo 便可。

相关文章
相关标签/搜索