一起养成写作习惯!这是我参与「掘金日新计划 · 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按钮:
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再来看
下面我们修改
handleClick2
const handleClick2 = useCallback(() => {
setCount2(() => count2 + 1)
}, [])
再去点击count1 + 1的按钮,我们发现还是渲染了,其实这时我要需要用React.memo来包裹一下Child2。
然后我们再去点击count1 + 1的按钮,虽然Child2没有渲染,但是我们点击h1标签但是只会渲染一次:
所以之时第二个参数就起作用了
const handleClick2 = useCallback(() => {
setCount2(() => count2 + 1)
}, [count2])
对于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;
三、useImperativeHandle
useImperativeHandle(ref, createHandle, [deps])
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用。
从这句话我们知道,我们平时使用ref就可以访问到一个DOM元素,但是呢,官方不建议我们这样使用,想要使用forwardRef与useImperativeHandle一起使用。
这里我们稍微说一下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;
点击聚焦按钮,我们看到上图所示那样;这样通过
useImperativeHandle,我们可以把想要暴露给父组件的属性、方法暴露出去。