Упознајте Материал-УИ - вашу нову омиљену библиотеку корисничког интерфејса

Ажурирање (17.5.2018.): Материјал-УИ в1.0.0 је изашао! Погледајте ову објаву Оливијеа.

А? Још једна библиотека? Шта није у реду са Боотстрапом? А зашто не в0.20?

Одлична питања! Почнимо са кратким уводом. Укратко, Материал-УИ је пројекат отвореног кода који садржи Реацт компоненте које имплементирају Гоогле-ов Материал Десигн.

Почео је 2014. године, недуго након што је Реацт изашао у јавност и од тада расте популарност. Са преко 35.000 звездица на ГитХуб-у, Материал-УИ је једна од најбољих библиотека корисничког интерфејса за Реацт.

Његов успех није прошао без изазова. Дизајниран са МАЊЕ, Материал-УИ в0.к био је склон уобичајеним ЦСС замкама, попут глобалног опсега, које воде пројекат на путањи ЦСС-ин-ЈС. Тако је nextдошло 2016. године.

Путовање ка бољем стилу, како каже Оливиер Тассинари, започело је уграђеним стиловима, али су њихови неоптимални перформанси и ограничена подршка карактеристика (помислите на псеудо селекторе или упите за медије) на крају учинили тим прелазом на ЈСС. И дечко су паметно одлучили.

Какав је хипе са в1 издањем?

Гадно је. Не само да се бави проблемима који су својствени ЛЕСС-у, већ такође отвара мноштво сјајних карактеристика, укључујући

  • динамички стилови генерисани током извођења
  • угнежђене теме са интуитивним заменама
  • смањено време учитавања поделом кода

И још много тога. Библиотека је такође довољно зрела за употребу у продукцији толико да тим предлаже в1 за све нове пројекте који иду даље.

У реду, хоћемо ли направити апликацију или шта?

Драго ми је што сте питали! За овај демо ћемо направити једноставну фитнес апликацију. Већ су досад свима досадне апликације, зар не?

Читање је сјајно, али гледање је често забавније! Погледајте ову плеј листу коју сам направио на ИоуТубе-у ако желите да направите напреднију апликацију.

Ок, убедио си ме. Како да започнем?

Прво ћемо покренути нашу апликацију помоћу цреате-респонсе-апп

create-react-app mui-fitnesscd mui-fitnesscode .

А шта је са Материал-УИ?

Ако имате предиво, инсталација је једноставна као

yarn add @material-ui/core

Иначе, са npm

npm i @material-ui/core

Недавно бисмо одредили @nextознаку за повлачење у најновијем издању (на пример, можда је изгледало v1.0.0-beta.47). Сада када су и в1 и в0.к material-uiобухваћени, треба да упутимо језгро библиотеке како /coreбисмо циљали најновије издање. Не пропустите тај последњи део, иначе ћете на крају имати стабилну 0.20зависност!

Чекај, је ли то заиста то?

Скоро! Последња ствар су фонтови. Упутићемо се на препоручени Робото фонт са Гоогле-овог ЦДН-а:

Можете га и повући из НПМ-а помоћу

yarn add typeface-roboto# or npm i typeface-roboto

у том случају ћете морати да имате увоз у корену вашег пројекта

// Make sure you only load 300, 400, & 500 font weights though!import 'typeface-roboto'

Готово! Шта да радим даље?

Па, хајде да преправимо нашу App.jsкомпоненту пре него што идемо даље

import React, { Component } from 'react'
export default class App extends Component { state = { exercises: [], title: '' }
 render() { return 

Exercises

}}

А зашто не почистити index.jsдок смо код тога?

import React from 'react'import { render } from 'react-dom'import App from './App'
render(, document.getElementById('root'))

Слободно уклоните преостале датотеке испод src, јер нам неће бити потребне.

Где долази Материал-УИ?

Поштено, време је да то видимо на делу. Променимо ружни h1у лепи Typographyнаслов:

import Typography from '@material-ui/core/Typography'
...
 render() { return (  Exercises  ) }}
