React Hooks onChange 不接受输入

React Hooks onChange not accepting input

我有一个奇怪的错误,它只在某些时候发生 - onChange 触发但不更改值。然后,如果我使用 onChange 函数在输入框外单击,然后在输入框内单击返回,onChange 函数就会开始工作。

onChange函数是这样的:

const handleBarAmountChange = (event) => {
    let newWidthAmount = event.target.value / 10;
    setNewWidth(newWidthAmount);
    setNewBarAmount(event.target.value);
};

父 div 正在使用传递给此函数的 useRef 的引用:

import { useEffect, useState } from 'react';
const useMousePosition = (barRef, barInputRef, barContainerRef) => {
    const [ mouseIsDown, setMouseIsDown ] = useState(null);

    useEffect(() => {
        const setMouseDownEvent = (e) => {
            if (e.which == 1) {
                if (barContainerRef.current.contains(e.target) && !barInputRef.current.contains(e.target)) {
                    setMouseIsDown(e.clientX);
                } else if (!barInputRef.current.contains(e.target)) {
                    setMouseIsDown(null);
                }
            }
        };

        window.addEventListener('mousemove', setMouseDownEvent);
        return () => {
            window.removeEventListener('mousemove', setMouseDownEvent);
        };
    }, []);
    return { mouseIsDown };
};

onChange 是否与 eventListener 有某种冲突?

我该如何解决这个问题?

React 使用 SyntheticEventEvent Pooling,来自 doc:

Event Pooling

The SyntheticEvent is pooled. This means that the SyntheticEvent object will be reused and all properties will be nullified after the event callback has been invoked. This is for performance reasons. As such, you cannot access the event in an asynchronous way.

您可以对事件调用 event.persist() 或将值存储在新变量中并按如下方式使用它:

const handleBarAmountChange = (event) => {
  // event.persist(); 
  // Or
  const { value } = event.target;

  let newWidthAmount = value / 10;
  setNewWidth(newWidthAmount);
  setNewBarAmount(value);
};

有一些语法错误和缺少钩子依赖项是导致错误的原因。但是,您可以通过一些调整大大简化您的代码。

当使用依赖于其他状态的状态时,我建议将其集中到一个对象中并使用回调函数同步更新它:setState(prevState => ({ ...prevState, example: "newValue" })。这类似于 this.setState(); 在基于 class 的组件中的工作方式。通过使用单个对象并展开它的属性 ({ ...prevState }),我们可以通过重新定义其中一个属性 ({ ...prevState, newWidth: 0 }) 来覆盖它的一个属性。这样可以确保值彼此同步。

下面的示例遵循上述单个对象模式,其中 newWidthnewBarAmountisDragging 是单个对象 (state) 的属性。然后,该示例同步使用 setState 到 update/override 值。此外,refs 已被删除并允许将栏拖过 window(如果你不想要这个,那么你会想将它限制在 barContainerRef 内,因为你已经以前做过)。当用户左键单击并按住栏时,该示例还会检查 state.isDragging 布尔值。释放左键后,将禁用拖动。

这是一个工作示例:


components/Bar/index.js

import React, { useEffect, useState, useCallback } from "react";
import PropTypes from "prop-types";
import "./Bar.css";

function Bar({ barName, barAmount, colour, maxWidth }) {
  const [state, setState] = useState({
    newWidth: barAmount / 2,
    newBarAmount: barAmount,
    isDragging: false
  });

  // manual input changes
  const handleBarAmountChange = useCallback(
    ({ target: { value } }) => {
      setState(prevState => ({
        ...prevState,
        newWidth: value / 2,
        newBarAmount: value
      }));
    },
    []
  );

  // mouse move
  const handleMouseMove = useCallback(
    ({ clientX }) => {
      if (state.isDragging) {
        setState(prevState => ({
          ...prevState,
          newWidth: clientX > 0 ? clientX / 2 : 0,
          newBarAmount: clientX > 0 ? clientX : 0
        }));
      }
    },
    [state.isDragging]
  );

  // mouse left click hold
  const handleMouseDown = useCallback(
    () => setState(prevState => ({ ...prevState, isDragging: true })),
    []
  );

  // mouse left click release
  const handleMouseUp = useCallback(() => {
    if (state.isDragging) {
      setState(prevState => ({
        ...prevState,
        isDragging: false
      }));
    }
  }, [state.isDragging]);

  useEffect(() => {
    window.addEventListener("mousemove", handleMouseMove);
    window.addEventListener("mouseup", handleMouseUp);

    return () => {
      window.removeEventListener("mousemove", handleMouseMove);
      window.removeEventListener("mouseup", handleMouseUp);
    };
  }, [handleMouseMove, handleMouseUp]);

  return (
    <div className="barContainer">
      <div className="barName">{barName}</div>
      <div
        style={{ cursor: state.isDragging ? "grabbing" : "pointer" }}
        onMouseDown={handleMouseDown}
        className="bar"
      >
        <svg
          width={state.newWidth > maxWidth ? maxWidth : state.newWidth}
          height="40"
          fill="none"
          xmlns="http://www.w3.org/2000/svg"
          colour={colour}
        >
          <rect width={state.newWidth} height="40" fill={colour} />
        </svg>
      </div>
      <div className="barAmountUnit">£</div>
      <input
        className="barAmount"
        type="number"
        value={state.newBarAmount}
        onChange={handleBarAmountChange}
      />
    </div>
  );
}

// default props (will be overridden if defined)
Bar.defaultProps = {
  barAmount: 300,
  maxWidth: 600
};

// check that passed in props match patterns below
Bar.propTypes = {
  barName: PropTypes.string,
  barAmount: PropTypes.number,
  colour: PropTypes.string,
  maxWidth: PropTypes.number
};

export default Bar;