保持对 React 状态变量的“引用”

IT技术 javascript reactjs
2021-05-27 04:27:13

我知道 Javascript 中没有指针。

不过,我有以下问题,我想知道是否有解决方案使我无法解决。解决方案可能是 vanilla Javascript,或者像 Context API ( useContext)这样的 React.js 钩子,或者更多 Javascript 和 React 的组合,或者解决方案可能是让我回到绘图板。

请记住,我将 React.js 引入到在前几年的开发中使用 jQuery 的前端。我的真实情况实际上是一个多表单向导组件,它使用 jQuery 进行 Next/Previous 步进和动画。

请不要认为我实际上是在尝试将 jQuery 与 React.js 混合使用,只是$('#myBtn').click在 jQuery 中执行以下操作。

我的软件中的实际事件不是“当 trackMe 更改事件时”,因此 useEffect 在这里无济于事。该软件是一个多窗体向导,实际事件是上一个/下一个按钮按下,回调需要知道 trackMe 的当前值。

这是一个简单的 React 功能组件来演示我的问题:

import React, { useState } from 'react';
import ReactDOM from 'react-dom';

function App() {
    const [trackMe, setTrackMe] = useState('INIT');

    useEffect(() => {
        console.log('trackMe change', trackMe);
    },[trackMe]);

    const handleOnChange = (e) => {
        setTrackMe(e.target.value);
    };

    const startEvent = () => {
        var trackMeVar = trackMe;
        setTimeout(() => {
            console.log('event complete, callback called');
            console.log('value of trackMe: ', trackMe); //always INIT even though state is changing
            console.log('value of trackMeVar: ', trackMeVar); //always INIT even though state is changing
        }, 1000);
    };

    function init() {
        console.log('init');
        $('#myBtn').click(function (e) {
            e.preventDefault();
            setTimeout(startEvent, 1000);
        });
    }

    window.onload = init;

    return (
        <div>
            <input className="form-control" type="text" name="trackme"
                onChange={e => handleOnChange(e)} value={trackMe} />
            <button type="button" id="myBtn">Start Event</button>
        </div>
    )
}

ReactDOM.render(<App />, document.getElementById('root'))

问题是我的回调函数中startEvent具有自己的trackMe状态变量范围版本,而不是外部范围的最新更新值,在状态更改后handleOnChange

要查看实际效果,只需在文本框中键入,状态就会改变。按“开始事件”按钮以查看trackMe在事件回调中始终是其初始值“INIT”。

我相信我的问题的答案的核心是,是否可以重写我的事件回调函数,以便该函数包含我的状态变量的最新值trackMe

2个回答

您可以将 的值保留trackMeref 中,并从中读取:

function App() {
  const [trackMe, setTrackMe] = useState('INIT');
  const track = useRef('INIT');

  const handleOnChange = (e) => {
      console.log('trackMe before:', trackMe);
      setTrackMe(e.target.value);
      track.current = e.target.value // Sync the state value with the ref's current property
  };

  const startEvent = () => {
      setTimeout(() => {
          console.log('event complete, callback called');
          console.log('value of trackMe: ', track.current); // Read current value in the timeout callback
      }, 1000);
  };

  return (
      <div>
          <input className="form-control" type="text" name="trackme"
              onChange={e => handleOnChange(e)} value={trackMe} />
          <button type="button" onClick={() => setTimeout(startEvent, 1000)} id="myBtn">Start Event</button>
      </div>
  )
}

演示

来源

React setState(和 useState 回调)动作是异步的,并且被批处理以提高性能。这在 setState 的文档中进行了解释。

setState() 不会立即改变 this.state 而是创建一个挂起的状态转换。调用此方法后访问 this.state 可能会返回现有值。无法保证对 setState 调用的同步操作,并且可能会批量调用以提高性能。

所以,

const handleOnChange = (e) => {
        console.log('trackMe before:', trackMe);
        setTrackMe(e.target.value);
        console.log('trackMe after: ', trackMe); // will not be updated synchronously
    };

要在每次trackMe更改时运行代码,您必须使用useEffect()钩子

const startEvent = () => {
        var trackMeVar = trackMe;
        setTimeout(() => {
            console.log('event complete, callback called');
            console.log('value of trackMe: ', trackMe); // always INIT even though state is changing, because it is not run again when state is updated
            console.log('value of trackMeVar: ', trackMeVar); //always INIT even though state is changing, because it always stores the same trackMe variable
        }, 1000);
    };

useEffect(() => {startEvent()},[trackMe]); //calls startEvent everytime trackMe is updated, giving the required result

参考useStateuseEffect

更新

如果将 jQuery 添加到组合中,则无法访问react状态。一个可能的解决方案是使用

const handleClick = (e) => {
    console.log('init');
    e.preventDefault();
    setTimeout(startEvent, 1000); //should work as intended
}

<button type="button" id="myBtn" onClick={handleClick}>Start Event</button>