nin-jin / HabHub

Peering social blog
The Unlicense
62 stars 0 forks source link

React'ивные Panel'и #6

Open nin-jin opened 7 years ago

nin-jin commented 7 years ago

https://page.hyoo.ru/#!=r53rve_8uszq7

Что такое панель? Это довольно простой компонент, разбивающий видимую область на 2-3 блока:

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

В шапке может быть, а может не быть:

Короче говоря, в шапке может быть почти что угодно. В теле же, определённо должна быть возможность выводить любое содержимое. В подвале содержимое так же может быть произвольным.

Получается, что у панели должно быть минимум 3 параметра, которые принимают "сложное содержимое", то есть такое, которое не является плоским текстом, а содержит иерархию вложенных блоков.

Далее идёт обзор тех готовых решений, которые можно найти в гугле. Для каждого указан размер реализации в строках кода (CLOS). Плюс бонус в конце, для тех, кто доберётся ;-)

ReactJS

wmira/react-panel - 180 CLOS

В шапку выводятся:

Размеры тела по умолчанию подстраиваются под содержимое. Полдвал не поддерживается.

Пример использования:

return (
    <Panel
        title={Привет, мир!}
        titleIcon="icon-idea"
        toolbox={[
            {
                className : "icon-close" ,
                onclick : this.onClose.bind( this )
            }
        ]}
        >
        <p>Ты прекрасен!</p>
    </Panel>
)

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

react-bootstrap - 235 CLOS

Шапка и подвал раcсчитанны на вывод простого текста, но есть возможность вывести туда что угодно. Тело подстраивается под размеры содержимого и может быть схлопнуто до нулевой высоты через флаг.

Пример использования:

return (
    <Panel
        header={
            <div>
                <span class="my-title">Привет, мир!</span>
                <Button
                    bsStyle="danger"
                    onclick={ this.onClose.bind( this ) }
                    >
                    Закрыть
                </Button>
            </div>
        }
        footer={
            <Button
                bsStyle="success"
                onclick={ this.onSuccess.bind( this ) }
                >
                О, да!
            </Button>
        }
    >
        <p>Ты прекрасен!</p>
    </Panel>
)

Резюме: не смотря на костыли с оборачиванием списка компонент в div и запихиванием целого дерева в атрибут использовать можно почти для всего. Разве что "схлопываемость" зачастую будет просто лишним грузом, а когда потребуется что-то особенное, то этой куцей реализации скорее всего не хватит.

pivotal-cf/pivotal-ui - 173 CLOS

Шапка разделена на две секции: левую (header) и опциональную правую (actions), куда вы можете выводить любое содержимое. Подвал и тело имеют по одной секции. Для тела можно включить "скроллируемость", чтобы панель не вылезала за пределы области просмотра.

Пример использования:

return (
    <Panel
        className="bg-neutral-10"
        header={
            <h1>Привет, мир!</h1>
        }
        actions={
            <DangerButton onclick={ this.onClose.bind( this ) } >
                Закрыть
            </DangerButton>
        }
        footer={
            <PrimaryButton onclick={ this.onSuccess.bind( this ) }>
                О, да!
            </PrimaryButton>
        }
        >
        <p>Ты прекрасен!</p>
    </Panel>
)

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

Велосипед - 44 CLOS

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

function MyPanel({ className , ...props }) {
    return (
        <div
            { ...props }
            className={ `my-panel-root ${ className || '' }` }
        />
    )
}

function MyPanelTitle({ className , ...props }) {
    return (
        <h1
            { ...props }
            className={ `my-panel-title ${ className || '' }` }
        />
    )
}

function MyPanelHead({ className , ...props }) {
    return (
        <div
            { ...props }
            className={ `my-panel-head ${ className || '' }` }
        />
    )
}

function MyPanelBody({ className , ...props }) {
    return (
        <div
            { ...props }
            className={ `my-panel-body ${ className || '' }` }
        />
    )
}

function MyPanelFoot({ className , ...props }) {
    return (
        <div
            { ...props }
            className={ `my-panel-foot ${ className || '' }` }
        />
    )
}

Панель состоит из 3 опциональных блоков: шапка, тело подвал. Бонусом: можно добавить несколько шапок/тел/подвалов. Для блоков можно использовать как стандартные компоненты от панели, так и собственные, а внутрь них помещать что угодно.

Правда использование чуть более многословно, но зато обошлись без тэгов в атрибутах:

return (
    <MyPanel className="my-panel-skin-pretty">

        <MyPanelHead>
            <MyPanelTitle>Привет, мир!</MyPanelTitle>
            <button onclick={ this.onClose.bind( this ) } >Закрыть</button>
        </MyPanelHead>

        <MyPanelBody>
            <p>Ты прекрасен!</p>
        </MyPanelBody>

        <MyPanelFoot>
            <button onclick={ this.onSuccess.bind( this ) }>О, да!</button>
        </MyPanelFoot>

    </MyPanel>
)

Резюме: относительно компактное и весьма гибкое решение, имеет простой, понятный, правда несколько многословный (что в принципе свойственно XML) интерфейс.

$mol_page - 11 CLOS

Шапка по умолчанию выводит заголовок. Содержимое любого блока, как и сами блоки могут быть заменены чем угодно. Тело скроллируется, а позиция скролла восстанавливается при перезагрузке.

Реализация настолько компактная, что её не страшно привести прямо тут:

$mol_page $mol_view
    sub /
        <= Head $mol_view
            sub <= head /
                <= Title $mol_view
                    sub /
                        <= title
        <= Body $mol_scroll
            sub <= body /
        < Foot $mol_view
            sub <= foot /

Пример использования:

$my_app $mol_page
    title \Привет, мир!
    head /
        <= Title
        <= Close $mol_button_minor
            click? <=> close? null
            sub / \Закрыть
    body /
        \Ты прекрасен!
    foot /
        <= Success $mol_button_major
            click? <=> success? null
            sub / \О, да!

Резюме: на порядок компактнее реализация дающая тем не менее высокую степень гибкости, использование обходится без костылей, но применяется достаточно необычный синтаксис, требующий освоения. И, да, это не React, а $mol, где интерфейс тоже строится из компонент, которые агрегируют в себе другие компоненты, но компоненты не пересоздаются при каждом рендеринге, а кешируются. :-)

Выводы

В JSX можно сделать и так и сяк, но всё-равно будет что-то не то. Типичные проблемы:

С другой стороны, есть простой и последовательный синтаксис view.tree, оптимизированный для создания гибких компонент с минимумом исходного кода, и которому можно обучить любого верстальщика в считанные дни, после чего и его эффективность значительно повысится (ему не придётся копипастить огромные куски html или вручную накликивать нужные состояния компонентам) и эффективность программиста (ему не придётся "натягивать" вёрстку на логику каждый раз, когда верстальщик обновит макет).

Даже если вы разнорабочий full-stack программист, который умеет и в паттерны, и в семантику, и в стили - ваша эффективность всё-равно повысится за счёт уменьшения объёмов кода и лёгкого и непринуждённого создания компонент (чтобы создать простейшую компоненту достаточно создать файл с содержимым из одной короткой строки).

А как бы вы реализовали компонент "Панель" на вашем любимом фреймворке?