تعرف على Material-UI - مكتبة واجهة المستخدم المفضلة الجديدة لديك

تحديث (17/05/2018): تم إصدار Material-UI v1.0.0! تحقق من هذا المنشور من قبل أوليفر.

هاه؟ مكتبة أخرى؟ ما هو الخطأ في Bootstrap؟ ولماذا لا v0.20؟

أسئلة رائعة! لنبدأ بمقدمة موجزة. باختصار ، يعد Material-UI مشروعًا مفتوح المصدر يتميز بمكونات React التي تنفذ تصميم المواد من Google.

بدأت في عام 2014 ، بعد وقت قصير من ظهور React للجمهور ، وازدادت شعبيتها منذ ذلك الحين. مع وجود أكثر من 35000 نجمة على GitHub ، تعد Material-UI واحدة من أفضل مكتبات واجهة المستخدم لـ React الموجودة هناك.

لم يأت نجاحها بدون تحديات رغم ذلك. نظرًا لتصميمه باستخدام LESS ، كان Material-UI v0.x عرضة لمخاطر CSS الشائعة ، مثل النطاق العالمي ، الذي يقود المشروع على مسار CSS-in-JS. هذا ما حدث nextفي عام 2016.

بدأت الرحلة نحو أسلوب أفضل ، كما قال أوليفييه تاسيناري ، بأنماط مضمنة ، لكن أدائها دون المستوى الأمثل ودعمها المحدود للميزات (فكر في المحددات الزائفة أو استعلامات الوسائط) ، في النهاية جعل الفريق ينتقل إلى JSS. وصبي قاموا باختيار ذكي.

ما هو الضجيج في إصدار v1؟

انها سيئة الحمار. لا يقتصر الأمر على معالجة المشكلات المتأصلة في LESS ، ولكنه يفتح أيضًا الكثير من الميزات الرائعة ، بما في ذلك

  • أنماط ديناميكية تم إنشاؤها في وقت التشغيل
  • نُسق متداخلة مع تجاوزات بديهية
  • تقليل وقت التحميل مع تقسيم الكود

و أكثر من ذلك بكثير. المكتبة أيضًا ناضجة بما يكفي لاستخدامها في الإنتاج لدرجة أن الفريق يقترح الإصدار 1 لجميع المشاريع الجديدة في المستقبل.

حسنًا ، هل سننشئ تطبيقًا أم ماذا؟

يسعدني أنك سألت! في هذا العرض التوضيحي ، سننشئ تطبيقًا بسيطًا للياقة البدنية. الجميع يشعر بالملل من تطبيقات المهام الآن على أي حال ، أليس كذلك؟

القراءة رائعة وكل شيء ، لكن المشاهدة غالبًا ما تكون أكثر متعة! تحقق من قائمة التشغيل هذه التي أنشأتها على YouTube إذا كنت تريد إنشاء تطبيق أكثر تقدمًا.

حسنًا ، لقد اقتنعتني. كيف أبدأ؟

سنقوم أولاً بتمهيد تطبيقنا باستخدام تطبيق create-response-app

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

وماذا عن Material-UI؟

إذا كان لديك خيوط ، فإن التثبيت بسيط مثل

yarn add @material-ui/core

خلاف ذلك ، مع npm

npm i @material-ui/core

منذ وقت ليس ببعيد ، كنا نحدد @nextعلامة لسحبها في أحدث إصدار تجريبي (على سبيل المثال ، ربما يبدو الأمر كذلك v1.0.0-beta.47). الآن بعد أن أصبح الإصداران 1 و v0.x ضمن material-uiالنطاق ، نحتاج إلى الرجوع إلى جوهر المكتبة /coreلاستهداف أحدث إصدار. لا تفوت هذا الجزء الأخير ، وإلا ستنتهي 0.20بالتبعية الثابتة !

انتظر ، هل هذا هو حقا؟

تقريبيا! آخر شيء هو الخطوط. سنذهب مع Roboto Font الموصى به من Google's CDN:

بدلاً من ذلك ، يمكنك سحبها من NPM باستخدام

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، لأننا لن نحتاج إليها.

