Hook Api的使用

84 阅读4分钟

useState

useState 返回的第一个值将始终是更新后最新的 state, 第二个值是更新state的函数

useState 的使用

const App = () => {

  const [num, setNum] = useState(0)


  const handleClick = () => {
    setNum(num + 1)
  }

  return (
    <div>
      App: {num}
      <p onClick={handleClick}>App Click:</p>
    </div>
  )
}

export default App
    

useEffect

完成副作用操作,组件渲染在屏幕后执行

    
    const Effect1 = () => {

      const [num, setNum] = useState(0)
      useEffect(() => {
        // 以后render都会执行
        console.log('Effect1')
      })
      
      // useEffect模拟 componentDidMount
      useEffect(() => {
         console.log('Effect2')
      }, [])
      
      // useEffect 模拟 componentDidUpdate
      useEffect(() => {
          if(num !== 0) {
             console.log('Effect2')
          }
      }, [num])
      
      
      useEffect(() => {
         console.log('Effect3')
          // useEffect 模拟 componentWillUnmount
         return () => {
             console.log('componentWillUnmount')
         }
      }, [])
      
      const handleClick = () => {
        setNum(num + 1)
      }

      return (
        <div>
          Effect1
          <p onClick={handleClick}>Effect1 Click:</p>
        </div>
      )
    }

    export default Effect1
    
    

补充:


补充一: useEffect 在刷新的的时候都会触发2次
补充二: useEffect 正确的为 DOM 设置事件监听,不应在函数中执行阻塞浏览器更新屏幕的操作

useContext

接收一个 context 对象,并返回该 context 的当前值

    // context.js
    import { createContext } from 'react'
    let obj = {name: 'kris'}
    
    const Context = createContext();
    
    
    // app.js
    // 引入obj, Context
    const app = () => {
        <Context.Provider value={obj}>
          <Toolbar />
        </Context.Provider>
    }
    
    
    // Toolbar.jsx
     // 引入Context
    const Toolbar = () => {
        const { obj } = useContext(Context)
        
        return(
            <div>{obj.name}</div>
        )
    }
    

useReducer

useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,和初始化的参数initialState, 并返回当前的 state 以及与其配套的 dispatch 方法


   const initialState = {num: 1}
   
   const reducer = (state, action) => {
       switch(action.type) {
           case: 'add'
               return {num: state.num + 1}
           case: 'reduce'
               return {num: state.num - 1}
       }
   }
   
   
   const App = () => {
       const [state, dispatch] = useReducer(reducer, initialState)
   
       return(
           <p>{state.num}</p>
           <button onClick={ ()=> dispatch({type: 'add'})}>+</button>
           <button onClick={ ()=> dispatch({type: 'reduce'})}>-</button>
       )
   }
   
   

useContext 和 useReducer 来实现redux

    // context.js
    
    import { useReducer, createContext } from 'react'
    
    export const Context = createContext();
    
    const initialState = {num: 1}
   
    const reducer = (state, action) => {
       switch(action.type) {
           case: 'add'
               return {num: state.num + 1}
           case: 'reduce'
               return {num: state.num - 1}
       }
    }
    
    
    export const Provider = ({ children }) => {
    const [state, dispatch] = useReducer(reducer, initState)

    return (
        <Context.Provider value={{ state, dispatch }}>{children}</Context.Provider>
      )
    }
    
    
    
    
     // app.js
    // 引入 context Provider
    const App = () => {
        <Context>
          <Toolbar />
        </Context>
    }
    
    
    
    
    //Toolbar.jsx
    
    import { useContext } from 'react'
    // 引入context.js 的 Context
    
    
    const Toolbar = () => {
        const { state, dispatch } = useContext(Context)
        
        return(
            <div>{state.num}</div>
            <button onClick={ ()=> dispatch({type: 'add'})}>+</button>
            <button onClick={ ()=> dispatch({type: 'reduce'})}>-</button>
        )
    }

补充React.memo

想直接写useCallback,useMemo,发现都有点绕不开memo,于是单独列出来

React.memo为高阶组件,和React.purecomponent 相似

React.memo: 第一个参数是自定义组件,第二个参数是一个函数,用来判断组件需不需要重新渲染。如果省略第二个参数,默认会对该组件的props进行浅比较

// parent.jsx
import { useState } from 'react'
import Child from './memoChild'

const MemoParent = () => {

  const [num, setNum] = useState(0)
  console.log('MemoParent')
  return(
    <div>
      {num}
      <button onClick={() => setNum(num + 1)}>num++</button>
      <Child />
    </div>
  )
}

export default MemoParent




// child.jsx
import { memo } from 'react'


const Child = () => {
  console.log('memo child')


  const memoClick = () => {}

  return (
    <button onClick={memoClick}>memoClick</button>
  )
}

export default Child

以上每次点击父元素的事件,子组件child, 都会刷新,造成子组件不必要的渲染

这时我们可以用react.memo 来进行优化,对child进行改造


