javascript - 在组件内部定义自定义 Hook 有什么问题吗?

标签 javascript reactjs closures react-hooks

我有一个组件多次使用相同的 React hook,使用传递给 hook 内部组件的 prop。对于这个例子,我使用 useCallback 钩子(Hook)和 finish 属性,这只是另一个要调用的函数:

const Example1 = ({ finish }) => {
  const runA = useCallback(
    () => {
      console.log("running A");
      finish();
    },
    [finish]
  );
  const runB = useCallback(
    () => {
      console.log("running B");
      finish();
    },
    [finish]
  );

  return (
    <Fragment>
      <button onClick={runA}>A</button>
      <button onClick={runB}>B</button>
    </Fragment>
  );
};

我想通过定义我自己的自定义 Hook 来稍微整理一下。我的第一次尝试直接在组件内部定义了钩子(Hook):

const Example2 = ({ finish }) => {
  const useCustomHook = action =>
    useCallback(
      () => {
        action();
        finish();
      },
      [finish] // See note 1 below
    );

  const runA = useCustomHook(() => console.log("running A"));
  const runB = useCustomHook(() => console.log("running B"));

  return (
    <Fragment>
      <button onClick={runA}>A</button>
      <button onClick={runB}>B</button>
    </Fragment>
  );
};

1 — this dependency array should be [action, finish]. It's a mistake I made because I didn't have linting rules enabled when creating this example. I understand the importance of correctly specifying these.

Full example on Codepen

这似乎有效,但是 examples for creating custom hooks都将钩子(Hook)放在文件的顶层。钩子(Hook)FAQ are hooks slow because of creating functions in render?解决了创建函数的性能问题,但示例是传递给 钩子(Hook)的函数。

在组件内部创建钩子(Hook)时是否有任何需要注意的功能问题?如果这通常是可以接受的,那么在钩子(Hook)中捕获组件的属性是否存在任何特定问题?


有些回答质疑为什么我不想把钩子(Hook)放在组件外面。主要原因是因为我希望避免这种解决方案的重复和冗长,如这个快速演示所示:

const useTopLevelCustomHook = ({ finish, action }) =>
  useCallback(
    () => {
      action();
      finish();
    },
    [action, finish]
  );

const Example3 = ({ finish }) => {
  const runA = useTopLevelCustomHook({
    action: () => console.log("running A"),
    finish
  });
  const runB = useTopLevelCustomHook({
    action: () => console.log("running B"),
    finish
  });

  return (
    <Fragment>
      <button onClick={runA}>A</button>
      <button onClick={runB}>B</button>
    </Fragment>
  );
};

一些答案​​侧重于在组件内创建闭包,这不是我的本意。在我最初的 TypeScript 应用程序中,我将 Redux 操作与传入的 prop 结合使用。 Action 创建者是相当静态的,因为它们是导入的:

import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';

import * as actions from './actions';

interface BuildMenuProps {
  close: () => void;
}

const BuildMenu: React.SFC<BuildMenuProps> = props => {
  const dispatch = useDispatch();

  const useDispatchAndClose = (action: () => void) => useCallback(
    () => {
      dispatch(action());
      props.close();
    },
    [action, props, dispatch]
  );

  const compile = useDispatchAndClose(actions.performCompile);
  const compileToAssembly = useDispatchAndClose(actions.performCompileToAssembly);
  const compileToLLVM = useDispatchAndClose(actions.performCompileToLLVM);
  const compileToMir = useDispatchAndClose(actions.performCompileToMir);
  const compileToWasm = useDispatchAndClose(actions.performCompileToNightlyWasm);
  const execute = useDispatchAndClose(actions.performExecute);
  const test = useDispatchAndClose(actions.performTest);

  // JSX that uses these callbacks
}

将此与渲染函数外部的钩子(Hook)进行对比,后者需要传入 props.close:

const useDispatchAndClose = (action: () => void, close: () => void) => {
  const dispatch = useDispatch();

  return useCallback(
    () => {
      dispatch(action());
      close();
    },
    [action, close, dispatch]
  );
}

const BuildMenu: React.SFC<BuildMenuProps> = props => {
  const compile = useDispatchAndClose(actions.performCompile, props.close);
  const compileToAssembly = useDispatchAndClose(actions.performCompileToAssembly, props.close);
  const compileToLLVM = useDispatchAndClose(actions.performCompileToLLVM, props.close);
  const compileToMir = useDispatchAndClose(actions.performCompileToMir, props.close);
  const compileToWasm = useDispatchAndClose(actions.performCompileToNightlyWasm, props.close);
  const execute = useDispatchAndClose(actions.performExecute, props.close);
  const test = useDispatchAndClose(actions.performTest, props.close);

  // JSX that uses these callbacks
}

最佳答案

我认为您的方法没有问题。所有的钩子(Hook)仍然在每次渲染时以相同的顺序无条件地被调用。 React 无法在运行时区分 Example1Example2 中的代码结构如何与 React API 交互。

This appears to work, but the examples for creating custom hooks all place the hook at the top-level of the file.

文档中的自定义 Hook 示例展示了如何创建可在多个组件中重复使用的 Hook 。为此,他们需要处于顶层。您有不同的用例。

至于这在技术上是否是“自定义 Hook ”? React Hooks ESLint 规则明确将其视为自定义钩子(Hook),否则您会收到关于在“既不是 React 函数组件也不是自定义 React Hook 函数”的函数中调用钩子(Hook)的警告(例如,如果您重命名 useCustomHookusingCustomHook 你会得到一个警告)。 ESLint 规则将强制执行 rules of hooks在您的自定义 Hook 上。 Here is a sandbox我在 useCallback 调用中故意遗漏了一个依赖项,ESLint 规则报告了缺少的依赖项('greetee')。

我唯一的警告是在 documentation它确实说:

Don’t call Hooks inside loops, conditions, or nested functions.

您的自定义 Hook 可以被视为嵌套函数。如果这种定义自定义 Hook 的方式在未来由于我没有看到的原因而被确定为有问题,您可能会面临 ESLint 规则稍后以提示此方式更改/增强的风险。

Are there any functional issues to be aware of with creating a hook inside of a component?

我看不出这种使用方式与在顶层定义 Hook 并向其传递额外参数的等效代码有任何不同。

If that's acceptable in general, are there any specific issues with capturing a property of the component in the hook?

自定义钩子(Hook)将在每次渲染时重新定义,因此它不会以任何可能改变可观察行为的有意义的方式“捕获”组件的属性,只要您只在它所在的同一组件中无条件地调用它被定义。如果您改为将自定义 Hook 作为属性传递给另一个组件,然后调用它,我认为这可能会有问题(这将是一种有问题的使用模式,可能会导致 ESLint 规则被收紧以提示你在做什么)。

关于javascript - 在组件内部定义自定义 Hook 有什么问题吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57386035/

相关文章:

javascript - 在 SystemJS 中延迟导入

swift - Swift3 中的 Curried 闭包?

javascript - Javascript/ECMAScript 中函数的作用域是什么?

javascript - 如何创建具有固定行数和非固定列数的矩阵/数组数组?

javascript - 如何从 Material UI 为我的应用中的 React 添加带有 Back to Top 按钮的 AppBar?

html - 由于未知原因,锥形梯度 css 在 React 中不起作用

javascript - 我知道闭包是什么,但我仍然不明白您为什么(或何时)使用它们

常规 : Closures or Methods

javascript - 使用 Node.js 将文件上传到 Firebase 存储

javascript - 从另一个 js 源文件调用函数时出现未定义错误