React: 错误:太难修复“无法从不同组件的函数体内部更新组件。”

创建于 2020-02-28  ·  109评论  ·  资料来源: facebook/react

# 注意:React 16.13.1 修复了一些过度触发的情况。 如果升级 React 和 ReactDOM 到 16.13.1 没有修复警告,请阅读: https :

反应版本:

16.13.0

重现步骤

  1. 建造时光机。
  2. 进入 2017 年。
  3. 用 10K 行代码构建一个巨大的应用程序。
  4. 在 package.json 文件中获取 80 (!) 个依赖项,包括不再维护的依赖项。
  5. 2020 年 2 月 27 日将 React 更新到最新版本。
  6. 获取大量您不知道如何修复的错误。
  7. 告诉您的客户修复将花费未知的时间并且将花费 $$$+ 数天或数周的调查,或者我们将永远停留在过时版本的 React 和相关库中,这将花费更多 $$$但后来。

说真的,我工作的公司对此根本不感兴趣。 显然,如果我早点得到它们,我从来没有让这些警告出现。 目前,要修复错误是不可能的,因为我在许多不同的情况下都得到了它们,并且具有巨大的堆栈跟踪。 我试图修复至少一个出现的错误,但已经花了很多时间。 我试图调试一些使用过的库,但没有运气。

举个例子:

image

在那里我们可以注意到使用了一个过时的 react-router,一个过时的 redux-connect(我不得不把它放到项目源中以修复过时的componentWillReceiveProps方法的错误),一些由 recompose 创建的 HOC 等等。它不仅仅是一个简单的虚拟 DOM 树,我可以在其中遍历我开发的组件并通过setState字符串搜索来修复错误,这比这要复杂得多。

请设置“不安全”选项以禁用此错误或提供一种更简单的方法来查找引发错误的位置🙏

Discussion

最有用的评论

给未来的评论者。 我知道看到新警告令人沮丧。 但它指出了可能导致代码中出现错误的合法问题。 如果您能避免表达讽刺和烦恼,我们将不胜感激。

如果您无法理解堆栈跟踪中的来源,请张贴屏幕截图或创建复制沙箱,我们将尽力提供帮助。 其中大部分可能来自少数几个库,因此最有效的做法是减少这些情况,然后将问题归档到这些库中。

谢谢你们。

所有109条评论

我希望我们早点添加这个警告。 很抱歉我们没有。 引入 Hooks 是一个疏忽。 我相信这一定是由使用 Hooks 的较新代码引起的,因为更早的类已经有相同的警告,所以任何较早的代码都会已经看到这个警告。

请注意,这可能会在 React 的未来版本中开始严重失败。 有意或无意(我们在这种模式下有很多错误)。 因此,无论如何,您最终可能会陷入旧版本。 如果无法修复它,我建议固定到旧版本的 React。

但是,话虽如此,我们希望帮助您并尽可能轻松地修复这些问题,包括查看我们是否可以从库作者那里获得帮助以发布修复版本。

如果您在 Chrome 控制台中展开小>箭头,您还应该看到额外的堆栈跟踪(除了屏幕截图中的组件堆栈)。 你也能发一下吗? 这应该会显示在渲染中导致副作用的确切调用站点。

和我一起,当我使用 formik 时会出现这种情况

image

@sebmarkbage感谢您的回复。 单击 > 后出现的堆栈跟踪很荒谬。 它包含 200 多个项目!

我打算将它粘贴到那里或提供一个指向 pastebin 的链接,但尝试了不同的方向。 我浏览了一些使用过的库的 Github 问题,发现其中一个嫌疑人是 redux-form: https :

但是,我仍然要求不要关闭此问题,并且我建议其他开发人员在此处提及也会导致这些错误的库。

@RodolfoSilva你确定它是由 formik 引起的吗? 如果是,您能否在那里创建一个问题并在此处发布一个链接? 如果列表将包含多个项目,我将在我的第一条消息的开头创建此类问题的列表。

这确实需要尽快解决。 它使升级变得不可能。 错误跟踪几乎是不可能的。

嗯。 我想知道在错误消息中描述要查找的行是否会有所帮助。

在这种情况下,要查找的第一行是dispatchAction之后的行。 这应该是调用 React 的东西。

@RodolfoSilva你能贴出FormItemInput.js的来源吗,如果你可以分享的话? 这似乎是在第 71 行调用 dispatch 或 setState。

我认为必须修改此错误消息以准确包含导致错误的代码行。 几乎不可能确定是本地代码还是库代码导致了问题。 像 react-redux 和 react-router 这样的外部

我很确定 React-Redux 在这里没有错,但同意错误消息和组件堆栈使得很难知道实际发生了什么。

我在 Redux-form 中遇到了同样的问题!

我有同样的问题,我看到警告是在我第一次redux 字段中写入或清除所有内容时显示的

我也有这个问题,这是我的组件:

`const [price, setPrice] = useState(0);

const updatePrice = (newPrice) => {
设置价格(新价格)
}
< CardContainer onPriceUpdated={updatePrice} > CardContainer >
`

因此,在这种情况下,我的 CardContainer 组件会在价格更新并且父组件呈现新王子时通知父组件。
所以我猜 React 是在警告我,我正在尝试使用其他组件的功能更新组件的状态。
我是 React 的新手,所以我不确定这是 React 模式还是实际上是一个错误。

如果你们有任何建议来解决这个警告,我将不胜感激``

@l0gicgate

我认为必须修改此错误消息以准确包含导致错误的代码行。

我们在 JavaScript 中可以做的事情是有限的。 但是所有信息都在您在浏览器中看到的堆栈中。 您需要做的就是跳过 React 中的行。

要查看 JavaScript 堆栈,您需要单击错误消息旁边的小箭头

例如,看看之前的截图:

75614021-cb812980-5b12-11ea-8a6e-a38f4cd6aeef

我很欣赏挖掘堆栈有点烦人,但跳过前几帧应该不难。 下一帧是问题的根源。 在这种情况下,它看起来像是 Formik 库中的一些东西。 所以你可以提出问题。

@martinezwilmer这个例子太小了,无济于事。 请创建一个沙箱。

给未来的评论者。 我知道看到新警告令人沮丧。 但它指出了可能导致代码中出现错误的合法问题。 如果您能避免表达讽刺和烦恼,我们将不胜感激。

如果您无法理解堆栈跟踪中的来源,请张贴屏幕截图或创建复制沙箱,我们将尽力提供帮助。 其中大部分可能来自少数几个库,因此最有效的做法是减少这些情况,然后将问题归档到这些库中。

谢谢你们。

很难在此处找到警告所涉及的确切行:

@gaearon你有什么关于它的提示吗?

image

很难在此处找到警告所涉及的确切行:

是什么让它变得困难? 正如我上面提到的,第一行没有在右侧说“反应”。 在这种情况下,它是来自 Redux 的connectAdvanced 。 请在 React Redux 中提交问题,以便维护人员有机会查看此内容。

正如我所说的,如果这里发生的任何事情都是 React-Redux 的问题,我会_非常_感到惊讶。

也就是说,我什至不确定最初是什么触发了这条消息。 我不太明白错误消息的意思,但是什么样的应用程序代码模式实际上是这种行为的一个例子?

我最近遇到了这个问题,解决方法是将setState处理程序调用站点包装在useEffect ,如下所示: https : onChangeonSubmit使用setState更高的链)。

@martinezwilmer onPriceUpdated在哪里被调用? 也许尝试将它包装在useEffect

urql似乎也发生了同样的问题

我们使用use-subscription + wonka(用于流)来编排我们的更新,但是更新可以同步进行。 这里我们已经获取了todos所以如果我们点击Open按钮,结果应该会立即弹出,但这似乎会触发以下错误。

image

在我们的例子中,这是故意的,我们不能只显示fetching: true的同步结果,这会导致跳跃的界面。

现在开始在第三方库中越来越多地出现: urqlApollo

