函数式组件的生命周期
使用 useEffect 钩子函数可以模拟类组件的生命周期
useEffect(() => {
// 类似于 componentDidMount
window.addEventListener('mousemove', () => {});
return () => {
// 类似于 componentWillUnMount
window.removeEventListener('mousemove', () => {}) }
}, [])
useComponentWillMount 的实现
然而要模拟实现 componentWillMount,我们只能另辟蹊径,这里我们来手写一个 useComponentWillMount hook:
const useComponentWillMount = (cb) => {
const willMount = useRef(true)
if (willMount.current) cb()
willMount.current = false
}
值得注意的是,这并不能完全等同于 componentWillMount,因为存在代码顺序带来的问题,比如:
console.log('111')
useComponentWillMount(() => console.log('222'))
// output:
// 111
// 222
在这里,111 会在 useComponentWillMount 之前执行,而在 class 的 componentWillMount 中,是优先其他代码执行的。
因此,在实际开发中,我们要根据场景和需求,去灵活使用。
钩子函数详解
useEffect
在 React hook 中,useEffect 用来取代 componentDidMount 和 componentDidUpdate。主要作用是当页面渲染后,进行一些副作用操作(比如访问 DOM,请求数据)。
useLayoutEffect
useLayoutEffect 的出现是为了解决 useEffect 的页面闪烁问题。useEffect 是在组件挂载后异步执行的,并且执行事件会更加往后,如果我们在 useEffect 里面改变 state 状态,那么页面会出现闪烁(state 可见性变化导致的)。而 useLayoutEffect 是在渲染之前同步执行的,在这里执行修改 DOM 相关操作,就会避免页面闪烁的情况。
useCallback
useCallback 不是用来解决组件中有过多内部函数导致的性能问题
1.我们要知道,js创建一个函数的成本是非常小的,这点计算对于计算机来说是小case
2.其实使用useCallback会产成额外的性能:对deps的判断
3.其实每次组件重新渲染时,都无所谓避免重新创建内部函数,因为即使useCallback的deps没有变,它也会重新创建内部函数作为useCallback的实参
那么,它的作用到底是什么?useCallback的作用其实是用来避免子组件不必要的reRender:首先,假如我们不使用useCallback,在父组件中创建了一个名为handleClick的事件处理函数,根据需求我们需要把这个handleClick传给子组件,当父组件中的一些state变化后(这些state跟子组件没有关系),父组件会reRender,然后会重新创建名为handleClick函数实例,并传给子组件,这时即使用React.memo把子组件包裹起来,子组件也会重新渲染,因为props已经变化了,但这个渲染是无意义的.
对于这种deps不是经常变化的情况,我们用useCallback和React.memo的方式可以很好地避免子组件无效的reRender。但其实社区中对这个useCallback的使用也有争议,比如子组件中只是渲染了几个div,没有其他的大量计算,而浏览器去重新渲染几个dom的性能损耗其实也是非常小的,我们花了这么大的劲,使用了useCallback和React.memo,换来的收益很小,所以一些人认为就不用useCallback,就让浏览器去重新渲染好了。至于到底用不用,此处不深入讨论,我的建议是当子组件中的dom数量很多,或者有一些大量的计算操作,是可以进行这样的优化的。
useReducer
总的来说,useReducer是useState的复杂版,所有useState的规则,useReducer都适用。当我们需要对一个对象执行不同的操作时,可以用 useReducer,比如查询、重置、切换页码都在操作查询参数,这个时候我们就可以使用 useReducer,传一个 reducer 函数,在不同的操作里面执行 dispatch 函数,从而设置不同的值给一个对象
const initFormData = {
name: "",
age: 18,
ethnicity: "汉族"
}
const reducer = (state, action) => {
switch (action.type) {
case 'patch': //更新
return {...state, ...action.formData} //把旧的数据复制到一个对象,把新的数据复制到一个对象,把两个对象合并
case "reset": //重置
return initFormData
default:
throw new Error()
}
}
const App = () => {
console.log('App执行了一遍')
const [formData, dispatch] = useReducer(reducer, initFormData)
const onSubmit = () => {
}
const onReset = () => {
dispatch({type: "reset"})
}
return (
<form onSubmit={onSubmit} onReset={onReset}>
<div>
<label >
姓名
<input value={formData.name} onChange={e=>dispatch({type:"patch",formData:{name: e.target.value}})}/>
</label>
</div>
<div>
<label >
年龄
<input value={formData.age} onChange={e=>dispatch({type:"patch",formData:{age: e.target.value}})}/>
</label>
</div>
<div>
<label >
民族
<input value={formData.ethnicity} onChange={e=>dispatch({type:"patch",formData:{age: e.target.value}})}/>
</label>
</div>
<div>
<button type="submit">提交</button>
<button type="reset">重置</button>
</div>
<hr/>
{JSON.stringify(formData)}
</form>
)
}
useMemo
useMemo 是一个 React Hook,它在每次重新渲染的时候能够缓存计算的结果。它可以用来做下面的事情:
1、跳过代价昂贵的重新计算
2、跳过组件的重新渲染
3、记忆另一个 Hook 的依赖
4、记忆一个函数
默认情况下,当一个组件重新渲染时,React 会递归地重新渲染它的所有子组件,我们可以使用 useMemo 包裹需要大量计算而产生的依赖项,如果依赖项的props值并么有变化,则我们使用缓存的依赖项,如果子组件只依赖通过计算产生的依赖项,则就可以跳过子组件的重新渲染