函数式组件的生命周期
使用 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 initFormDatadefault: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值并么有变化,则我们使用缓存的依赖项,如果子组件只依赖通过计算产生的依赖项,则就可以跳过子组件的重新渲染