"对react的自带hooks进行的归纳总结"
react-hooks:
常见,useState、useEffect、useMemo、useRef、useCallback、useContext
少见,useReducer、useImperativeHandle、useDebugValue、useTransition、useLayoutEffect

useState
因为函数式组件没有this与state,无法直接修改state,因此useState是react对外暴露的一个对state操作的钩子函数。
该函数可接收一个参数,是为变量的默认值,如果不写的话,变量默认为undefined
useState返回一个数组,数组的第一位是变量名,第二位是改变该变量的函数,查看源码,会发现这个函数是一个dispatch函数(异步),所以直接查看该变量得到的是原先的值,如果要查看该变量值,配合使用useEffect去监听。
改变变量的函数有一个参数,使用的时候,直接放值类型,不可是表达式等。

 const [number, setNumber] = useState(0) /* 0为初始值 */
 return (<div>
     <span>{ number }</span>
     <button onClick={ ()=> {
       setNumber(number+1) /* 写法一 */
       setNumber(number=>number + 1 ) /* 写法二 */
       console.log(number) /* 这里的number是不能够即时改变的  */
     } } >num++</button>
 </div>)

useEffect
函数式组件也没有生命周期,用useEffect取代生命周期。同时对应前文所讲,useState修改的值,在这里是可以取到的,实现数据的交互。useEffect也叫副作用,顾名思义,是在渲染完成后执行。实际使用中useEffect必须填写第二个参数来使用,否则这可能会导致性能问题,比如两次渲染的数据完全一样。
第一个参数是effect函数,第二个参数是依赖项,是一个数组,如果是一个空数组,useEffect不依赖于props或state中的任何值,所以它永远都不需要重复执行,该钩子会实现componentDidMount生命周期函数的功能,即页面加载时触发一次。
如果不是空数组,就要添加useState中的变量,这个变量每次发生变化就会触发useEffect,即componentDidUpdate,同时也包括componentDidMount

  const [num, setNum] = useState(1)
  useEffect(() => {
    console.log('componentDidUpdate')
  }, [num])
  useEffect(() => {
    console.log('componentDidMount')
  }, [])
  return (
    <>
      <div>{num}</div>
      <button onClick={() => { setNum(num + 1) }}>点击</button>
    </>
  )


数组中可以放多个useState的值,每个值发生变化都会触发,实际开发中,这个依赖项数组只放一个变量。第二个参数一般情况下不要使用引用类型,因为比对的是浅比较,引用类型的指针地址没有变化的话,进入不到useEffect中。
useEffect第一个effect函数参数中还可以返回一个函数,表示解绑副作用,比如在effect函数中有个定时器,在组件销毁时需要清除定时器,此时要解绑,就可以在effect函数中返回一个函数,这个函数相当于componentWillUnmount生命周期。在使用中,重新渲染依赖项,也会先执行解绑副作用,在进行副作用,即effect函数。

  const [num, setNum] = useState(1)
  useEffect(() => {
    console.log('componentDidUpdate')
    return ()=>{
      console.log('componentWillUnmount')
    }
  }, [num])
  return (
    <>
      <div>{num}</div>
      <button onClick={() => { setNum(num + 1) }}>点击</button>
    </>
  )


useMemo
与useEffect相似,参数也是一个函数与一个依赖项数组,主要作用是有助于避免在每次渲染时都进行高开销的计算。
它仅会在某个依赖项改变时才重新计算值,这个过程会在页面渲染时进行,类比生命周期就是shouldComponentUpdate
如果没有提供依赖项数组,即没有第二个参数,useMemo在每次渲染时都会计算新的值,相当于没有使用useMemo。
不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于useEffect的适用范畴,而不是useMemo。
useMemo会返回一个memo函数的结果return () => name,用定义的变量去接收并使用该定义的变量。
与useEffect的比较:
useMemo: 渲染期间控制,值/函数的触发/改变
useEffect: 渲染结束后控制,值/函数的触发/改变

// 产品名称列表
const nameList = ['apple', 'peer', 'banana', 'lemon']

const example = (props) => {
  // 产品名称、价格
  const [price, setPrice] = useState(0)
  const [name, setName] = useState('apple')

  function getProductName() {
    console.log('getProductName触发')
    return name
  }

  // 只对price响应
  useEffect(() => {
    console.log('price effect 触发')
  }, [price])

  // 只对name响应
  useEffect(() => {
    console.log('name effect 触发')
    getProductName()
  }, [name])

  // memo化的getProductName函数
  const memo_getProductName = useMemo(() => {
    console.log('name memo 触发')
    return () => name  // 返回一个函数
  }, [name])

  return (
    <Fragment>
      <p>{name}</p>
      <p>{price}</p>
      <p>普通的name:{getProductName()}</p>
      <p>memo化的name:{memo_getProductName()}</p>
      <button onClick={() => setPrice(price + 1)}>价钱+1</button>
      <button onClick={() => setName(nameList[Math.random() * nameList.length << 0])}>修改名字</button>
    </Fragment>
  )
}

useMemo也可以包裹标签与子组件

/* 用 useMemo包裹的list可以限定当且仅当list改变的时候才更新此list,这样就可以避免selectList重新循环 */
 {useMemo(() => (
      <div>{
          selectList.map((i, v) => (
              <span
                  className={style.listSpan}
                  key={v} >
                  {i.patentName}
              </span>
          ))}
      </div>
), [selectList])}
/* 只有当props中,list列表改变的时候,子组件才渲染 */
const  goodListChild = useMemo(()=> <GoodList list={ props.list } /> ,[ props.list ])

