1

在反应中我正在使用功能组件,我有两个功能(getBooks)和(loadMore)

getBooks从端点获取数据。但是,当我在函数loadMore内部单击按钮时调用函数getBooks(loadMoreClicked)没有改变,即使在延迟(5秒)调用它之后,它也会使用以前的状态。但是当我loadMore再次调用时,状态会发生变化,一切正常。

有人可以解释为什么最初调用 (getBooks) 时的 (loadMoreClicked) 没有更新,即使在延迟 5 秒后调用它也是如此。

function component() {
  const [loadMoreClicked, setLoadMore] = useState(false);
  const getBooks = () => {
    const endPoint = `http://localhost/getBooks`; //this is my end point
    axios
      .get(endPoint, {
        params: newFilters
      })
      .then(res => {
        console.log(loadMoreClicked); //the (loadMoreClicked) value is still (false) after (5 sec)
      })
      .catch(err => {
        console.log(err);
      });
  };

  const loadMore = () => {
    setLoadMore(true); //here i am changing (loadMoreClicked) value to (true)

    setTimeout(() => {
      getBooks(); // i am calling (getBooks()) after 5 seconds.
    }, 5000);
  };

  return (
    <div>
      <button onClick={() => loadMore()}>loadMore</button> //calling (loadMore)
      function
    </div>
  );
}
4

2 回答 2

3

有两件事发生:

  1. getBooks()正在使用const在周围函数中定义的值。当一个函数在其定义之外引用constlet变量时,它会创建所谓的闭包。闭包从那些外部变量中获取值,并为内部函数提供这些值的副本,就像它们在构建函数时一样。在这种情况下,该函数是在最初调用状态后立即构建的,loadMoreClicked设置为false.

  2. 那么为什么不setLoadMore(true)触发重新渲染并重写函数呢?当我们设置状态时,重新渲染不会立即发生。它被添加到 React 管理的队列中。这意味着,当loadMore()执行时,setLoadMore(true)表示“在我运行完其余代码后更新状态”。重新渲染发生在函数结束之后,因此getBooks()used 的副本是在此循环中构建并排队的副本,其中包含内置的原始值。

对于您正在执行的操作,您可能希望在超时时调用不同的函数,具体取决于是否单击了按钮。或者,您可以根据是否要getBooks()考虑单击按钮来创建另一个更直接的关闭,如下所示:

const getBooks = wasClicked => // Now calling getBooks(boolean) returns the following function, with wasClicked frozen
  () => {
    const endPoint = `http://localhost/getBooks`;
    axios
    .get(endPoint, {
      params: newFilters
    })
    .then(res => {
      console.log(wasClicked); // This references the value copied when the inner function was created by calling getBooks()
    })
    .catch(err => {
      console.log(err);
    });
  }

...

const loadMore = () => {
  setLoadMore(true);
  setTimeout(
    getBooks(true), // Calling getBooks(true) returns the inner function, with wasClicked frozen to true for this instance of the function
    5000
  );
};

还有第三种选择,即重写const [loadMoreClicked, setLoadMore]var [loadMoreClicked, setLoadMore]. 虽然引用const变量会冻结那一刻的值,var但不会。var允许函数动态引用变量,以便在函数执行时确定值,而不是在定义函数时确定。

这听起来像是一个快速而简单的解决方法,但是当用于闭包(例如上面的第二个解决方案)时可能会引起混淆。在这种情况下,由于闭包的工作方式,该值再次固定。因此,您的代码会在闭包中冻结值,但不会在常规函数中冻结,这可能会导致更多混乱。

我个人的建议是保留const定义。var开发社区的使用频率较低,因为它在闭包和标准函数中的工作方式令人困惑。在实践中,大多数(如果不是全部)钩子都会填充 const。将其作为单独的var参考将使未来的开发人员感到困惑,他们可能会认为这是一个错误并更改它以适应模式,从而破坏您的代码。

如果您确实想动态引用 的状态loadMoreClicked,并且您不一定需要重新渲染组件,我实际上建议使用useRef()而不是useState().

useRef创建一个具有单个属性 的对象,该属性current保存您放入其中的任何值。当您更改时current,您正在更新可变对象上的值。因此,即使对对象的引用被及时冻结,它也指的是具有最新值的可用对象。

这看起来像:

function component() {
  const loadMoreClicked = useRef(false);
  const getBooks = () => {
    const endPoint = `http://localhost/getBooks`;
    axios
    .get(endPoint, {
      params: newFilters
    })
    .then(res => {
      console.log(loadMoreClicked.current); // This references the property as it is currently defined
    })
    .catch(err => {
      console.log(err);
    });
 }


  const loadMore = () => {
    loadMoreClicked.current = true; // property is uodated immediately
    setTimeout(getBooks(), 5000);
  };

}

这是有效的,因为虽然在顶部loadMoreClicked定义为 a ,但它是对对象的常量引用,而不是常量值。被引用的对象可以随意改变。const

这是 Javascript 中最令人困惑的事情之一,它通常在教程中被掩盖,所以除非你有一些使用 C 或 C++ 等指针的后端经验,否则它会很奇怪。

因此,对于您正在做的事情,我建议使用 useRef() 而不是 useState()。如果您确实想重新渲染组件,例如,如果您想在加载内容时禁用按钮,然后在加载内容时重新启用它,我可能会同时使用它们,并重命名它们以更清楚地了解它们的用途:

function component() {
  const isLoadPending = useRef(false);
  const [isLoadButtonDisabled, setLoadButtonDisabled] = useState(false);
  const getBooks = () => {
    const endPoint = `http://localhost/getBooks`;
    axios
    .get(endPoint, {
      params: newFilters
    })
    .then(res => {
      if (isLoadPending.current) {
        isLoadPending.current = false:
        setLoadButtonDisabled(false);
      }
    })
    .catch(err => {
      console.log(err);
    });
 };

  const loadMore = () => {
    isLoadPending.current = true;
    setLoadButtonDisabled(true);
    setTimeout(getBooks(), 5000);
  };

}

它有点冗长,但它有效,并且可以将您的关注点分开。ref 是你的标志,告诉你的组件它现在在做什么。状态指示组件应如何呈现以反映按钮。

设置状态是一种即发即弃的操作。在组件的整个功能执行之前,您实际上不会看到它的变化。请记住,在使用 setter 函数之前,您已经获得了价值。所以当你设置状态时,你并没有改变这个循环中的任何东西,你是在告诉 React 运行另一个循环。在第二个周期完成之前不渲染任何东西是足够聪明的,所以它很快,但它仍然运行两个完整的周期,从上到下。

于 2020-03-14T08:59:02.163 回答
0

您可以使用该useEffect方法来监视生命周期方法等loadMoreClicked更新componentDidUpdate并调用其setTimeout内部,

useEffect(() => {
    if(loadMoreClicked){
      setTimeout(() => {
        getBooks();
      }, 5000);
    }
  }, [loadMoreClicked])

这种方式只有在loadMoreClicked更改为之后true我们才调用setTimeout.

这归结为闭包在 JavaScript 中的工作方式。给定的函数将从初始渲染中setTimeout获取变量,因为它没有变异。loadMoreClickedloadMoreClicked

于 2020-03-14T08:03:07.627 回答