Имајте на уму да се од в1.0.0-рц.0, МУИ преселио у @material-ui/coreи путања увоза је поравната. Ово је била последња промена у предизбору.

Затим напред и трчите yarn startда видите магију.

Добро смо кренули! Typographyкомпонента долази са унапред дефинисаним скупом величина типова. Остали variantа укључују body1, title, display2, и тако даље. Међу осталим уграђеним реквизитима су они alignкоје овде користимо за хоризонтално центрирање текста и gutterBottomкоји додају доњу маргину.

Зашто ово не бисмо проширили у форму, па бисмо могли да креирамо сопствене вежбе? Почећемо са а TextFieldи повезаћемо га са titleдржавом

import Typography from '@material-ui/core/Typography'import TextField from '@material-ui/core/TextField'
...
 handleChange = ({ target: { name, value } }) => this.setState({ [name]: value })
 render() { const { title } = this.state return ( ...    ) }}

Наравно, морали бисмо да усрећимо Реацт умотавањем Typographyи formса надређеним елементом. Шта би могла бити боља прилика за позадину сличну картицама у облику папира? Идемо до Paperтада

import Paper from '@material-ui/core/Paper'
...
 render() { const { title } = this.state return  ...  } }}

Такође је време да почнемо да користимо именовани увоз (под претпоставком да наша поставка Вебпацк-а омогућава дрмање дрвећа):

import { Paper, Typography, TextField } from '@material-ui/core'

Слатко! А каква корист од обрасца без дугмета за слање? Buttonс су главна компонента у Материал-УИ; видећете их свуда. На пример,

import { Paper, Typography, TextField, Button } from '@material-ui/core'...  Create    }}

Требало би добро читати. typeје редован Реацт реквизит colorи variantспецифичан је за УИ материјала и чини дугме у облику правоугаоника. Друга варијанта би била fabна пример плутајуће дугме.

Ипак то не чини пуно. Мораћемо да пресретнемо догађај слања обрасца

 return  ...  ...   }}

а затим то решите са

 handleCreate = e => { e.preventDefault()
 if (this.state.title) { this.setState(({ exercises, title }) => ({ exercises: [ ...exercises, { title, id: Date.now() } ], title: '' })) } }

Вхоа! Шта је тај тајновити код? Врло брзо, ми

  1. Спречите подразумевано поновно учитавање странице
  2. Проверите да ли titleпоље није празно
  3. Подесите стање помоћу функције ажурирања да ублажи асинхријска ажурирања
  4. Деструктура exercisesи titleодмак од prevStateобјекта
  5. Ширите exercisesследеће стање новим објектом за вежбање
  6. Ресетујте тастер titleза брисање поља за унос

Претпостављам да је требало да напоменем да сам и ја заљубљен у ЕС6. Зар нисмо сви?

Али како да их набројимо?

Сада је право време за. Да ли постоји компонента листе? Наравно, глупа гуско!

Унутар а List, провући ћемо се кроз наш exercisesи вратити ListItemса неколико ListItemTextза сваки

import { List, ListItem, ListItemText } from '@material-ui/core'
...
 render() { const { title, exercises } = this.state return  ...  {exercises.map(({ id, title }) =>    )}   }}

Хајде да такође кодирамо неколико почетних вежби да би се нешто појавило на екрану. Погађате, тројство свих вежби за дизање тегова, даме и господо:

 state = { exercises: [ { id: 1, title: 'Bench Press' }, { id: 2, title: 'Deadlift' }, { id: 3, title: 'Squats' } ], title: '' }

И на крају, али не најмање важно, наши корисници ће вероватно правити грешке у куцању, па је боље да поред сваке вежбе додамо дугме за брисање како би могли да уклоне уносе које више не желе са своје листе.

Можемо користити ListItemSecondaryActionуправо то. Постављен крајње десно од ставке листе, може садржати секундарни контролни елемент, као што је IconButtonса неком радњом

import { /*...*/, ListItemSecondaryAction, IconButton} from '@material-ui/core'
...
     this.handleDelete(id)} > {/* ??? */}   
...

