前言
来源:https://shallowink.com/2022/04/26/coding/front-end/React/React_Hooks
Hook是一个特殊的函数,可以让函数式组件也具有类组件的特性。
在同一个函数作用域中,可以多次使用同名的 Hook,每次使用相当于开辟一个新的空间去存储相关对象信息。
1 useState()
可以在函数组件中进行状态的相关保存。
const [state, setState] = useState('initial value')
- useState() 可以传入参数作为该状态的初始值。
- 调用 useState() 会返回一个数组,第一个值是当前的状态值,第二个值是修改状态值的方法。
- 因为返回值是一个数组,因此状态值名称与修改状态函数名称都可以由我们自己来进行自定义。
特殊情况:
连续调用 setState() 出现的一些问题。
const [age, setAge] = useState(0)
const increaseAge = () => {
setAge(age + 1)
setAge(age + 1)
setAge(age + 1)
}
我们调用 increaseAge() 方法,发现 age 并没有增加 3 而只增加了 1 !
下面给出解决方案:
const [age, setAge] = useState(0)
const increaseAge = () => {
setAge((pre) => pre + 1)
setAge((pre) => pre + 1)
setAge((pre) => pre + 1)
}
React 是进行如下处理的:
- setState( value ) : 合并更新一次状态, 只调用一次 render() 更新界面(状态更新和界面更新都合并了)
- setState( fn ) : 更新多次状态, 只调用一次 render() 更新界面(状态更新没有合并, 但界面更新合并了)
补充:
关于 setState( fn ) 回调函数传值的使用方法,一般用于获取变化后的状态值,函数的返回值就是设置的新的状态值。(注意:如果该回调函数没有设置返回值,则返回值会默认被设置为 undefined)
2 useEffect()
主要是为了实现函数式组件常用生命周期而存在的
可以看成是三个生命周期的集合:挂载、更新和卸载
useEffect(() => {
console.log('component mounts or updates')
return () => {
console.log('component unmounts')
}
}, [])
useEffect() 的默认功能是在组件每次 render 之后将会被执行!
第二个参数是一个数组,他是一个可选参数:
- 不传参数,类似于类组件中的 componentDidMoount 以及 componentDidUpdate ,不仅在组件初次渲染时执行,并且任何 state 值改变都会导致其被执行。
- 传入空数组,类似于类组件中的 componentDidMoount ,只有在组件初次渲染时执行。
- 有一个或者多个值,比较每一个值 ,前后不相等将会执行(必须为 state 值,普通值没有被监听,无法进行新旧比较!),类似于类组件中的 componentDidUpdate 。
3 useContext()
为了实现更舒适的体验祖先元素与后代元素传值!
如果想要用组件自带的 props 进行,需要经过中间元素,这是非常繁琐且不美观的! ```javascript import React, { createContext, useContext } from ‘react’ = const firstDataContext = createContext({}) const secondDataContext = createContext({})
const FatherComponent = () => { return (
const ChildComponent = () => { const firstDataInfo = useContext(firstDataContext) const secondDataInfo = useContext(secondDataContext) return (
- {firstDataInfo.name}
- {firstDataInfo.sex}
- {firstDataInfo.age}
- {secondDataInfo.hobby}
const DescendantComponent = () => { const firstDataInfo = useContext(firstDataContext) return (
YourName: {firstDataInfo.name}
上面的代码简洁明了,之所以看起来如此舒适,多亏了 useContext() 的包装!<br />原来想要获取 Provider 提供的值需要使用对应的 Consumer 标签包裹元素进行提供,现在可以使用 useContext() 直接获取提供的值~
<a name="n9jRw"></a>
# 4 useReducer()
看似是一个替换 Redux 的一个方法,但是其实它替换或者说是优化 useState() 的,不过它可以视为一个不能解决组件间传值的小 Redux 啦~<br />本意是为了包装 state 而存在,useState() 是细粒度的状态管理,而 useReducer 常用来实现组件间数据流管理(配合 useContext() 实现 ),其实在 React 内部 useState() 就是 useReducer() 实现的!<br />下面两个组件间共享了一个计数逻辑。
```javascript
import { React, useReducer } from 'react'
const reducer = (state, action) => {
switch (action.type) {
case 'add':
return { ...state, count: state.count + 1 }
case 'sub':
return { ...state, count: state.count - 1 }
default:
return { ...state }
}
}
const App = () => {
return (
<div>
<Home />
<About />
</div>
)
}
const Home = () => {
const [state, dispatch] = useReducer(reducer, { count: 0 })
return (
<div>
<h2>数据:{state.count}</h2>
<button
onClick={() => {
dispatch({ type: 'add' })
}}
>
+ 1
</button>
<button
onClick={() => {
dispatch({ type: 'sub' })
}}
>
- 1
</button>
</div>
)
}
const About = () => {
const [state, dispatch] = useReducer(reducer, { count: 0 })
return (
<div>
<h2>数据:{state.count}</h2>
<button
onClick={() => {
dispatch({ type: 'add' })
}}
>
+ 1
</button>
<button
onClick={() => {
dispatch({ type: 'sub' })
}}
>
- 1
</button>
</div>
)
}
export default App
透过应用案例可以看到,该 hook 的主干骨是:
const [state, dispatch] = useReducer(reducer, { count: 0 })
其中需要一个 reducer 处理函数,一般由父元素或是祖先元素给予,也可以通过工具类管理。
第二个参数用于初始化 state ,可以在 reducer 处理函数中进行操作。
调用该 hook 会返回一个数组,第一个元素是 state 值,第二个元素则是一个 action 方法,用于操作数据。
然后我们再把目光放到 reducer 这个处理函数上:
const reducer = (state, action) => {
switch (action.type) {
case 'type':
// ...
default:
return { ...state }
}
}
该函数在被 useReducer() 调用时,会被传入两个参数,state 用与获取初始化的 state,而 action 是 dispatch 函数的入参。
整体通过 dispatch 来操作 state 值,一般 reducer 函数会有个 switch 判断,通过不同的 dispatch 入参来决定对数据进行某些操作。
reducer 函数中的返回值,就是更新后的 state 内容。
5 useCallback()
父组件如果将一个方法传递给自组件,若如组件其他状态发生改变会导致父组件重新渲染,这样子组件也会跟着渲染多次,会造成性能的浪费。useCallback() 就是通过缓存父组件传递方法的手段,来避免子组件多次重新渲染的问题的。
注意:由于父组件状态数据改变会导致父组件内部全部组件重新渲染,因此使用 useCallback() 避免子组件刷新需要配合 memo 进行使用!
useCallback(() => {
// function content
}, [])
调用该 hook函数需要传入两个入参,第一个是需要缓存的函数主体,第二个是判断是否需要更新函数的依赖变量。
在调用后会返回该函数本身,直接使用即可!
别忘了子组件都套上一层 memo 哦:
const MemoComponent = memo(Component)
6 useMemo()
本身是 useCallback() 的父集,useCallback() 就是由 useMemo() 实现的,它可以做到很多 useCallback() 做不了的事。
useMemo(() => {
return anything
}, [])
可以看到 useMemo() 对于可以缓存的值类型有一个拓展,不仅可以缓存函数,并且可以缓存对象、数组等。
调用该 hook函数需要传入两个入参,第一个是一个函数,其中返回值就是需要缓存的内容,第二个是判断是否需要更新缓存内容的依赖变量。
7 useRef()
首先拥有 createRef() 的功能,可以进行 DOM 元素的获取。
const Component = () => {
const oDiv = createRef()
const oP = useRef()
return (
<div>
<div ref={oDiv}>Div 标签</div>
<p ref={oP}>P 标签</p>
</div>
)
}
然后 useRef() 也可以用于保存数据,保存后的数据是不会随着原数据改变而改变的。
const Component = () => {
const [state, setState] = useState('data')
const data = createRef(state)
return (
<div>
<div>{ data.current }</div>
</div>
)
}
8 useImperativeHandle()
当我们使用 useRef() 想要获取 DOM 元素时,我们发现只能获取真实的 DOM 元素,当我们想要获取函数组件的时候,就需要用到 useImperativeHandle() 了。
const Home = (props, oHome) => {
const oH2 = useRef()
const oInput = useRef()
useImperativeHandle(oHome, () => {
return {
oH2: oH2,
onInput: oInput,
}
})
return (
<div>
<h2 ref={oH2}>输入框:</h2>
<input type="text" ref={oInput} />
</div>
)
}
const ForwardHome = forwardRef(Home)
const App = () => {
const oHome = useRef()
useEffect(() => {
console.log(oHome)
}, [])
return (
<div>
<ForwardHome ref={oHome} />
</div>
)
}
export default App
整体用法一目了然,useImperativeHandle() 主要就是当自定义函数组件被 useRef() 或者 createRef() 获取 DOM 元素的时候,将想要暴露的元素进行包装返回,在亲自封装组件的时候会被用到。
其中的 forwardRef 函数,可以将函数组件进行处理,被处理过的函数组件才可以和 ref 属性进行联动。
9 useLayoutEffect()
看到名字就知道,与 useEffect() 也许有什么联系!
useLayoutEffect() 会比 useEffect() 先进行执行,一般用于调整布局与样式,布局与样式的调整不要使用 useEffect() 避免出现一些视觉上的问题,如:渲染残影等。
useLayoutEffect(() => {
// content
}, [])
整体使用格式无需多言,与 useEffect() 几乎完全一样!
