什么是Hook
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
常用的hook
useState
语法
const [xxx, setXxx] = useState(initValue)
参数:
initValue
:第一次初始化指定的值在内部作缓存
返回值:
- 包含2个元素的数组,第1个为内部当前状态值,第2个为更新状态值的函数
setXxx()2种写法:
setXxx(newValue)
: 参数为非函数值,直接指定新的状态值,内部用其覆盖原来的状态值setXxx(value => newValue)
: 参数为函数,接收原本的状态值,返回新的状态值,内部用其覆盖原来的状态值
例子:
import React, { useState } from 'react'
const UseStateDemo = () => {
const [count, setCount] = useState(0)
const onAdd = () => {
setCount(count + 1)
}
return (
<>
<p> {count}</p>
<button onClick={() => onAdd()}>Count++</button>
</>
)
}
export default UseStateDemo
useEffect
语法
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
参数说明:
useEffect()
接受两个参数,第一个参数是要进行的异步操作,第二个参数是一个数组,用来给出Effect的依赖项,只要这个数组发生变化,useEffect()
里面的异步操作就会执行。- 当第二项省略不填时,useEffect()会在每次组件渲染时都会执行useEffect。
- 当第二项传 空数组
[ ]
时,只会在组件挂载后运行一次。
返回值:
- useEffect()返回值可以是一个函数,在组件销毁的时候会被调用。清理这些副作用可以进行如取消订阅、清除定时器操作。
useRef
useRef
可以在函数组件中存储、查找组件内的标签或任意其它数据。
useRef返回一个可变的ref对象,useRef接受一个参数绑定在返回的ref对象的current
属性上,返回的ref对象在整个生命周期中保持不变。
作用:保存标签对象,功能与React.createRef()一样
例子:
import React, { useRef } from 'react'
const UseRefDemo = () => {
const inputRef = useRef<HTMLInputElement>(null)
const handleOK = () => {
console.log(inputRef.current?.value)
}
return (
<div>
<input ref={inputRef} />
<button onClick={handleOK}>OK</button>
</div>
)
}
export default UseRefDemo
useMemo
useMemo()
主要用来解决使用React hooks产生的无用渲染的性能问题。
语法:
onst cachedValue = useMemo(calculateValue, dependencies)
参数:
cachedValue
:第一个参数为一个函数,函数的返回值作为缓存值dependencies
:放当前useMemo
的依赖项,在函数组件下一次执行的时候,会对比 依赖项里面的状态,是否有改变,如果有改变重新执行cachedValue
,得到新的缓存值。
useMemo应用场景:
- 可以缓存 element 对象,从而达到按条件渲染组件,优化性能的作用。
- 如果组件中不期望每次 render 都重新计算一些值,可以利用 useMemo 把它缓存起来。
- 可以把函数和属性缓存起来,作为 PureComponent 的绑定方法,或者配合其他Hooks一起使用
例子:
import React, { useState, useMemo, useRef } from 'react'
const UseMemoDemo = () => {
const inputRef = useRef<HTMLInputElement>(null)
const [text, setText] = useState('')
const [data, setData] = useState([
{ id: 1, name: 'test1111' },
{ id: 2, name: 'test222' },
{ id: 3, name: 'test334' },
{ id: 4, name: 'test444' }
])
const getList = useMemo(() => {
return data.filter(item => {
if (item.name.includes(text)) {
return item
}
})
}, [text])
const onSearch = () => {
setText(inputRef.current!.value)
}
return (
<div>
<input ref={inputRef} />
<button onClick={onSearch}>查询</button>
{getList.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
)
}
export default UseMemoDemo
useCallback
缓存的是函数
语法:
const cachedFn = useCallback(fn, dependencies)
例子:
import React, { useState, useCallback } from 'react'
interface ChildProps {
result: () => number
}
const Child = React.memo((props: ChildProps) => {
console.log('child-render()')
return <div>I'm Child Component: {props.result()}</div>
})
function UseCallbackDemo() {
const [count, setCount] = useState(0)
const [val, setVal] = useState(0)
const getResult = useCallback(() => {
// 下面计算假设是比较复杂,耗时的计算,否则没必要缓存
return count ** 2
}, [count])
return (
<div>
<p>count: {count}</p>
<p>val: {val}</p>
<Child result={getResult} />
<button onClick={() => setCount(count + 1)}>add count</button>
<button onClick={() => setVal(val + 2)}>add val</button>
</div>
)
}
export default UseCallbackDemo
useContext
共享状态钩子,作用就是可以做状态的分发。常用于【祖组件】与【后代组件】间通信。
例子:
import React, { useContext } from 'react'
const GlobalContent = React.createContext({ color: 'red', background: 'green' })
function UseContextDemo() {
return (
<GlobalContent.Provider value={{ color: 'blue', background: 'red' }}>
<ChildComp />
</GlobalContent.Provider>
)
}
function ChildComp() {
return (
<div>
<GrandchildComp />
</div>
)
}
function GrandchildComp() {
const context = useContext(GlobalContent)
const { color, background } = context
return (
<p>
color: {color} ========== background: {background}
</p>
)
}
export default UseContextDemo
useReducer
useReducer
是 useState
的替代方案。它接收一个形如 (state, action) => newState
的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。
定义:
const [state, dispatch] = useReducer(reducer, initialArg, init);
例子:
// reducer 计数器
const initialState = {count: 0}
const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return {count: state.count + 1}
case 'decrement':
return {count: state.count - 1}
default:
throw new Error()
}
};
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
useLayoutEffect
useLayoutEffect()
:和useEffect
相同,都是用来执行副作用,但是它会在所有的DOM变更之后同步调用effect。useLayoutEffect和useEffect最大的区别就是一个是同步,一个是异步。
从这个Hook的名字上也可以看出,它主要用来读取DOM布局并触发同步渲染,在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
官网建议还是尽可能的是使用标准的useEffec以避免阻塞视觉更新。
useImperativeHandle
可以在使用 ref 时自定义暴露给父组件的实例值。
例子:
import { useEffect, useRef, useImperativeHandle } from 'react'
import { forwardRef } from 'react'
const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef<HTMLInputElement>(null)
useImperativeHandle(ref, () => ({
focus: () => {
if (inputRef.current) {
inputRef.current.value = 'Hello'
}
}
}))
return <input ref={inputRef} />
})
const ImperativeHandleDemo = () => {
const ref = useRef<HTMLInputElement>(null)
useEffect(() => {
ref.current?.focus()
})
return (
<>
<FancyInput ref={ref} />
</>
)
}
export default ImperativeHandleDemo
React18 新增的hook
useId
生成唯一 ID
语法:
const id = useId()
例子
import { useId } from 'react'
const UseIdDemo = () => {
const id1 = useId()
const id2 = useId()
const id3 = useId()
const id4 = useId()
const id5 = useId()
return (
<div>
<h1>useId</h1>
<p>id1 === {id1}</p>
<p>id2 === {id2}</p>
<p>id3 === {id3}</p>
<p>id4 === {id4}</p>
<p>id5 === {id5}</p>
</div>
)
}
export default UseIdDemo
useTransition
useTransition
是一个让你在不阻塞 UI 的情况下来更新状态的 React Hook。
语法:
const [isPending, startTransition] = useTransition()
例子:
import React, { useState, useTransition } from 'react'
const UseTransitionDemo = () => {
const getProducts = () => {
const dataList: any[] = []
for (let i = 0; i < 30000; i++) {
dataList.push({
id: i,
name: `产品${i}`
})
}
return dataList
}
const [isPending, startTransition] = useTransition()
const [searchText, setSearchText] = useState('')
const [products, setProducts] = useState<any>([])
const handleChangeText = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchText(e.target.value)
startTransition(() => {
const list = getProducts().filter(product => product.name.includes(e.target.value))
setProducts(list)
})
}
const productComp = () => {
return products.map(product => {
return (
<div
style={{
height: '40px',
lineHeight: '40px',
background: 'skyblue',
marginTop: '8px',
textAlign: 'center',
color: 'white'
}}
>
{product.name}
</div>
)
})
}
return (
<div style={{ width: '200px', margin: '0 auto' }}>
<input value={searchText} onChange={handleChangeText} />
{isPending && <p>正在输入</p>}
{productComp()}
</div>
)
}
export default UseTransitionDemo
useDeferredValue
需要接收一个值, 返回这个值的副本, 副本的更新会在值更新渲染之后进行更新, 以此来避免一些不必要的重复渲染。
语法:
const deferredValue = useDeferredValue(value)
例子:
import React, { useState, useDeferredValue } from 'react'
const UseDeferredValueDemo = () => {
const getProducts = () => {
const dataList: any[] = []
for (let i = 0; i < 30000; i++) {
dataList.push({
id: i,
name: `产品${i}`
})
}
return dataList
}
const [searchText, setSearchText] = useState('')
const [products, setProducts] = useState<any>([])
const handleChangeText = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchText(e.target.value)
const list = getProducts().filter(product => product.name.includes(e.target.value))
setProducts(list)
}
const defferedProducts = useDeferredValue(products)
const productComp = () => {
return defferedProducts.map(product => {
return (
<div
style={{
height: '40px',
lineHeight: '40px',
background: 'skyblue',
marginTop: '8px',
textAlign: 'center',
color: 'white'
}}
>
{product.name}
</div>
)
})
}
return (
<div style={{ width: '200px', margin: '0 auto' }}>
<input value={searchText} onChange={handleChangeText} />
{productComp()}
</div>
)
}
export default UseDeferredValueDemo
useTransition和useDeferredValue区别
- 对同一个资源的优化,这两个接口的提供的优化效果是一样的,因此不需要同时使用,
也就是说使用一个就行了,因为一旦使用这两个任何一个都会带来一定性能上的损耗。 - 建议只有数据量大的时候考虑使用这两个接口中的一个,平时的普通组件不需要使用,
原因是使用这两个任何一个都会带来一定性能上的损耗。 - 既然使用哪个接口都一样,为啥做了两个接口?因为:useTransition是用来处理更新
函数的,而useDeferredValue:是用来处理更新函数执行后所更新的数据本身的。有些情
况下,你并不能直接获得更新函数,比如你是用的是第三方的ooks库,你在使用的时候
更新函数并不能直接对外暴露,这时候你就只能去优化数据,从而只能使用
useDeferredValue,useTransition的好处是它可以一次性的处理好几个更新函数。
useSyncExternalStore
useSyncExternalStore
是一个可以订阅组件外部数据的Hook。
语法:
useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
例子:
import React, { useSyncExternalStore } from 'react'
function subscribe(callback) {
window.addEventListener('online', callback)
window.addEventListener('offline', callback)
return () => {
window.removeEventListener('online', callback)
window.removeEventListener('offline', callback)
}
}
function useOnlineStatus() {
return useSyncExternalStore(
subscribe,
() => navigator.onLine,
() => true
)
}
const UseSyncExternalStore = () => {
const isOnline = useOnlineStatus()
return <div>Hello, {isOnline ? '在线' : '离线'}</div>
}
export default UseSyncExternalStore
useInsertionEffect
useInsertionEffect
是React中的一个自定义hook,它允许开发人员在元素被插入到DOM中时执行一些操作。在React中,元素被插入到DOM中通常是在组件挂载时发生的。useInsertionEffect的语法类似于React的其他hook,它接受一个回调函数作为参数,该回调函数会在元素被插入到DOM中时被调用。
语法:
useInsertionEffect(setup, dependencies?)
例子:
import { useInsertionEffect } from 'react';
import './styles.css';
function MyComponent() {
useInsertionEffect(() => {
document.querySelector('.my-element').classList.add('fade-in');
});
return (
<div className="my-element">Hello World!</div>
);
}