6.React自定义Hook使用向导

现如今,React 官方以不再推荐使用 class 组件,而是推崇 Function 组件,曾经的深层嵌套传值问题,也可以通过 Redux Took-kit + Hook 来进行轻松解决。以下,我们从多个实例,来总结学习一些,项目中通用的 hook 使用。

摘录的react hook使用场景:

  1. 状态管理:自定义 Hooks 可以用于封装状态管理逻辑,使多个组件能够共享和管理相同的状态。例如,可以创建一个自定义 Hooks 用于处理全局应用状态、用户身份验证状态或者表单字段的状态管理。
  2. 副作用处理:自定义 Hooks 可以封装处理副作用操作的逻辑,如数据订阅、网络请求、本地存储等。通过自定义 Hooks,可以在多个组件中共享副作用相关的代码,减少重复工作。例如,可以创建一个自定义 Hooks 用于处理数据获取、定时器操作或者订阅事件的逻辑。
  3. 数据获取和处理:自定义 Hooks 可以用于封装数据获取和处理的逻辑,以便在组件中使用。这样可以使组件更专注于渲染和交互的逻辑。例如,可以创建一个自定义 Hooks 用于从 API 中获取数据、对数据进行转换或者缓存数据。
  4. 表单处理:自定义 Hooks 可以用于处理表单的逻辑,包括表单校验、表单提交、表单重置等。通过自定义 Hooks,可以将表单相关的逻辑抽象出来,使得表单处理变得更简单和可复用。例如,可以创建一个自定义 Hooks 用于处理表单校验和提交逻辑。
  5. 定时器和动画效果:自定义 Hooks 可以用于处理定时器和动画效果的逻辑。通过自定义 Hooks,可以集中处理定时器相关的逻辑,实现定时器的启动、暂停、停止等操作。同样,可以封装常见的动画逻辑,使其在多个组件中可复用。
  6. 访问浏览器 API:自定义 Hooks 可以用于封装访问浏览器 API 的逻辑,如获取地理位置信息、访问本地存储、处理浏览器历史记录等。通过自定义 Hooks,可以在组件中方便地使用这些浏览器 API,提供更简洁的接口和更好的复用性。
  7. 复杂逻辑的封装:自定义 Hooks 还可以用于封装处理复杂逻辑的代码块,使其在多个组件中可复用。例如,可以创建一个自定义 Hooks 来处理分页逻辑、排序逻辑、权限控制逻辑等,从而避免在多个组件中重复编写这些逻辑。

这些仅是自定义 Hooks 的一些应用场景示例,实际上,自定义 Hooks 的应用范围非常广泛,几乎可以用于任何需要共享逻辑的情况。通过合理利用自定义 Hooks,我们可以提高代码的可维护性、可重用性和可读性,使得开发过程更加高效和愉悦。

1. React 自带 Hook

