反应 useRef useImperativeHandle

React useRef useImperativeHandle

在下面的登录表单中,我在提交功能中使用了 document.getElementById()。它有效,但我今天听说这不是一个很好的实践,我最好改用 useRef()

但经过一些研究,useRef() 无法使用自定义组件,例如我的 <FormInput/>。经过一些重新研究,我听说了 forwardRef()useImperativeHandle()

它仍然有效,但我很困惑,我的代码似乎更复杂且更难管理。

您对此用例有何看法?

提前致谢

初始代码

function Login() {
  const navigate = useNavigate()
  const dispatch = useDispatch()     

  async function handleSubmit(e) {
    e.preventDefault()
    await dispatch(
      fetchOrUpdateLogin({
        email: document.getElementById('username').value,
        password: document.getElementById('password').value,
      })
    )
    navigate('/user')
  }

  return (
    <main className="main bg-dark">
      <section className="sign-in-content">
        <i className="fa fa-user-circle sign-in-icon"></i>
        <h1>Sign In</h1>
        <form onSubmit={handleSubmit}>
          <FormInput
            content="Username"
            type="text"
            idFor="username"
            prefill={USER.email}
          />
          <FormInput
            content="Password"
            type="password"
            idFor="password"
            prefill={USER.password}
          />
          <div className="input-remember">
            <input type="checkbox" id="remember-me" />
            <label htmlFor="remember-me">Remember me</label>
          </div>
          <Button type="submit" content="Sign In" classStyle="sign-in-button" />
        </form>
      </section>
    </main>
  )
}

function FormInput({ content, type, idFor, prefill }) {
  return (
    <div className="input-wrapper">
      <label htmlFor={idFor}>{content}</label>
      <input type={type} id={idFor} autoComplete="off" defaultValue={prefill} />
    </div>
  )
}

最终代码

function Login() {
  const navigate = useNavigate()
  const dispatch = useDispatch()

  const emailValue = useRef(null)
  const passwordValue = useRef(null)

  async function handleSubmit(e) {
    e.preventDefault()
    await dispatch(
      fetchOrUpdateLogin({
        email: emailValue.current.inputValue(),
        password: passwordValue.current.inputValue(),
      })
    )
    navigate('/user')
  }

  return (
    <main className="main bg-dark">
      <section className="sign-in-content">
        <i className="fa fa-user-circle sign-in-icon"></i>
        <h1>Sign In</h1>
        <form onSubmit={handleSubmit}>
          <FormInput
            content="Username"
            type="text"
            idFor="username"
            prefill={USER.email}
            ref={emailValue}
          />
          <FormInput
            content="Password"
            type="password"
            idFor="password"
            prefill={USER.password}
            ref={passwordValue}
          />
          <div className="input-remember">
            <input type="checkbox" id="remember-me" />
            <label htmlFor="remember-me">Remember me</label>
          </div>
          <Button type="submit" content="Sign In" classStyle="sign-in-button" />
        </form>
      </section>
    </main>
  )
}
const FormInput = ({ content, type, idFor, prefill }, ref) => {
  const valueRef = useRef(null)

  useImperativeHandle(ref, () => ({
    inputValue() {
      return valueRef.current.value
    },
  }))
  return (
    <div className="input-wrapper">
      <label htmlFor={idFor}>{content}</label>
      <input
        ref={valueRef}
        type={type}
        id={idFor}
        autoComplete="off"
        defaultValue={prefill}
      />
    </div>
  )
}

export default forwardRef(FormInput)

我就是想问这个。为什么不尝试基于状态的方法而不是使用 refs?我认为这将是一个更好的做法。这就是你可以做的。

  1. 在类似

    的组件内部创建一个状态变量

    const [value, setValue] = useState();

  2. 将您的值和 setValue 函数作为输入作为

    <input {...rest} value={value} onChange={e => setValue(e.target.value)} />

  3. 每当值发生变化时,将其传递给您的父组件,这样您的组件将 运行 本身作为一个模块化组件,您将只为您的输入组件提供一项工作。你可以这样做。将道具传递给您的输入组件,例如 onChange,它是一个函数:

    useEffect(() => { props.onChange(value)}, [value])

  4. 将它们保存在父组件的状态中。

  5. 从状态变量中使用它而不是从引用中获取值。

我这样写是因为你正在使用 React,你也应该使用基于它们的组件 re-rendering。会更好use-case

正如 Cemil 所提到的,使用状态将是更常见的解决方案。但是如果你需要使用 refs 这样的东西,你只需要 forwardRef,不需要 useImperativeHandle:

const FormInput = ({ content, type, idFor, prefill }, ref) => {
  return (
    <div className="input-wrapper">
      <label htmlFor={idFor}>{content}</label>
      <input
        ref={ref}
        type={type}
        id={idFor}
        autoComplete="off"
        defaultValue={prefill}
      />
    </div>
  )
}

export default forwardRef(FormInput)

// in Login:
fetchOrUpdateLogin({
  email: emailValue.current.value,
  password: passwordValue.current.value
})