import { memo } from 'react'


const Child = memo(() => {
  console.log('memo child')


  const memoClick = () => {}

  return (
    <button onClick={memoClick}>memoClick</button>
  )
})

export default Child

再次点击父组件点击事件的时候,子组件不会刷新了

useCallback

第一次渲染时执行,缓存函数,之后只有在依赖项改变时才会更新缓存

useCallback

// parent.jsx
import { useState } from 'react'
import Child from './memoChild'

const Parent = () => {

  const [num, setNum] = useState(0)
  console.log('MemoParent')

  const childClick = () => {
    console.log('childClick')
  }
  return(
    <div>
      {num}
      <button onClick={() => setNum(num + 1)}>num++</button>
      <Child childClick = {childClick} />
    </div>
  )
}

export default Parent




import { memo } from 'react'


const Child = memo((props) => {

  const { childClick } = props
  console.log('memo child')


  return (
    <button onClick={childClick}>memoClick</button>
  )
})

export default Child

当在child组件传入一个函数时,发现,再次点击父组件的num++,子组件会重新刷新

这时候useCallback的作用就来了,对父组件稍微的修改一下


import { useState, useCallback } from 'react'
import Child from './memoChild'

const MemoParent = () => {

  const [num, setNum] = useState(0)
  const [title, setTitle] = useState('')
  console.log('MemoParent')

  // 修改处
  const childClick = useCallback(() => {
    setTitle('xxxxxxxxx')
  }, [])
  return(
    <div>
      {num}
      {title}
      <button onClick={() => setNum(num + 1)}>num++</button>
      <Child childClick = {childClick} />
    </div>
  )
}

export default MemoParent

结果: 再次点击 num++ 的时候,子组件不会render

结论: memo会进行一个浅比较,刚进行setXXX 的时候,会把props的参数进行一个浅比较,但是以上在进行setXXX 的时候,传入在子组件的函数地址已经变成另外一个,所以在进行比较的时候,他们是不相等的。 栈和堆

useMemo

第一次渲染时执行,缓存变量,之后只有在依赖项改变时才会重新计算记忆值

// parent.jsx
import { useState, useCallback } from 'react'
import Child from './memoChild'

const MemoParent = () => {

  const [num, setNum] = useState(0)
  const [title, setTitle] = useState('')
  console.log('MemoParent')

  const childClick = useCallback(() => {
    setTitle('xxxxxxxxx')
  }, [])
  return(
    <div>
      {num}
      <button onClick={() => setNum(num + 1)}>num++</button>
      <Child title={title} childClick = {childClick} />
    </div>
  )
}

export default MemoParent



// child.jsx

import { memo } from 'react'


const Child = memo((props) => {

  const { childClick, title } = props
  console.log('memo child')


  return (
    <>
      {title}
      <button onClick={childClick}>memoClick</button>
    </>
  )
})

export default Child

其实如果熟悉了useCallback, 在来看useMemo,发现他们只是缓存的值不一样。

// parent.jsx
import { useState, useCallback, useMome } from 'react'
import Child from './memoChild'

const MemoParent = () => {

  const [num, setNum] = useState(0)
  const [title, setTitle] = useState('')
  console.log('MemoParent')

  const childClick = useCallback(() => {
    setTitle('xxxxxxxxx')
  }, [])

  const memoTtle = useMome(() => ({ value: title }), [title])
  return(
    <div>
      {num}
      {title}
      <button onClick={() => setNum(num + 1)}>num++</button>
      <Child title={memoTtle} childClick = {childClick} />
    </div>
  )
}

export default MemoParent


// child.jsx

import { memo } from 'react'


const Child = memo((props) => {

  const { childClick, title } = props
  console.log('memo child')


  return (
    <>
      {title.value}
      <button onClick={childClick}>memoClick</button>
    </>
  )
})

export default Child

点击父组件的num++ 时候,不会刷新子组件

useRef useImperativeHandle forwardRef

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值

useImperativeHandle 应当与 forwardRef 一起使用

下面是3个实用的联合案列


// parent.jsx
import { useRef } from 'react'
import Child from './memoChild'

const Parent = () => {

  const ref = useRef()

  const getChildValue = () => {
    ref.current.click()
    ref.current.focus()
  }

  return(
    <div>
      <button onClick={getChildValue}>num++</button>
      <Child ref={ref} />
    </div>
  )
}

export default Parent



// child.jsx

import { forwardRef, useImperativeHandle, useRef } from 'react'


const Child = (props, ref) => {

  console.log('memo child')

  const inputRef = useRef();
  useImperativeHandle(ref, () => (
    {
      click: () => {
        console.log('1111')
      },
      focus: () => {
        inputRef.current.focus();
      }
    }
  ))

  const childClick = () => {}


  return (
    <>
    <input ref={inputRef} />
      <button onClick={childClick}>memoClick</button>
    </>
  )
}

export default forwardRef(Child)

自定义hook

react-use:自定大全