React Hooks useReducer组件渲染
qwer 人气:0前言
在快乐使用 React Hooks
开发自定义 Hooks
过程中,使用了 useEffect
,useReducer
,useRef
,useCallback
等官方提供的 Hooks
,将一些通用逻辑抽离出来,提高代码复用性。
但在组合使用 useEffect
,useReducer
,React.memo
时,发生了组件在状态未发生变化时触发渲染,因为此动作发生在 mousemove
鼠标移动时,所以组件不必要渲染次数非常多。
自定义 Hooks 简单实现
import { useReducer } from "react"; const reducer = (state, action) => { const { sliding, lastPos, ratio } = state; switch (action.type) { case "start": return { ...state, slideRange: action.slideRange, lastPos: action.x, sliding: true, }; case "move": if (!sliding) { return state; } const offsetX = action.x - lastPos; const newRatio = ratio + offsetX / state.slideRange; if (newRatio > 1 || newRatio < 0) { return state; } return { ...state, lastPos: action.x, ratio: newRatio, }; case "end": if (!sliding) { return state; } return { ...state, sliding: false, }; case "updateRatio": return { ...state, ratio: action.ratio, }; default: return state; } }; export function useSlider(initialState) { const [state, dispatch] = useReducer(reducer, initialState); return [state, dispatch]; }
在组件中使用自定义 Hooks
const [state, dispatch] = useSlider(initialState); const { ratio, sliding, lastPos, slideRange } = state; useEffect(() => { const onSliding = (e) => { dispatch({ type: "move", x: e.pageX }); }; const onSlideEnd = () => { dispatch({ type: "end" }); }; document.addEventListener("mousemove", onSliding); document.addEventListener("mouseup", onSlideEnd); return () => { document.removeEventListener("mousemove", onSliding); document.removeEventListener("mouseup", onSlideEnd); }; }, [dispatch]); const handleThumbMouseDown = useCallback( (event) => { const hotArea = hotAreaRef.current; dispatch({ type: "start", x: event.pageX, slideRange: hotArea.clientWidth, }); if (event.target.className !== "point") { dispatch({ type: "updateRatio", ratio: (event.pageX - 30) / hotArea.clientWidth, }); } }, [dispatch] );
鼠标每次移动,都会触发 dispatch({ type: "move", x: e.pageX })
,在 reducer
函数中,当 !sliding
时,不修改 state 数据原样返回,但是组件仍然进行了渲染。
提前阻止 dispatch 触发
将 sliding
判断移动到 useEffect
中,提前阻止 dispatch
触发,并将 sliding
设置到 useEffect(fn, deps)
deps 中,保证监听函数中能取到 sliding
最新值。
useEffect(() => { const onSliding = (e) => { if(!sliding) { return; } dispatch({ type: "move", x: e.pageX }); }; const onSlideEnd = () => { if (!sliding) { return; } dispatch({ type: "end" }); }; document.addEventListener("mousemove", onSliding); document.addEventListener("mouseup", onSlideEnd); return () => { document.removeEventListener("mousemove", onSliding); document.removeEventListener("mouseup", onSlideEnd); }; }, [sliding]);
优化后再测试
鼠标仅移动时,sliding
为 false
,直接 return
,不会触发 dispatch
动作。
好处
避免了组件在 state
未修改时不必要渲染。
坏处
部分处理逻辑被移动到使用自定义 hooks
的组件中,sliding
数据改变时,add EventListener
函数会重新注册。
结论
不能为了不在 useEffect(fn, deps)
设置 deps
,使用 useReducer
,并把所有数据变更都放在 reducer
中。 本篇文章通过把不修改 reducer
state
的动作提前阻止,避免使用此自定义 hooks 的组件发生不必要渲染,提高代码复用性的同时也兼顾了组件性能。
题外
React.memo
对props
进行浅比较,一种组件性能优化方式useCallback(fn, deps)
缓存函数useMemo(() => fn, deps)
缓存昂贵变量- useState(initialState)惰性初始state
,
initialState` 只会在组件初始渲染中起作用,后续渲染时会被
参考文献
加载全部内容