js приложение шаблона дизайна - спроектируйте простое украшение магазина

внешний интерфейс Шаблоны проектирования
js приложение шаблона дизайна - спроектируйте простое украшение магазина

Это 13-й день, когда я участвую в Gengwen Challenge, проверьте подробности мероприятия:Обновить вызов

Предварительная статья:

задний план

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

Взгляните на большой дизайн.

设计思路.png

Первый взгляд на эффект

Портал регистрации бизнеса

Запись бизнес-компонентов здесь унифицирована и не имеет ничего общего с системной структурой, различающей их. Только таким образом необходимо зарегистрировать новые бизнес-компоненты.

import React from 'react';
import ShopDecorationNode from '../LeftNode/Nodes';
import { Yihangsitu, YihangsituProperty } from './Yihangsitu';
import Yihangyitu from './Yihangyitu';

const { registerNode, registerNodeProperty } = ShopDecorationNode;

registerNode('yihangsitu', (config: any) => {
  return React.createElement((Yihangsitu as unknown) as React.FC<{}>, config);
});

registerNodeProperty('yihangsitu', (config: any) => {
  return React.createElement((YihangsituProperty as unknown) as React.FC<{}>, config);
});

registerNode('yihangyitu', (config: any) => {
  return React.createElement(Yihangyitu, config);
});


функциональный дисплей

屏幕录制2021-06-13 下午8.07.22.2021-06-13 20_09_30.gif


Принципы шаблонов проектирования

Принцип инверсии зависимости

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

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

image.png
Все оформление магазина разделено на две части по принципу дизайна: уровень структуры и уровень бизнес-компонента. У них свое разделение труда и взаимодействие с данными.


Принцип единой ответственности

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

image.png

принцип открыто-закрыто

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

  • Откройте: разверните [Добавить и изменить бизнес-компоненты]
  • Закрыть: Изменить [Изменения в полке рамы]


Принцип замещения в стиле Ли

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


Идеи дизайна

Анализ поведения пользователей

Пользовательские операции, соответствующие оформлению магазина, в основном следующие:

image.png

Анализ перспективы развития

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

image.png

Анализ архитектуры

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

Где здесь ключевой момент?
Для ссылки и загрузки определенных бизнес-компонентов необходимо импортировать список компонентов слева. Рендеринг компонента требует загрузки соответствующего компонента. Свойства компонента также необходимо импортировать в соответствующий файл свойств компонента, а затем динамически загружать.

Ключевым моментом здесь является введение динамической загрузки компонентов [концепция регистрации]. Внедрение бизнес-компонентов не имеет существенного значения.
image.png


Примечания к дизайну

设计思路.png

Этот текст немного сложно объяснить. Затем ввести его через мономер, ввести функцию самоконтроля и функцию взаимодействия с внешним миром.

левый компонент

часть данных

Реестр узлов: класс

  • nodeTypes: внутреннее частное свойство, в котором хранится, сколько компонентов оформления магазина зарегистрировано в текущей системе.
  • nodePropertyTypes: внутренние частные свойства, в которых хранятся компоненты свойств, соответствующие компонентам хранилища.
  • registerNode: зарегистрировать компонент
  • renderNode: визуализировать компонент
  • registerNodeProperty: зарегистрировать компонент свойства
  • renderNodeProperty: компоненты свойства рендеринга

раздел страницы

Страница проходит через компоненты, зарегистрированные в текущих nodeTypes, и отображает список компонентов оформления магазина слева.

Поведение и относительно других компонентов

написание бизнес-кода

Разработчик вызывает метод register** для регистрации компонента.

registerNode('yihangsitu', (config: any) => {
  return React.createElement((Yihangsitu as unknown) as React.FC<{}>, config);
});

registerNodeProperty('yihangsitu', (config: any) => {
  return React.createElement((YihangsituProperty as unknown) as React.FC<{}>, config);
});

Промежуточная область рендеринга страницы

Рендеринг компонента вызовет компонент renderNode. В то же время передаются соответствующие данные атрибута.

 {renderNode(item.type, getCurrentNodeContent(item.key))}

метод рендерноде

  public renderNode = (name: string, config: any) => {
    return this.nodeTypes[name](config);
  };

Правая зона рендеринга свойства

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

<div key={property.key}>{renderNodeProperty(property.type, {
                                    keyString: property.key, 
                                    onValuesChange: store.updateNodeContent.bind(store), 
                                    content: JSON.parse(property.content || '{}')
                                  })}
                    </div>;

renderNodeProperty

  public renderNodeProperty = (name: string, config?: any) => {
    const callback = this.nodePropertyTypes[name];
    return callback ? callback(config) : '';
  };


Рендеринг промежуточного компонента

часть данных

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

раздел страницы

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

Поведение и относительно других компонентов

  • Вызовите renderNode для рендеринга страницы
  • Подпишитесь на StoreData для получения обновлений данных и повторного рендеринга страницы.
  • вызов StoreDatasetCurrentNodeМетод устанавливает узел справа, который необходимо отобразить.


