Skip to main content

03 React Hook的应用

1 Hook是什么?为什么要用它?

hook本质是个有点不一样的函数,不一样在于返回的值,如常用的useState,用于保存状态,那么它是怎么保存其状态的。我们知道一个组件函数被调用(渲染) 里面的引用函数就一定也会被调用了,之所以useState能保存状态是因为它能返回跟上次组件函数一样的结果,所以它是稳定的,也就是能保存组件的了状态.
这样一来,函数组件不就跟类组件一样有状态了吗。而这只是hook中的一个.
好,回到标题的问题:Hook是什么?Hook本质是个函数,但会根据函数组件的情况,不同的hook有不同的实现功能。
那为什么要用它?相较类组件,函数组件简单轻量,好拆分,但函数组件就是函数,没有类组件的对应的状态和生命周期功能,而这些功能或多或少可能通过 Hook来弥补实现出来,这就是Hook的意义。

2 useCallback的使用

userCallbackuserCallback的作用是什么? React有一个过滤调用(渲染)的情况,组件的更新本质上就是把该函数重新调用一遍,但往后的子组件, 可能不需要调用了,就是没必要了。而组件的调用(渲染)触发条件是stateprops发生变动才重新调用(渲染)自身。说白了,一但组件内有一丁点的关于propsstate或其它hook的变化都会引发组件全部重新再运行一次,也直接波及到全部子组件的更新。这是是函数组件更新的方式,避免不了,但可以避免一些代码块 就重复调用和子组件被动调用的渲染的情况。而组件内的代码块避免再次被调用则可以用userCallback来实现

2.1 useCallback示例

useCallback的接收2个参数,一个是不变的回调函数,一个是依赖的state,依赖变了,函数才会重新刷新一个新的版本,如果没有则,默认不变。

import React, {useCallback, useState} from "react";

const UseCallbackDemo = (): React.ReactElement => {
const [count, setCount] = useState<number>(0)

const callback = useCallback(() => {
console.log("callback的内部访问到的count值: " + count)
// todo some thing ...
}, [])

return (
<>
<p>count: {count}</p>
<button onClick={() => setCount(() => count + 1) }>修改count</button>
<button onClick={callback}>在useCallback内部打印count</button>
</>
)
}

export default UseCallbackDemo

后面不管理怎么修改count,而对于callback来说,count就是0,因为callback是不变的,哪怕由于外面的count引发组件多次刷新,可由于callback 并没有更新,而callback内部一开始访问到的count也就是组件第一次初始化时的参数就是0. 所以它就只能在控制台中打印出"callback的内部访问到的count值: 0"这样 的结果。

而这样一来,我们是得到了一个不会随着组件渲染而产生的新的版本的函数了,但有时userCallback中还是需要使用最新的state的,最直接的方式有:

  • 1 把需要用到的最新state的加入userCallback的依赖数组中。
  • 2 也可以使用useReducer的方式,通过dispatcher的方式,把对state的处理委托给useReducer来处理, 这样的好处是相对于依赖的每次都要更新一次, 不可以直观地在代码中决定要不要处理关于state的问题,另一个好处就是普通的防抖算法是基于时间的来采样的,这样把逻辑写userCallback可以避免产生新的 版本,从而保证防抖算法的稳定,而需要处理最新的state时,则直接委托给useReducer来处理就可以了。

3 React.memo 函数的使用

这个函数的作用于子组件和父组件之间,如果父组件更新了,子组件也会被动更新,不管有没有props的变动。而React.memo的作用包裹子组件用的,充当一个中间者的作用, 就是当父组件更新时,如果没有props的更新,那么React.memo就是不会让子组件渲染

React.memo示例代码
import React, {useState} from "react";

const Children1 = () => {
console.log('Children1 reload.')
return <>Children1</>
}
const Children2 = React.memo(() => {
console.log('Children2 reload.')
return <>Children1</>
})

const UseMemoDemo = (): React.ReactElement => {
const [count, setCount] = useState<number>(0)
console.log("Parent component reload." + count)
return (<>
<Children1 />
<br />
<Children2 />
<br/>
<button onClick={() => setCount(() => count + 1)}>Reload component</button>
</>)
}

export default UseMemoDemo;

以上示例,在打印台中可以看出,如果父组件更新了,子组件也会被动更新,而使用了React.memo函数则避免了这种过多的渲染。

4 useMemo的使用

useMemouseCallback的用法一样,而跟后者返回一个函数不一样的是useMemo返回的是一个固定的变量。它的作用是,如果子组件接收的属性是复合类型,也就是 一个自定义的对象时,尽管数值没变,但还是会引发子组件的更新,我猜这是因为在js中,自定义的复合类型尽管每次都值是一样的,但底层的内存地址已经不一样了,从而 子组件把该属性判定为新的组件而触发子组件的更新。而useMemo则能保证被自定义的值是不一变的。

useMemo示例代码

import React, {useMemo, useState} from "react";
type InfoType = {text: string}
type ChildrenPropsType = {info: InfoType}
const Children1 = () => {
console.log('Children1 reload.')
return <>Children1</>
}
const Children2 = React.memo(({info}: ChildrenPropsType) => {
console.log('Children2 reload.' + JSON.stringify(info))
return <>Children2</>
})
const Children3 = React.memo(({info}: ChildrenPropsType) => {
console.log('Children3 reload.' + JSON.stringify(info))
return <>Children3</>
})
const UseMemoDemo = (): React.ReactElement => {
const [count, setCount] = useState<number>(0)
console.log("Parent component reload." + count)
const text2: InfoType = {text: 'text2'}
const text3 = useMemo((): InfoType => ({text: "text3"}), [])
return (<>
<Children1 />
<br />
<Children2 info={text2} />
<br/>
<Children3 info={text3} />
<button onClick={() => setCount(() => count + 1)}>Reload component</button>
</>)
}

export default UseMemoDemo;

以上示例中会打印:

控制台打印
Parent component reload.2
index.tsx:5 Children1 reload.
index.tsx:9 Children2 reload.{"text":"text2"}

参考资料

useCallback()、useMemo() 解决了什么问题? React useCallback的实际应用 为什么要用这个函数?