React的Hooks学习

449 阅读7分钟

Hooks简介及引入目的:

以前的组件分为有状态(state)和无状态,当时只有类声明的组件可以有状态管理,生命周期。而函数式组件本质上是函数,没有state概念,不存在生命周期,只是一个render函数。但有了Hooks之后,函数式组件也可以像类式组件一样拥有state管理状态,使用类似于生命周期的Hook,使用函数式组件完全不用担心出现各种this指向问题

注意:只能在函数内部的最外层调用 Hook,不要在循环、条件判断或者子函数中调用

useState

用法:const [state, setState] = useState(initialState);

initialState:state的初始值

setState:用于更新state的值,它接收一个新的state值并将组件的一次重新渲染加入队列,在后续渲染中useState返回的第一个值将始终是更新后最新的state

1、如果新的state需要通过使用先前的state计算得出,那么可以将函数传递给setState。该函数将接收先前的state,并返回一个更新后的值

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
    </>
  );
}

2、如果初始state需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的state,此函数只在初始渲染时被调用

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

useEffect

1、在函数体内执行有其他副作用的操作都是不被允许的,因为会产生莫名其妙的bug并且会破坏UI的一致性。

2、默认情况下,effect将在每轮渲染结束后执行,这样的话,一旦effect的依赖发生变化,它就会被重新创建。但你也可以选择让它在只有某些值改变的时候才执行。

3、虽然useEffect会在浏览器绘制后延迟执行,但会保证在任何新的渲染前执行。在开始新的更新前,React总会先清除上一轮渲染的effect。

4、useEffect最后,不加[]就表示每一次渲染都执行,用来替代willUpdate等每次渲染都会执行的生命函数

useEffect(()=>{
    const users = 每次都获取全国人民的信息()
})

5、useEffect最后,加了[]就表示只第一次执行,类似于componentDidMount

useEffect(()=>{
    const users = 获取全国人民的信息()
},[])

6、useEffect最后,加[],并且[]里面加的字段就表示,这个字段更改了,我这个effect才执行

useEffect(() => {
    const users = (name改变了我才获取全国人民的信息())
},[name])

6、我们不需要在每次组件更新时都创建新的订阅,而是仅需要在source prop改变时重新创建。要实现这一点,可以给useEffect传递第二个参数,它是effect所依赖的值数组。在effect的return里面可以做取消订阅的事,类似于willUnMount

useEffect(
  () => {
    const subscription = props.source.subscribe();
    return () => {
      subscription.unsubscribe();
    };
  },[props.source]);   //此时,只有当props.source改变后才会重新创建订阅

注意:如果你要使用此优化方式,请确保数组中包含了所有外部作用域中会发生变化且在effect中使用的变量,否则你的代码会引用到先前渲染中的旧变量。请记得React会等待浏览器完成画面渲染之后才会延迟调用useEffect。

7、规则细节

1)useEffect 里面使用到的state的值, 固定在了useEffect内部, 不会被改变,除非useEffect刷新,重新固定state的值

 const [count, setCount] = useState(0)
    useEffect(() => {
        console.log('use effect...',count)
        const timer = setInterval(() => {
            console.log('timer...count:', count)
            setCount(count + 1)
        }, 1000)
        return ()=> clearInterval(timer)
    },[])

2)useEffect不能被判断包裹

const [count, setCount] = useState(0)
if(2 < 5){
   useEffect(() => {
        console.log('use effect...',count)
        const timer = setInterval(() => setCount(count +1), 1000)
        return ()=> clearInterval(timer)
    }) 
}

8、理解失误导致的坑:对useEffect来说,初始值赋值都算一次改变。

比如:我需要值resource被改变之后再调取setAlert函数,但是跟我想象中不太一样。一进入页面setAlert函数被直接调取,之后相应的resource值改变函数调取,对我的需求来说是不需要第一次调取。

    const [resource,setResource] = useState("");
    
    const setAlert = () => {
        alert('setAlert')
    }
    
    useEffect(() => {
        setAlert()
    },[resource]);