useCallback
useMemo和useCallback接收的参数都是一样,都是在其依赖项发生变化后才执行,都是返回缓存的值,两者结合React.Memo方法的使用是常见的性能优化方式,可以避免由于父组件状态变更导致不必要的子组件进行重新渲染。区别在于useMemo返回的是函数运行的结果,useCallback返回的是函数。返回的callback可以作为props回调函数传递给子组件。
如果react + ts,useCallback必须传入第二个参数,useMemo可选填。
如果传入的第二个参数是一个空数组,后续的渲染,只会记住第一次渲染的,不会再触发渲染。只有匹配的依赖项改变才会触发渲染,如果不匹配依赖项,效果与空数组相同。

// 子组件
function Childs(props) {
    console.log("子组件渲染了");
    return (
        <>
            <button onClick={props.onClick}>子组件改subtitle</button>
        </>
    );
}
const Child = React.memo(Childs);
function DemoUseCallback() {
    const [title, setTitle] = useState(1);
    const [subtitle, setSubtitle] = useState(1);
    const callBack = () => {
        setTitle(title + 1);
    };
    const callBackHook = useCallback(()=>{
        setTitle(title + 1);
    },[title])
    return (
        <div className="App">
            <h1>这是一个 title{title}</h1>
            <h2>我是一个 subtitle{subtitle}</h2>
            <button onClick={() => setSubtitle(subtitle + 1)}>改subtitle</button>
            // 如果不用useCallback,改变subtitle时都会渲染子组件
            {/* <Child onClick={callBack} /> */}
            <Child onClick={callBackHook} />
        </div>
    );
}

一个组件重新渲染,有三种情况:
1、子组件自身状态;
2、父组件状态改变,但是传递子组件的props没有改变;
3、父组件状态改变,但是传递子组件的props改变。
例子中子组件自身状态没有改变,使用了React.memo也避免了父组件状态改变但props没变的情况下渲染子组件的情况,所以,就是父组件渲染,函数组件都会重头开始重新执行,传递给子组件的props如果包含了函数方法,就会被改变,从而子组件也被重新渲染。
使用了useCallback后,父组件重新渲染时,如果useCallback的依赖项没有变化,则不会被重新执行,也就不会重新渲染子组件了。

useCallback与useMemo一个缓存的是函数,一个缓存的是函数的返回的结果。useCallback是来优化子组件的,防止子组件的重复渲染。useMemo可以优化当前组件也可以优化子组件,优化当前组件主要是通过memoize来将一些复杂的计算逻辑进行缓存。当然如果只是进行一些简单的计算也没必要使用useMemo。
我们可以将useMemo的返回值定义为返回一个函数这样就可以变通的实现了useCallback。useCallback(fn, deps) 相当于useMemo(() => fn, deps)。


useContext
用于组件之间的传值,需要注意的是useContext和redux的作用是不同的,一个解决的是组件之间值传递的问题,一个是应用中统一管理状态的问题,但通过和useReducer的配合使用,可以实现类似Redux的作用。
在最外层组件引用createContext,创建Context对象,再在组建中使用Context对象的Provider组件包裹需要接收数据的后代组件,通过value传递需要共享的状态。
子组件使用useContext并导入根组件的Context对象,即可获取value中共享的状态。

import React, { useState, createContext, useContext } from 'react';
const CountContext = createContext();
function Box() {
    const [count, setCount] = useState(0);
    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={() => { setCount(count + 1) }}>click me</button>
            <CountContext.Provider value={count}>
                <Counter />
            </CountContext.Provider>
        </div>
    )
}
function Counter() {
    const count = useContext(CountContext)  //一句话就可以得到count
    return (
        <div>
            <h2>{count}</h2>
        </div>
    )
}

useRef
有两个作用,一个是获取dom,一个是缓存数据
先说第一种情况,在函数组件中声明一个useRef的变量,在dom中将该变量绑定在ref属性上,这样除了第一次渲染的时候获取不到dom结构外,之后的操作逻辑都可以通过变量的current属性获取到该dom并可以对此dom直接进行操作。
第二种情况,声明一个useRef的变量,该hook的参数为默认值,修改变量的current属性时,不会引发组件重新渲染,因此,在下次渲染的时候,可以获取到上次useRef存储的值,实现缓存数据的功能。

function UseRef() {
  const divRef = useRef();
  console.log("整个div", divRef);
  function changeDOM() {
    // 获取整个div
    console.log("整个div", divRef.current);
    // 获取div的class
    console.log("div的class", divRef.current.className);
    // 获取div自定义属性
    console.log("div自定义属性", divRef.current.getAttribute("data-clj"));
  }
  //*************************************************************************
  const [count, setCount] = useState(0);
  const numRef = useRef(count);
  console.log(numRef)
  useEffect(() => {
    numRef.current = count;
  }, [count]);

  return (
    <div>
      <div className="div-class" data-clj="我是div的自定义属性" ref={divRef}>
        我是div
      </div>
      <button onClick={(e) => changeDOM()}>获取DOM</button>
      {/********************************************************************/}
      <h2>count上一次的值: {numRef.current}</h2>
      <h2>count这一次的值: {count}</h2>
      <button onClick={(e) => setCount(count + 10)}>+10</button>
    </div>
  );
}
03-05 15:18