跳转到内容

🎉 Material UI v5 is out! Head to the migration guide to get started.

常见问题解答

您在一个特定的问题上停滞不前吗? 您可以先在常见 FAQ(问题解答)中检索一下常见问题。

如果仍然找不到所需的内容,您可以参考我们的 支持页面

Material-UI 超赞。 我该如何支持该项目?

其实有很多方法可以支持 Material-UI:

  • 口口相传。 通过在您的网站上 链接到 material-ui.com 来传播 Material-UI ,每个反向链接对我们来说都很重要。 在 Twitter 上关注我们 ,点赞并转发一些重要的新闻。 或者只是与您的朋友谈论我们。
  • 给我们反馈 。 告诉我们一些做得好的地方或者可以改进的地方。 请给您最希望看到能够解决的问题投票(👍)。
  • 帮助新的用户 。 您可以在 StackOverflow 中回答一些问题。
  • 做出一些改变吧
  • OpenCollective 上资助我们。 如果您在商业项目中使用了 Material-UI,并希望通过成为我们的赞助商来支持我们的持续发展,或者在一个业余的或者爱好的项目中使用了,并想成为我们的一个支持者, 您都可以通过 OpenCollective 来资助我们。 筹集的所有资金都是透明管理的,赞助商在 README 和 Material-UI 主页上都会获得认可。

为什么我的组件在生产构造中没有正确地渲染?

发生这种情况的首要原因,很有可能是您的代码在生产环境中的捆绑包中出现了类名冲突。 如果想要 Material-UI 正常工作,页面上所有组件的 classname 值必须由单个实例的 类名称生成器 生成。

要纠正这个问题,您需要对页面上的所有组件进行初始化,使它们之间永远只有一个类名生成器

在很多情况下,您可能最终会意外地使用两个类名生成器:

  • 您不小心打包了两个版本的 Material-UI。 您没有正确设置某个和 material-ui 的同等依赖的依赖包。
  • 对于你的 React 树控件而言,你在使用 JssProvider 构建一个 subject(分支)
  • 您正在使用打包根据,而它拆分代码的方式导致创建了多个类名生成器的实例。

如果你正使用的 webpack 带有 SplitChunksPlugin 插件 ,请尝试在设置里的 optimizations 下配置 runtimeChunk

总的来说,您只需要在每个 Material-UI 应用程序的组件树顶部使用 StylesProvider 组件进行包装,并在它们之间共享一个单一的类名生成器,就可以很容易地解决这个问题。

为什么当打开一个 Modal(模态框)时,位置固定的元素会移动?

当模态框打开的那一刹那,滚动行为就会被禁止。 这样就能够阻止用户与下层背景内容进行交互,而模态框应该是唯一的交互内容。 然而,移除滚动条会移动一些固定位置的元素。 在这种情况下,您可以应用全局 .mui-fixed 类名来告知 Material-UI 去处理这些元素。

如何在全局禁用 ripple effect(涟漪效果)?

涟漪效果完全来自 BaseButton 组件。 您可以通过在您的主题中提供以下内容,来全局地禁用涟漪效果:

import { createTheme } from '@material-ui/core';

const theme = createTheme({
  props: {
    // 组件的名字 ⚛️
    MuiButtonBase: {
      // 需要应用的属性
      disableRipple: true, // 在整个应用中都不会有涟漪效果 💣!
    },
  },
});

如何禁用全局过渡动画?

Material-UI 使用相同的主题助手来创建其所有的过渡动画。 因此,您可以通过覆盖主题助手来禁用所有的过渡:

import { createTheme } from '@material-ui/core';

const theme = createTheme({
  transitions: {
    // 这样就设定了全局的 `transition: none;`
    create: () => 'none',
  },
});

而在视觉测试过程,或者在低端设备上提高性能的时候,禁用过渡动画是很有帮助的。

您可以更进一步地禁用所有的过渡和动画效果。

import { createTheme } from '@material-ui/core';

const theme = createTheme({
  overrides: {
    // 组件名称 ⚛️
    MuiCssBaseline: {
      // 规则名称
      '@global': {
        '*, *::before, *::after': {
          transition: 'none !important',
          animation: 'none !important',
        },
      },
    },
  },
});

请注意,若想使用上述方法,您必须使用 CssBaseline 使其奏效。 如果您选择不使用它,您仍然可以通过加入这些 CSS 规则来禁用过渡和动画:

*, *::before, *::after {
  transition: 'none !important';
  animation: 'none !important';
}

我是否必须使用 JSS 给我的应用程序来设置样式呢?

不用的,JSS 不是一个必须选择。 但是它是一个内置的插件,所以使用它并不会产生额外的捆绑包尺寸。

然而,也许您正在给应用程序添加一些 Material-UI 组件,而应用程序以及使用了其他的样式解决方案,或者您已经熟悉了不同的 API,而不想学习一个新的 API? 在这种情况下,请访问 样式库互用 章节,在那你可以发现我们使用了一些替代样式库来重新设置 Material-UI 组件的样式,而这是多么的简单。

内联样式与 CSS 之间我应该怎么选择使用的时机?