من أين تأتي Material-UI؟

عادل بما فيه الكفاية ، حان الوقت لرؤيته في العمل. دعونا نغير القبيح h1إلى Typographyعنوان جميل :

import Typography from '@material-ui/core/Typography'
...
 render() { return (  Exercises  ) }}
لاحظ أنه منذ الإصدار 1.0.0-rc.0 ، انتقلت MUI إلى @material-ui/coreوتم تسوية مسار الاستيراد. كان هذا آخر تغيير فاصل في الإصدار التجريبي.

ثم انطلق واركض yarn startلترى السحر.

نحن في بداية جيدة! Typographyيأتي المكون مع مجموعة محددة مسبقًا من أحجام النوع. أخرى variantتشمل الصورة body1، title، display2، وهلم جرا. من بين الدعائم المضمنة الأخرى alignالتي نستخدمها هنا لتوسيط النص أفقيًا ، gutterBottomوالتي تضيف هامشًا سفليًا.

Why don’t we expand this to a form, so we can create our own exercises? We’ll start with a TextField and bind it to the title on the state

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 ( ...    ) }}

Of course, we’d need to make React happy by wrapping Typography and form with a parent element. What could be a better opportunity for a paper-sheet card-like background? Let’s reach out to Paper then

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

It’s also about time to start using named imports (assuming our Webpack setup allows for tree shaking):

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

Sweet! And what good is a form without the submit button? Buttons are a staple component in Material-UI; you’ll see them everywhere. For instance,

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

It should read well. type is a regular React prop, color and variant are Material-UI-specific, and make up a rectangle-shaped button. Another variant would be fab for a floating button, for example.

It doesn’t do much though. We’ll have to intercept the form submit event

 return  ...  ...   }}

and then handle it with

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

Whoa! What’s that cryptic code all about? Very quickly, we

  1. Prevent the default page reload
  2. Check if the title field is non-empty
  3. Set the state with an updater function to mitigate async updates
  4. Destructure exercises and title off the prevState object
  5. Spread out the exercises on the next state with a new exercise object
  6. Reset the title to clear out the input field

Guess I should have mentioned that I’m in love with ES6 too. Aren’t we all?

But how do we list them?

Now is the right time to. Is there a list component? Of course, you silly goose!

Inside a List, we’ll loop through our exercises and return a ListItem with some ListItemText for each

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

Let’s also hard-code a few initial exercises to get something on the screen. You guessed it, the trinity of all weight lifting workouts, ladies and gents:

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

Last but not least, our users are likely to make typos, so we better add a delete button next to each exercise, so they could remove entries they no longer want in their list.

We can use ListItemSecondaryAction to do exactly that. Placed on the far right of the list item, it can hold a secondary control element, such as an IconButton with some action

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

And let’s not forget the delete handler as well:

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

which will simply filter our exercises down to those that don’t match the id of the one that needs to be removed.

Can we have a trash bin icon inside the button?

Yes, that would be great! Though you could use Material Icons from Google’s CDN directly with either Icon or SvgIcon components, it’s often preferable to go with a ready-made preset.

Luckily, there’s a Material-UI package for those

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

It exports 900+ official material icons as React components, and the icon names are nearly identical, as you’ll see below.

Let’s say we wanted to add a trash icon. We’d first head over to material.io/icons to find out its precise name

Then, we turn that name into PascalCase in our import path

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

Just like with Material-UI components, if your setup has tree-shaking enabled, you could shorten the import to

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 }}

بعد ذلك ، قم باستيراد withStylesHOC منmaterial-ui

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

ولف Appالمكون به ، وتمرير stylesالكائن باعتباره الوسيطة

export default withStyles(styles)( class App extends Component { ... })
لاحظ أنه يمكنك أيضًا استخدام withStylesHOC كديكور. ضع في اعتبارك أن إنشاء رد فعل لا يدعم الزخارف خارج الصندوق حتى الآن ، لذلك إذا كنت تصر على استخدامها ، فستحتاج إلى إخراجها أو شوكة لتعديل التكوين.

سيؤدي هذا 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