Hook 作用 用法
use 读取 Promise 或 context 等资源的值,与 useContext 的区别:可以在 if 等循环中使用 循环中获取 context: if (isShow) { const ctx = use(ThemContext); } 读取 promie 数据(msgPromise 是接口请求): const content = use(msgPromise);
useCallback 在重新渲染之间缓存函数定义, 当 dependencies 变更时,返回新的函数 useCallback(fn, dependencies)
useContext 从组件读取和订阅上下文 const value = useContext(SomeContext)
useDeferredValue 推迟更新部分 UI,当新数据没有回来,则使用旧的数据进行显示 const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); 代码中使用 deferredQuery
useEffect 用于处理组件中的副作用操作,当依赖变更时,return 函数将被执行 useEffect(() => { const subscription = props.source.subscribe(); return () => { subscription.unsubscribe(); }; }, [props.source]);
useLayoutEffect useEffect 被异步调度,当页面渲染完成后再去执行,不会阻塞页面渲染。 useLayoutEffect 是在 commit 阶段新的 DOM 准备完成,但还未渲染到屏幕之前,同步执行 用法与 useEffect 一致
useId 用于生成可以传递给可访问性属性的唯一 ID const id = useId()
useImperativeHandle 在组件的顶层调用 useImperativeHandle 来定制它所暴露的 ref 句柄 详情看下面实例
useMemo 缓存重新渲染之间的计算结果 组件内创建缓存数据:const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]); 用于缓存组件: const Component = memo(({ items}) => { ...}) 组件内创建记忆函数,等同于 useCallback:const method = useMemo(() => { return () => { console.log('xxx')}}, [productId])
useOptimistic 乐观地更新 UI 详情见下面的实例
useReducer 可以让你在组件中添加一个 reducer 初始化:const [state, dispatch] = useReducer(reducer, initialArg, init?) 使用:dispatch({ type: 'incremented_age', vaule: 10 }) reducer: function reducer(state, action) { if (action.type === 'incremented_age') { return {age: state.age + Number(action.vaule)}; }}
useRef 引用渲染不需要的值,参数为 ref.current 属性的初始值,更改 current 属性不会触发渲染,dom 应用实例参考 useImperativeHandle 实例 const intervalRef = useRef(0); const intervalId = setInterval(() => {}, 1000); intervalRef.current = intervalId;
useState 组件添加状态变量 const [state, setState] = useState(initialState)
useSyncExternalStore 允许您订阅外部存储 没用过
useTransition 在不阻塞 UI 的情况下更新状态 (没用过) const [isPending, startTransition] = useTransition()

1.1 use Hook

通过将 Promise 作为 prop 从服务器组件传递到客户端组件,可以将数据从服务器流式传输到客户端

javascript 复制代码
// Parent Component: use hook
export default function App() {
  const messagePromise = new Promise((resolve, reject) => {
    reject();
  }).catch(() => {
    return "no new message found.";
  });
  return <MessageContainer messagePromise={messagePromise} />;
}

// use无法使用try-catch, 需要使用ErrorBoundary来捕获异常
export function MessageContainer({ messagePromise }) {
  return (
    <ErrorBoundary fallback={<p>⚠️Something went wrong</p>}>
      <Suspense fallback={<p>⌛Downloading message...</p>}>
        <Message messagePromise={messagePromise} />
      </Suspense>
    </ErrorBoundary>
  );
}

// child Compnent
export function Message({ messagePromise }) {
  const content = use(messagePromise);
  return <p>Here is the message: {content}</p>;
}

1.2 useImperativeHandle 像父组件暴露方法

javascript 复制代码
import { forwardRef, useImperativeHandle } from "react";

const MyInput = forwardRef(function MyInput(props, ref) {
  const inputRef = useRef(null);
  const customMethod = () => {
    console.log("父组件可以调用");
    inputRef.current.focus();
  };
  useImperativeHandle(
    ref,
    () => {
      return {
        customMethod,
      };
    },
    []
  );

  return <input {...props} ref={inputRef} />;
});

// 父组件中调用
import { useRef } from "react";
import MyInput from "./MyInput.js";

export default function Form() {
  const ref = useRef(null);

  function handleClick() {
    ref.current.customMethod();
  }
  return (
    <form>
      <MyInput placeholder="Enter your name" ref={ref} />
      <button type="button" onClick={handleClick}>
        Edit
      </button>
    </form>
  );
}

1.3 useOptimistic

可以再异步操作正在进行时,显示不同的状态。它接受某些状态为参数,并返回该状态参数的副本,该副本在异步操作(例如网络请求)期间可能不同,你提供一个函数,该函数接受当前状态和操作的输入,并返回操作待处理时要使用的乐观状态。会立即向用户呈现执行操作的结果,而该结果实际上是需要时间才能完成的。

