如何修复“警告:useLayoutEffect 在服务器上啥都不做”?

Posted

技术标签:

【中文标题】如何修复“警告:useLayoutEffect 在服务器上啥都不做”?【英文标题】:How to fix the "Warning: useLayoutEffect does nothing on the server"?如何修复“警告:useLayoutEffect 在服务器上什么都不做”? 【发布时间】:2020-01-24 00:41:27 【问题描述】:

这是完整的错误:

警告:useLayoutEffect 在服务器上什么都不做,因为它 效果无法编码为服务器渲染器的输出格式。 这将导致初始的非水合 UI 和 预期的用户界面。为避免这种情况,useLayoutEffect 只能用于 仅在客户端上呈现的组件

      in ForwardRef(ButtonBase)
      in WithStyles(ForwardRef(ButtonBase))
      in ForwardRef(Button)
      in WithStyles(ForwardRef(Button))
      in form
      in div

我每次运行测试时都会得到它。这是我的测试

/* eslint-disable quotes */
import React from "react"
import  shallow, configure  from "enzyme"
import LoginForm from "../src/components/LoginForm"
import Button from "@material-ui/core/Button"
import Adapter from "enzyme-adapter-react-16"
import  render, fireEvent, cleanup  from "@testing-library/react"

configure( adapter: new Adapter() )

describe("<LoginForm />", () => 
  let wrapper
  let usernameInput
  let passwordInput
  let signInButton

  // Create initial props that get passed into the component
  const initialProps = 
    location: 
      state: 
        from: 
          pathname: "/",
        ,
      ,
    ,
  

  // Unit testing
  describe("Unit tests", () => 
    // what to do before each test
    beforeEach(() => 
      // Render the login form component, pass in props. (Shallow method renders the component without its children, good for unit tests.)
      wrapper = shallow(<LoginForm ...initialProps />)
      usernameInput = wrapper.find("#username")
      passwordInput = wrapper.find("#password")
      signInButton = wrapper.find(Button)
    )

    // what to do after each test
    afterEach(() => 
      jest.clearAllMocks()
    )

    // UI Integrity test
    it("should match the snapshot", () => 
      // snapshots are text references of the html of the rendered component.
      expect(wrapper.html()).toMatchSnapshot()
    )

    it("should have a username inputs", () => 
      expect(usernameInput.length).toEqual(1)
    )

    it("should have the expected props on the username field", () => 
      expect(usernameInput.props()).toEqual(
        id: "username",
        name: "username",
        value: "",
        type: "username",
        onChange: expect.any(Function),
        required: true,
      )
    )

    it("should have a password field", () => 
      expect(passwordInput.length).toEqual(1)
    )

    it("should have the expected props on the password field", () => 
      expect(passwordInput.props()).toEqual(
        id: "password",
        name: "password",
        value: "",
        type: "password",
        onChange: expect.any(Function),
        required: true,
      )
    )

    it("should have a submit button", () => 
      expect(signInButton.length).toEqual(1)
    )

    it("should have the expected props on the button", () => 
      expect(signInButton.props()).toEqual(
        type: "button",
        variant: "contained",
        style: expect.objectContaining(
          marginTop: "10px",
        ),
        onClick: expect.any(Function),
        children: "Sign In",
      )
    )
  )

  // Integrations Testing
  describe("Integrations tests", () => 
    beforeEach(() => 
      // Render the login form component, pass in props. (render method renders the component with its children, good for integrations tests, uses react-test-library.)
      const  getByLabelText, getByText  = render(
        <LoginForm ...initialProps />
      )
      usernameInput = getByLabelText(/Username/i)
      passwordInput = getByLabelText(/Password/i)
      signInButton = getByText("Sign In")
    )

    afterEach(cleanup)

    it("Username text change in onChange event", () => 
      expect(usernameInput.value).toBe("")

      fireEvent.change(usernameInput,  target:  value: "James"  )

      expect(usernameInput.value).toBe("James")
    )

    it("Password text change in onChange event", () => 
      expect(passwordInput.value).toBe("")

      fireEvent.change(passwordInput,  target:  value: "mypassword"  )

      expect(passwordInput.value).toBe("mypassword")
    )

    it("Test button submit", () => 
      const mockLogin = jest.fn()

      const button = shallow(<Button onClick=mockLogin />)

      button.simulate("click")

      expect(mockLogin.mock.calls.length).toEqual(1)
    )
  )
)

