React之HOOK

226 阅读4分钟

一、HookReact类组件

为什么使用Hook

  1. 先说下class组件的不足
  • class组件之间复用状态逻辑很难(render props(渲染属性)和高阶组件,会导致层级冗余)
  • 复杂组件变得难以理解
  • 难以理解的 class
  • this指向混乱
  1. Hook 优势
  • 函数组件无this指向问题
  • 方便复用状态逻辑
  • 副作用的关注点分离

二、Hook

1、Hook简介

问题列表

  • Hook 是什么?
  • 什么时候我会用 Hook?

Hook 是一些可以让你在函数组件里 “钩入” React state 及生命周期等特性的函数。 Hooks解决的问题:

  • 可以将组件之间的共同逻辑提取处理,共享状态逻辑
  • 业务逻辑更统一,避免了同个一逻辑分散的不同的生命周期函数种,写法更统一。

2、Hook API

基础 Hook

  • useState
  • useEffect
  • useContext

额外的 Hook

  • useReducer
  • useCallback
  • useMemo
  • useRef
  • useImperativeHandle
  • useLayoutEffect
  • useDebugValue

2、State Hook

useState问题列表:

  • 调用 useState 方法的时候做了什么?
  • useState 需要哪些参数?
  • useState 方法的返回值是什么?
  • useState 与 class 组件中的 setState 方法有何不同?
  • 如果新的 state 需要通过使用先前的 state 计算得出,useState怎么处理?

3、Effect Hook

Effect 问题列表:

  • useEffect 做了什么?
  • 为什么在组件内部调用 useEffect?
  • useEffect 会在每次渲染后都执行吗?
  • effectcomponentDidMountcomponentDidUpdate 有何不同?执行时机有和区别?
  • 为什么要在 effect 中返回一个函数?
  • React 何时清除 effect?
  • 为什么每次更新的时候都要运行 Effect?
  • 怎么通过跳过 Effect 进行性能优化?

useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 具有相同的用途,只不过被合并成了一个 API。可以访问到组件的 propsstate

4、Hook规则

Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:

  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。?
  • 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。

在单个组件中使用多个 State Hook 或 Effect Hook,React 怎么知道哪个 state 对应哪个 useState?

5. useContext

import React, {createContext, useContext} from 'react';
const CountContext = createContext();

function App(props) {
    return (
        <div>
          <button onClick={()=> {setCount(count+1);console.log(count)}}>click:{count}</button>
          {
            count % 2 ?
                <span id='size'>size: {size.width} x {size.height}</span>
                : <p id='size'>size: {size.width} x {size.height}</p>
          }
          <CountContext.Provider value={count}>
            <Counter/>
          </CountContext.Provider>
        </div>
  );
}

function Counter() {
  const count = useContext(CountContext);
  return (
      <div>
        这是count:
        <h1>{count}</h1>
      </div>
  )
}

6. useMemo

把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo

import React, { useState, useMemo } from 'react'

export default function MemoCallback() {
    const [count, setCount] = useState(0);
    const double = useMemo(() => {
        return count * 2;
    }, [count === 9])
    return (
        <div>
            <button onClick={() => {setCount(count + 1)}}>click: {count}</button>
            <span>double: {double}</span>
        </div>
    )
}

7. useCallback

useCallbackuseMemo一种分类。当useMemo返回是一个函数的时候,等级于useCallback

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

8. useRef

用途:

  • 获取子组件或者DOM节点的句柄
  • 渲染周期之间共享数据的存储
// 可以利用 useRef 实现自动计时器,因为 useRef 能够记录上次数据
import React, {useState, useMemo, memo, useRef, useCallback, useEffect, PureComponent} from 'react'

class Counter extends PureComponent{

    speak() {
        console.log('hahhah')
    }

    render() {
        const {props} = this;
        return (
            <h1 onClick={props.onClick}>{props.count}</h1>
        )
    }
}

export default function UseRef(props) {
    const [count, setCount] = useState(0);
    const [clickCount, setClickCount] = useState(0)
    const counterRef = useRef();
    let it = useRef();

    const onClick = useCallback(() => {
        setClickCount((clickCount)=> clickCount + 1)

        console.log(counterRef.current)
    },[counterRef])

    useEffect(() => {
        it.current = setInterval(() => {
            setCount(count => count + 1)
        }, 1000)
    }, []);

    useEffect(() => {
        console.log(234)
        if(count > 6) {
            clearInterval(it.current)
        }
    })
    return (
        <div>
            <span>{count}</span>
            <Counter ref={counterRef} count={double} onClick={onClick}/>
        </div>
    )
}

9. 自定义Hook

概念

  • 自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。
  • 自定义 Hook 是一种自然遵循 Hook 设计的约定,而并不是 React 的特性。

使用注意:

  • 自定义 Hook 必须以 “use” 开头吗?
  • 在两个组件中使用相同的 Hook 会共享 state 吗?
  • 自定义 Hook 如何获取独立的 state?
  • 如何在多个 Hook 之间传递信息?
import React, {useState, useRef, useCallback, useEffect} from 'react'

// 自定义hook
 function useCount(defaultValue) {
     const [count, setCount] = useState(defaultValue);

     let it = useRef();

     useEffect(() => {
         it.current = setInterval(() => {
             setCount(count => count + 1)
         }, 1000)
     }, []);

     useEffect(() => {
         if(count > 6) {
             clearInterval(it.current)
         }
     });
     return [count, setCount]
 }

// 自定义hook
function useSize() {
     const [size, setSize] = useState({
         width: document.documentElement.clientWidth,
         height: document.documentElement.clientHeight
     });

     const onResize = useCallback(() => {
         setSize({
             width: document.documentElement.clientWidth,
             height: document.documentElement.clientHeight
         })
     }, [])

     useEffect(() => {
         window.addEventListener('resize', onResize, false);
         return () => {
             window.removeEventListener('resize', onResize, false);
         }
     }, [])

     return size;
 }
 
// 自定义hook
function useCounter(count) {
    const size = useSize();

    return (
        <h1>{count}
            size: {size.width} X {size.height}</h1>
    )
}

// 函数组件
export default function ZidingyiHook(props) {
    const [count, setCount] = useCount(0);
    const Counter = useCounter(count)
    let size = useSize();

    return (
        <div>
            {Counter}
            <p>{size.width} x {size.height}</p>
        </div>
    )
}

10. Hook常见问题

  • 类实例成员变量如何映射到Hook?
  • Hook 中如何获取历史props 和state
  • 如何强制更新一个Hoos组件?