javascript 复制代码
// parent Component
export default function App() {
  const [messages, setMessages] = useState([
    { text: "Hello there!", sending: false, key: 1 }
  ]);
  async function sendMessage(formData) {
    const sentMessage = await deliverMessage(formData.get("message"));
    setMessages((messages) => [...messages, { text: sentMessage }]);
  }
  return <Thread messages={messages} sendMessage={sendMessage} />;
}


// useOptimistic 组件使用
function Thread({ messages, sendMessage }) {
  const formRef = useRef();
  async function formAction(formData) {
    addOptimisticMessage(formData.get("message"));
    formRef.current.reset();
    await sendMessage(formData);
  }

  // const [optimisticState, addOptimistic] = useOptimistic(state, // updateFn
  //   (currentState, optimisticValue) => {
  //     // merge and return new state
  //     // with optimistic value
  //   }
  // );
  const [optimisticMessages, addOptimisticMessage] = useOptimistic(
    messages,  // messages: 异步中等待处理的值
    (state, newMessage) => [  // 接受当前状态和传递给的乐观值addOptimisticMessage并返回结果乐观状态。它必须是一个纯函数。updateFn接受两个参数,返回新的值给optimisticMessages,当  `messages`处理完成,optimisticMessages将同步更新
      ...state,
      {
        text: newMessage,
        sending: true,
        other: 'other value'
      }
    ]
  );

  return (
    <>
      {optimisticMessages.map((message, index) => (
        <div key={index}>
          {message.text}
          {!!message.sending && <small> (Sending...)</small>}
        </div>
      ))}
      <form action={formAction} ref={formRef}>
        <input type="text" name="message" placeholder="Hello!" />
        <button type="submit">Send</button>
      </form>
    </>
  );
}

2. 自定义 hook => 封装通用逻辑

这里整理一些常用的自定义 hook,在项目中都都可以通用的。

2.1 useCopyToClipboard Hook

使用

javascript 复制代码
const { copiedText, copy } = useCopyToClipboard();

<>
  <button
    onClick={() => {
      copy("A" + new Date().valueOf());
    }}
  >
    copied me
  </button>
  <div>copied content: {copiedText}</div>
</>;

useCopyToClipboard 组件

javascript 复制代码
import { useCallback, useState } from "react";

let textArea: any;
const useCopyToClipboard = () => {
  const [copiedText, setCopiedText] = useState("");

  const createTextArea = (text: string) => {
    textArea = document.createElement("textArea");
    textArea.readOnly = true;
    textArea.contentEditable = "true";
    textArea.value = text;
    document.body.appendChild(textArea);
  };

  const isOS = useCallback(() => {
    //can use a better detection logic here
    return navigator.userAgent.match(/ipad|iphone/i);
  }, []);

  const selectText = useCallback(() => {
    if (isOS()) {
      const editable = textArea.contentEditable;
      const readOnly = textArea.readOnly;
      textArea.readOnly = "false";

      textArea.focus();
      textArea.select();

      const range = document.createRange();
      range.selectNodeContents(textArea);
      const sel = window.getSelection();
      if (!sel) return;

      sel.removeAllRanges();
      sel.addRange(range);
      textArea.setSelectionRange(0, 999999);

      textArea.contentEditable = editable;
      textArea.readOnly = readOnly;
    } else {
      textArea.select();
    }
  }, []);

  const copyTo = useCallback(() => {
    document.execCommand("copy");
    document.body.removeChild(textArea);
  }, []);

  const copyToClipboard = useCallback((text: string) => {
    createTextArea(text);
    selectText();
    copyTo();
  }, []);

  const copy = useCallback((text: string) => {
    setCopiedText(text);

    // current method
    if (navigator.clipboard) {
      navigator.clipboard.writeText(text);
      return;
    }

    // ie
    if (window.clipboardData) {
      window.clipboardData.clearData();
      window.clipboardData.setData("Text", text);
      return;
    }

    // deprecated
    copyToClipboard(text);
  }, []);

  return {
    copy,
    copiedText,
  };
};

export default useCopyToClipboard;