然后产生了疑问,是useState的机制问题?接着把resource值写死,发现仍然会一进入页面就调取函数,原来对react来说,值第一次的定义跟赋值都算这个值改变了,即使让resource值为空,为null

    const resource = "2";
    
    const setAlert = () => {
        alert('setAlert')
    }
    
    useEffect(() => {
        setAlert()
    },[resource]);

最后,在useEffect里用if做了一个小判断了,如果resource为true,再调取函数,这才达到我所想的实现。进入页面不首先调取函数,在之后随着resource值的改变再调取函数,而不是resource初次被定义赋值的时候就调取。

export default function text() {
    
    const [resource,setResource] = useState("");

    const setAlert = () => {
        alert('setAlert')
    }

    const btnClick = () => {
        setResource("9999");
    };

    useEffect(() => {
        if(resource)
        setAlert()
    },[resource]);

    return (
        <div>
            {
                resource === "9999"? <p>resource存在</p>: <p>resource不存在</p>
            }
            <div onClick={btnClick}>点击我</div>
        </div>
    )
}

useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

1、把“创建”函数和依赖项数组作为参数传入useMemo,它仅会在某个依赖项改变时才重新计算memoized值,这种优化有助于避免在每次渲染时都进行高开销的计算。

2、传入useMemo的函数会在渲染期间执行,请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于useEffect的适用范畴,而不是useMemo。

3、先根据[name]里面的name值判断一下,因为useMemo 作为一个有着暂存能力的,暂存了上一次的name结果。对比上一次的name,我们发现name值没有改变,那么这次data就不重新赋值成新的对象了!

  const data = useMemo(()=>{
        return {
            name
        }
    },[name])

4、React.memo和useMemo的区别:如果函数组件被 React.memo 包裹,且其实现中拥有useState或useContext的Hook,当context发生变化时,它仍会重新渲染。memo是浅比较,对象只比较内存地址,只要你内存地址没变,管你对象里面的值千变万化都不会触发render,于是就需要使用到useMemo。useMemo解决值的缓存。

useRef

useEffect里面的state的值,是固定的,这个是有办法解决的,就是用useRef,可以理解成useRef的一个作用:

1、就是相当于全局作用域,一处被修改,其他地方全更新...

 const [count, setCount] = useState(0)
 const countRef = useRef(0)
useEffect(() => {
    console.log('use effect...',count)
    const timer = setInterval(() => {
        console.log('timer...count:', countRef.current)
        setCount(++countRef.current)
    }, 1000)
    return ()=> clearInterval(timer)
},[])

2、普遍操作,用来操作dom

const refContainer = useRef(initialValue);

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

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

本质上,useRef就像是可以在其.current属性中保存一个可变值的“盒子”,useRef()比ref属性更有用。它可以很方便地保存任何可变值,useRef()和自建一个{current: ...}对象的唯一区别是,useRef会在每次渲染时返回同一个ref对象。

useCallback

1、useMemo是缓存值的,而useCallback是缓存函数

2、没有依赖,添加空的依赖,就是空数组

   const onChange = useCallback((e)=>{
        setText(e.target.value)
   },[])

3、useCallback在什么时候使用会进行性能优化?

场景:在将一个组件中的函数,传递给子元素进行回调使用时,使用useCallback对函数进行处理,子元素加上嵌套memo。

useLayoutEffect

看起来跟useEffect非常相似,区别在于:

  • useEffect会在渲染的内容更新到DOM上之后再执行,不会阻塞DOM的更新;
  • useLayoutEffect会在渲染的内容更新到DOM上之前执行,会阻塞DOM的更新;

场景:如果我们希望在某些操作之后再更新DOM,那么应该将这个操作放在useLayoutEffect,一般情况下尽量不使用,在useEffect没法实现的时候再考虑此hook;