react hooks学习篇(二)

528 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

一、useCallback、useMemo

useMemo和useCallback都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,useMemo返回一个memoized值,useCallback返回memoized 回调函数。

  • useMemo

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

useMemo相比于useCallback会比较好理解,我们来通过一个例子来理解:

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

const Child1 = (props) => {
  const {count1} = props
  
  function doubleCount() {
    console.log('我没有使用useMemo,组件更新了。。。。')
    return count1 * 2
  }

  const useMemoDoubleCount = useMemo(() => {
    console.log('我使用了useMemo,组件更新了。。。。')
    return count1 * 2
  }, [count1])

  return (
    <h1>
      {doubleCount()}---{useMemoDoubleCount}
    </h1>
  )
}

const App = () => {
  const [count1, setCount1] = useState(0)
  const [count2, setCount2] = useState(0)
  return (
    <div>
      <Child1 count1={count1} />
      <button onClick={() => setCount1(count1 + 1)}>count1 + 1</button>
      <button onClick={() => setCount2(count2 + 1)}>count2 + 1</button>
    </div>
  )
}

export default App;

在上面的例子里面,我们点击count2 + 1按钮: image.png Child1中的doubleCount方法执行了,但是doubleCount只和count1有关,我们点击count2加一,doubleCount其实不应该执行的,我们可以优化一下,用useMemo包裹一下,如useMemoDoubleCount只有count1变化时,才会重新计算。

  • useCallback

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

对于useCallback,我们通过传递方法来看,先看一个没有使用useCallback例子:

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

const Child2 = React.memo((props: any) => {
  console.log(222, 'Child2组件,渲染了。。。。')
  const { count, handleClick } = props
  return (
    <h1 onClick={handleClick}>{count}</h1>
  )
})

const App = () => {
  const [count1, setCount1] = useState(0)
  const [count2, setCount2] = useState(0)
  const handleClick2 = () => {
    setCount2(() => count2 + 1)
  }

  return (
    <div>
      <h2>{count1}</h2>
      <Child2 handleClick={handleClick2} count={count2}/>
      <button onClick={() => setCount1(count1 + 1)}>count1 + 1</button>
    </div>
  )
}
export default App;

我们点击count1 + 1的按钮,发现Child2组件渲染了,其实是没有必要渲染的。下面我们使用useCallback再来看 image.png 下面我们修改handleClick2

const handleClick2 = useCallback(() => {
    setCount2(() => count2 + 1)
}, [])

再去点击count1 + 1的按钮,我们发现还是渲染了,其实这时我要需要用React.memo来包裹一下Child2

然后我们再去点击count1 + 1的按钮,虽然Child2没有渲染,但是我们点击h1标签但是只会渲染一次:

image.png

所以之时第二个参数就起作用了

const handleClick2 = useCallback(() => {
    setCount2(() => count2 + 1)
}, [count2])

image.png

对于useCallback、useMemo是用于优化,但是如果使用不当可能造成反作用,所以要谨慎使用。

二、useRef

我们应该熟悉通过ref来访问DOM,至于useRef呢?官方解释:会返回一个可变的 ref 对象其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

const refContainer = useRef(initialValue);

我们看个例子,使用useRef访问input框,获取它的值:

import React, { useState, useRef } from 'react';
import './App.css';

function App() {
  const [inputVal, setInputVal] = useState<string>()
  const inputRef = useRef<HTMLInputElement>(null)

  const handleFocus = () => {
    inputRef?.current?.focus()
  }

  const handleBlur = () => {
    setInputVal(inputRef?.current?.value)
  }

  return (
    <div className="App">
      <header className="App-header">
      <h1>{inputVal}</h1>
      <input ref={inputRef} type="text"  onBlur={handleBlur}/>
      <button onClick={handleFocus}>聚焦</button>
      </header>
    </div>
  );
}

export default App;

image.png

三、useImperativeHandle

useImperativeHandle(ref, createHandle, [deps])

useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用。

从这句话我们知道,我们平时使用ref就可以访问到一个DOM元素,但是呢,官方不建议我们这样使用,想要使用forwardRefuseImperativeHandle一起使用。

这里我们稍微说一下forwardRef;接受渲染函数作为参数,将使用 props 和 ref 作为参数来调用此函数。

import React, { useState, useRef, forwardRef, useImperativeHandle } from 'react';
import './App.css';

const Child = forwardRef((props, ref) => {
  const inputRef = useRef<HTMLInputElement>(null)

  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef?.current?.focus();
    }
  }))

  return (
    <>
      <input ref={inputRef} type="text" />
    </>
  )
})

function App() {
  const [inputVal, setInputVal] = useState<string>()
  const childRef = useRef<HTMLElement>(null)

  const handleFocus = () => {
    setInputVal('聚焦了。。。')
    childRef?.current?.focus()
  }

  return (
    <div className="App">
      <header className="App-header">
      <h1>{inputVal}</h1>
      <Child ref={childRef}/>
      <button onClick={handleFocus}>聚焦</button>
      </header>
    </div>
  );
}
export default App;

image.png 点击聚焦按钮,我们看到上图所示那样;这样通过useImperativeHandle,我们可以把想要暴露给父组件的属性、方法暴露出去。