2.2 通用弹窗

使用

项目中会有很多弹窗,只是内容区域展示的内容不一样,可能是一段文字,可能是一个标案,可能是一个图片等等,同一个组件中,可以应用多个 Modal,使用方式如下:

javascript 复制代码
const { open, Modal } = useModal();
const openModal = () => {
  const time = new Date().valueOf();
  open({
    title: "custom" + time,
    content: "hello",
    cancelText: "cancel_btn",
    isCancelBtn: true,
    isOkBtn: false,
    isMask: true,
  });
};

// 内容将通过组件显示
<>
  <Modal>custom content</Modal>
  <button onClick={openModal}>open modal</button>
</>;

useModal

javascript 复制代码
const initInfo = {
  cancelText: "cancel_btn",
  okText: "confirm_btn",
  title: "",
  content: "",
  onCancel: undefined,
  onOk: undefined,
  isOkBtn: true,
  isCancelBtn: true,
  isMask: true,
};

const useModal = () => {
  const [visiable, setVisiable] = useState(false);
  const [info, setInfo] = useState < ModalProps > initInfo;

  const open = useCallback(
    (newInfo: ModalProps = {}) => {
      setInfo((pre) => {
        return { ...pre, ...newInfo };
      });
      setVisiable(true);
    },
    [info]
  );

  const close = useCallback(() => {
    setInfo(initInfo);
    setVisiable(false);
  }, []);

  const Modal = ({ children }: ModalProps): React.ReactNode => {
    const [documentCanUse, setDocumentCanuse] = useState(false);
    const {
      cancelText = "",
      okText = "",
      title,
      content,
      onCancel,
      onOk,
      csClass,
      isOkBtn,
      isCancelBtn,
      isMask,
    } = info;

    useEffect(() => {
      setDocumentCanuse(true);
    }, []);

    const cancelEvent = useCallback(() => {
      onCancel && onCancel();
      close();
    }, []);

    const okEvent = useCallback(() => {
      onOk && onOk();
      close();
    }, []);

    const i1n8n = useLocaleContext().i18n;

    if (!visiable) return null;

    return (
      <div>
        {documentCanUse &&
          createPortal(
            <div className={isMask ? styles.modalContainer : ""}>
              <div className={`${styles.modal} ${csClass}`}>
                {title && <h2 className={styles.title}>{title}</h2>}
                <div className={styles.content}>
                  {content}
                  {children}
                </div>
                {isCancelBtn && (
                  <button onClick={okEvent}>{i1n8n[cancelText]}</button>
                )}
                {isOkBtn && (
                  <button onClick={cancelEvent}>{i1n8n[okText]}</button>
                )}
              </div>
            </div>,
            document.body
          )}
      </div>
    );
  };

  return {
    Modal: memo(Modal),
    open,
  };
};

export default useModal;

2.3 集合 conext 实现全局 Notification

虽然也相当于是弹窗,但是不同于上面的 useModal, 在全局只能出现一个,用于提示信息,确认信息等。

注意:全局多国语也可以仿造该例子实现。

引入 NotificationProvider 和组件 NotificationBar

包裹在项目最外层,只需要应用一次

javascript 复制代码
<NotificationProvider>
  <NotificationBar />
  <header className={styles.header}>header content</header>

  <main className={styles.main}>{children}</main>

  <footer className={styles.footer}>footer</footer>
</NotificationProvider>

使用

javascript 复制代码
const notificationCtx = useContext(NotificationContext);

const openNotification = () => {
  notificationCtx.open({
    content: "Success: Project was fetched!-------------",
    cancelText: "cancel_btn",
  });
};

<button onClick={openNotification}>open </button>;

NotificationBar