我遇到了这个问题,有几个小时我认为问题出在我的代码中。 压缩的堆栈跟踪指向我的组件,当我 _did_ 显式触发错误时,我在扩展的堆栈跟踪中看到第三方库并不罕见。 从我(尽管有限)对这个特定警告的研究来看,似乎大多数开发人员不是自己导致这个问题,而是取决于导致这个问题的代码。 通常假设它不是上游错误是一个好习惯,但是当它是一个上游错误时,浪费时间跟踪代码中不存在的问题是相当令人沮丧的。 React 可以做些什么来帮助普通用户确定是他们编写的代码还是他们依赖的代码导致了问题?

我从 Apollo 问题中注意到的一件事是:

警告的堆栈跟踪显示了初始化更改的组件,而不是那些被这些更改重新渲染的组件 [原文如此]

如果这是正确的,React 可以在这里提供更多信息吗? 也许告诉我们启动组件和它导致更新的组件?

@hugo一样,我在测试新的 Ionic 应用程序时遇到了这个问题:

  1. npx ionic start demo sidemenu --type=react
  2. react-scripts test

事实上,原因被埋在堆栈跟踪的中间和底部。

console.error node_modules/react-dom/cjs/react-dom.development.js:88
    Warning: Cannot update a component from inside the function body of a different component.
        in Route (at App.tsx:37)
        in View (created by StackManagerInner)
        in ViewTransitionManager (created by StackManagerInner)
        in ion-router-outlet (created by IonRouterOutlet)
        in IonRouterOutlet (created by ForwardRef(IonRouterOutlet))
        in ForwardRef(IonRouterOutlet) (created by StackManagerInner)
        in StackManagerInner (created by Context.Consumer)
        in Unknown (created by Component)
        in Component (created by ForwardRef(IonRouterOutlet))
        in ForwardRef(IonRouterOutlet) (at App.tsx:36)
        in ion-split-pane (created by IonSplitPane)
        in IonSplitPane (created by ForwardRef(IonSplitPane))
        in ForwardRef(IonSplitPane) (at App.tsx:34)
        in NavManager (created by RouteManager)
        in RouteManager (created by Context.Consumer)
        in RouteManager (created by IonReactRouter)
        in Router (created by BrowserRouter)
        in BrowserRouter (created by IonReactRouter)
        in IonReactRouter (at App.tsx:33)
        in ion-app (created by IonApp)
        in IonApp (created by ForwardRef(IonApp))
        in ForwardRef(IonApp) (at App.tsx:32)
        in App (at App.test.tsx:6)

这个问题是我能找到的最接近这个问题的问题。

我们在https://github.com/mobxjs/mobx-react/issues/846 中发现了导致 mobx 出现此问题的特定模式

@sebmarkbage我无法再重现这个问题。 我们更新了一些库并解决了问题。

@jgoux我们似乎面临同样的问题@Clovis ^^ 发现

在更新对react 16.13.0做出反应后,我开始收到此错误。 问题很明显,因为我的一个组件在完成特定操作后正在更新另一个组件。 但是,不确定为什么会发出警告。 关于如何解决这个问题的任何建议?

@gaearon感谢您提供有关如何从堆栈进行调试的详细信息,如果您没有给出该示例,我个人将无法弄清楚此错误的来源。 🙏

不确定我的问题是否相关,但是,我正在尝试更新表单组件的状态,但是每当我尝试添加 onChange 处理程序时,它都会给我这个错误。 请注意,我正在使用 react-jsonschema-form 并导入了 Form 组件,我正在使用它的 onChange 属性来更新状态..

对我来说,这是导致问题的模式。

image

可能有办法解决这个问题。 但是控制台日志确实将我直接指向了第 385 行

我是新手,但我有这样的代码:

import React, { useState } from "react";

function Home() {
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(true)}
        type="button"
        className="btn"
      >
        X
      </button>
      {mobileNavOpen && <MobileNav setMobileNavOpen={setMobileNavOpen}/>}
    </div>
  );
}

function MobileNav() {
  return (
    <div>
      <button
        onClick={setMobileNavOpen(false)} //problem here
        type="button"
        className="btn"
      >
        X
      </button>
    </div>
  );
}

export default Home;

结果是: Cannot update a component from inside the function body of a different component.

我所要做的就是在 MobileNav 中为 setMobileNavOpen 添加一个箭头函数,如下所示:

import React, { useState } from "react";

function Home() {
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(true)}
        type="button"
        className="btn"
      >
        X
      </button>
      {mobileNavOpen && <MobileNav setMobileNavOpen={setMobileNavOpen}/>}
    </div>
  );
}

function MobileNav() {
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(false)} //fixes problem
        type="button"
        className="btn"
      >
        X
      </button>
    </div>
  );
}

export default Home;

这解决了问题,希望这对某人有所帮助!

我是新手,但我有这样的代码:

import React, { useState } from "react";

function Home() {
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(true)}
        type="button"
        className="btn"
      >
        X
      </button>
      {mobileNavOpen && <MobileNav setMobileNavOpen={setMobileNavOpen}/>}
    </div>
  );
}

function MobileNav() {
  return (
    <div>
      <button
        onClick={setMobileNavOpen(false)} //problem here
        type="button"
        className="btn"
      >
        X
      </button>
    </div>
  );
}

export default Home;

结果是: Cannot update a component from inside the function body of a different component.

我所要做的就是在 MobileNav 中为 setMobileNavOpen 添加一个箭头函数,如下所示:

import React, { useState } from "react";

function Home() {
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(true)}
        type="button"
        className="btn"
      >
        X
      </button>
      {mobileNavOpen && <MobileNav setMobileNavOpen={setMobileNavOpen}/>}
    </div>
  );
}

function MobileNav() {
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(false)} //fixes problem
        type="button"
        className="btn"
      >
        X
      </button>
    </div>
  );
}

export default Home;

这解决了问题,希望这对某人有所帮助!

