当前位置 : 主页 > 网络编程 > JavaScript >

React Hooks useReducer 逃避deps组件渲染次数增加陷阱

来源:互联网 收集:自由互联 发布时间:2023-02-08
目录 前言 自定义 Hooks 简单实现 在组件中使用自定义 Hooks 提前阻止 dispatch 触发 优化后再测试 结论 题外 前言 在快乐使用 React Hooks 开发自定义 Hooks 过程中,使用了 useEffect , useReduc
目录
  • 前言
  • 自定义 Hooks 简单实现
  • 在组件中使用自定义 Hooks
  • 提前阻止 dispatch 触发
    • 优化后再测试
  • 结论
    • 题外

      前言

      在快乐使用 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` 只会在组件初始渲染中起作用,后续渲染时会被

      参考文献

      React 官方文档

      以上就是React Hooks 之 useReducer 逃避deps后增加组件渲染次数的陷阱的详细内容,更多关于React Hooks useReducer组件渲染的资料请关注易盾网络其它相关文章!

      上一篇:vue实现前端按钮组件权限管理
      下一篇:没有了
      网友评论