javascript 复制代码
const NotificationBar = () => {
  const { notification, show } = useContext(NotificationContext);
  const [documentCanUse, setDocumentCanuse] = useState(false);

  const {
    cancelText = "",
    okText = "",
    title,
    content,
    children,
    onCancel,
    onOk,
    csClass,
    isOkBtn,
    isCancelBtn,
    isMask,
  } = notification;

  useEffect(() => {
    setDocumentCanuse(true);
  }, []);

  const i1n8n = useLocaleContext().i18n;

  if (!show) return null;

  return (
    <div>
      {documentCanUse &&
        createPortal(
          <div className={isMask ? styles.modalContainer : ""}>
            <div className={`${styles.modal} ${csClass}`}>
              {title && <h2 className={styles.title}>{title}</h2>}
              <div className={styles.content}>
                {content}
                {children}
              </div>
              {isCancelBtn && (
                <button onClick={onOk}>{i1n8n[cancelText]}</button>
              )}
              {isOkBtn && <button onClick={onCancel}>{i1n8n[okText]}</button>}
            </div>
          </div>,
          document.body
        )}
    </div>
  );
};
export default NotificationBar;

NotificationProvider

javascript 复制代码
const initNotification = {
  cancelText: "cancel_btn",
  okText: "confirm_btn",
  title: "",
  content: "",
  onCancel: undefined,
  onOk: undefined,
  isOkBtn: true,
  isCancelBtn: true,
  isMask: true,
};

const NotificationContext =
  React.createContext <
  NotificationContextType >
  { notification: initNotification, open: () => {}, show: false };

const NotificationProvider = (props: NotificationProps) => {
  const [notification, setNotification] =
    useState < NotificationProps > initNotification;
  const [show, setShow] = useState(false);

  const close = () => {
    setShow(false);
  };

  const open = (noti: NotificationProps) => {
    setNotification({
      ...notification,
      ...noti,
      onOk: () => {
        noti.onOk && noti.onOk();
        close();
      },
      onCancel: () => {
        noti.onCancel && noti.onCancel();
        close();
      },
    });
    setShow(true);
  };

  return (
    <NotificationContext.Provider
      value={{
        open,
        notification,
        show,
      }}
    >
      {props.children}
    </NotificationContext.Provider>
  );
};

export { NotificationProvider };
export default NotificationContext;

2.4 Form 表单通用验证

当项目中出现了许多表单,通常我们都需要对每一个表单字段进行字段初始化,例如 onChange, value 都能够绑定,有些还需要进行字段验证,或者再对表单字段是否更改做判断等等,那么可以提取一个通用的自定义 hook。

使用

在项目中引入 useForm Hook, 利用其功用方法,可以对 input, select, upload 等表单进行字段注入,校验。

javascript 复制代码
const { register, handleSubmit, formState: { errors, data, isChange } } = useFrom(formValue);

render() {
  return
  <>
    <input { ...register('lastName', { maxLength: 10, required: true, pattern: /^\d+$/ }) } />
    { errors?.lastName?.maxLength && <p>min length is 10</p> }
    <Select { ...register('size', { required: true }) } options={ options } placeholder={ '请选择' } />
    <Switch { ...register('isOpen') } />

    <input { ...register('file', { required: true }) } />
    { errors?.file?.required && <p>File is required.</p> }
     <Upload { ...register('file', undefined, { onChange: uploadEvent }) } />
    <button onClick={ (e) => handleSubmit(e, onSubmit) }>提交</button>
  </>
}

useForm

