Используйте concent, чтобы пройти путь поэтапного рефакторинга реагирующих приложений.

внешний интерфейс React.js
Используйте concent, чтобы пройти путь поэтапного рефакторинга реагирующих приложений.

В традиционных редукс-проектах состояние, которое мы пишем в редьюсере, должно быть подключено к хранилищу. Мы должны спланировать определения состояния и редьюсера в начале. Есть ли способ быстро воспользоваться преимуществами разделения пользовательского интерфейса и логики? Вам не нужно начинать из коробки? Эта статья начинается с общего метода написания реакции.Когда вы получаете требование, у вас в голове есть грубое определение интерфейса компонента, а затем вы можете плавно подключаться к миру контента, чувствуя постепенное удовольствие и уникальность. нового API Очаруйте его!

спрос идет

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

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

Это очень распространенное требование, я полагаю, что после его прочтения многие боги кода примерно закончили писать прототип кода в уме, хе-хе, но, пожалуйста, наберитесь терпения и прочитайте эту статью.concentС благословения вашегоreactКак приложение станет более гибким и красивым, как наш слоган:

concent, power your react

Готов к работе

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

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

  • компонент с именемColumnConfModal, на основеantdизModal, Cardреализовать макет,antdизListреализовать список выбора слева на основеreact-beautiful-dndПеретаскиваемый API для реализации списка перетаскивания справа.

ui布局

  • Поскольку этот компонент всплывающего окна используется разными таблицами на разных страницах, данные определения входящего столбца различаются, поэтому мы используем метод события, чтобы инициировать открытие всплывающего окна, передать идентификатор таблицы и получить таблицу. после открытия всплывающего окна. Все определения полей, а также выбранные пользователем данные поля для двоюродного брата, чтобы работа по инициализации метаданных таблицы сходилась вColumnConfModalвнутренний.
  • На основании взаимодействия на левой и правой сторонах таблицы, примерно определяют внутренний интерфейс 1 MOVETOSELECTEDLISTLIST (перейти в выбранный список) 2 MOVETOSELECTALLELISTALIS (перейдите в выбираемый список) 3 SavesEluctedList (сохранить выбранный список пользователей) 4 ручной работы (обрабатывает, когда выбранный список настроен на настройку) 5 других слегка .....

Реализация пользовательского интерфейса

потому что зарегистрирован какconcentКомпоненты рождаются сemit&onвозможности без необходимости ручногоoff,concentПрежде чем экземпляр будет уничтожен, он автоматически освободит для вас свой прослушиватель событий, чтобы мы могли легко отслеживать его после завершения регистрации.openColumnConfмероприятие.

Сначала мы отбрасываем различные определения хранилища и редуктора и быстро основываемся наclassВытащите прототип, используйтеregisterИнтерфейс регистрирует обычные компоненты какconcentкомпонент, псевдокод выглядит следующим образом

import { register } from 'concent';

class ColumnConfModal extends React.Component {
  state = {
    selectedColumnKeys: [],
    selectableColumnKeys: [],
    visible: false,
  };
  componentDidMount(){
    this.ctx.on('openColumnConf', ()=>{
      this.setState({visible:true});
    });
  }
  moveToSelectedList = ()=>{
    //code here
  }
  moveToSelectableList = ()=>{
    //code here
  }
  saveSelectedList = ()=>{
    //code here
  }
  handleDragEnd = ()=>{
    //code here
  }
  render(){
    const {selectedColumnKeys, selectableColumnKeys, visible} = this.state;
    return (
      <Modal title="设置显示字段" visible={state._visible} onCancel={settings.closeModal}>
        <Head />
        <Card title="可选字段">
          <List dataSource={selectableColumnKeys} render={item=>{
            //...code here
          }}/>
        </Card>
        <Card title="已选字段">
          <DraggableList dataSource={selectedColumnKeys} onDragEnd={this.handleDragEnd}/>
        </Card>
      </Modal>
    );
  }
}

// es6装饰器还处于实验阶段,这里就直接包裹类了
// 等同于在class上@register( )来装饰类
export default register( )(ColumnConfModal)

Можно обнаружить, что внутренние и традиционныеreactНет никакой разницы в написании уроков, единственная разница в том,concentОбъект контекста вводится для каждого экземпляраctxВыставлятьconcentдляreactПриносит новые функции API.

