Серия React Wheel: Компоненты диалога — Идеи диалога

React.js

Ставь лайк и смотри, поиск в WeChat【Переезд в мир】Обратите внимание на этого человека, который не имеет большого фабричного прошлого, но имеет восходящий и позитивный настрой. эта статьяGitHub GitHub.com/QQ449245884…Он был включен, статьи были классифицированы, и многие мои документы и учебные материалы были систематизированы.

Все говорили, что нет проекта для написания резюме, поэтому я помог вам найти проект, и это было с бонусом.【Учебник по строительству】.

Эта статья является второй в серии React Wheels.

Это колесо построено на React + TypeScript + Webpack, а что касается построения окружения, я не буду здесь вдаваться в подробности, просто сделайте это сами. Конечно, вы можете обратиться к моемуисходный код.

Чтобы читать больше качественных статей, пожалуйстаПосетите блог GitHub, Сотни качественных статей ждут вас каждый год!

UI

Диалоговое окно вообще такая штука, нажимаем на кнопку, чтобы всплыло, основные виды этоAlter, Confirmа такжеModal, Модал обычно имеет полупрозрачный черный фон. Конечно, внешний вид может относиться к AntD или Framework и т. д.

Определить API

В плане API вам все же нужно обратиться к своим ровесникам, потому что если однажды, когда кто-то подумает об используемом вами UI фреймворке, ваш API им ранее не использовался, что увеличивает порог вхождения, поэтому API следует сохранить максимально похоже на существующий. .

Помимо предоставления свойств отображения, диалоговое окно также имеет функцию воспроизведения после нажатия для подтверждения, например:

alert('你好').then(fn)
confirm('确定?').then(fn)
modal(组件名)

выполнить

Исходный код диалога загружен наздесь.

dialog/dialog.example.tsx, здесь укажите, жизненный цикл использует новый хук React 16.8, если вы не знакомы с хуком, то можете сначала прочитать егоОфициальная документация сайта.

dialog/dialog.example.tsx

import React, {useState} from 'react'
import Dialog from './dialog'
export default function () {
  const [x, setX] = useState(false)
  return (
    <div>
      <button onClick={() => {setX(!x)}}>点击</button>
      <Dialog visible={x}></Dialog>
    </div>
  )
}

dialog/dialog.tsx

import React from 'react'

interface Props {
  visible: boolean
}

const Dialog: React.FunctionComponent<Props> = (props) => {
  return (
    props.visible ? 
      <div>dialog</div> : 
      null
  )
}

export default Dialog

текущий результат

Показать содержимое

Все еще есть проблема с вышеизложенным, наш диалог жестко запрограммирован в компоненте, мы хотим напрямую передать содержимое, завернутое в компонент, например:

// dialog/dialog.example.tsx
...
<Dialog visible={x}>
  <strong>hi</strong>
</Dialog>
...

Пишите так, это не будет отображаться на страницеhi, ГдеchildrenСвойства пригодны, нам нужно еще больше изменять следующее в диалоговом окне:

// dialog/dialog.tsx
...
return (
    props.visible ? 
      <div>
        {props.children}
      </div>
      : 
      null
)
...

показать маску

Обычно диалоговое окно будет иметь слой маски, обычно мы обычно пишем так:

// dialog/dialog.tsx
...
props.visible ? 
  <div className="fui-dialog-mask">
    <div className="fui-dialog">
    {props.children}
    </div>
  </div>
  : 
  null
...

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

// dialog/dialog.tsx
...
<div>
    <div className="fui-dialog-mask">
    </div>
    <div className="fui-dialog">
      {props.children}
    </div>
 </div>
...

Поскольку React требует только один элемент на самом внешнем уровне, мы используем еще одинdivОберните это, но этот метод незримо большеdiv, чтобы вы могли использовать новые после React 16Fragment, Фрагмент такой же, как шаблон в vue, он не будет отображаться на странице.