根据经验,仅对动态的样式属性使用内联样式。 CSS 的替代方案也有诸多优势,例如:

  • 自动前缀
  • 更好地调试
  • 媒体查询
  • keyframes

我应该怎么使用 react-router?

在我们的指南中详细介绍了如何与 react-router、Gatsby 或 Next.js 这样的 第三方路由库 整合。

我应该怎么访问 DOM 元素?

所有应该在 DOM 中渲染内容的 Material-UI 组件都会都将其 ref 转发给底层的 DOM 组件。 这意味着您可以通过读取附加在 Material-UI 组件上的 ref 来获取 DOM 元素。

// 或者使用一个 ref setter 函数
const ref = React.createRef();
// 渲染
<Button ref={ref} />;
// 使用
const element = ref.current;

如果您对相关 Material-UI 组件是否转发了它的 ref 存在疑问的时候,你可以查看“Props”下的 API 文档,例如 Button API 包含了

ref 会被转发到根元素。

这就表明您可以使用一个 ref 来访问这个 DOM 元素。

我的页面上有多个样式实例。

如果您在控制台中看到类似下面的警告消息,那么您可能已经在页面上初始化了多个 @material-ui/styles 实例。

看起来在这个应用程序中初始化了多个 @material-ui/styles 实例。 这可能会导致主题传播问题、类名称损坏、专一性问题,并使你的应用程序尺寸无端变大。

可能的原因

出现这些问题通常有几个常见的原因:

  • 在您的依赖包中还存在另一个 @material-ui/styles 库。
  • 您的项目是 monorepo 结构(例如,lerna,yarn workspaces),并且有多个包依赖着 @material-ui/styles 模块(这与前一个包或多或少相同)。
  • 您有几个使用 @material-ui/styles 的应用程序在同一页面上运行(例如,webpack 中的几个入口点被加载在同一页面上)。

在 node_modules 中重复的模块

如果您认为问题可能出现在您的依赖关系中的 @material-ui/styles 模块的重复,那么有几种方法可以检查。 您可以在应用程序文件夹中使用 npm ls @material-ui/stylesyarn list @material-ui/stylesfind -L ./node_modules | grep /@material-ui/styles/package.json 这些命令行来检查。

如果使用了这些命令之后都没有发现重复的依赖,请尝试分析您的捆绑包中是否有多个 @material-ui/styles 实例。 您可以直接去检查捆绑包的源代码,或者使用 source-map-explorerwebpack-bundle-analyzer 这样的工具来帮助检查。

如果您确定当前遇到的问题是模块重复,那么您可以尝试这样解决:

如果您正在使用的是 npm,那么您可以尝试运行 npm dedupe 命令。 这条命令将会搜索本地的依赖关系,并试图通过将共同的依赖包移到树的更上层,这样来简化结构。

如果您使用的是 webpack,您可以更改 解析 @material-ui/styles 模块的方式。 您可以使用覆盖 webpack 查找依赖项的默认顺序这个方法,这样应用程序中的 node_modules 比默认的 node module 解析顺序更优先地进行渲染。

  resolve: {
+   alias: {
+     "@material-ui/styles": path.resolve(appFolder, "node_modules", "@material-ui/styles"),
+   }
  }

与 Lerna 一起使用

如果您想要让 @material-ui/styles 在 Lerna monorepo 中跨包运行,一个可行的修复方法是 提升(hoist)共享的依赖包到 monorepo 文件的根部。 您可以尝试使用 --hoist 标识运行引导的选项。

lerna bootstrap --hoist

另外,您也可以从 package.json 文件中删除 @material-ui/styles 项,然后手动将它移动到您顶层的 package.json 文件中。

Lerna 根目录下的 package.json 文件示例:

{
  "name": "my-monorepo",
  "devDependencies": {
    "lerna": "latest"
  },
  "dependencies": {
    "@material-ui/styles": "^4.0.0"
  },
  "scripts": {
    "bootstrap": "lerna bootstrap",
    "clean": "lerna clean",
    "start": "lerna run start",
    "build": "lerna run build"
  }
}

在一个页面上运行多个应用程序

如果您在一个页面上需要运行多个程序,那么请考虑在所有程序中使用一个 @material-ui/styles 模块。 如果您正在使用 webpack,那么您可以使用 CommonsChunkPlugin 来创建一个显式的 第三方代码块(vendor chunk),其中将包含 @material-ui/styles 模块:

  module.exports = {
    entry: {
+     vendor: ["@material-ui/styles"],
      app1: "./src/app.1.js",
      app2: "./src/app.2.js",
    },
    plugins: [
+     new webpack.optimize.CommonsChunkPlugin({
+       name: "vendor",
+       minChunks: Infinity,
+     }),
    ]
  }

我的应用没有在服务器上正确的渲染。

如果您的程序渲染不正常,99% 的情况下都是配置问题: 缺少属性、调用顺序错误或缺少组件 — 服务端渲染对配置的要求是很严格的,要找出问题的最好方法是将您的项目与一个已经正常运行的项目配置进行比较。 请逐位查看 参考实现

CSS 仅在第一次加载时生效,然后就消失了