您的示例实际上是人们在使用 react 时犯的早期错误之一。 我不确定这是否与此处讨论的问题完全相同。 您在这里的行: onClick={setMobileNavOpen(false)在按钮投标期间调用该函数,而不是在单击时调用。 这就是为什么将它包装在箭头函数中可以修复它。

我在使用 React Router 时遇到了这个问题,我需要在<Redirect>将用户发送到不同位置之前分派 Redux 操作。 问题似乎是重定向发生在调度完成之前。 我通过承诺我的行动解决了这个问题。

前:

<Route
  render={routeProps => {
    setRedirectionTarget(somePath(routeProps));
    return <Redirect to={someOtherPath} />;
  }}
/>;

function mapDispatchToProps(dispatch: ThunkDispatch) {
  return {
    setRedirectionTarget: (target: string | null) => dispatch(setRedirectionTarget(target))
  };
}

export const setRedirectionTarget = (location: string | null): SetRedirectionTarget => {
  return {
    type: SET_REDIRECTION_TARGET,
    location
  };
};

后:

function mapDispatchToProps(dispatch: ThunkDispatch) {
  return {
    setRedirectionTarget: async (target: string | null) => dispatch(await setRedirectionTarget(target))
  };
}

export const setRedirectionTarget = (location: string | null): Promise<SetRedirectionTarget> => {
  return Promise.resolve({
    type: SET_REDIRECTION_TARGET,
    location
  });
};

我认为使错误消息包含当前正在呈现的组件和正在调用其 setState 的组件的名称。 有人想为此发送 PR 吗?

我认为使错误消息包含当前正在呈现的组件和正在调用其 setState 的组件的名称。 有人想为此发送 PR 吗?

我很高兴看到这个。 我应该注意的任何指示?

@samcooke98我已经为此https://github.com/facebook/react/pull/18316打开了 PR

正如其他人指出的那样,如果您在钩子中订阅了诸如 Apollo 的订阅并尝试更新接收数据的存储,则可能会遇到此问题。 简单的解决方法是使用useEffect例如

export const useOrderbookSubscription = marketId => {
  const { data, error, loading } = useSubscription(ORDERBOOK_SUBSCRIPTION, {
    variables: {
      marketId,
    },
  })

  const formattedData = useMemo(() => {
    // Don't dispatch in here.
  }, [data])

  // Don't dispatch here either

  // Dispatch in a useEffect
  useEffect(() => {
    orderbookStore.dispatch(setOrderbookData(formattedData))
  }, [formattedData])

  return { data: formattedData, error, loading }
}

我们在Hyper 中遇到了这个问题,但我们没有使用钩子,并且在调用堆栈的渲染调用中找不到任何东西。 但是在堆栈中UNSAFE_componentWillReceiveProps中有一个调用。 用componentDidUpdate更新它为我们解决了这个问题https://github.com/zeit/hyper/pull/4382
如果它可以帮助某人,请在此处发布

同样在这里,有一个 UNSAFE_componentWillMount 调用,更改/删除它修复了问题

但是我们没有使用钩子,并且在调用堆栈中的渲染调用中找不到任何内容

这听起来很奇怪。 我不确定你是如何得到这个警告的。 只有当 setState 属于函数组件时才会触发。 你的堆栈是什么样的?

但是我们没有使用钩子,并且在调用堆栈中的渲染调用中找不到任何内容

这听起来很奇怪。 我不确定你是如何得到这个警告的。 只有当 setState 属于函数组件时才会触发。 你的堆栈是什么样的?

使用 Redux 对组件进行分类,以及将插件应用于组件的函数。 也许这就是它被算作功能组件的原因? 但是为什么更新生命周期钩子会修复它呢?

我不知道。 您可以尝试在沙箱中创建一个孤立的示例吗? 我有一种预感,你可能正在做一些意想不到的事情。

不确定我是否能够在一个孤立的例子中复制它。 我很难找到原因,并且刚刚更新了生命周期钩子,因为它在堆栈跟踪中并且等待更新。 这以某种方式解决了这个问题。
您可以在此处查看出现问题时的回购

可能是因为当时UNSAFE_componentWillReceivePropscomponentDidUpdate都在组件中(不安全的一个错误地留在了那里)?

我也收到此警告,堆栈跟踪指向setScriptLoaded钩子(请参阅下面的自定义钩子)。 所以看起来即使我使用useEffect ,如果setState处理程序嵌套在其他异步回调中(在我的情况下,它嵌套在setTimeoutload事件处理程序)? 我第一次使用 Hooks,所以我很感激任何建议。 谢谢!

/**
 * Detect when 3rd party script is ready to use
 * 
 * <strong i="11">@param</strong> {function} verifyScriptLoaded Callback to verify if script loaded correctly
 * <strong i="12">@param</strong> {string} scriptTagId 
 */

export const useScriptLoadStatus = (verifyScriptLoaded, scriptTagId) => {
    let initLoadStatus = true; // HTML already includes script tag when rendered server-side
    if (__BROWSER__) {
        initLoadStatus = typeof verifyScriptLoaded === 'function' ? verifyScriptLoaded() : false;
    }
    const [isScriptLoaded, setScriptLoaded] = useState(initLoadStatus); 

    useEffect(() => {
        if (!isScriptLoaded) {
            // need to wrap in setTimeout because Helmet appends the script tags async-ly after component mounts (https://github.com/nfl/react-helmet/issues/146)
            setTimeout(() => {
                let newScriptTag = document.querySelector(`#${scriptTagId}`);
                if (newScriptTag && typeof verifyScriptLoaded === 'function') {
                    newScriptTag.addEventListener('load', () => { 
                        return verifyScriptLoaded() ? setScriptLoaded(true) : null;
                    });
                    // double check if script is already loaded before the event listener is added
                    return verifyScriptLoaded() ? setScriptLoaded(true) : null;
                }
            }, 100);
        }
    });

    return isScriptLoaded;
};

@LabhanshAgrawal

是不是因为当时 UNSAFE_componentWillReceiveProps 和 componentDidUpdate 都在组件中(不安全的一个错误地留在了那里)?

我认为这里根本没有任何生命周期方法相关。 这就是为什么我说你的例子有些奇怪。

@suhanw请在 CodeSandbox 中提供一个完整的示例。 我认为您的代码没有任何问题应该导致此警告。

@LabhanshAgrawal你能发布你的完整堆栈吗? 我认为可能发生的情况是您的UNSAFE_componentWillReceiveProps (相当于渲染)在另一个组件上调用了setState

backend.js:6 Warning: Cannot update a component from inside the function body of a different component.
    in Term                           (created by _exposeDecorated(Term))
    in _exposeDecorated(Term)         (created by DecoratedComponent)
    in DecoratedComponent             (created by TermGroup_)
    in TermGroup_                     (created by ConnectFunction)
    in ConnectFunction                (created by Connect(TermGroup_))
    in Connect(TermGroup_)            (created by _exposeDecorated(TermGroup))
    in _exposeDecorated(TermGroup)    (created by DecoratedComponent)
    in DecoratedComponent             (created by Terms)
    in div                            (created by Terms)
    in div                            (created by Terms)
    in Terms                          (created by _exposeDecorated(Terms))
    in _exposeDecorated(Terms)        (created by DecoratedComponent)
    in DecoratedComponent             (created by ConnectFunction)
    in ConnectFunction                (created by Connect(DecoratedComponent))
    in Connect(DecoratedComponent)    (created by Hyper)
    in div                            (created by Hyper)
    in div                            (created by Hyper)
    in Hyper                          (created by _exposeDecorated(Hyper))
    in _exposeDecorated(Hyper)        (created by DecoratedComponent)
    in DecoratedComponent             (created by ConnectFunction)
    in ConnectFunction                (created by Connect(DecoratedComponent))
    in Connect(DecoratedComponent)
    in Provider
r                                     @ backend.js:6
printWarning                          @ react-dom.development.js:88
error                                 @ react-dom.development.js:60
warnAboutRenderPhaseUpdatesInDEV      @ react-dom.development.js:23260
scheduleUpdateOnFiber                 @ react-dom.development.js:21196
dispatchAction                        @ react-dom.development.js:15682
checkForUpdates                       @ connectAdvanced.js:88
handleChangeWrapper                   @ Subscription.js:97
(anonymous)                           @ Subscription.js:23
batchedUpdates$1                      @ react-dom.development.js:21887
notify                                @ Subscription.js:19
notifyNestedSubs                      @ Subscription.js:92
checkForUpdates                       @ connectAdvanced.js:77
handleChangeWrapper                   @ Subscription.js:97
(anonymous)                           @ Subscription.js:23
batchedUpdates$1                      @ react-dom.development.js:21887
notify                                @ Subscription.js:19
notifyNestedSubs                      @ Subscription.js:92
handleChangeWrapper                   @ Subscription.js:97
dispatch                              @ redux.js:222
e                                     @ VM64:1
(anonymous)                           @ effects.ts:11
(anonymous)                           @ write-middleware.ts:14
(anonymous)                           @ index.js:11
(anonymous)                           @ plugins.ts:538
(anonymous)                           @ plugins.ts:540
(anonymous)                           @ index.js:11
dispatch                              @ redux.js:638
(anonymous)                           @ sessions.ts:124
(anonymous)                           @ index.js:8
dispatch                              @ VM64:1
onResize                              @ terms.ts:62
(anonymous)                           @ term.js:179
e.fire                                @ xterm.js:1
t.resize                              @ xterm.js:1
e.resize                              @ xterm.js:1
e.fit                                 @ xterm-addon-fit.js:1
fitResize                             @ term.js:291
UNSAFE_componentWillReceiveProps      @ term.js:408
callComponentWillReceiveProps         @ react-dom.development.js:12998
updateClassInstance                   @ react-dom.development.js:13200
updateClassComponent                  @ react-dom.development.js:17131
beginWork                             @ react-dom.development.js:18653
beginWork$1                           @ react-dom.development.js:23210
performUnitOfWork                     @ react-dom.development.js:22185
workLoopSync                          @ react-dom.development.js:22161
performSyncWorkOnRoot                 @ react-dom.development.js:21787
(anonymous)                           @ react-dom.development.js:11111
unstable_runWithPriority              @ scheduler.development.js:653
runWithPriority$1                     @ react-dom.development.js:11061
flushSyncCallbackQueueImpl            @ react-dom.development.js:11106
flushSyncCallbackQueue                @ react-dom.development.js:11094
batchedUpdates$1                      @ react-dom.development.js:21893
notify                                @ Subscription.js:19
notifyNestedSubs                      @ Subscription.js:92
handleChangeWrapper                   @ Subscription.js:97
dispatch                              @ redux.js:222
e                                     @ VM64:1
(anonymous)                           @ effects.ts:11
(anonymous)                           @ write-middleware.ts:14
(anonymous)                           @ index.js:11
(anonymous)                           @ plugins.ts:538
(anonymous)                           @ plugins.ts:540
(anonymous)                           @ index.js:11
dispatch                              @ redux.js:638
effect                                @ ui.ts:60
(anonymous)                           @ effects.ts:13
(anonymous)                           @ write-middleware.ts:14
(anonymous)                           @ index.js:11
(anonymous)                           @ plugins.ts:538
(anonymous)                           @ plugins.ts:540
(anonymous)                           @ index.js:11
dispatch                              @ redux.js:638
(anonymous)                           @ ui.ts:54
(anonymous)                           @ index.js:8
dispatch                              @ VM64:1
(anonymous)                           @ index.tsx:162
emit                                  @ events.js:210
(anonymous)                           @ rpc.ts:31
emit                                  @ events.js:210
onMessage                             @ init.ts:50

@gaearon感谢您的快速响应。 我会想办法在 CodeSandbox 中创建一个示例(这很有挑战性),但与此同时,这是我的完整堆栈跟踪

指向我的自定义钩子的行是这样的: https: //gist.github.com/suhanw/bcc2688bba131df8301dae073977654f#file -stack-trace-L144

如果您能看一看并让我知道在我的自定义钩子之前/之后的堆栈跟踪中是否有任何我应该进一步调查的内容,那就太棒了。 谢谢!

@LabhanshAgrawal在你的堆栈中, UNSAFE_componentWillReceiveProps调用了一些fitResize ,它调度了一个 Redux 动作,这反过来更新了一堆组件。 因此问题来了。 所以是的,将其更改为componentDidUpdate有效。

@suhanw在您的堆栈中,名为ModuleImpressionTracker似乎在构造函数期间调度 Redux 操作。 构造函数不应该有副作用。 我认为这是问题的原因,而不是你的 Hook。

我明白了,所以我从这个中UNSAFE_componentWillReceiveProps被算作渲染但componentDidUpdate不是。
您能否澄清一下它是功能组件和钩子执行 setState 的一个问题,无法从上面的讨论中清楚地得到它。
抱歉,如果问题有点傻或者我的看法是错误的,我对此有点陌生。

@LabhanshAgrawal

您能否澄清一下它是功能组件和钩子执行 setState 的一个问题,无法从上面的讨论中清楚地得到它。

老实说,我不确定自己,因为中间有所有 Redux 的东西。 由于 React Redux 的实现方式,这非常令人困惑。 如果有人能得到一个单独涉及 React 但使用类组件的清晰重现,那就太好了。

似乎仍然有很多堆栈跟踪被粘贴,并且无论类型如何,实际问题的实际重现都不是很多。

我猜urql的目的是@gaearon,所以会发生什么:

  • 我们已经挂载了<Todos /> ,它获取数据并渲染它
  • 前面设置了一个流连接到urqlClient
  • 我们渲染第二个<Todos />这将产生相同的查询 + 变量组合,因此将刷新第一步<Todos />的结果。
  • use-subscription两者都会触发。

Repro - 当第一个查询呈现时按顶部的“打开”。

我们可以对更新进行排队,但只要我们不使用上下文进行top-down渲染,我们就无法绕过我假设的这个问题? 理论上,这是这种情况下的预期行为。 好奇 Relay 如何解决这个问题。

编辑:我们可能已经找到了urql的情况下的解决方案通过在不触发所有订户更新getCurrentValue a的use-subscription

https://github.com/FormidableLabs/urql/commit/3a597dd92587ef852c18139e9781e853f763e930

好的,所以深入研究这一点,我认为我们在比我们最初预期的更多情况下(例如对于类)发出警告。 我们会考虑让其中一些静音。

@JoviDeCroock这个例子不是很有帮助,因为我不知道urql做了什么。 :-) 如果您想获得有关某个模式的反馈,您能否准备一个沙箱来单独演示该模式?