Уничтожить функции жизненного цикла

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

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

  componentDidMount() {
    this.ctx.on('openColumnConf', () => {
      this.setState({ visible: true });
    });

    const tableId = this.props.tid;
    tableService.getColumnMeta(`/getMeta/${tableId}`, (columns) => {
      userService.getUserColumns(`/getUserColumns/${tableId}`, (userColumns) => {
        //根据columns userColumns 计算selectedList selectableList
      });
    });
  }

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

Теперь давайте использоватьsetupзаменить этот жизненный цикл

  //class 里定义的setup加?前缀
  $$setup(ctx){
    //这里定义on监听,在组件挂载完毕后开始真正监听on事件
    ctx.on('openColumnConf', () => {
      this.setState({ visible: true });
    });

    //标记依赖列表为空数组,在组件初次渲染只执行一次
    //模拟componentDidMount
    ctx.effect(()=>{
      //service call balabala.....
    }, []);
  }

Если вы знакомы сhookОдноклассники, см.setupвнутреннийeffectЯвляется ли синтаксис API иuseEffectНемного как?

effectа такжеuseEffectВремя выполнения такое же, то есть после рендеринга каждого компонента, ноeffectпросто нужноsetupОднократный вызов эквивалентен статическому и имеет больше возможностей для повышения производительности Предположим, мы добавляем требование каждый разvibibleКогда оно становится ложным, на серверную часть передается журнал операций, который можно записать как

    //依赖列表填入key的名称,表示当这个key的值发生变化时,触发副作用
    ctx.effect( ctx=>{
      if(!ctx.state.visible){
        //当前最新的visible已是false,上报
      }
    }, ['visible']);

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

Превратить состояние в магазин

Мы надеемся, что изменения состояния компонентов могут быть записаны, чтобы облегчить наблюдение за изменениями данных, поэтому сначала мы определяем подмодуль хранилища с именемColumnConf,

Определить его состояние как

// code in ColumnConfModal/model/state.js
export function getInitialState() {
  return {
    selectedColumnKeys: [],
    selectableColumnKeys: [],
	visible: false,
  };
}

export default getInitialState();

затем используйтеconcentизconfigureИнтерфейс загружает эту конфигурацию

// code in ColumnConfModal/model/index.js
import { configure } from 'concent';
import state from './state';

// 配置模块ColumnConf
configure('ColumnConf', {
  state,
});

Заметьте, пустьmodelСледуйте определению компонента, чтобы облегчить наше обслуживаниеmodelбизнес-логика внутри.

весьstoreБылconcentустановлен наwindow.sssДалее для удобства просмотра магазина можно открыть Dangdangdangdangconsole, смотреть напрямуюstoreТекущие последние данные для каждого модуля.

window.sss

Затем мы регистрируем класс как «модуль конфигурацииColumnConfкомпоненты, сейчасclassУтверждение состояния в может быть непосредственно убито нами.

import './model';//引用一下model文件,触发model配置到concent

@register('ColumnConf')
class ColumnConfModal extends React.Component {
  // state = {
  //   selectedColumnKeys: [],
  //   selectableColumnKeys: [],
  //   visible: false,
  // };
  render(){
    const {selectedColumnKeys, selectableColumnKeys, visible} = this.state;
  }
}

Вы, наверное, заметили, такой яростный комментарий вышел,renderНе будет ли в нем проблем с кодом? Не беспокойтесь, нет, состояние компонента контента иstoreЭто естественно пройти, то же самоеsetStateтакже иstoreПроходите, давайте сначала установим плагинconcent-plugin-redux-devtool.

import ReduxDevToolPlugin from 'concent-plugin-redux-devtool';
import { run } from 'concent';

// storeConfig配置略,详情可参考concent官网
run(storeConfig, {
	plugins: [ ReduxDevToolPlugin ]
});

О, обратите вниманиеconcentПринцип управления рендерингом пользовательского интерфейса иreduxСовершенно другое, основной логической части нет вreduxобертывание сверху иreduxЭто вообще ни при чем ^_^, здесь просто мостredux-dev-toolПлагин, для помощи в записи изменения состояния, друзья, не поймите неправильно, нетredux,concentможет работать нормально, но из-заconcentОбеспечить полный подключаемый механизм, что невыгодно с существующими выдающимися ресурсами, повторять бессмысленное колесо сложно (﹏] b ...

