Реагируйте с Ant Design

Последние три месяца я работаю над личным проектом. Я решил использовать React в качестве интерфейса внешнего интерфейса и хотел выбрать для него библиотеку пользовательского интерфейса. Раньше я использовал Semantic-UI и решил выбрать что-то новое, поэтому выбрал Ant Design. Для большего контекста я использую React-Router для маршрутизации и Mobx для управления состоянием.

Эта проблема

Ant Design, в данном контексте именуемый A ntd, представляет собой отличную библиотеку. Он содержит все компоненты, которые мне могут понадобиться в хороших компонентах-оболочках, что упрощает разработку пользовательского интерфейса. Однако одним серьезным упущением является отзывчивый мобильный заголовок. Мы привыкли видеть значок гамбургера в мобильных приложениях, популяризированных, как мне кажется, библиотекой Bootstrap.

Хотя Antd действительно содержит компонент Header для использования в качестве панели навигации, ему не хватало готовой к работе мобильной версии. Решил исправить это в своем приложении.

Настройка компонентов

Решение, которое я придумал, требует, конечно же, установки следующих пакетов в дополнение к React.

  • мобкс
  • Mobx-React
  • antd
  • размер окна реакции

У меня мое приложение настроено следующим образом:

-src
--Containers
---NavigationBar.js
--Common
---Stores
----SessionStore.js
---routes

Решение

Хотя сам код подлежит рефакторингу, основная идея должна остаться прежней.

Мы проверим ширину экрана с помощью response-window-size. Затем, в зависимости от этого значения, отобразить либо меню мобильной навигации, либо меню рабочего стола. Мобильное меню будет содержать кнопку, которая открывает компонент Antd, называемый Drawer, который открывается сбоку от экрана.

Прежде чем мы сможем начать работу с пользовательским интерфейсом, нам нужно настроить наш магазин Mobx и наши маршруты для React-Router.

Создание хранилища сеансов и маршрутов

//Common/Stores/SessionStore.js
import { observable, action, computed } from 'mobx';
class SessionStore {
    @observable authUser = {}; //also handled in store
    @observable mobileMenuOpen = false;
    @observable isMobileMenu = false;
    @observable tab = 0;
    @action
    setIsMobileMenu = windowWidth => {
        this.isMobileMenu = windowWidth <= 768;
        //breakpoint in antd grid
    }
    @action
    toggleMobileMenuOpen = () => {
        if(!this.mobileMenuOpen) {
            if(this.isMobileMenu) {
                this.mobileMenuOpen = true;
            }
        } else {
            this.mobileMenuOpen = false;
        }
    }
}

Хотя authUser также используется в этом решении, объяснение того, как он устанавливается, значительно расширит рамки этого руководства. Помните, что он должен быть установлен, когда пользователь вошел в систему, и пустой объект, когда ни один пользователь не вошел в систему.

Наблюдаемая вкладка также не будет охвачена, поскольку она устанавливается компонентами, а не навигационной панелью. По сути, при визуализации маршрута для вкладки устанавливается соответствующий индекс, чтобы панель навигации знала, какая вкладка должна быть активной.

//Common/routes.js
export const HOME = {route: '/home', tab: 0, link: "Home"};
export const ACCOUNT = {route: '/account', tab: 1, link: "Account"};
export const PROJECTS = {route: '/projects', link: "Projects", tab: 2};
export const SIGN_IN = {route: '/signin', tab: 3, link: "Sign In"};

Объект, определяющий маршруты, используемые приложением. Вкладка используется Session Store при принятии решения, какую вкладку сделать активной для стилизации.

Создайте компонент навигации

//containers/Navigation.js
import { Menu, Icon, Layout, Avatar, Dropdown, Button, Drawer } from 'antd';
import * as routes from '../common/routes';
import windowSize from 'react-window-size';
@inject('sessionStore')
@observer
class Navigation extends Component {
    /*...*/
    render() {
        /*...*/
    }
}
export default class windowSize(Navigation);

Как видите, windowSize - это компонент более высокого порядка. Декораторы Inject и Observer используются Mobx, чтобы предоставить компоненту доступ к хранилищу сеансов и позволить ему наблюдать за внесенными в него изменениями.

Добавьте компоненты макета и ящика

Внутри функции рендеринга мы хотим вернуть следующее:

//containers/Navigation.js
let { toggleMobileMenuOpen, mobileMenuOpen, authUser, tab } = this.props.sessionStore;
return (
    <div>
        <Drawer
            title='StudentCon'
            placement='left'
            closable={ false }
            onClose{() => 
                 toggleMobileMenuOpen()
            }
            visible={ mobileMenuOpen }        
        >
            <Menu>{ this.renderNavLinks() }</Menu>
        </Drawer>
        <Layout.Header
            style={ {position: 'fixed', zIndex: 1, width: '100%', 
            padding: isMobileMenu ? '0' : '0 10%'} }
        >
            <Menu
                theme="dark"
                mode="horizontal"
                style={ {lineHeight: '64px'} }
                selectedKeys={ [tab.toString()] }
            >
                { this.props.windowWidth >= 768 ?
                  this.renderDesktopNavbar() :
                  this.renderMobileNavbar() }
                { authUser && this.renderUserLinks() }
            </Menu>
        </Layout.Header>
    </div>
)