И не заборавимо и обрађивач брисања:

 handleDelete = id => this.setState(({ exercises }) => ({ exercises: exercises.filter(ex => ex.id !== id) }))

који ће наше вежбе једноставно филтрирати до оних које се не подударају idса оном коју треба уклонити.

Да ли можемо да имамо икону канте за смеће унутар дугмета?

Да, то би било сјајно! Иако можете користити Материал иконе из Гоогле ЦДН директно или Iconили SvgIconкомпоненте, често је пожељно да се иде са готовим поставку.

Срећом, за њих постоји пакет Материал-УИ

yarn add @material-ui/icons# or npm i @material-ui/icons

Извози 900+ званичних икона материјала као Реацт компоненте, а називи икона су готово идентични, као што ћете видети доле.

Рецимо да смо желели да додамо икону отпада. Прво бисмо прешли на материал.ио/ицонс да бисмо сазнали његово тачно име

Затим на нашем путу за увоз претварамо то име у ПасцалЦасе

import Delete from '@material-ui/icons/Delete'

Баш као и код компонената Материал-УИ, ако је у вашем подешавању омогућено дрмање дрвећа, можете скратити увоз на

import { Delete } from '@material-ui/icons'

which is especially useful when importing several icons at once.

Now that we have our trash icon, let’s display it inside our delete button

 this.handleDelete(id)}>

How can I make the form look less ugly?

Ah, styling. I thought you’d never ask! A gentle touch of CSS wouldn’t hurt. So then, do we import an external stylesheet with global styles? Or, perhaps, use CSS modules and assign scoped class names to our elements? Not quite.

Under the hood, Material-UI forks a CSS-in-JS library known as react-jss.

It’s a React integration of the JSS library by the same author, Oleg Isonen. Remember we touched on it in the beginning? Its basic idea is to enable you to define styles in JavaScript. What makes JSS stand out among other libs though, is its support for SSR, small bundle size, and rich plugin support.

Хајде да пробамо! У нашој Appкомпоненти направите објекат стилова баш као што бисте то урадили и са уграђеним стиловима. Затим, смислите кључ, на пример root, који се односи на основни Paperелемент, и напишите неке стилове у цамелЦасе

const styles = { root: { margin: 20, padding: 20, maxWidth: 400 }}

Затим увезите withStylesХОЦ изmaterial-ui

import { withStyles } from '@material-ui/core/styles'

и омотајте Appкомпоненту њоме, предајући stylesобјекат као аргумент

export default withStyles(styles)( class App extends Component { ... })
Имајте на уму да бисте withStylesХОЦ могли да користите и као декоратор. Имајте на уму да цреате-реакција-реакција још увек не подржава декораторе, тако да ако инсистирате на њиховој употреби, мораћете да избаците или виљушком прилагодите конфигурацију.

Ово ће убризгати classesреквизит у Appдинамички генерисано име класе за наш rootелемент

The class name is guaranteed to be unique, and it will often be shortened in a production build. We then assign it to Paper via className attribute

 render() { const { title, exercises } = this.state const { classes } = this.props
 return  ...  }

How does this magic work? Turns out, withStyles is responsible for the dirty work. Behind the scenes, it injected an array of styles into the DOM under le> tags. You could spot them if you dig int o the with dev tools

You could also see other style tags related to native components, such as MuiListItem for the ListItem component we imported earlier. Those are auto-injected on demand, for each given UI element that you import.

That means that Material-UI will never load any styles for the components that we don’t use. Hence, increased performance and faster load times. This is very different from Bootstrap, which requires loading the entire monolithic CSS bundle, whether you happen to use its vast assortment of classes or not.

Let’s also style the form so it looks neat

const styles = { root: { ... }, form: { display: 'flex', alignItems: 'baseline', justifyContent: 'space-evenly' }}

This will make the text field and the button nicely spaced out. Feel free to refer to align-items and justify-content at CSS-Tricks should you need any further clarification on the Flexbox layout.

Sure, but what’s up with theming then?

withStyles HOC is tailored for customizing a one-off component, but it’s not suited for application-wide overwrites. Whenever you need to apply global changes to all components in Material-UI, your first instinct would be to reach out to the theme object.