@JoviDeCroock是的, getCurrentValue绝对不会产生副作用。 我们认为这个名字非常明确。

我遇到了一个问题,在自定义钩子的根范围内调度 redux 操作时收到此警告。

function useCustomHook() {
   const dispatch = useDispatch()
   const value = useSomeOtherHook()
   dispatch(action(value))
}

我通过将调度包裹在useEffect

@Glinkis 无论如何,这听起来像是一个糟糕的模式。 为什么要在组件呈现时通知整个树?

@gaearon是的,我们试图解决的问题在这里得到了更好的解释

@Glinkis 无论如何,这听起来像是一个糟糕的模式。 为什么要在组件呈现时通知整个树?

我需要保持我的 redux 状态与我的路线的 ID 同步,所以当我的路线改变时我会发送这个更新。

我知道这可能不是最理想的,但我发现没有其他方法可以访问我的选择器中的路由数据。

@Glinkis路线数据来自哪里?

@JoviDeCroock我认为我们对该模式的最新建议是自定义计划垃圾收集传递。

@Glinkis路线数据来自哪里?

不确定这是否属于我的讨论,但这是我的钩子。

export function useOpenFolder() {
  const dispatch = useDispatch()
  const match = useRouteMatch('/:workspace/(super|settings)?/:type?/:id?/(item)?/:item?')
  const { id, item } = match?.params || {}

  useEffect(() => {
    dispatch(
      openFolder({
        id: id || '',
        item: item || '',
      })
    )
  }, [dispatch, item, id])
}

我后来将此状态用于选择器,例如:

export const getActiveItem = createSelector(
  [getActiveFolderItem, getItems],
  (activeItem, items) => items.all[activeItem]
)

@Glinkis是的,也许让我们在这里总结一下,但我的建议是在父组件中读取useRouteMatch ,将 ID 作为道具传递,然后像往常一样在选择器中读取道具。 (我实际上不知道这些天在 Redux 中这是如何完成的,但必须有某种方式。)

我明白了,所以我从这个中UNSAFE_componentWillReceiveProps被算作渲染但componentDidUpdate不是。

@LabhanshAgrawal没错。 这里的一些背景解释:

从概念上讲,React 分两个阶段工作:

  • 渲染阶段确定需要对 DOM 等进行哪些更改。 在这个阶段,React 调用render ,然后将结果与之前的渲染进行比较。
  • 提交阶段是 React 应用任何更改的时候。 (在 React DOM 的情况下,这是 React 插入、更新和删除 DOM 节点的时间。)React 在此阶段还会调用生命周期,如componentDidMountcomponentDidUpdate

提交阶段通常非常快,但渲染可能会很慢。 出于这个原因,即将推出的并发模式(默认情况下尚未启用)将渲染工作分解为多个部分,暂停和恢复工作以避免阻塞浏览器。 这意味着 React 可能会在提交之前多次调用渲染阶段生命周期,或者它可能根本不提交就调用它们(因为错误或更高优先级的中断)。