javascript 复制代码
const useFrom = (defaultData: Data) => {
  const [errors, setErrors] = useState<Errors>({})
  const [data, setData] = useState<Data>(defaultData);
  const [isChange, setIsChange] = useState(false);
  const rules: Rules = {};

  const validate = useCallback((key: string, rule: Rule, value: string | number) => {

    const { required, pattern, minLength = 0, maxLength = 0 } = rule;
    if (required !== undefined) {
      const requiredError = required ? (value === '' || value === undefined || value === null || Number.isNaN(value as number)) : false;
      setErrors({ ...errors, [key]: { required: requiredError } });
    }

    if (minLength > 0 && (String(value).length) < minLength) {
      setErrors({ ...errors, [key]: { minLength: true } });
    }

    if (maxLength > 0 && (String(value).length) > maxLength) {
      setErrors({ ...errors, [key]: { maxLength: true } });
    }

    if (pattern !== undefined && value !== '' && value !== undefined) {
      const patternError = (!pattern?.test(String(value))) || false;
      setErrors({ ...errors, [key]: { pattern: patternError } });
    }

  }
    , [errors]);

  const register = useCallback((key: string, rule?: Rule, extra?: Extra) => {
    if (rule) {
      rules[key] = rule;
    }
    return {
      onChange: async (e: ChangeEvent<HTMLInputElement> | string | number) => {
        let value: string | number = '';
        value = (e as ChangeEvent<HTMLInputElement>).target ? (e as ChangeEvent<HTMLInputElement>).target.value : (e as string | number);

        // upload address
        value = await extra?.onChange(e as  ChangeEvent<HTMLInputElement>) || value;

        const newData = { ...data, [key]: value };
        setData(newData);

        rules[key] && validate(key, rules[key], value);
        setIsChange(!isEqual(newData, defaultData));
      },

      onBlur: (e: ChangeEvent<HTMLInputElement>) => {
        const value = e.target.value;
        rules[key] && validate(key, rules[key], value);
      },
      name: key,
      value: data[key] || '',
    }
  }, [errors, data, rules]);

  const handleSubmit = useCallback((e: MouseEvent<HTMLButtonElement>, onSubmit: () => void) => {
    let pass = false;
    Object.keys(rules).map(key => {
      validate(key, rules[key], data[key]);
    })
    Object.keys(errors).map(key => {
      if (!errors[key].required && !errors[key].pattern) {
        pass = true;
      }
    })
    if (pass) {
      return onSubmit();
    } else {
      e.preventDefault();
      e.stopPropagation();
    }
  }, [])

  return {
    register,
    handleSubmit,
    formState: { errors, data, isChange }
  }
}

export default useFrom;

3. 自定义 Hook => 抽离业务逻辑

项目中,我结合了 redu-toolkit 的 createApi,因此当我在组件中获取数据的时候,才会有 loading 相关的数据,如果需要对 loading 时,进行 loading 状态的显示,那么,每个组件都需要进行重新写一次 loading 的判断:

javascript 复制代码
  const { isLoading, isError, data } = useGetQuotesQuery(numberOfQuotes);
  if (isLoading) {
    ...
    return;
  }
  if (isError) {
    ...
    return;
  }

  render() {
    data....
  }

useLoadingHook

那么这就显得有点繁琐,因此这里我们将通用业务逻辑抽离为一个 hook。

javascript 复制代码
export const useLoadingHook = <T extends {
  data?: U,
  isError: boolean,
  isLoading: boolean,
}, U> (result: T) => {

  const [isLoading, setLoading] = useState(result.isLoading);
  const [isError, setIsError] = useState(result.isError);
  const  [documentCanUse, setDocumentCanuse] = useState(false);

  useEffect(() => {
    setLoading(result.isLoading);
    setIsError(result.isError);
    setDocumentCanuse(true);
  }, [result]);


  if (isError && documentCanUse) {
    return (
      <div>
        { createPortal(
          <p className={ styles.modal }>There was an error!!!222</p>,
          document.body
        ) }
      </div>
    );
  }

  if (isLoading && documentCanUse) {
    return (
      <div>
        { createPortal(
          <p className={ styles.modal }>loading</p>,
          document.body
        ) }
      </div>
    );
  }

  return '';
};

使用

javascript 复制代码
  const result = useGetQuotesQuery(numberOfQuotes);

  const loadingInfo = useApiLoadingHook<typeof result, { quotes: Quote[]}>(result);
  if (loadingInfo) return loadingInfo;

    return <div className={ styles.container }>
     content.....
    </div>