Themes are designed to control colors, spacing, shadows, and other style attributes of your UI elements. Material-UI comes with built-in light and dark theme types, light being the default.

If we turn our styles into an anonymous function, it will receive the theme object as an arg, so we can inspect it

const styles = theme => console.log(theme) || ({ root: ..., form: ...})

The way you customize your theme is through configuration variables, like palette, type, typography, etc. To have a closer look at all the nested properties and options, visit the Default Theme section of the Material-UI docs.

Let’s say we wanted to change the primary color from blue to orange. First off, we need to create a theme with createMuiTheme helper in index.js

import { createMuiTheme } from '@material-ui/core/styles'
const theme = createMuiTheme({ /* config */ })

In Material-UI, colors are defined under the palette property of theme. The color palette is subdivided into intentions which include primary, secondary, and error. To customize an intention, you can simply provide a color object

import { orange } from '@material-ui/core/colors'
const theme = createMuiTheme({ palette: { primary: orange }})

When applied, the color will then be calculated for light, main, dark, and contrastText variations. For more granular control though, you could pass in a plain object with any of those four keys

const theme = createMuiTheme({ palette: { primary: { light: orange[200] // same as '#FFCC80', main: '#FB8C00', // same as orange[600] dark: '#EF6C00', contrastText: 'rgb(0,0,0)' } }})

As you can see, individual colors can be expressed as both a hex or rgba string (#FFCC80) and a hue/shade pair (orange[200]).

Creating a theme on its own won’t suffice. To overwrite the default theme, we would need to position MuiThemeProvider at the root of our app and pass our custom theme as a prop

import { /*...*/, MuiThemeProvider } from '@material-ui/core/styles'
const theme = createMuiTheme({ palette: { primary: orange }})
render(   , document.getElementById('root'))

MuiThemeProvider will then pass down the theme to all its child elements through React context.

Though it may seem like a lot of work to change a color, keep in mind that this overwrite will propagate to all components nested under the provider. And apart from colors, we can now fine-tune viewport sizes, spacing, opacity, and many other parameters.

Utilizing config variables when styling your components will aid with consistency and symmetry in your app’s UI. For example, instead of hard-coding magic values for margin and padding on our Paper component, we could instead rely on the spacing unit off the theme

const styles = ({ spacing: { unit } }) => ({ root: { margin: unit, padding: unit * 3, maxWidth: 400 }, form: ...}

theme.spacing.unit comes at 8px by default, but if it’s used uniformly across the app, when we need to update its value, rather than scavenging across the entire codebase, we only need to change it in one place, that is, in our options object that we pass to createMuiTheme.

Theme variables are plentiful, and if you run into a use case that’s not covered by the built-in theme object, you could always define your own custom vars. Here’s a slightly modified version of our fitness app that showcases color palette, theme type, and spacing unit options

Note that the example above is only a demo. It re-creates a new theme each time an option changes, which leads to a new CSS object being re-computed and re-injected into the DOM. Most often than not, your theme config will remain static.

There are far more interesting features that we haven’t covered. For example, Material-UI comes with an opt-in CssBaseline component that applies cross-browser normalizations, such as resetting margins or font family (very much like normalize.css does).

As far as components go, we have our standard Grid with a 12-column layout and five viewports (xs, sm, md, lg, and xl). We’ve also got familiar components like Dialog, Menu, and Tabs, as well as elements, such as Chip and Tooltip. Indeed, there’s a whole slue of others, and fortunately, they are all very-well documented with runnable demo code from CodeSandbox

Aside from that, Material-UI Next also works with SSR, if you’re into that. Besides, although it comes with JSS out of the box, it can me made to work with just about any other library, like Styled Components, or even raw CSS.

Be sure to check out the official docs for more info.

I hope you found this read useful! And if you like it so much that you are excited to learn more about Material-UI or React, then check out my YouTube channel maybe?

Thanks for stopping by! And big thanks to the team over at Call-Em-All and all the backers who helped to build this awesome library ❤️

Cheers,

Alex