Теперь давайте откроемchromeДавайте посмотрим на эффект плагина redux.

state tree

Картинка выше содержит много ccApi/setState, потому что еще много не извлеченной логики.reducer,dispatch/***Тип внешности такойdispatchПозвонил, об этом мы упомянем позже.

Глядя на переход состояния таким образом, это лучше, чемwindow.sssгораздо лучше, потому чтоsssВидеть только последний статус.

Поскольку здесь упоминаетсяredux-dev-tool, давайте кратко рассмотрим, как выглядят данные, отправленные concent

action

Вы можете увидеть 5 полей на рисунке выше,renderKeyОн используется для повышения производительности, вы не можете понять это сначала, здесь мы поговорим об остальных четырех,moduleУказывает имя модуля, которому принадлежат измененные данные,committedStateУказывает статус коммита,sharedStateозначает совместное использованиеstoreположение дел,ccUniqueKeyУказывает идентификатор экземпляра, который инициирует изменение данных.

Зачем различатьcommittedStateа такжеsharedStateШерстяная ткань? потому чтоsetStateПри вызове разрешено передавать свой закрытый ключ (то есть ключ, не объявленный в модуле), поэтомуcommittedStateзаключается в том, что все состояние снова отправляется вызывающей стороне, иsharedStateсинхронизирован сstoreПосле этого он распределяется по тому жеmoduleЗначение других экземпляров компонента cc.

Вот картинка с официального сайта для наглядности:

cc-core

Таким образом, мы можем объявить другие немодульные ключи в компоненте, а затем вthis.stateполучен из

@register('ColumnConf')
class ColumnConfModal extends React.Component {
   state = {
		_myPrivKey:'i am a private field value, not for store',
   };
  render(){
  	//这里同时取到了模块的数据和私有的数据
    const {selectedColumnKeys, selectableColumnKeys, visible, _myPrivKey} = this.state;
  }
}

Разделение бизнес-логики и пользовательского интерфейса

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

export function setLoading(loading) {
  return { loading };
};

/** 移入到已选择列表 */
export function moveToSelectedList() {
}

/** 移入到可选择列表 */
export function moveToSelectableList() {
}

/** 初始化列表 */
export async function initSelectedList(tableId, moduleState, ctx) {
  //这里可以不用基于字符串 ctx.dispatch('setLoading', true) 去调用了,虽然这样写也是有效的
  await ctx.dispatch(setLoading, true);
  const columnMeta = await tableService..getColumnMeta(`/getMeta/${tableId}`);
  const userColumsn = await userService.getUserColumns(`/getUserColumns/${tableId}`);
  //计算 selectedColumnKeys selectableColumnKeys 略

  //仅返回需要设置到模块的片断state就可以了
  return { loading: false, selectedColumnKeys, selectableColumnKeys };
}

/** 保存已选择列表 */
export async function saveSelectedList(tableId, moduleState, ctx) {
}

export function handleDragEnd() {
}

использоватьconcentизconfigureдескриптор интерфейсаreducerтакже настроить

// code in ColumnConfModal/model/index.js
import { configure } from 'concent';
import * as reducer from 'reducer';
import state from './state';

// 配置模块ColumnConf
configure('ColumnConf', {
  state,
  reducer,
});

помните вышеsetup,setupОбъект может быть возвращен, и возвращенные результаты будут собраны вsettiings, теперь давайте внесем некоторые изменения, а потом посмотрим на класс, не стал ли мир намного тише?

import { register } from 'concent';

class ColumnConfModal extends React.Component {
  $$setup(ctx) {
    //这里定义on监听,在组件挂载完毕后开始真正监听on事件
    ctx.on('openColumnConf', () => {
      this.setState({ visible: true });
    });

    //标记依赖列表为空数组,在组件初次渲染只执行一次
    //模拟componentDidMount
    ctx.effect(() => {
      ctx.dispatch('initSelectedList', this.props.tid);
    }, []);

    return {
      moveToSelectedList: (payload) => {
        ctx.dispatch('moveToSelectedList', payload);
      },
      moveToSelectableList: (payload) => {
        ctx.dispatch('moveToSelectableList', payload);
      },
      saveSelectedList: (payload) => {
        ctx.dispatch('saveSelectedList', payload);
      },
      handleDragEnd: (payload) => {
        ctx.dispatch('handleDragEnd', payload);
      }
    }
  }
  render() {
    //从settings里取出这些方法
    const { moveToSelectedList, moveToSelectableList, saveSelectedList, handleDragEnd } = this.ctx.settings;
  }
}

