【问题标题】:Correct way to define handlers in functional React components?在功能性 React 组件中定义处理程序的正确方法?
【发布时间】:2019-12-11 16:54:21
【问题描述】:

据我所知,在 JavaScript 中定义函数有三种方式。

1.声明

function handleEvent(e) {}

2。作业

var handleEvent = function(e) {}

3.箭头

var handleEvent = (e) => {}

我已经搜索了好几个小时,试图找到这些选项中的哪一个是在功能性 React 组件中声明处理程序的首选方式的信息。我发现的所有文章都在讨论类组件、绑定等。但是随着新的Hooks 出来,函数内部也必须有定义它们的标准。 (毕竟功能组件一直都存在。)

考虑以下组件,它声明了三个处理程序,它们是您在 React 组件中可能需要的不同行为的示例。

function NameForm(props) {
    const [inputName, setInputName] = useState("");

    useEffect(() => setInputName(props.initialValue), [props.initialValue]);

    const handleInputChange = function(event) {
        setInputName(event.target.value);
    };

    const resetForm = function() {
        setInputName(props.initialValue);
        props.onReset();
    };

    const handleFormSubmit = function(event) {
        event.preventDefault();
        props.onSubmit(inputName);
        resetForm();
    };

    /* React-Bootstrap simple form example using these handlers. */
    return (
        <Form onSubmit={handleFormSubmit}>
            <Form.Group controlId="NameForm">
                <Form.Control
                    type="text"
                    placeholder="Enter your name here"
                    value={inputName}
                    onChange={handleInputChange}
                />
                <Button type="submit">Submit</Button>
                <Button onClick={resetForm}>Reset</Button>
            </Form.Group>
        </Form>
    );
}

所有这些处理程序都直接作为回调传递给其他组件。它们可能会在任何时候被调用,但是在那一刻,我们需要访问props当前 值以及像inputName 这样的任何状态。此外,您可能已经注意到,handleFormSubmit 处理程序也调用了resetForm 处理程序。

从性能角度定义处理程序的推荐方法是什么?是否可以避免在每次渲染时重新定义它们?

useCallback 也适合这里的某个地方吗?

【问题讨论】:

  • 您目前拥有的不是有效的 javascript。如果你初始化一个 const,你不能只定义一个函数而不先将 const 设置为等于某个值。
  • 谢谢,打错字了。

标签: javascript reactjs function react-hooks nested-function


【解决方案1】:

当前的标准是将处理程序声明为不变性的常量和用于绑定目的的箭头函数。

function NameForm(props) {
    const [inputName, setInputName] = useState("");

    useEffect(() => setInputName(props.initialValue), [props.initialValue]);

    const handleInputChange = (event) => {
        setInputName(event.target.value);
    }

    const resetForm = () => {
        setInputName(props.initialValue);
        props.onReset();
    }

    const handleFormSubmit = (event) => {
        event.preventDefault();
        props.onSubmit(inputName);
        resetForm();
    }

    /* React-Bootstrap simple form example using these handlers. */
    return (
        <Form onSubmit={handleFormSubmit}>
            <Form.Group controlId="NameForm">
                <Form.Control
                    type="text"
                    placeholder="Enter your name here"
                    value={inputName}
                    onChange={handleInputChange}
                />
                <Button type="submit">Submit</Button>
                <Button onClick={resetForm}>Reset</Button>
            </Form.Group>
        </Form>
    );
}

所有这些处理程序都直接作为回调传递给其他 成分。他们可能会在任何时候被调用,但在那个确切的时刻, 我们需要访问 props 的当前值和任何状态 比如输入名称

按照目前的结构,我们满足您的描述要求。由于propsstate 被定义为组件可以访问的更高级别的数据,因此所有事件处理程序都可以访问它们。当在另一个组件中用作回调时,它们将保持绑定到定义它们的初始组件。

这意味着如果您有这样的事件处理程序:

const handleInputChange = (e) => {
    setValue(e.target.value)
}

然后像这样将它传递给 ChildComponent:

<Child handleInputChange={handleInputChange}/>

Child 像这样使用道具/事件处理程序:

<input onChange={props.handleInputChange}/>

您将从 Child 输入中获取 event,但您将更新 Parent 的 state,这是定义事件处理程序的原始组件。

【讨论】:

  • 那么,有没有办法避免在每次渲染时重新定义函数?
  • @anddero 不幸的是没有,考虑到每次状态或道具更改时组件都会重新渲染。但是,这对整体性能影响非常微不足道。
  • 我相信这个答案可以通过添加useCallback 来改进。它可以防止在每次渲染时重新定义处理程序,除非某些道具发生变化。
【解决方案2】:

“声明”、“赋值”和“箭头”方法之间几乎没有区别。唯一重要的是,您并不总是在每次渲染时都创建处理函数的新实例。为此,请使用 useCallback 挂钩来记忆函数引用:

const handleInputChange = useCallback((event) => {
  setInputName(event.target.value);
}, []); // `setInputName` is guaranteed to be unique, from the React Hooks docs, so no need to pass it as a dependency

const resetForm = useCallback(() => {
  setInputName(props.initialValue);
  props.onReset();
}, [props.onReset, props.initialValue]; // these come from props, so we don't know if they're unique => need to be passed as dependencies

const handleFormSubmit = useCallback((event) => {
    event.preventDefault();
    props.onSubmit(inputName);
    resetForm();
}, [props.onSubmit, resetForm, inputName]); // `resetForm` and `inputName`, although defined locally, will change between renders, so we also need to pass them as dependencies

useCallback 文档:https://reactjs.org/docs/hooks-reference.html#usecallback

【讨论】:

  • 你是说声明、赋值和箭头方法在行为上真的没有区别吗?从 JavaScript 理论来看,情况似乎并非如此。例如,赋值和箭头函数是在执行该行代码时定义的,但声明(第一种方法)可以在前面使用提升定义。
  • 存在技术差异,是的,比如吊装。但就你的问题而言,这并不重要。重要的是不要在每次新渲染时重新声明它们。而useCallback 正是这样做的。
【解决方案3】:

在功能组件中,我们不需要访问 this(甚至更多 - 大多数 linter 在这种情况下会给你警告 - 这是有原因的!)。所以无论我们使用箭头表达式还是声明一个函数都没有关系。

但性能很重要。您从列表中选择的任何选项都将在每次渲染时重新创建。声明函数本身并不是什么大问题,但是:

  1. 传递给子组件可能会导致对 PureComponent/React.memo-wrapped 组件进行不必要的重新渲染。
  2. 如果后面的处理程序将您的处理程序包含到依赖项列表中,可能会导致 useMemo/other useCallback/useEffect 的冗余运行。

所以要么在组件之外声明处理程序(一旦它根本不依赖内部状态)或使用useCallback。请注意,它需要您明确列出所有依赖项 - 不要忽略这一点。否则,结果处理程序可能会对陈旧数据进行操作。

【讨论】:

    猜你喜欢
    • 2020-09-28
    • 1970-01-01
    • 2016-05-26
    • 1970-01-01
    • 2021-09-11
    • 1970-01-01
    • 2017-06-14
    • 2022-06-14
    • 2019-07-26
    相关资源
    最近更新 更多