import React, {Fragment} from 'react'
import './dialog.scss';
interface Props {
  visible: boolean
}

const Dialog: React.FunctionComponent<Props> = (props) => {
  return (
    props.visible ? 
     <Fragment>
        <div className="fui-dialog-mask">
        </div>
        <div className="fui-dialog">
          {props.children}
        </div>
     </Fragment>
      : 
      null
  )
}

export default Dialog

Полный заголовок, содержание и нижняя часть

Тут особо нечего сказать, сразу к коду

import React, {Fragment} from 'react'
import './dialog.scss';
import {Icon} from '../index'
interface Props {
  visible: boolean
}

const Dialog: React.FunctionComponent<Props> = (props) => {
  return (
    props.visible ? 
      <Fragment>
          <div className="fui-dialog-mask">
          </div>
          <div className="fui-dialog">
            <div className='fui-dialog-close'>
              <Icon name='close'/>
            </div>
            <header className='fui-dialog-header'>提示</header>
            <main className='fui-dialog-main'>
              {props.children}
            </main>
            <footer className='fui-dialog-footer'>
              <button>ok</button>
              <button>cancel</button>
            </footer>
          </div>
      </Fragment>
      : 
      null
  )
}

export default Dialog

Из приведенного выше кода мы можем обнаружить, что когда мы пишем имя стиля, чтобы оно не было перезаписано при третьем использовании, мы настроилиfui-dialogПрефикс, при написании каждого имени стиля, приходится писать заново, что явно неразумно, на случай, если я однажды не использую этот префикс, каждый надо менять заново, поэтому нужен метод для его инкапсуляции.

Мы могли бы написать такой метод:

function scopedClass(name) {
  return `fui-dialog-${name}`
}

Это не может быть написано, потому что наше имя не может быть передано, так что будет еще один-, поэтому требуется дальнейшее суждение:

function scopedClass(name) { return fui-dialog-${name ? '-' + name : ''} }

Есть ли более краткий способ использованияfilterметод:

function scopedClass(name ?: string) {
  return ['fui-dialog', name].filter(Boolean).join('-')
}

Метод вызова следующий: ....

<div className={scopedClass('close')}>
Советы
{реквизиты.дети}
Ok отменить ... Все думают, есть проблема с написанием таким образом, вы пишете функцию для каждого компонента?Если компонент Icon, мне нужно написать еще одинfui-icon, решение состоит в том, чтобы положить前缀Когда такой параметр, как:

function scopedClass(name ?: string) {
  return ['fui-dialog', name].filter(Boolean).join('-')
}

Метод вызова следующий:

className={scopedClass('fui-dialog', 'mask')}

Таким образом, лучше написать стиль напрямую, это равносильно написанию метода напрасно, так что же мне делать? Это требуетФункции высшего порядкапоявился. Реализация выглядит следующим образом:

function scopeClassMaker(prefix: string) {
  return function (name ?: string) {
    return [prefix, name].filter(Boolean).join('-')
  }
}

const scopedClass = scopeClassMaker('fui-dialog')

scopeClassMakerФункция представляет собой функцию высокого уровня, которая возвращаетprefixфункция параметров.

обработка событий

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

// dialog/dialog.example.tsx
...
<Dialog visible={x} buttons = {
  [
    <button onClick={()=> {setX(false)}}>1</button>,
    <button onClick={()=> {setX(false)}}>2</button>,
  ]
}>
  <div>hi</div>
</Dialog>
...

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

Рендеринг внутри компонента выглядит следующим образом:

<footer className={sc('footer')}>
  {
    props.buttons
  }
</footer>

Когда вы запустите его, вы найдете предупреждение:

В основном означает, что когда мы визуализируем массив, нам нужно добавитьkey, Есть два решения, то есть не использовать метод массива, конечно, это не вылечит первопричину, поэтому здесьReact.cloneElemenКогда он выйдет, он может клонировать элемент и добавить соответствующее значение атрибута следующим образом:

{
  props.buttons.map((button, index) => {
    React.cloneElement(button, {key: index})
  })
}

Соответствующее событие click-to-close относительно простое, я не буду говорить об этом здесь, вы можете проверить это сами.исходный код.

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

.fui-dialog {
  position: fixed; background: white; min-width: 20em;
  z-index: 2;
  border-radius: 4px; top: 50%; left: 50%; transform: translate(-50%, -50%);
  &-mask { position: fixed; top: 0; left: 0; width: 100%; height: 100%;
    background: fade_out(black, 0.5);
    z-index: 1;
  }
  .... 以下省略其它样式
}

мы маскируем.fui-dialog-maskиспользоватьfixedЧувство позиционирования не проблема, тогда, если вы вызываете диалог на том же уровне, добавьте следующие элементы:

<div style={{position:'relative', zIndex: 10, background:'#fff'}}>666</div>
   
<button onClick={() => {setX(!x)}}>点击</button>
<Dialog visible={x}>
...
</Dialog>

текущий результат:

Выяснено, что маска не закрывает содержимое 666. почему это?

Это также легко понять, посмотрев на структуру, элемент маски того же уровня, что и 666, и уровень ниже, чем у 666. Конечно, его нельзя прикрыть. Тогда мы могли бы сделать это, дать.fui-dialog-maskустановить одинzIndexбольше, чем это, как9999.

Эффект:

Что ж, я не вижу проблем.В настоящее время мы вкладываем слой zIndex в компонент Dialog.9, Такие как:

<div style={{position:'relative', zIndex: 9, background:'#fff'}}>
  <Dialog visible={x}>
    ...
  </Dialog>
</div>

Эффект операции следующий:

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

Так как же его сломать? Ответ: не позволяйте ему появляться внутри какого-либо элемента, как это возможно. Здесь нам необходимо ввестиВолшебный API. Этот API называетсяпортал.

Использование заключается в следующем:

return ReactDOM.createPortal(
  this.props.children,
  domNode
);

Первый параметр — это ваш div, а второй — куда вы хотите перейти.

import React, {Fragment, ReactElement} from 'react'
import ReactDOM from 'react-dom'
import './dialog.scss';
import {Icon} from '../index'
import {scopedClassMaker} from '../classes'

interface Props {
  visible: boolean,
  buttons: Array<ReactElement>,
  onClose: React.MouseEventHandler,
  closeOnClickMask?: boolean
}

const scopedClass = scopedClassMaker('fui-dialog')
const sc = scopedClass

const Dialog: React.FunctionComponent<Props> = (props) => {

  const onClickClose: React.MouseEventHandler = (e) => {
    props.onClose(e)
  }
  const onClickMask: React.MouseEventHandler = (e) => {
    if (props.closeOnClickMask) {
      props.onClose(e)
    }
  }
  const x = props.visible ? 
  <Fragment>
      <div className={sc('mask')} onClick={onClickMask}>
      </div>
      <div className={sc()}>
        <div className={sc('close')} onClick={onClickClose}>
          <Icon name='close'/>
        </div>
        <header className={sc('header')}>提示</header>
        <main className={sc('main')}>
          {props.children}
        </main>
        <footer className={sc('footer')}>
          {
            props.buttons.map((button, index) => {
              React.cloneElement(button, {key: index})
            })
          }
        </footer>
      </div>
  </Fragment>
  : 
  null
  return (
    ReactDOM.createPortal(x, document.body)
  )
}

Dialog.defaultProps = {
  closeOnClickMask: false
}


export default Dialog

текущий результат:

Конечно, если уровень Dialog меньше, чем zIndex того же уровня, он все равно не может быть покрыт. ЭтоzIndexКак правило, установить, насколько это разумнее. в общемDialogЭтот слой настроен на1, слой маски установлен на2. Чем меньше параметр, тем лучше, потому что пользователь может его изменить.