Занятия любовью, зацепки любви, пусть сосуществуют в гармонии двое

reactСообщество толкает с трескомHookРеволюция, пусть все постепенно используютHookкомпоненты вместоclassкомпоненты, но по существуHookСбежалthis, упрощенныйdomУровень рендеринга, но также приносит большое количество временных анонимных замыканий, неоднократно создаваемых за время существования компонента.

приди и посмотриconcentКак решить эту проблему, было упомянуто вышеsetupСлужба поддержки возвращает результаты, которые будут собраны вsettiings, теперь немного подкорректируем код, поставимclassкомпоненты становятсяHookкомпоненты.

import { useConcent } from 'concent';

const setup = (ctx) => {
  //这里定义on监听,在组件挂载完毕后开始真正监听on事件
  ctx.on('openColumnConf', (tid) => {
    ctx.setState({ visible: true, tid });
  });

  //标记依赖列表为空数组,在组件初次渲染只执行一次
  //模拟componentDidMount
  ctx.effect(() => {
    ctx.dispatch('initSelectedList', ctx.state.tid);
  }, []);

  return {
    moveToSelectedList: (payload) => {
      ctx.dispatch('moveToSelectedList', payload);
    },
    moveToSelectableList: (payload) => {
      ctx.dispatch('moveToSelectableList', payload);
    },
    saveSelectedList: (payload) => {
      ctx.dispatch('saveSelectedList', payload);
    },
    handleDragEnd: (payload) => {
      ctx.dispatch('handleDragEnd', payload);
    }
  }
}

const iState = { _myPrivKey: 'myPrivate state', tid:null };

export function ColumnConfModal() {
  const ctx = useConcent({ module: 'ColumnConf', setup, state: iState });
  const { moveToSelectedList, moveToSelectableList, saveSelectedList, handleDragEnd } = ctx.settings;
  const { selectedColumnKeys, selectableColumnKeys, visible, _myPrivKey } = ctx.state;

  // return your ui
}

Я хотел бы поблагодарить г-на Ю Юйси за эту статью.Vue Function-based API RFC, дал мне много вдохновения, теперь вы можете увидеть все методы вsetupКогда определение завершено, когда у вас много компонентов, очевидно, что давление на gc с целью уменьшения.

Поскольку два метода письма очень согласуются между собой, отclassприбытьHookЭто очень естественно? Нам действительно не нужно спорить о том, какой из них лучше, это зависит от ваших личных предпочтений, даже если однажды вы увидитеclassНе радуют глаз, вconcent, стоимость рефакторинга практически равна нулю.

использовать компоненты

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

import { emit } from 'concent';

class Foo extends React.Component {
  openColumnConfModal = () => {
    //如果这个类是一个concent组件
    this.ctx.emit('openColumnConfModal', 3);
    //如果不是则可以调用顶层api emit
    emit('openColumnConfModal', 3);
  }
  render() {
    return (
      <div>
        <button onClick={this.openColumnConfModal}>配置可见字段</button>
        <Table />
          <ColumnConfModal />
      </div>
    );
  }
}

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

//code in service/modal.js
import { emit } from 'concent';

export function openColumnConfModal(tid) {
  emit('openColumnConfModal', tid);
}

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

import * as modalService from 'service/modal';

class Foo extends React.Component {
  openColumnConfModal = () => {
    modalService.openColumnConfModal(6);
  }
  render() {
    return (
      <div>
        <button onClick={this.openColumnConfModal}>配置可见字段</button>
        <Table />
        <ColumnConfModal />
      </div>
    );
  }
}

Эпилог

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

Так как эта тема в основном ознакомительная渐进式Рефакторинг компонентов, поэтому другие функции, такие какsync,computed$watch, высокопроизводительный убийцаrenderKeyИ многое здесь нет, чтобы объяснить расширение, уходя на следующую статью, поэтому оставайтесь на улице.

Если инспектору понравится, приходите.указать звездуповторять,concentпосвященныйreactПредлагая новый опыт кодирования и функциональные улучшения, ждите новых функций и экологичных периферийных устройств.