渲染阶段生命周期包括以下类组件方法:

  • constructor
  • componentWillMount (或UNSAFE_componentWillMount
  • componentWillReceiveProps (或UNSAFE_componentWillReceiveProps
  • componentWillUpdate (或UNSAFE_componentWillUpdate
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • setState更新函数(第一个参数)

因为上述方法可能会被多次调用,所以它们不包含副作用很重要。 忽略此规则会导致各种问题,包括内存泄漏和无效的应用程序状态。 不幸的是,很难检测到这些问题,因为它们通常是不确定的

好的,所以深入研究这一点,我认为我们在比我们最初预期的更多情况下(例如对于类)发出警告。 我们会考虑让其中一些静音。

我想即使它超出了预期,在我看来这是一件好事,因为它有助于使用类来改进我们的项目。

此不言自明的错误消息“无法从不同组件的函数体内部更新组件”

这意味着作为函数执行而不是调用组件。

像这样的例子:

const App = () => {  
  const fetchRecords = () => { 
    return <div>Loading..</div>;
  };  
  return fetchRecords() // and not like <FetchRecords /> unless it is functional component.
};
export default App;

@rpateld我认为您展示的示例与此警告无关。

https://github.com/facebook/react/pull/18330将解决我们不打算开始射击的情况。

我也面临这个问题react@experimental + react-redux + redux
image

代码如下所示:

import React, { Suspense } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { Redirect } from 'react-router-dom'
import PropTypes from 'prop-types'
import { userActions, cabinetActions, tokensActions } from '../../actions'
import { CircularProgress } from '@material-ui/core'
import { api } from './api'

const Cabinet = ({ resource }) => {
    resource.read()
    return <h1>cabinet</h1>
}

Cabinet.propTypes = {
    resource: PropTypes.shape({
        read: PropTypes.func
    })
}

const CabinetPage = ({
    failedToLoad,
    tokensRefreshFailed,
    logout,
    loadCabinet,
    clearTokens,
    clearCabinet
}) => {
    if (tokensRefreshFailed || failedToLoad) {
        clearTokens()
        clearCabinet()
        logout()
        return <Redirect to='/login' />
    }

    return (
        <Suspense fallback={<CircularProgress />}>
            <Cabinet resource={loadCabinet()} />
        </Suspense>
    )
}

CabinetPage.propTypes = {
    loadCabinet: PropTypes.func,
    failedToLoad: PropTypes.bool,
    tokensRefreshFailed: PropTypes.bool,
    logout: PropTypes.func,
    clearTokens: PropTypes.func,
    clearCabinet: PropTypes.func
}

const mapStateToProps = ({
    alert,
    tokens: { tokensRefreshFailed },
    cabinet: { failedToLoad }
}) => ({
    alert,
    tokensRefreshFailed,
    failedToLoad
})

const mapDispatchToProps = dispatch =>
    bindActionCreators(
        {
            cabinetLoad: cabinetActions.load,
            logout: userActions.logoutWithoutRedirect,
            loadCabinet: api.loadCabinet,
            clearCabinet: cabinetActions.clear,
            clearTokens: tokensActions.clear
        },
        dispatch
    )

export default connect(mapStateToProps, mapDispatchToProps)(CabinetPage)

loadCabinet()调度三阶段渲染的异步操作,如并发文档所说,并返回一个带有read()属性的对象。
但是,我在这里看不到任何家长更新。

@h0tw4t3r您正在组件渲染期间分派 Redux 操作。 这不受支持,这就是警告的内容。 最好向 React Router 专家询问如何优雅地处理这种情况(重定向),对那部分无能为力。

关于并发模式,请注意 Redux 目前一般不兼容。 因此,如果您正在试验 CM,您可能希望避免它。

此线程的小更新

我们将很快发布一个 React 补丁,修复类的过度触发这个警告。 因此,如果您遇到这种情况,请考虑等待几天,然后在 16.13.1 发布后尝试。

如果你想继续寻找原因,我希望https://github.com/facebook/react/issues/18178#issuecomment -595846312 解释如何。

我觉得很奇怪,在类组件中使用相同的逻辑时,功能(钩子)不会发出任何警告:

功能组件(Hooks):

import React, { Component } from "react"
import SortableTree from "react-sortable-tree"
import "react-sortable-tree/style.css"

const data = [
  {
    title: "Windows 10",
    subtitle: "running",
    children: [
      {
        title: "Ubuntu 12",
        subtitle: "halted",
        children: [
          {
            title: "Debian",
            subtitle: "gone"
          }
        ]
      },
      {
        title: "Centos 8",
        subtitle: "hardening"
      },
      {
        title: "Suse",
        subtitle: "license"
      }
    ]
  }
]

const nodeInfo = row => console.log(row)

export default class App extends Component {
  constructor(props) {
    super(props)

    this.state = {
      searchString: "",
      searchFocusIndex: 0,
      searchFoundCount: null,
      treeData: data
    }
  }

  render() {
    const { searchString, searchFocusIndex, searchFoundCount } = this.state

    const customSearchMethod = ({ node, searchQuery }) =>
      searchQuery &&
      ((node.title &&
        node.title.toLowerCase().indexOf(searchQuery.toLowerCase()) > -1) ||
        (node.subtitle &&
          node.subtitle.toLowerCase().indexOf(searchQuery.toLowerCase()) > -1))

    const selectPrevMatch = () =>
      this.setState({
        searchFocusIndex:
          searchFocusIndex !== null
            ? (searchFoundCount + searchFocusIndex - 1) % searchFoundCount
            : searchFoundCount - 1
      })

    const selectNextMatch = () =>
      this.setState({
        searchFocusIndex:
          searchFocusIndex !== null
            ? (searchFocusIndex + 1) % searchFoundCount
            : 0
      })

    return (
      <div>
        <h2>Find the needle!</h2>
        <form
          style={{ display: "inline-block" }}
          onSubmit={event => {
            event.preventDefault()
          }}
        >
          <input
            id="find-box"
            type="text"
            placeholder="Search..."
            style={{ fontSize: "1rem" }}
            value={searchString}
            onChange={event =>
              this.setState({ searchString: event.target.value })
            }
          />
          &nbsp;
          <button
            type="button"
            disabled={!searchFoundCount}
            onClick={selectPrevMatch}
          >
            &lt;
          </button>
          &nbsp;
          <button
            type="submit"
            disabled={!searchFoundCount}
            onClick={selectNextMatch}
          >
            &gt;
          </button>
          &nbsp;
          <span>
            &nbsp;
            {searchFoundCount > 0 ? searchFocusIndex + 1 : 0}
            &nbsp;/&nbsp;
            {searchFoundCount || 0}
          </span>
        </form>

        <div style={{ height: 300 }}>
          <SortableTree
            treeData={this.state.treeData}
            onChange={treeData => this.setState({ treeData })}
            searchMethod={customSearchMethod}
            searchQuery={searchString}
            searchFocusOffset={searchFocusIndex}
            searchFinishCallback={matches =>
              this.setState({
                searchFoundCount: matches.length,
                searchFocusIndex:
                  matches.length > 0 ? searchFocusIndex % matches.length : 0
              })
            }
            generateNodeProps={row => {
              return {
                title: row.node.title,
                subtitle: (
                  <div style={{ lineHeight: "2em" }}>{row.node.subtitle}</div>
                ),
                buttons: [
                  <button
                    type="button"
                    className="btn btn-outline-success"
                    style={{
                      verticalAlign: "middle"
                    }}
                    onClick={() => nodeInfo(row)}
                  >
                    ℹ
                  </button>
                ]
              }
            }}
          />
        </div>
      </div>
    )
  }
}

image

类组件:

import React from "react";
import SortableTree from "react-sortable-tree";
import "react-sortable-tree/style.css";

const data = [
  {
    title: "Windows 10",
    subtitle: "running",
    children: [
      {
        title: "Ubuntu 12",
        subtitle: "halted",
        children: [
          {
            title: "Debian",
            subtitle: "gone"
          }
        ]
      },
      {
        title: "Centos 8",
        subtitle: "hardening"
      },
      {
        title: "Suse",
        subtitle: "license"
      }
    ]
  }
];

const nodeInfo = row => console.log(row);

export default class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      searchString: "",
      searchFocusIndex: 0,
      searchFoundCount: null,
      treeData: data
    };
  }

  render() {
    const { searchString, searchFocusIndex, searchFoundCount } = this.state;

    const customSearchMethod = ({ node, searchQuery }) =>
      searchQuery &&
      ((node.title &&
        node.title.toLowerCase().indexOf(searchQuery.toLowerCase()) > -1) ||
        (node.subtitle &&
          node.subtitle.toLowerCase().indexOf(searchQuery.toLowerCase()) > -1));

    const selectPrevMatch = () =>
      this.setState({
        searchFocusIndex:
          searchFocusIndex !== null
            ? (searchFoundCount + searchFocusIndex - 1) % searchFoundCount
            : searchFoundCount - 1
      });

    const selectNextMatch = () =>
      this.setState({
        searchFocusIndex:
          searchFocusIndex !== null
            ? (searchFocusIndex + 1) % searchFoundCount
            : 0
      });

    return (
      <div>
        <h2>Find the needle!</h2>
        <form
          style={{ display: "inline-block" }}
          onSubmit={event => {
            event.preventDefault();
          }}
        >
          <input
            id="find-box"
            type="text"
            placeholder="Search..."
            style={{ fontSize: "1rem" }}
            value={searchString}
            onChange={event =>
              this.setState({ searchString: event.target.value })
            }
          />
          &nbsp;
          <button
            type="button"
            disabled={!searchFoundCount}
            onClick={selectPrevMatch}
          >
            &lt;
          </button>
          &nbsp;
          <button
            type="submit"
            disabled={!searchFoundCount}
            onClick={selectNextMatch}
          >
            &gt;
          </button>
          &nbsp;
          <span>
            &nbsp;
            {searchFoundCount > 0 ? searchFocusIndex + 1 : 0}
            &nbsp;/&nbsp;
            {searchFoundCount || 0}
          </span>
        </form>

        <div style={{ height: 300 }}>
          <SortableTree
            treeData={this.state.treeData}
            onChange={treeData => this.setState({ treeData })}
            searchMethod={customSearchMethod}
            searchQuery={searchString}
            searchFocusOffset={searchFocusIndex}
            searchFinishCallback={matches =>
              this.setState({
                searchFoundCount: matches.length,
                searchFocusIndex:
                  matches.length > 0 ? searchFocusIndex % matches.length : 0
              })
            }
            generateNodeProps={row => {
              return {
                title: row.node.title,
                subtitle: (
                  <div style={{ lineHeight: "2em" }}>{row.node.subtitle}</div>
                ),
                buttons: [
                  <button
                    type="button"
                    className="btn btn-outline-success"
                    style={{
                      verticalAlign: "middle"
                    }}
                    onClick={() => nodeInfo(row)}
                  >
                    ℹ
                  </button>
                ]
              };
            }}
          />
        </div>
      </div>
    );
  }
}