Управление zIndex

Управлением zIndex обычно занимаются фронтенд-архитекторы, оно разделено по бизнес-сценариям, например, реклама должна быть вверху страницы, поэтому zIndex, как правило, самый продвинутый.

Оповещение с удобным API

То, как мы используем компонент Dialog для вызова вышеуказанного, более проблематично, и мы написали много.Иногда мы думаем об использовании предупреждения для прямого всплывающего диалогового окна.Это так просто и удобно. Такие как

  <h1>example 3</h1>
  <button onClick={() => alert('1')}>alert</button>

Мы хотим щелкнуть кнопку напрямую, а затем открыть наш настраиваемый диалог с содержимым 1, который необходимо экспортировать в компонент «Диалог».alertМетоды, как показано ниже:

// dialog/dialog.tsx
...
const alert = (content: string) => {
  const component = <Dialog visible={true} onClose={() => {}}>
    {content}
  </Dialog>
  const div = document.createElement('div')
  document.body.append(div)
  ReactDOM.render(component, div)
}

export {alert}
...

текущий результат:

Но есть проблема, потому что видимость диалога передается извне, а React — это односторонний поток данных, и видимость нельзя изменить напрямую в компоненте, поэтому в методе onClose нам нужно отрендерить новый компонент снова и установить новые компонентыvisibleдляture, перезапишите исходный компонент:

...
const alert = (content: string) => {
  const component = <Dialog visible={true} onClose={() => {
    ReactDOM.render(React.cloneElement(component, {visible: false}), div)
    ReactDOM.unmountComponentAtNode(div)
    div.remove()
  }}>
    {content}
  </Dialog>
  const div = document.createElement('div')
  document.body.append(div)
  ReactDOM.render(component, div)
}
..

Удобный API для подтверждения

подтвердить метод вызова:

<button onClick={() => confirm('1', ()=>{}, ()=> {})}>confirm</button>

Первый параметр — отображаемое содержимое, второй параметр — обратный вызов подтверждения, а третий параметр — функция обратного вызова отмены.

Метод реализации:

const confirm = (content: string, yes?: () => void, no?: () => void) => {
  const onYes = () => {
    ReactDOM.render(React.cloneElement(component, {visible: false}), div)
    ReactDOM.unmountComponentAtNode(div)
    div.remove()
    yes && yes()
  }
  const onNo = () => {
    ReactDOM.render(React.cloneElement(component, {visible: false}), div)
    ReactDOM.unmountComponentAtNode(div)
    div.remove()
    no && no()
  }
  const component = (
  <Dialog 
    visible={true} onClose={() => { onNo()}}
    buttons={[<button onClick={onYes}>yes</button>, 
              <button onClick={onNo}>no</button>
            ]}
  >
    {content}
  </Dialog>)
  const div = document.createElement('div')
  document.body.appendChild(div)
  ReactDOM.render(component, div)
}

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

Удобный модальный API

метод модального вызова:

<button onClick={() => {modal(<h1>你好</h1>)}}>modal</button>

Содержимое, передаваемое модальным окном, — это не просто текст, а элементы.

Метод реализации:

const modal = (content: ReactNode | ReactFragment) => {
  const onClose = () => {
    ReactDOM.render(React.cloneElement(component, {visible: false}), div)
    ReactDOM.unmountComponentAtNode(div)
    div.remove()
  }
  const component = <Dialog onClose={onClose} visible={true}>
    {content}
  </Dialog>
  const div = document.createElement('div')
  document.body.appendChild(div)
  ReactDOM.render(component, div)
}

Обратите внимание, тип контента здесь.

текущий результат:

Есть еще одна проблема, если вам нужно добавить кнопку, вы можете написать это так:

 <button onClick={() => {modal(<h1>
     你好 <button>close</button></h1> 
  )}}>modal</button>