4.自定义 hook => 常用工具

4.1 useDebounce(防抖)

javascript 复制代码
import { useCallback, useRef } from "react";

const useDebounce = (fn, delay, argu) => {
  const timer = useRef();

  return useCallback(
    (...args) => {
      if (timer.current) {
        clearTimeout(timer.current);
      }
      timer.current = setTimeout(() => {
        fn && fn(...args);
      }, delay);
    },
    [delay, fn, ...argu]
  );
};

export default useDebounce;

4.2 useThrottle(节流)

javascript 复制代码
import { useCallback, useRef } from "react";

const useThrottle = (fn: any, delay: any) => {
  const timer: any = useRef();
  return useCallback(() => {
    if (timer.current) return;
    timer.current = setTimeout(() => {
      fn && fn();
      timer.current = null;
    }, delay);
  }, [delay, fn]);
};

4.3 useResize (窗口 resize)

javascript 复制代码
const useResize = () => {
  const isWindow = typeof window !== "undefined";
  const [size, setSize] = useState({
    width: isWindow && window.innerWidth,
    height: isWindow && window.innerHeight,
  });

  const method = useThrottle(() => {
    setSize({
      width: window.innerWidth,
      height: window.innerHeight,
    });
  }, 100);
  useEffect(() => {
    window.addEventListener("resize", method);
  }, []);

  return size;
};

4.4 useScroll (页面滚动)

页面滚动到固定位置,可以显示一个to top的按钮等。

javascript 复制代码
import { useState, useEffect } from "react";

const getPosition = () => {
  return {
    x: document.body.scrollLeft,
    y: document.body.scrollTop,
  };
};

const useScroll = () => {
  const [position, setPosition] = useState(getPosition());
  useEffect(() => {
    const handler = () => {
      setPosition(getPosition(document));
    };
    document.addEventListener("scroll", handler);
    return () => {
      document.removeEventListener("scroll", handler);
    };
  }, []);
  return position;
};

useScroll 应用

javascript 复制代码
import React, { useCallback } from "react";
import useScroll from "./useScroll";

function ScrollTop() {
  const { y } = useScroll();

  const goTop = useCallback(() => {
    document.body.scrollTop = 0;
  }, []);

  if (y > 300) {
    return (
      <button
        onClick={goTop}
        style={{ position: "fixed", right: "10px", bottom: "10px" }}
      >
        Back to Top
      </button>
    );
  }
  return null;
}

5.自定义 hook => 拆分复杂组件

javascript 复制代码
const useGetChildren = () => {
  const { data } = useGetChldrenQuery(clasId);
  const children = useMemo(() => {
    return ...newChilren;
  }, clasId);

  return children;
};


const useGetTeachers = () => {
  const { data } = useGetTeachersQuery(clasId);
  const teachers = useMemo(() => {
    return ...newCteachers;
  }, clasId);

  return teachers;
};


const ClassInfoCompnoent = () => {
  const chilren = useGetChildren();
  const teachers = useGetTeachers();

  render() => {
    ......
  }
}

当然,会有很多业务场景都可以通过hook来实现,让我们的代码粒度变得更小,更容易维护与测试,这都需要我们在开发中进行思考,加油。

相关推荐
Мартин.3 小时前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
昨天;明天。今天。4 小时前
案例-表白墙简单实现
前端·javascript·css
数云界4 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
风清扬_jd4 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome
安冬的码畜日常4 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine
ChinaDragonDreamer4 小时前
Vite:为什么选 Vite
前端
小御姐@stella4 小时前
Vue 之组件插槽Slot用法(组件间通信一种方式)
前端·javascript·vue.js
GISer_Jing4 小时前
【React】增量传输与渲染
前端·javascript·面试
eHackyd4 小时前
前端知识汇总(持续更新)
前端
万叶学编程7 小时前
Day02-JavaScript-Vue
前端·javascript·vue.js