Отображение свойств правого компонента

часть данных

Свойство частной собственности используется для обновления страницы свойств справа.

раздел страницы

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

<div key={property.key}>{renderNodeProperty(property.type, {
                                    keyString: property.key, 
                                    onValuesChange: store.updateNodeContent.bind(store), 
                                    content: JSON.parse(property.content || '{}')
                                  })}
                    </div>;

Поведение и относительно других компонентов

  • Подпишитесь на обновление текущего выбранного узла в промежуточной области storeData


Взаимодействие с данными StoreData

часть данных

  • storeData: данные для взаимодействия данных между компонентами всей системы
  • currentNode: узел компонента, выбранный в данный момент в средней области.
  • subscribeNodeArray: Массив событий для подписки на изменения currentNode.
  • subscribeStoreDataArray: массив событий для изменений данных storeData подписки
  • setCurrentNode: установить узел компонента, выбранный в данный момент в средней области.
  • updateStoreData: обновить SotrData
  • updateNodeContent: обновить данные данных currentNode
  • подпискаNodeChange: метод для добавления подписки на изменение currentNode
  • подпискаStoreDataChange: метод для добавления подписки на изменение storeData

Поведение и относительно других компонентов

  • Перетащите левый компонент в среднюю область: вызовите updateStoreData
  • Компонент промежуточного контента подписывается на обновления storeData.
  • В среднем компоненте содержимого переключитесь, чтобы выбрать свойства компонента, которые необходимо отредактировать: вызовите метод setCurrentNode
  • Свойство правого компонента подписывается на обновления currentNode
  • Обновление компонента правого свойства вызывает метод updateNodeContent.


Принцип замещения в стиле Ли - правила разработки бизнес-компонентов

способ зарегистрироваться

import React from 'react';
import ShopDecorationNode from '../LeftNode/Nodes';
import { Yihangsitu, YihangsituProperty } from './Yihangsitu';
import Yihangyitu from './Yihangyitu';

const { registerNode, registerNodeProperty } = ShopDecorationNode;

registerNode('yihangsitu', (config: any) => {
  return React.createElement((Yihangsitu as unknown) as React.FC<{}>, config);
});

registerNodeProperty('yihangsitu', (config: any) => {
  return React.createElement((YihangsituProperty as unknown) as React.FC<{}>, config);
});

registerNode('yihangyitu', (config: any) => {
  return React.createElement(Yihangyitu, config);
});

Собственная логическая обработка бизнес-компонента

  • Значение свойства внутри компонента соответствует самому разработчику, то есть компонент свойства имеет свойство имени, и если вы хотите отобразить соответствующее значение свойства в узле узла, вам также необходимо получить значение свойства имени реквизита .
  • Для обновления компонента свойства необходимо вызвать метод реквизита onValuesChange для обновления данных, который обновляет все данные текущего компонента.

Код

Я до сих пор использую здесь dumi для создания проекта.

yarn create @umijs/dumi-lib --site


структура кода

image.png

основной код

NodeRegistry

class NodeRegistry {
  public nodeTypes: Record<string, (config: any) => {}> = Object.create(null);
  public nodePropertyTypes: Record<string, (config: any) => {}> = Object.create(null);

  public registerNode = (name: string, callback: any) => {
    this.nodeTypes[name] = callback;
  };

  public renderNode = (name: string, config: any) => {
    return this.nodeTypes[name](config);
  };

  public registerNodeProperty = (name: string, callback: any) => {
    this.nodePropertyTypes[name] = callback;
  };

  public renderNodeProperty = (name: string, config?: any) => {
    const callback = this.nodePropertyTypes[name];
    return callback ? callback(config) : '';
  };
}

const ShopDecoration = new NodeRegistry();

export default ShopDecoration;

левый компонент

import React from 'react';
import { Button } from 'antd';
import styles from './index.less';
import ShopDecorationNode from './Nodes';

export default () => {
  const ondragstart = (event: any, text: string) => {
    event.dataTransfer.setData('Text', text);
  };

  const { nodeTypes } = ShopDecorationNode;
  const nodes = Object.keys(nodeTypes);

  return (
    <div className={styles.left_node}>
      {nodes.map((item) => (
        <Button
          type="primary"
          draggable={true}
          onDragStart={(event) => {
            ondragstart(event, item);
          }}
        >
          {item}
        </Button>
      ))}
    </div>
  );
};

Область промежуточного компонента

/*
 * @Description: 
 * @Author: rodchen
 * @Date: 2021-06-13 14:14:46
 * @LastEditTime: 2021-06-13 18:20:11
 * @LastEditors: rodchen
 */

import React, { useState } from 'react';
import styles from './index.less';
import store from '../Utils/store';
import ShopDecorationNode from '../LeftNode/Nodes';
import { NodeClass } from '../Type/interface';
import { NodeClassType } from '../Type/type';