В следующем разделе мы напишем выделенные функции. Важно отметить, что некоторые встроенные стили являются обязательными. Документация Antd предоставляет примеры, но по существу необходимо установить lineHeight, position, zIndex и width.

Добавить вспомогательные функции

Прежде чем перейти к коду, вот номенклатура, которую я решил использовать:

  • Навигационные ссылки: ссылки, предназначенные для навигации по веб-сайту, но могут отличаться, независимо от того, присутствует ли вошедший пользователь или нет. Появляется слева на панели навигации.
  • Пользовательские ссылки: ссылки, которые увидит только вошедший в систему пользователь. Появляется справа от навигационной панели.

Во-первых, как мы отображаем навигационные ссылки.

//containers/Navigation.js
renderNavLinks = () => {
let { HOME, ACCOUNT, SIGN_IN, PROJECTS } = routes;
let { authUser, toggleMobileMenuOpen, isMobileMenu } = this.props.sessionStore;
let signedInUserId = authUser && authUser.uid;
if(signedInUserId){
    return [
        <Menu.Item key = {0}>
            <Link 
                onClick = { () => toggleMobileMenuOpen() }
                to={HOME.route}
            >
                {HOME.link}
            </Link>
        </Menu.Item>,
        <Menu.Item key = {1}>
            <Link 
                onClick = { () => toggleMobileMenuOpen() } 
                to={`${ACCOUNT.route}/${signedInUserId}`}
            >
                {ACCOUNT.link}
            </Link>
        </Menu.Item>,
        <Menu.Item key = {2}>
            <Link 
                onClick = { () => toggleMobileMenuOpen() } 
                to={`${PROJECTS.route}/tvF7OMS1g9omqcdHNvpo`}
            >
                {PROJECTS.link}
            </Link>
        </Menu.Item>,
        isMobileMenu && <Menu.Item key = {100}>
            <Link 
                onClick = { () => {
                             toggleMobileMenuOpen();
                           }
                 to={`${HOME.route}`}
             >
                 Sign out
            </Link>
        </Menu.Item>
     ]
} else {
    return (
        <Menu.Item key = {3}>
            <Link to={SIGN_IN.route}>
                <Button ghost>
                    {SIGN_IN.link}
                </Button>
            </Link>
        </Menu.Item>
    )
}
}

Обратите внимание, что отступы немного сбиты во имя экономии места.

Далее, как мы отображаем пользовательские ссылки:

//containers/Navigation.js
renderUserLinks = () => {
return (
        <Menu.Item 
            style={ {float:'right', display:'flex',
                     justifyContent:"space-around",
                     alignItems:'center', width:"180px"} }
        >
            <a>
                <Icon 
                    type="notification"  
                    style={ { fontSize: 20, color: 'white'} } 
                />
            </a>
            <a>
                <Icon 
                    type="message" 
                    style={ { fontSize: 20, color: 'white'} }
                />
            </a>
            { this.props.sessionStore.isMobileMenu ?
                <Button onClick = { () =>
                    this.props.sessionStore.toggleMobileMenuOpen() }
                        icon="bars"
                        ghost
                /> : 
                <Avatar 
                    src={this.props.sessionStore.authUser.photoURL)}
                /> 
             }
        </Menu.Item>
    )
}

Наконец, мы добавим функции-оболочки, которые вызываются в зависимости от того, создаем ли мы настольную или мобильную навигационную панель.

//containers/Navigation.js
renderDesktopNavbar = () => {
    this.props.sessionStore.setIsMobileMenu(this.props.windowWidth);
    return this.renderNavLinks();
}
renderMobileNavbar = () => {
    this.props.sessionStore.setIsMobileMenu(this.props.windowWidth);
    return this.props.sessionStore.authUser ? 
        '' : this.renderNavLinks();
}

Обновите Session Store, чтобы указать, выполняем ли мы рендеринг для мобильных устройств или компьютеров. Мы просто рендерим то, что нам нужно для нашего представления.

Полученные результаты

Ниже приведен пример того, как выглядит конечный продукт.

На рабочем столе:

На мобильном телефоне:

Как это выглядит в действии:

tl;dr

Имейте два отдельных компонента для вашей панели навигации: один для настольного компьютера и один для мобильного устройства. Визуализируйте мобильный внутри компонента Drawer.

Также вы можете увидеть весь компонент в этой сути.

Спасибо за прочтение. Обычно у меня одновременно ведется несколько проектов, вы можете посмотреть их на моем сайте.