Шаблоны проектирования React и анализ сценариев

внешний интерфейс Шаблоны проектирования JavaScript React.js
Шаблоны проектирования React и анализ сценариев

На этой неделе подряд были опубликованы две статьи о React:

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

введение сцены

页面展现

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

Если просмотра картинок Gif недостаточно, вы можете перейти кCodeSandboxУчитесь онлайн.

Конкретная структура кода:

class App extends Component {
  render() {
    return (
        <Stepper stage={1}/>
    );
  }
}

Свойство «стадия» компонента Stepper указывает, какой блок следует запускать по умолчанию, и имеет одноименное состояние «стадии». stage здесь представляет блоки контента слева. Метод handleClick переключает this.stata.stage.

class Stepper extends Component {
  state = {
    stage: this.props.stage
  }
  static defaultProps = {
    stage: 1
  }
  handleClick = () => {
    this.setState({ stage: this.state.stage + 1 })
  }
  render() {
    const { stage } = this.state;
    return (
      <div style={styles.container}>
        <Progress stage={stage}/>
        <Steps handleClick={this.handleClick} stage={stage}/>
      </div>
    );
  }
}

Мы видим, что компонент Stepper содержит компонент Progress (левая навигация) и компонент Steps. Такой код работает хорошо, но имеет некоторые проблемы с возможностью повторного использования и гибкостью. Например:

  • Что, если нам нужно будет переключить порядок отображения прогресса и шаги компонентов (слева и справа)?
  • Что мне делать, если нашему Степперу нужно нести больше ступеней?
  • Что делать, если нам нужно изменить содержимое этапа?
  • Что если мы захотим поменять порядок этапов?

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

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

Редизайн

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

Мы используем Function as Child Component для рефакторинга компонента Stepper. (Если вы не знакомы с функцией как дочерним компонентом, обратитесь к моей предыдущей статьеКомпонент повторно использует эти вещи — React реализует колеса загрузки по требованию.)

Как показано ниже:

Function as Child Component 重构

Компоненты Progress и Steps больше не отображаются непосредственно в методе рендеринга компонента Stepper. Мы используем this.props.children для рендеринга всех дочерних элементов компонента Stepper. Таким образом, содержимое, отображаемое компонентом Stepper, становится более гибким.

Но одной этой модификации невозможно выполнить требование, когда пользователь нажимает кнопку «Продолжить», этап не переключается. Поскольку компоненты Progress и Steps больше не знают о методах stage и handleClick через реквизиты.

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

const children = React.Children.map(this.props.children, child => {
		return React.cloneElement(child, {stage, handleClick: this.handleClick})
	})

Используйте React.Children.map для обхода дочерних узлов и копирования дочерних компонентов с помощью метода React.cloneElement, у которого есть возможность добавлять дополнительные реквизиты через второй параметр. Метод рендеринга компонента Stepper нужно применять только специально:

const { stage } = this.state;
const children = React.Children.map(this.props.children, child => {
	return React.cloneElement(child, {stage, handleClick: this.handleClick})
})
return (
	<div style={styles.container}>
		{children}
	</div>
	);

Таким образом, приложение снова работает правильно!

class App extends Component {
  render() {
    return (
      <div>
        <Stepper stage={1}>
          <Progress />
          <Steps />
        </Stepper>
      </div>
    );
  }
}

Тот же метод мы также можем применить к компоненту Progress. Здесь он не будет расширяться.

Использование статических свойств

Стоит отметить, что мы можем использовать статические свойства для улучшения читабельности кода. Статические свойства позволяют нам вызывать методы непосредственно в классе. Во-первых, мы создаем два статических метода в компоненте Stepper и назначаем их компонентам Progress и Steps:

static Progress = Progress;
static Steps = Steps

Теперь в App.js мы можем напрямую:

import React, { Component } from 'react';
import Stepper from "./Stepper"

class App extends Component {
  render() {
    return (
      <Stepper stage={1}>
        <Stepper.Progress />
        <Stepper.Steps />
      </Stepper>
    );
  }
}
export default App;

Преимущество этого заключается в том, что нет необходимости снова и снова импортировать компоненты Progress и Steps, все они будут отображаться как статические свойства Stepper. Мне лично такой подход не очень нравится.

Использование групп перехода React

