如何修复“警告: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 */
<LoginForm ... />
是 HOC 吗?我也有同样的问题。
嘿@skyboyer,感谢您的提示。实际上我之前曾尝试过这样做:将"jest": "testEnvironment": "node"
添加到我的package.json 文件中。但它在运行纱线测试时出现了一个新问题:ReferenceError: window is not defined 24 | // ) 25 | > 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 (<anonymous>)
所以我现在正在研究......
好的,所以我在添加节点 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 在服务器上啥都不做”?的主要内容,如果未能解决你的问题,请参考以下文章