react hooks 归纳

React hooks的作用就是把无状态组件(只用于展示的组件)升级为函数组件(可以完成类组件的功能)

//类组件
import { Component } from 'react'

class app extends Component {
    constructor() {
        this.state = {
            count: 0
        }
    }
    render() {
        let { count } = this.state
        return (
            <div>
                <div> { count } </div>
                <button onClick={() => this.setState({ count: count++ })}>点击</button>
            </div>
        )
    }
}

//函数组件
import { useState } from 'react'

function App () {
    const [ count, setCount ] = useState(0) 
    //count 的初始值为0 
    //setCount 是专门用来设置count值的函数
    
    return (
      <div>
        <div> { count } </div>
        <button onClick={() => { setCount(count + 1)}}>点击</button>
      </div>
      )
  }
//上面两种方式实现的是同样的功能。

1、useState

(请参照上方代码)
使用useState可以使用多个,用来放不同的数据。遵循规范,放在最上面。

 //初始值
 const [ count, setCount ] = useState(0) 可以写为
 const [ count, setCount ] = useState( () => {
 	你的逻辑代码
 	return 0
 } ) 
  
//设置值
在使用setCount时如果值未改变,组件不会重新渲染

setCount可以放一个回调函数
<button onClick={ () => {setCount( a => a+1)} }>点击</button>
其中a表示count上次的值,同样可以完成count+1的功能

2、useEffect

useEffect官方执行副作用的函数。副作用指除了状态相关的逻辑,比如网络请求,监听事件,查找 dom
我理解为就是一个函数,是componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合拳

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

  • 什么都不传,组件每次 render 之后 useEffect 都会调用,相当于 componentDidMount 和 componentDidUpdate
  • 传入一个空数组 [], 只会调用一次,相当于 componentDidMount 和 componentWillUnmount
  • 传入一个数组,其中包括变量,只有这些变量变动时,useEffect 才会执行
 function App () {
    const [ count, setCount ] = useState(0)
    const [ width, setWidth ] = useState(document.body.clientWidth)

    const onChange = () => {
      setWidth(document.body.clientWidth)
    }

    useEffect(() => {
      // 相当于 componentDidMount
      window.addEventListener('resize', onChange, false)
      
	  console.log('我只在最开始执行一次')
	  
      return () => {
        // 相当于 componentWillUnmount
        window.removeEventListener('resize', onChange, false)
      }
    }, [])

    useEffect(() => {
    	// 相当于 componentDidUpdate
        console.log(`每次重新渲染我都会执行`)
    })

    useEffect(() => {
      console.log(`count 每次变化我都会执行`)
    }, [ count ])

    return (
      <div>
        页面名称: { count } 
        页面宽度: { width }
        <button onClick={() => { setCount(count + 1)}}>点我</button>
      </div>
      )
  }

3、useContext

获取上层通过.Provider共享的数据,会一直向上找,没找到就用默认值

// 创建一个 context
const Context = createContext(0)

// 组件一
function Item1 () {
  const count = useContext(Context);
  return (
    <div>{ count }</div>
  )
}

// 组件二
function Item2 () {
  const count = useContext(Context);
  return (
    <div>{ count }</div>
  )
}

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

4、useMemo和useCallback

两者区别不大,类似于Vue中的computed,用于优化性能

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

useMemo 也支持传入第二个参数,用法和 useEffect 类似

  1. 不给第二个参数,每次更新都执行
  2. 空数组,只执行一次
  3. 有值数组,值变化后执行

需要注意的是,useMemo 会在渲染的时候执行,而不是渲染之后执行,这一点和 useEffect 有区别,所以 useMemo 不建议有 副作用相关的逻辑

useCallback 可以说是 useMemo 的语法糖,能用 useCallback 实现的,都可以使用 useMemo。 在 react 中我们经常面临一个子组件渲染优化的问题,尤其是在向子组件传递函数props时,每次 render 都会创建新函数,导致子组件不必要的渲染,浪费性能,这个时候,就是 useCallback 的用武之地了,useCallback 可以保证,无论 render 多少次,我们的函数都是同一个函数,减小不断创建的开销,具体如何使用看下面例子

const onClick = `useMemo`(() => {
  return () => {
    console.log('button click')
  }
}, [])

const onClick = useCallback(() => {
 console.log('button click')
}, [])

同样,useCallback 的第二个参数和useMemo一样,没有区别

5、useRef

  1. 获取子组件的实例(只有类组件可用)
  2. 在函数组件中的一个全局变量,不会因为重复 render 重复申明, 类似于类组件的 this.xxx
// 使用 ref 子组件必须是类组件
class Children extends PureComponent {
 render () {
   const { count } = this.props
   return (
     <div>{ count }</div>
   )
 }
}

function App () {
 const [ count, setCount ] = useState(0)
 const childrenRef = useRef(null)
 
 const onClick = useMemo(() => {
   return () => {
     console.log(childrenRef.current)
     setCount((count) => count + 1)
   }
 }, [])
 
 return (
   <div>
     点击次数: { count }
     <Children ref={childrenRef}  count={count}></Children>
     <button onClick={onClick}>点我</button>
   </div>
   )
}

有些情况下,我们需要保证函数组件每次 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 = useCallback(() => {
    clearInterval(timer.current)
  }, [])

  const onClick = useCallback(() => {
    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 的另外一种用法

6、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>
   </>
 );
}

7、useImperativeHandle

useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。
简单理解:子传父 子组件自定义东西给父组件用
(如果子组件是类组件不需要这么麻烦)

官方建议,useImperativeHandle应当与 forwardRef 一起使用,具体如何使用看下面例子

function Child (props, ref) {
const [num,setNum] = useState(0)

 const parentUse = useCallback (() => {
   console.log('给父组件调用')
 }, [])
 
 //自定义暴露给父组件的实例
 useImperativeHandle(ref, () => ({
   parentUse : () => {
     parentUse()
   },
   setChildNum : (value) => {
   	setNum(value)
   }
 }));

 return (
   <div > { props.count }</div>
 )
}

const ChildDiv = forwardRef(Child)
function App () {
 const [ count, setCount ] = useState(0)
 const childRef = useRef(null)

 const onClick = useCallback (() => {
   setCount(count => count + 1)
   childRef.current.setChildNum(num => num + count )
   childRef.current.parentUse()
 }, [])
 
 return (
   <div>
     点击次数: { count }
     <ChildDiv ref={childRef}  count={count}></ChildDiv >
     <button onClick={onClick}>点我</button>
   </div>
   )
}

其它hook

  1. useLayoutEffect
  2. useDebugValue

自定义hook

自定义 Hook,可以将组件逻辑提取到可重用的函数中,来解决逻辑难以复用问题

function useWidth (defaultWidth) {
  const [width, setWidth] = useState(document.body.clientWidth)

  const onChange = useCallback (() => {
    setWidth(document.body.clientWidth)
  }, [])

  useEffect(() => {
    window.addEventListener('resize', onChange, false)

    return () => {
      window.removeEventListener('resize', onChange, false)
    }
  }, [onChange])

  return width
}

function App () {

  const width = useWidth(document.body.clientWidth)

  return (
    <div> 
      页面宽度: { width }
    </div>
    )
}

自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。它的名字应该始终以 use 开头,这样可以一眼看出其符合 Hook 的规则。

参考文献

  1. https://zh-hans.reactjs.org/docs/hooks-intro.html
  2. 呕心沥血,一文看懂 react hooks
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值