Мы используем React Transition Group, чтобы добавить анимацию перехода к содержимому компонента Steps. Содержимое блока становится видимым, только если props.num равен this.props.stage:

class Steps extends Component {
	render() {
		const {stage,handleClick} = this.props
		const children = React.Children.map(this.props.children, child => {
			console.log(child.props)
			return (
				stage === child.props.num &&
				<Transition appear={true} timeout={300} onEntering={entering} onExiting={exiting}>
					{child}
				</Transition>
			)
		})
		return (
			<div style={styles.stagesContainer}>
				<div style={styles.stages}>
					<TransitionGroup>
						{children}
					</TransitionGroup>
				</div>
				<div style={styles.stageButton}>
					<Button disabled={stage === 4} click={handleClick}>Continue</Button>
				</div>
			</div>
		);
	}
}

Мы также можем добавить произвольный контент в компонент Steps:

import Stepper from "./Stepper"

class App extends Component {
  render() {
    return (
        <Stepper stage={1}>
          <Stepper.Progress>
            <Stepper.Stage num={1} />
            <Stepper.Stage num={2} />
            <Stepper.Stage num={3} />
          </Stepper.Progress>
          <Stepper.Steps>
            <Stepper.Step num={1} text={"Stage 1"}/>
            <Stepper.Step num={2} text={"Stage 2"}/>
            <Stepper.Step num={3} text={"Stage 3"}/>
            <Stepper.Step num={4} text={"Stage 4"}/>
          </Stepper.Steps>
        </Stepper>
    );
  }
}

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

Рефакторинговый код и эффекты могут быть доступныздесьПроверить.

Подумай и продолжай

Если вы считаете, что приведенный выше код безупречен, то, очевидно, вы хотите, чтобы он был простым. Требования меняются, а что, если мы захотим добавить большой заголовок в блок «Шаги»?

class App extends Component {
  render() {
    return (
        <Stepper stage={1}>
          <Stepper.Progress>
            <Stepper.Stage num={1} />
            <Stepper.Stage num={2} />
            <Stepper.Stage num={3} />
          </Stepper.Progress>
          <div>
            <div>Title</div>
            <Stepper.Steps>
              <Stepper.Step num={1} text={"Stage 1"}/>
              <Stepper.Step num={2} text={"Stage 2"}/>
              <Stepper.Step num={3} text={"Stage 3"}/>
              <Stepper.Step num={4} text={"Complete!"}/>
            </Stepper.Steps>
          </div>
        </Stepper>
    );
  }
}

Как показано на рисунке,

加入标题

Таким образом, компонент Stepper.Steps больше не является прямым и единственным дочерним узлом компонента Stepper, а ожидаемые пропсы, естественно, снова недоступны!

Проблема не только в этом.笔者本人不是很喜欢类似 React.cloneElement 顶层 API,除了偏好以外,也有一个难以规避的问题:Расширение React.cloneElement при использовании реквизита, конфликты имен реквизита, если как это сделать?

Например, если на вход попадает именованное значение свойства, можно представить себе последствия.

Итак, вопрос в том, есть ли более элегантный и эффективный способ решения вышеуказанных проблем? Или есть лучший способ добиться более гибкого дизайна?

Ответ должен быть да, и я оставлю его в следующей статье, чтобы объяснить.

Эта статья из:How To Master Advanced React Design Patterns, часть содержимого изменена.

коммерческое время:Если вы интересуетесь фронтенд-разработкой, особенно стеком React: возможно, в моей новой книге есть что-то, что вы хотели бы увидеть. Следите за авторомLucas HC, после выхода новой книги будет розыгрыш.

Happy Coding!

ПС: авторРепозиторий на гитхабе а также Знай ссылку на вопрос и ответПриветствуются все формы общения!

Пара других моих статей о стеке React:

Из обсуждения обещания setState познакомьтесь с дизайнерским мышлением команды React.

Путь дизайна приложений React — волшебное использование каррирования

Компонент повторно использует эти вещи — React реализует колеса загрузки по требованию.

Изучите «лучшие практики» для написания компонентов React на примере

React компонентный дизайн и декомпозиционное мышление

Связывание этого с React, см. Разработка языка JS и проектирование фреймворка.

Сделать мобильную веб-версию Uber недостаточно

React+Redux создает одностраничное приложение «NEWS EARLY», проект понимает истинное значение стека передовых технологий.