Его нельзя закрыть, потому чтоDialogупакован вmodalвнутри. Если вы хотите закрыть, вы должны контролироватьvisible, то, очевидно, я не могу управлять изнутри извнеvisible, так что этоbuttonнет способа поставить этоmodalвыключенный.

Решение состоит в использовании замыкания, мы можем вернуть метод close в модальном методе:

const modal = (content: ReactNode | ReactFragment) => {
  const onClose = () => {
    ReactDOM.render(React.cloneElement(component, {visible: false}), div)
    ReactDOM.unmountComponentAtNode(div)
    div.remove()
  }
  const component = <Dialog onClose={onClose} visible={true}>
    {content}
  </Dialog>
  const div = document.createElement('div')
  document.body.appendChild(div)
  ReactDOM.render(component, div)
  return onClose;
}

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

Метод вызова:

const openModal = () => {
  const close = modal(<h1>你好
    <button onClick={() => close()}>close</button>
  </h1>)
}
<button onClick={openModal}>modal</button>

Рефакторинг API

Перед рефакторингом нам нужно абстрагировать соответствующие методы в alert, confirm, modal:

Как видно из таблицы, модальный и два других имеют только еще один rerun api, на самом деле два других тоже могут возвращать соответствующий Api, но мы его не вызывали, так что добавляем:

Таким образом, три функции похожи на абстрактном уровне, поэтому три функции следует объединить в одну.

Сначала извлеките общедоступную часть и назовите ее первой.x, содержание следующее:

const x= (content: ReactNode, buttons ?:Array<ReactElement>, afterClose?: () => void) => {
  const close = () => {
    ReactDOM.render(React.cloneElement(component, {visible: false}), div)
    ReactDOM.unmountComponentAtNode(div)
    div.remove()
    afterClose && afterClose()
  }
  const component = 
  <Dialog visible={true} 
    onClose={() => {
      close(); afterClose && afterClose()
    }}
    buttons={buttons}
  >
    {content}
  </Dialog>
  const div = document.createElement('div')
  document.body.append(div)
  ReactDOM.render(component, div)
  return close
}

Переработанный код оповещения выглядит следующим образом:

const alert = (content: string) => {
  const button = <button onClick={() => close()}>ok</button>
  const close = x(content, [button])
}

Переработанный код подтверждения выглядит следующим образом:

const confirm = (content: string, yes?: () => void, no?: () => void) => {

  const onYes = () => {
    close()
    yes && yes()
  }
  const onNo = () => {
    close()
    no && no()
  }
  const buttons = [
    <button onClick={onYes}>yes</button>, 
    <button onClick={onNo}>no</button>
  ]
  const close =  modal(content, buttons, no)
}

Модальная реконструкция следующего кода:

const modal = (content: ReactNode | ReactFragment) => {
  return x(content)
}

Наконец нашел, чтоxПуть этоmodalметод, поэтому изменитеxназванныйmodal, удалить соответствующийmodalопределение.

Суммировать

  1. Использование функций более высокого порядка scopedClass
  2. портал портал
  3. Динамически генерировать компоненты
  4. API закрытия прохода

Этот компонент использует оптимизированный стиль. Если вам интересно, вы можете оптимизировать его самостоятельно. Исходный код этого раздела выложен наздесьсерединаlib/dialog.

Ссылаться на

Курс React Wheel Building учителя Фан Инхана

общаться с

Статья постоянно обновляется каждую неделю. Вы можете выполнить поиск «Big Move to the World» в WeChat, чтобы прочитать и обновить ее как можно скорее (на одну или две статьи раньше, чем в блоге). Эта статья находится на GitHub.GitHub.com/QQ449245884…Он был включен, и многие мои документы были разобраны. Добро пожаловать в Звезду и совершенство. Вы можете обратиться в тестовый центр для ознакомления во время собеседования. Кроме того, обратите внимание на паблик-аккаунт и ответьте в фоновом режиме.Благосостояние, вы можете увидеть преимущества, вы знаете.