CSS 只在页面第一次加载时生成。 那么,若连续地请求服务器,就会导致 CSS 的丢失。

要运行的操作

样式解决方案依赖于缓存,即 sheets manager,来为每个组件类只注入一次 CSS(如果您使用了两个按钮,则只需要应用一次按钮的 CSS)。 您需要为每个请求创建 一个新的 sheet 实例

修复示例:

-// 创建一个 sheets 实例

-const sheets = new ServerStyleSheets();

function handleRender(req, res) {

+ // 创建一个 sheets 实例。
+ const sheets = new ServerStyleSheets();

  //…

  // 将组件渲染成一个字符串。
  const html = ReactDOMServer.renderToString(

React 类名渲染不匹配

您会遇到客户端和服务端之间存在类名不匹配的情况。 可能在第一次请求时会出现这种情况。 另一个征兆是,在初始页面加载和下载客户端脚本之间,样式会发生变化。

要运行的操作

类名值依赖于 类名生成器 的概念。 整个页面需要用一个类名生成器来渲染。 这个生成器需要在服务端和客户端上的行为一致。 就像这样:

  • 您需要为每个请求提供一个新的类名生成器。 但是您不应该在不同的请求之间共享 createGenerateClassName()

修复示例:

-  // 创建一个新的类名生成器。
-const generateClassName = createGenerateClassName();

function handleRender(req, res) {

+ // 创建一个新的类名生成器。
+ const generateClassName = createGenerateClassName();

  //…

  // 将组件渲染为一个字符串。
  const html = ReactDOMServer.renderToString(
  • 您需要验证您的客户端和服务端运行的 Material-UI 的版本 是否完全相同。 即使是小小的版本的不匹配也可能导致样式问题。 若想检查版本号,您可以在搭建应用程序的环境以及部署环境中都运行 npm list @material-ui/core

    您也可以通过在 package.json 的依赖项中指定某一个特定的 MUI 版本,这样能够确保在不同环境中使用的版本是一致的。

修复(package.json)的示例:

  "dependencies": {
    ...

-   "@material-ui/core": "^4.0.0",
+   "@material-ui/core": "4.0.0",
    ...
  },
  • 请确保服务端和客户端之间所共享的是相同的 process.env.NODE_ENV 值。

为什么我看到的颜色和文档这里的颜色大相径庭?

这是因为文档网站使用了一个自定义的主题。 因此,调色板和 Material-UI 的默认的主题所展示的效果是截然不同的。 请参考 这个页面 来了解自定义主题。

为什么组件 X 需要属性中的 DOM 节点而不是一个 ref 对象?

PortalPopper 这样的组件分别需要 containeranchorEl 属性中的 DOM 节点。 若需在这些属性中传递一个 ref 对象,并让 Material-UI 访问当前值,这看起来更加简洁有效。 这在一个简单的方案中就可以实现:

function App() {
  const container = React.useRef(null);

  return (
    <div className="App">
      <Portal container={container}>
        <span>传送门的子组件</span>
      </Portal>
      <div ref={container} />
    </div>
  );
}

其中,Portal 仅在 container.current 可用时才会将其子项挂载到容器中。 下面是一个简单的 Portal 实现例子:

function Portal({ children, container }) {
  const [node, setNode] = React.useState(null);

  React.useEffect(() => {
    setNode(container.current);
  }, [container]);

  if (node === null) {
    return null;
  }
  return ReactDOM.createPortal(children, node);
}

这个简单的方法可能会启发您,Portal 可能会在挂载后重新渲染,因为在任何效果运行之前,refs 都是最新的。 然而,仅仅因为 ref 是最新的并不意味着它会指向一个定义好的实例。 如果 ref 是附着在一个 ref 所转发组件上的话,那么 DOM 节点何时可用是不明确的。 在上面的例子中,Portal 将产生一次效果,但可能不会重新渲染,因为 ref.current 的值仍然是 null。 而 Suspense 中的 React.lazy 组件中,这一点尤为明显。 上述实现也并不能代表 DOM 节点的一个变化。

综上所述,这就是为什么我们需要一个具有实际 DOM 节点的属性,这样 React 就可以负责确定 Portal 何时应该重新渲染。

function App() {
  const [container, setContainer] = React.useState(null);
  const handleRef = React.useCallback(instance => setContainer(instance), [setContainer])

  return (
    <div className="App">
      <Portal container={container}>
        <span>传送的子组件</span>
      </Portal>
      <div ref={handleRef} />
    </div>
  );
}

clsx 依赖是用于什么?

clsx是一个小型工具集,用于有条件地从一个对象中构造 className 字符串,此对象的键是类字符串(class strings),而值是布尔值(booleans)。

请不要这样写:

// let disabled = false, selected = true;

return (
  <div
    className={`MuiButton-root ${disabled ? 'Mui-disabled' : ''} ${selected ? 'Mui-selected' : ''}`}
  />
);

您可以这样做:

import clsx from 'clsx';

return (
  <div
    className={clsx('MuiButton-root', {
      'Mui-disabled': disabled,
      'Mui-selected': selected,
    })}
  />
);