@radulle这本身并不是一个很有帮助的例子。 我尝试将它放入 CodeSandbox 但它不起作用: https ://codesandbox.io/s/clever-taussig-9xixs

@gaearon我想创建一个 codeandbox,但该库的最新版本存在一些问题。 旧版本不会抛出错误。 目前看来,重现它的唯一方法是在本地创建 React App 中启动它:(

@radulle我可以尝试在 CodeSandbox 上使用哪个版本?

@gaearon 2.6.2 使用此设置工作并抛出错误/警告:
image
所以对于相同的设置:
功能组件:错误/警告
类组件:没有错误/警告
也许我错过了一些东西,它们并不相同。

是的,这是我在https://github.com/facebook/react/issues/18178#issuecomment -600369392 中提到的案例之一。 在这种情况下,我们会将警告静音。 警告本身是合法的,正如您所说的那样,从概念上讲,这也是类中的一个问题。 但是,这种差异没有意义,因此当它来自一个类时(在本例中就是这样),我们现在将在这两种情况下将其静音。

我相信有一个合法的用例可以从渲染函数而不是从效果中触发状态更新,那就是保留执行顺序。

用一个实际例子来说明:在我们的应用程序中,我们有一个特殊的系统来管理面包屑。

  • 我们有一个包含所有面包屑的全局上下文
  • 我们有一个钩子可以让我们在这个上下文中添加面包屑,像这样:

    const addBreadcrumb = useAddBreadcrumb();
    addBreadcrumb(<Breadcrumb>{item.name}</Breadcrumb>, [item.name]);
    
  • 我们有一个从上下文中读取信息并呈现所有面包屑的组件。 所以,每当我们想要渲染面包屑时,我们只需要在没有参数的情况下调用它。

这是非常实用的,因为我们不需要维护任何面包屑结构:这个结构是在代码本身中声明的。 如果我们想将路由移动到应用程序的另一部分,面包屑系统将继续工作。

所以,结合react-router ,我们可以做这样的事情:

// Main/index.tsx
import { useAddBreadcrumb } from 'components/Breadcrumbs';
import React from 'react';
import Home from './Home';
import Movies from './Movies';

const Main = () => {
    const addBreadcrumb = useAddBreadcrumb();
    addBreadcrumb(<Breadcrumb to="/">Home</Breadcrumb>, []);

    return <Switch>
        <Route path="/movies">
            <Movies />
        </Route>
        <Route path="/" />
            <Home />
        </Route>
    </Switch>
}

// Movies/index.tsx
import { useAddBreadcrumb } from 'components/Breadcrumbs';
import React from 'react';
import Detail from './Detail';
import Master from './Master';

const Movies = ({ url }) => {
    const addBreadcrumb = useAddBreadcrumb();
    addBreadcrumb(<Breadcrumb to={url}>Movies</Breadcrumb>, [url]);

    return <Switch>
        <Route path="/:id">
            <Detail />
        </Route>
        <Route path="/" />
            <Master />
        </Route>
    </Switch>
}

// Movies/Detail/index.tsx
import Breadcrumbs, { useAddBreadcrumb } from 'components/Breadcrumbs';
import React from 'react';
import { useRouteMatch } from 'react-router-dom';

const MovieDetail = ({ url }) => {
    const addBreadcrumb = useAddBreadcrumb();
    const { params: { id } } = useRouteMatch<{ id: string; }>();
    const movie = useMovie(id);

    addBreadcrumb(
        <Breadcrumb to={url}>{movie?.name}</Breadcrumb>,
        [movie?.name, url]
    );

    return <div>
        <Breadcrumbs />
    </div>
}

现在,如果我们转到/movies/gone-with-the-wind ,我们的面包屑将如下所示:

Home > Movies > Gone with the wind

现在,这是我的观点:为了让它起作用,我们需要保证执行顺序。 在这种情况下,执行顺序很明显:首先Main呈现,然后呈现其子项,其中包括Movies ,最后是MovieDetail 。 在这种情况下, addBreadcrumb调用将以正确的顺序执行。

现在,变更日志说明了这一点:

在极少数情况下,您故意希望更改另一个组件的状态作为渲染的结果,您可以将 setState 调用包装到 useEffect 中。

这确实是我们有意改变另一个组件状态的极少数情况之一。 但是,如果我们这样做,更改日志建议并将addBreadcrumb (最终是美化的setState )包装到useEffect ,则不再保证执行顺序。 所有三个setStates都将在渲染完成后执行,这会产生竞争条件,并破坏我们的系统。

我不知道这个奇特的系统是否被认为是一种反模式,但对我来说这是有道理的,而且我还没有找到任何更简单的替代方案。

所以,总而言之,我欢迎这个新警告,但我认为我们的最佳解决方案是有办法抑制它。 也许setState的第二个可选参数可以解决问题。

@MeLlamoPablo

依赖于兄弟姐妹或父/子渲染顺序的渲染调用听起来非常脆弱。 React 实际上并不能保证这一点。 儿童可以(并且将会)在没有父母的情况下重新渲染,反之亦然。 如果子项被延迟渲染(例如代码拆分),此代码也会中断。 或者如果动态插入或删除一些子项。 更不用说,这在性能方面非常糟糕,因为您有太多级联渲染。

我对你试图解决的问题深有同感——事实上,它在今天的 React 中没有一个惯用的解决方案。 我们对此有一些想法,但适当的解决方案需要大不相同。 同时,我们强烈反对这种变通方法。

@gaearon ,感谢您的洞察力。 我有一个问题:第一次渲染的顺序是否有保证? 因为这就是我们正确运行所需的全部内容(一旦我们知道面包屑的顺序,我们就不必关心后续渲染的顺序)。

在我看来,保证第一次渲染的顺序是合乎逻辑的。 否则 React 如何知道父组件有任何子组件?

关于级联渲染的性能,您是绝对正确的。 我会想办法改进我们的系统。

@MeLlamoPablo

我有一个问题:第一次渲染的顺序是否有保证? 因为这就是我们正确运行所需的全部内容(一旦我们知道面包屑的顺序,我们就不必关心后续渲染的顺序)。

不强烈。 我认为它主要适用于当前版本的 React,但未来可能会发生变化。 即使在今天,结合lazySuspense也不能保证。

否则 React 如何知道父组件有任何子组件?

不能保证兄弟顺序。 对于父/子顺序,您是正确的父母必须先渲染; 然而,React 可能需要它到达子级

这是脆弱的。

@gaearon ,再次感谢。 非常感谢。

也许应该有一个 ESLint 规则来警告不要在渲染体中调用useState mutators?

在渲染期间调用您自己的组件的 setState一种受支持的模式(尽管应该谨慎使用)。 它是其他组件上的 setState 不好。 您无法静态检测这些。

理论上可以使用 ts-eslint 来完成,假设他们使用正确的上游 React 类型,但是是的,可能比它值得付出更多的努力。

我认为没有某种效果跟踪就无法完成。 一旦你在中间有一个功能,你就会丢失信息。

我也面临这个问题react@experimental + react-redux + redux
image

代码如下所示:

import React, { Suspense } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { Redirect } from 'react-router-dom'
import PropTypes from 'prop-types'
import { userActions, cabinetActions, tokensActions } from '../../actions'
import { CircularProgress } from '@material-ui/core'
import { api } from './api'

const Cabinet = ({ resource }) => {
  resource.read()
  return <h1>cabinet</h1>
}

Cabinet.propTypes = {
  resource: PropTypes.shape({
      read: PropTypes.func
  })
}

const CabinetPage = ({
  failedToLoad,
  tokensRefreshFailed,
  logout,
  loadCabinet,
  clearTokens,
  clearCabinet
}) => {
  if (tokensRefreshFailed || failedToLoad) {
      clearTokens()
      clearCabinet()
      logout()
      return <Redirect to='/login' />
  }

  return (
      <Suspense fallback={<CircularProgress />}>
          <Cabinet resource={loadCabinet()} />
      </Suspense>
  )
}

CabinetPage.propTypes = {
  loadCabinet: PropTypes.func,
  failedToLoad: PropTypes.bool,
  tokensRefreshFailed: PropTypes.bool,
  logout: PropTypes.func,
  clearTokens: PropTypes.func,
  clearCabinet: PropTypes.func
}

const mapStateToProps = ({
  alert,
  tokens: { tokensRefreshFailed },
  cabinet: { failedToLoad }
}) => ({
  alert,
  tokensRefreshFailed,
  failedToLoad
})

const mapDispatchToProps = dispatch =>
  bindActionCreators(
      {
          cabinetLoad: cabinetActions.load,
          logout: userActions.logoutWithoutRedirect,
          loadCabinet: api.loadCabinet,
          clearCabinet: cabinetActions.clear,
          clearTokens: tokensActions.clear
      },
      dispatch
  )

export default connect(mapStateToProps, mapDispatchToProps)(CabinetPage)

loadCabinet()调度三阶段渲染的异步操作,如并发文档所说,并返回一个带有read()属性的对象。
但是,我在这里看不到任何家长更新。

对于遇到此问题的其他任何人,我通过将 redux 操作调度移动到返回的组件来修复它。 这是现在的样子:

const CabinetPage = ({
    alert,
    failedToLoad,
    tokensRefreshFailed,
    logout,
    loadCabinet,
    clearTokens,
    clearCabinet,
    clearAlert
}) => (
    <Suspense fallback={<MUIBackdropProgress />}>
        {alert.message && (failedToLoad || tokensRefreshFailed) ? (
            <MUIAlertDialog
                title={alert.message}
                text={errorText}
                onClose={() => {
                    clearAlert()
                    clearCabinet()
                    clearTokens()
                    logout()
                }}
            />
        ) : (
            <Cabinet resource={loadCabinet()} />
        )}
    </Suspense>
)

我喜欢这个警告,因为它迫使你选择正确的设计模式。 现在一切正常!

我们修复了 16.13.1 中警告过度触发的情况。 其余案例是合法的,需要修复。

@gaearon刚刚升级,错误消失了! 谢谢你们的工作!

@gaearon谢谢。 你刚刚救了我的一天:-)