export default () => {
  const [tempStoreData, setTempStoreData] = useState<NodeClassType[]>([])
  const { renderNode } = ShopDecorationNode;

  const onDrop = (event: any) => {
    const data: string = event.dataTransfer.getData('Text');
    event.preventDefault();
    const newNode = new NodeClass(data)
    store.updateStoreData(store.storeData.concat([newNode]))
    store.setCurrentNode(newNode);
  };

  store.subscriptionStoreDataChange((storeData: NodeClassType[]) => {
    setTempStoreData(storeData)
  })

  const allowDrop = (ev: any) => {
    ev.preventDefault();
  };

  const onClickForHanldeProperty = (item: NodeClassType) => {
    store.setCurrentNode(item);
  };

  const getCurrentNodeContent = (key: string) => {
    const content = tempStoreData.filter(innerItem => innerItem.key === key)[0].content;
    
    try {
      return JSON.parse(content as string)
    } catch (e) {
      return ''
    }
  }
  
  return (
    <div onDrop={onDrop} onDragOver={allowDrop} className={styles.content_render}>
      {tempStoreData.map((item) => (
        <div
          key={item.key}
          className={styles.content_node}
          onClick={() => {
            onClickForHanldeProperty(item);
          }}
        >
          {renderNode(item.type, getCurrentNodeContent(item.key))}
        </div>
      ))}
    </div>
  );
};

Право собственности компонентов рендеринга

import React, { useState } from 'react';
import store from '../Utils/store';
import ShopDecorationNode from '../LeftNode/Nodes';
import { NodeClassType } from '../Type/type';

export default () => {
  const { renderNodeProperty } = ShopDecorationNode;
  const [property, setProperty] = useState<NodeClassType>({type: '', key: ''});

  store.subscriptionNodeChange((item: any) => {
    setProperty(item);
  });
  
  return <div key={property.key}>{renderNodeProperty(property.type, {
                                    keyString: property.key, 
                                    onValuesChange: store.updateNodeContent.bind(store), 
                                    content: JSON.parse(property.content || '{}')
                                  })}
                    </div>;
};

StoreData

import { NodeClass } from '../Type/interface';
import { NodeClassType } from '../Type/type';

class Store {
  public storeData: NodeClassType[] = [];
  public currentNode: NodeClassType = new NodeClass('');
  public subscriptionNodeArray: any[] = [];
  public subscriptionStoreDataArray: any[] = [];


  public setCurrentNode(node: NodeClassType) {
    this.currentNode = node;
    this.subscriptionNodeArray.forEach((item) => {
      item(node);
    });
  }

  public updateStoreData(storeData: NodeClassType[]) {
    this.storeData = storeData;
    this.subscriptionStoreDataArray.forEach((item) => {
      item(storeData);
    });
  }

  public updateNodeContent({key, content}: {key: string, content: Object}) {
    this.storeData = this.storeData.map(item => item.key === key ? ((item.content = JSON.stringify(content)), item) : item);
    this.subscriptionStoreDataArray.forEach((item) => {
      item(this.storeData);
    });
  }

  public subscriptionNodeChange(callback: Function) {
    this.subscriptionNodeArray.push(callback);
  }

  public subscriptionStoreDataChange(callback: Function) {
    this.subscriptionStoreDataArray.push(callback);
  }
}

const store = new Store();

export default store;

Регистрация компонента

import React from 'react';
import ShopDecorationNode from '../LeftNode/Nodes';
import { Yihangsitu, YihangsituProperty } from './Yihangsitu';
import Yihangyitu from './Yihangyitu';

const { registerNode, registerNodeProperty } = ShopDecorationNode;

registerNode('yihangsitu', (config: any) => {
  return React.createElement((Yihangsitu as unknown) as React.FC<{}>, config);
});

registerNodeProperty('yihangsitu', (config: any) => {
  return React.createElement((YihangsituProperty as unknown) as React.FC<{}>, config);
});

registerNode('yihangyitu', (config: any) => {
  return React.createElement(Yihangyitu, config);
});

быть оптимизированным

Дело времени, писать сегодня некогда, так как функция недостаточно совершенна, поэтому код выкладывать не буду.

  • Функция удаления, среднюю область содержимого можно перетаскивать вверх и вниз, чтобы отрегулировать положение
  • В части взаимодействия с данными, повторной визуализации промежуточного содержимого, я хочу выполнить уровень обработки с помощью функции высшего порядка HOC Цель, которую я хочу достичь, заключается в следующем: обновление атрибута вызовет только текущий выбранный компонент для повторной визуализации, не все компоненты будут повторно визуализированы.
  • Вы также можете добавить область оболочки, которая может быть настроена самим разработчиком, в область рендеринга компонента и область компонента свойства, чтобы общедоступная отображаемая часть была доступна разработчику.