跳转到内容

常见问题解答

您在一个特定的问题上停滞不前吗? 您可以先在常见 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({
  components: {
    // Name of the component ⚛️
    MuiButtonBase: {
      defaultProps: {
        // The props to apply
        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({
  components: {
    // Name of the component ⚛️
    MuiCssBaseline: {
      styleOverrides: {
        '*, *::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 类名渲染不匹配

Warning: Prop className did not match.

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

要运行的操作

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

  • 您需要为每个请求提供一个新的类名生成器。 但是您不应该在不同的请求之间共享 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,
    })}
  />
);