虽然升级没有解决我的问题,但它确实在控制台中为我提供了更多信息以帮助我找到我的问题。 谢谢@gaearon

如果你调度一个动作导致另一个组件返回与上次相同的状态怎么办? 这被认为是坏事吗? 我认为在这种情况下它会短路。

我可以这么说吗,虽然我完全理解这个警告背后的逻辑......这几乎感觉像是对 React 团队一直在告诉社区的东西的背叛,因为感觉团队已经教授了这些关于如何编码的重要真理反应:

1)在层次结构中保持您的状态尽可能高(不更高),然后将数据和设置器向下传递给子组件

2)函数组件真棒! 忘记那类噪音,在一个函数中完成您的整个组件!

现在,当人们遵循这两个规则,将他们的状态设置器传递给他们的功能组件,并在这些组件中调用它们时......去做”。

这些都没有改变这里的技术需求,我也没有说这个改变是错误的或坏的......我只是觉得有一个消息传递问题,因为你没有传达好的清晰规则(就像两个我刚刚提到)在这个新世界中编码。 如果您要更改对我们的规则,我认为首先解释最佳实践会有所帮助。

所以,我真正要问的是......我认为如果团队中的某个人写一些东西(比如一篇文章),他们说“我知道我们以前给过你这两条规则:这里是新规则”,然后在文档/发行说明中的​​每个地方添加一个链接到该文章的每个地方引用这个新警告(所以每个人都在谷歌上搜索“这是 WTF?”可以学习编写 React 的正确方法,在“新世界”)。

@machineghost :我认为您误解了消息警告的内容。

将回调传递给更新父母状态的孩子并没有错。 那一直都很好。

问题是当一个组件在另一个组件中排队更新时,_而第一个组件正在渲染_。

换句话说,不要这样做:

function SomeChildComponent(props) {
    props.updateSomething();
    return <div />
}

但这很好:

function SomeChildComponent(props) {
    // or make a callback click handler and call it in there
    return <button onClick={props.updateSomething}>Click Me</button>
}

而且,正如 Dan 多次指出的那样,在渲染时在 _same_ 组件中排队更新也很好:

function SomeChildComponent(props) {
  const [number, setNumber] = useState(0);

  if(props.someValue > 10 && number < 5) {
    // queue an update while rendering, equivalent to getDerivedStateFromProps
    setNumber(42);
  }

  return <div>{number}</div>
}

是的,但是当您对组件进行编码时,您不会考虑其父组件的时间。 这就是 React 组件之美的一部分:封装。

再说一次,我并不是说新警告是不好的,我是说在我们有任何优秀的 React 开发人员都可以遵循的两条规则之前。 现在,在 X 条件下,这些规则会消失,但仅在 X 下(这听起来像 X = “同时父组件也在更新”)。