我相信它与material-ui组件有关。我已经调查过了,here 上有一个类似的问题,说这个问题与我的项目没有的依赖关系有关。所以我认为这与使用useEffectLayout 的material-ui 组件有关,并且由于某种原因,测试环境不喜欢这样。我用 yarn 和 jest yarn test 运行我的测试来运行测试套件。

【问题讨论】:

您可以通过为测试gist.github.com/gaearon/… 指定环境来解决问题,例如/* @jest-environment node */ &lt;LoginForm ... /&gt; 是 HOC 吗?我也有同样的问题。 嘿@skyboyer,感谢您的提示。实际上我之前曾尝试过这样做:将"jest": "testEnvironment": "node" 添加到我的package.json 文件中。但它在运行纱线测试时出现了一个新问题:ReferenceError: window is not defined 24 | // ) 25 | &gt; 26 | jest.spyOn(window.localStorage.__proto__, "setItem") | ^ 27 | window.localStorage.__proto__.setItem = jest.fn() 28 | at Object.window (src/setupTests.js:26:12) at Array.forEach (&lt;anonymous&gt;) 所以我现在正在研究...... 好的,所以我在添加节点 jest-environement 时无法摆脱上述window not defined problem。但是对我有用的是你喜欢@skyboyer的评论,说在我的设置文件中添加import React from "react" React.useLayoutEffect = React.useEffect会起作用。它确实做到了。它很老套,但无论如何。 嗨 @LCIII 它不是 HOC 【参考方案1】:

添加

import React from "react" 
React.useLayoutEffect = React.useEffect 

我的 frontend/src/setupTests.js 测试文件是一种抑制开玩笑警告的方法。最终,这似乎是 Material-UI 组件与 Jest 存在问题的问题。

【讨论】:

这很漂亮【参考方案2】:

更简洁的解决方案可能是像这样使用 jest mock:

jest.mock('react', () => (
  ...jest.requireActual('react'),
  useLayoutEffect: jest.requireActual('react').useEffect,
));

【讨论】:

这应该是正确的答案,因为它实际上在所有情况下都有效,并且是正确的“模拟”做事方式。忽略上面接受的答案。 在 Next JS 中,不需要显式导入 React,所以这个更时尚。 这个给error - ./node_modules/@jest/core/build/collectHandles.js:10:0 Module not found: Can't resolve 'async_hooks' 有人知道怎么解决吗?我尝试将 async_hooks 添加为依赖项,但无济于事。 我们把这个放在什么文件里?我猜它必须是适用于所有测试的全局文件?更新:如果你有的话,它可以进入你的setupTests.js file。见:create-react-app.dev/docs/running-tests/…【参考方案3】:

你应该检查 useLayoutEffect 是否可以这样使用:

import React, useEffect, useLayoutEffect from 'react';

const canUseDOM = typeof window !== 'undefined';
const useIsomorphicLayoutEffect = canUseDOM ? useLayoutEffect : useEffect;

然后用useIsomorphicLayoutEffect代替useLayoutEffect

【讨论】:

这在测试之外工作,因此可以用于服务器端渲染。【参考方案4】:

您可以通过将以下 if 语句添加到启动文件(例如 index.js)的开头来解决此问题:

if (typeof document === 'undefined') 
  React.useLayoutEffect = React.useEffect;

这将确保 s-s-r 使用 React.useEffect 而不是 React.useLayoutEffect,因为 document 在服务器端未定义。

【讨论】:

以上是关于如何修复“警告:useLayoutEffect 在服务器上啥都不做”?的主要内容,如果未能解决你的问题,请参考以下文章

如何修复drv?

如何修复漏洞

如何修复WMI

PHP网站漏洞怎么修复 如何修补网站程序代码漏洞

如何修复这些漏洞? (npm audit fix 无法修复这些漏洞)

如何修复AppScan漏洞