我只是认为需要更多地关注解释这一点,以及如何避免这个问题,而不是像“现在这是一个问题!”。

@machineghost :你_真的_没有明白我在这里说的话。

父/子时间不是问题。

队列状态更新_在呈现函数组件时_在其他组件中_是问题所在。

根据定义,它必须是父/子(或孙子):否则它怎么可能将状态设置器传递下去?

我并不是说这在其他组件关系中也不会成为问题,但我特别指的是人们遵循 React 最佳实践,传递状态设置器,然后收到此警告。

这就是我要说的全部内容,而且我要说的是,“可以更好地解释它,更多地关注如何编写好代码,而不仅仅是'这是一个新错误,这就是它的含义'”。

定时。 不是。 问题。

一个函数组件被允许在渲染时将状态更新排入队列,并且只为它自己。 正如我的例子所示,它相当于getDerivedStateFromProps

在函数组件的实际呈现主体内为 _any_ 其他组件排队更新是非法的。

这就是这个警告告诉你的。

我不知道我怎么能解释得更清楚。

时间不是问题:不是你的,也不是我的。 我的问题是文档,或缺乏文档。

但是你决定在一个问题线程中与一个互联网陌生人开始一场战争,而不是听他们在说什么......我不想继续与你接触。

关键是规则没有改变。 这一直是错误的模式。 它刚刚被突出显示,以便您知道您的代码有问题。

从字面上看,您刚才所说的任何内容都与我所写的内容不一致。 事实上,这几乎就像我从一开始就说过完全相同的事情......而我一直要求的是对相同规则的更好解释,你说的那些没有改变(当然他们没有......改变并“创造了一个新世界”的是警告)。

PS 你似乎也没有意识到这里的讽刺。 如果我什么都不明白,那就说明文档可以改进。 对我大喊大叫,说我对事情的理解很差,只会加强我的地位; 它并没有神奇地改进文档。

大家好,让我们冷静一下。 🙂

@markerikson感谢您的参与,但我认为这个讨论有点太激烈了。

@machineghost感谢您表达您的担忧。 我知道当针对以前似乎无害的模式弹出新警告时,这很烦人。

我同意这个警告需要一些事先的背景。 从本质上讲,您需要了解班级时代的两件事:

  • 你不应该在渲染期间设置状态。 班级总是警告这一点。

  • 该函数组件主体本质上与类组件渲染方法相同。

确实是我们的疏忽,函数组件体中另一个组件上的 setState 之前没有发出警告。 您可以从以上两点推断出这是一种糟糕的模式,但可以说人们无法意识到这一点。 给您带来的不便,我们深表歉意。

如果您觉得文档中有任何特定的地方应该提到这一点,请在文档存储库中提出问题。 我们计划基于 Hooks 重写文档,以便我们可以牢记这一点。 谢谢!

我绝不想让任何人对任何事情感到难过,我拒绝接受您的道歉;) Hooks 是天才,你们都是发明它们的天才。 任何因为没有完美地想象每个结果而指责另一位工程师的工程师都是......一个混蛋。

我一直试图传达的是,目前当我收到这个警告时,我做了每个人都会做的事情:我用谷歌搜索了它。 然后我找到了一个页面,上面写着“我们收到了这个新警告”。

我只是认为如果在该公告中存在(可以是)一个链接,或者有人可能通过谷歌搜索找到的类似地方,到“这里是为什么我们会引入这个错误,这里是如何成为一名出色的 React 开发人员并遵循一些基本准则,以免自己遇到它。”

但同样,钩子很棒,React 团队很棒,甚至这个新警告也很棒(我敢肯定它会发现它试图防范的错误而失败)。 如果有人欠任何人一个道歉,那是我没有领导。

当然,没有什么难受的感觉。 “为什么”的答案并不比“如果一个组件在渲染期间触发其他组件的更新,跟踪应用程序的数据流变得非常困难,因为它不再是自上而下的”。 所以如果你这样做,你就有点放弃了 React 的好处。

再次澄清,这本身并不新鲜——类总是有相同的警告。 我们在 Hooks 中错过了它,并且正在纠正错误。 我想你修复它的方式取决于具体情况——所以我们不能给你确切的说明——但如果你分享一个你正在努力解决的例子,我们很乐意提供帮助。 一般而言,您可以通过与修复始终存在的相应类警告类似的方式来修复它。

希望有所帮助!

我将在此讨论中添加我的两分钱,并同意@machineghost 的观点,即自从引入功能组件和钩子以来,已经有很多困惑。 社区正在寻求 react 团队的领导,但过去很简单的事情变得复杂,似乎缺乏沟通和清晰的例子。

案例和重点是 ComponentDidMount 和 Unmount,首先我们被告知使用函数组件,然后使用带有空数组的 useEffect,然后我们被告知这不好,现在我们得到了这个错误消息。 我不知道,我感谢 React 的所有辛勤工作,但我们确实需要在文档和最佳实践方面投入更多精力。

我一直在函数的潮流中这么久(试图避免使用 Recompose 的类,甚至在钩子之前),我什至不记得那些类警告。

虽然我很感激你的回复,但我主要只是希望“经验法则”、指导方针、最佳实践等。诸如“保持你的状态尽可能高以覆盖所有使用它的组件,但没有更高”或“使用控制模式反转将状态设置器传递给子组件”。

也许这里没有任何东西,但可能类似于“如果功能组件 A 更改状态,它不应该渲染它传递状态设置器的子组件 B(它应该立即返回或其他什么),因为如果子组件呈现和更改状态,您将遇到问题”。

或者可能是星期天很晚了,我一整天都在做一个个人项目,我的大脑太炸了,正在把简单的事情变成困难的事情。 无论哪种方式,如果我对指导方针有任何进一步的建议,我都会回帖,但除此之外,我当然不希望其他人在周日晚上花在这上面。

再次感谢!

我认为我们已经到了这个线程已经不再有用的地步。

案例和重点是 ComponentDidMount 和 Unmount,首先我们被告知使用函数组件,然后使用带有空数组的 useEffect,然后我们被告知这不好,现在我们得到了这个错误消息。

很抱歉我们的文档没有帮助您,但很难说您遇到了哪些具体问题。 不幸的是,这非常含糊,并且歪曲了我们试图说的内容。 就像一个电话坏了的游戏。 如果您有特定问题,请提交一个新问题并尝试更详细地描述它。 如果您能提供更具体的信息,我们会尽力帮助您。

虽然我很感激你的回应,但我主要只是希望得到“经验法则”、指导方针、最佳实践等

经验法则一直是“渲染时不要执行副作用”。 将渲染视为一种纯粹的计算。 副作用进入不同的地方(类中的生命周期方法,或函数组件中的 useEffect)。 没有更多了。

“如果功能组件 A 更改状态,它不应该呈现它传递状态设置器的子组件 B(它应该立即返回或其他东西),因为如果子组件呈现并更改状态,你就会遇到问题”。

我认为这里仍然存在一些误解。 将状态设置器传递给子组件完全没问题。 一直都很好,而且永远都是。

问题在于在渲染

这两次谈话的总主题是我们在兜圈子。 讨论已转为元讨论,而不是讨论具体案例,我们讨论的是模糊的概括。 我们很可能相互误解,但缺乏具体的例子使这种误解无法解决。

为此,我将锁定此线程。 我非常感谢这里的每个人的意见,如果您努力解决此警告,我们很乐意听取您的更多意见。 获得帮助的方法是使用最小的复制示例提交问题。 然后我们可以讨论您的具体问题并帮助头脑风暴一个解决方案。 这对所有参与的人来说都会更有效率,并且还可以避免向数十个已经对此主题发表评论并最终订阅的人发送电子邮件。 谢谢!

作为一个小更新(抱歉让大家ping通),我听说https://github.com/final-form/react-final-form/issues/751#issuecomment -606212893为使用它的人解决了一堆这些错误图书馆。

此页面是否有帮助?
0 / 5 - 0 等级