Разговор об управлении состоянием и концепции дизайна Concent

внешний интерфейс React.js
Разговор об управлении состоянием и концепции дизайна Concent

❤ отметьте меня, если вам нравится концепт ^_^

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

Зачем нужно государственное управление

Зачем нужно вводить управление состоянием во front-end reference?В принципе все пришли к единому мнению.Здесь я резюмирую его в 3 пункта:

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

Состояние существующих решений по управлению состоянием

redux

Решение для управления состоянием, которое следует неизменной идее реакции, будь то звездный рейтинг git или процветание сообщества, первая рекомендация должна бытьreduxЭто первый брат управления состоянием в мире реагирования, и ограничение использует уникальный путь.reducerЧистые функции используются для изменения данных хранилища, чтобы поток состояний всего приложения был понятным и отслеживаемым.

image.png

mbox

Следите за отзывчивым поздним шоуmbox, придумалcomputed,reactionконцепция, ее официальный лозунгВсе, что может быть получено из состояния приложения, должно быть получено, превратив исходный простой объект json в наблюдаемый, мы можем напрямую изменить состояние,mboxБудет автоматически запускать обновления рендеринга пользовательского интерфейса из-за его адаптивной концепции иvueочень близко, вreactсоответствоватьmobx-reactПосле использования многие шутилиmobxэто воляreactстановится классомvueСхема управления состоянием опыта разработки.

image.png

Конечно, потому чтоmboxЭто очень удобно для работы с данными, и это не соответствует требованию четкого и отслеживаемого пути потока состояния в крупномасштабных приложениях.Чтобы ограничить поведение пользователя при обновлении,mobx-state-tree,в общем,mobxСтаньте отзывчивым представителем.

разное

Остальные решения по управлению состоянием в основном делятся на три категории.

Класс не устраиваетreduxКод избыточен и многословен, а интерфейс недостаточно дружелюбен.reduxСделайте два пакета сверху, которые обычно представляют собой иностранные пакеты, такие какrematch, внутренний какdva,mirrorд., я называю ихreduxПроизводные семейные произведения или интерпретацииreduxИсходный код, интегрируйте свои собственные идеи и переделайте библиотеку, такую ​​какfinal-state,retalk,hyduxи т. д., я называю их классамиreduxработай.

Одна категория — это программы, которые выбирают адаптивный путь, иmobxНапример, захват обычных объектов состояния в наблюдаемые объекты, такие какdob, я называю их классамиmobxработай.

Остальное - использоватьreact context apiили последнийhookФункции, ориентированные на легкие, простые в использовании и несложные решения, такие какunstated-next,reactn,smox,react-modelЖдать.

Мое идеальное решение

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

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

  • Когда его используют новички, им не нужно понимать новый API функций, и они не знают о существовании управления состоянием.
  • Это позволяет ветеранам использовать недавно предоставленные API-интерфейсы функций в сочетании с их существующими знаниями об управлении состоянием, восстанавливать лучшие практики, признанные различными сообществами, и в то же время продолжать исследовать и совершенствовать, чтобы использовать больше преимуществ, связанных с управлением состоянием.
  • существуетreactимеютhookПосле этой функции как компоненты класса, так и компоненты функции могут пользоваться одним и тем же мышлением и согласованным управлением состоянием доступа к API, не создавая ощущения разделения.
  • На основе соблюдения вышеуказанных 3 пунктов пользователи могут использовать более упорядоченную и более интуитивную организацию для написания кода, и в то же время они также могут получить огромные преимущества в повышении производительности.

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

Говоря человеческим языком, он достаточно прост для понимания, достаточно элегантен для кодирования, достаточно надежен для инженерной архитектуры и достаточно хорош для использования... ^_^

concent.png

предсказуемый

reactосновывается наpull basedЧтобы сделать структуру пользовательского интерфейса для обнаружения изменений, для пользователя вам нужно явно вызватьsetStateпозволитьreactОщущается изменение состояния, поэтомуconcentСледуйте классическому принципу неизменности, реагируя на предсказуемость, и не используйте перехватывающие объекты для преобразования в наблюдаемые объекты для обнаружения изменений состояния (или снова становитесь классом).mobx......), ни глобальный при использованииpub&subрежим для управления обновлениями связанных представлений, а также для настройки различныхreselect,redux-sagaПодождите, пока промежуточное программное обеспечение решит проблемы с кешем вычислений, асинхронным действием и т. д. (Если это так, не будет ли это точкой невозврата для ковшового колеса семейства Redux...)

Tucao: Обширная гранулярность подписки Redux - это все больше и больше компонентов, а состояние все более и более сложное, часто из-за того, что компоненты подписываются на ненужные данные и вызывают избыточные обновления, а различные рукописные карты XXXToYYY раздражают. это не может повредить...

Нулевое вторжение

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

setState,下达更新指令

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

setState<K extends keyof S>(
    state: ((prevState: Readonly<S>, props: Readonly<P>) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null),
    callback?: () => void
): void;

Из описания подписи видно, чтоsetStateявляется частичным состоянием (состоянием фрагмента), на самом деле мы вызываемsetStateЭто тоже часто делается, и кто его изменит, тот пройдет соответствующийstateKeyи значение.

传递部分状态

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

merge partial state

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

set state Intelligently

Очевидно, нам нужно угнатьsetState, чтобы внедрить свою собственную логику, а затем вызвать原生setState.

//伪代码实现
class Foo extends Component{
  constructor(props, context){
    this.state = { ... };
    this.reactSetState = this.setState.bind(this);
    this.setState = (partialState, callback){
      //commit partialState to store .....
      this.reactSetState(partialState, callback);
    }
  }
}

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

Хотя хранилище представляет собой единое дерево состояний, фактическая бизнес-логика состоит из множества модулей, поэтому я использую ключ первого уровня хранилища в качестве имени модуля (аналогично пространству имен), что порождает концепцию модуля.

//concent代码示意
import { run, register } from 'concent';

run({
  foo:{//foo模块定义
    state:{
      name: 'concent',
    }
  }
})

@register('foo')
class Foo extends Component {
  changeName = ()=> {
    this.setState({ name: e.currentTarget.value });//修改name
  }
  render(){
    const { name } = this.state;//读取name
    return <input value={name} onChange={this.changeName} />
  }
}

Образец онлайн-кода здесь

Теперь давайте взглянем на приведенный выше код, за исключением того, что не показано наFooСостояние объявлено в компоненте, и другие места, кажется, дают вам ощущение: разве это не стандартный способ написания компонентов React? concent снижает стоимость управления состоянием доступа практически до незначительного уровня.

Конечно, вам также разрешено объявлять другие немодульные состояния в компоненте, чтобы они были эквивалентны частному состоянию, еслиsetStateЗафиксированное состояние содержит как модульные, так и немодульные состояния, и состояние модуля будет рассматриваться какsharedStateИзвлеченный и переданный другим экземплярам, ​​privName передается только самому себе.

@register('foo')
class Foo extends Component {
  state = { privName: 'i am private, not from store' };
  fooMethod = ()=>{
    //name会被当做sharedState分发到其他实例,privName仅提交给自己
    this.setState({name: 'newName', privName: 'vewPrivName' });
  }
  render(){
    const { name, privName } = this.state;//读取name, privName
  }
}

Учитывая, что это составное состояние будет иметь определенную неоднозначность при стыковке с ts, концентрат позволяет отображать полное состояние объявления.Когда ваше состояние содержит stateKey с тем же именем, что и симуляция, к которой оно принадлежит, вдо первого рендераЭти значения stateKey будут перезаписаны соответствующими значениями в состоянии модуля.

@register('foo')
class Foo extends Component {
  // name对应的值在首次渲染前被替换为模块状态里name对应的值
  state = { name:'', privName: 'i am private, not from store' };
  render(){
    // name: 'concent', privName: 'i am private, not from store'
    const { name, privName } = this.state;
  }
}

В таком шаблоне вы можете создать несколько экземпляровFoo, любой экземпляр изменяетсяnameзначение, другие экземпляры будут обновлены, и вам не нужно обертывать корневой компонент верхнего уровня, напримерProviderвспомогательный тег для внедрения в контекст магазина.

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

прогрессивный

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

Более детальный контроль над потреблением данных

Гранулярность потребления компонентом состояния модуля не всегда очень грубая и напрямую соответствует модулю, то есть компонент, принадлежащий модулю fooCompAможет потреблять только модули в foof1,f2,f3Значения, соответствующие трем полям, принадлежащим компоненту модуля fooCompBможет потреблять только другие модули в foof4,f5,f6Значения, соответствующие трем полям, мы уж точно не ожидаемCompAЭкземпляры только модифицированныхf2,f3был запущенCompBРендеринг экземпляра.

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

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

  • пройти черезmoduleОтметьте, к какому конкретному модулю относится компонент

Это необязательный параметр, если вы его не укажете, пусть он относится к встроенному?defaultмодуль (пустой модуль), сmodule, вы можете позволить концентрату внедрить состояние модуля в состояние экземпляра после создания экземпляра его компонента.stateвверх.

  • пройти черезconnectотметить другие подключенные модули

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

  • пройти черезccClassKeyУстановить текущее имя класса компонента

Это необязательная опция, после настройки удобноreact dom treeПросмотрите именованный узел компонента concent вверху, если он не установлен, concent будет автоматически изменяться в соответствии с егоmoduleа такжеconnectЗначение параметра рассчитывается как 1. В это время один и тот же модуль регистрируется и помечается одним и тем жеconnectРазличные реагирующие компоненты параметров находятся вreact dom treeВсе, что вы видите выше, это одно и то же имя тега.

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

отstoreОтношения между классами и модулями с точки зрения

image.png

примерstateТак как контейнер данных уже содержал состояние модуля, которому он принадлежит, то при использованииconnectКак следует вводить эти данные, когда компонент подключен к нескольким другим модулям? Вслед за этим вопросом давайте вспомним то, что мы упоминали выше, экземпляр, вызывающийsetStateПредставленный статус будетconcentКоторый принадлежит к состоянию модуля добычи, как оноsharedStateТочное распределение на другие экземпляры.

Точное распределение может быть достигнуто, поскольку при создании экземпляров этих зарегистрированных компонентовconcentсоздаст для него контекст экземпляраctx, экземпляр соответствует уникальномуctx,ПотомconcentЭтиctxСсылки тщательно хранятся в глобальном контекстеccContextin (одноэлементный объект, inrunпри создании), поэтому процесс создания экземпляра компонента завершен.concentОчень важная часть основного принципа работы:коллекция цитат, разумеется, после уничтожения экземпляра соответствующийctxтакже будет удален.

имеютctxобъект,concentЕстественно реализовать на нем различные функции.Для компонентов, упомянутых выше, которые соединяют несколько модулей, данные модуля будут вводиться вctx.connectedStateЗатем получите соответствующие данные через конкретное имя модуля.

ctx.png

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

//concent代码示意
import { run, register, getState } from 'concent';

run({
  foo:{//foo模块定义
    state:{
      name: 'concent',
      age: 19,
      info: { addr: 'bj', mail: 'xxxx@qq.com' },
    }
  },
  bar: { ... },
  baz: { ... },
})


//显示的设定ccClassKey名称,方便查看引用池时知道来自哪个类
@register({module:'foo' }, 'Foo2')
class Foo2 extends Component { ... }

// 属于foo模块,同时也连接bar、baz两个模块
@register({
  module:'foo', 
  connect: ['bar', 'baz']
}, 'Foo2')
class Foo2 extends Component {
  render(){
    //获取到bar,baz两个模块的数据
    const { bar, baz } = this.ctx.connectedState;
    // 读取了barKey1,barKey1,当前实例数据的数据依赖是['barKey1', 'barKey2']
    // 即仅当模块里的'barKey1', 'barKey2'发生变化时,才会触发当前实例渲染
    const { barKey1, barKey2 } = bar;
  }
 }

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

工作原理

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

Как упоминалось в начале статьи «Зачем нам нужно управление состоянием», логика изменения состояния на самом деле является нашей бизнес-логикой.Разделение пользовательского интерфейса и бизнеса, что способствует повторному использованию логики, а также непрерывному обслуживанию и повторению.

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

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

Добавьте определение редуктора

import { run } from 'concent';
run({
  counter: {//定义counter模块
    state: { count: 1 },//state定义,必需
    reducer: {//reducer函数定义,可选
      inc(payload, moduleState) {
        return { count: moduleState.count + 1 }
      },
      dec(payload, moduleState) {
        return { count: moduleState.count - 1 }
      }
    },
  },
})

Изменить состояние через диспетчеризацию

import { register } from 'concent';
//注册成为Concent Class组件,指定其属于counter模块
@register('counter')
class CounterComp extends Component {
  render() {
    //ctx是concent为所有组件注入的上下文对象,携带为react组件提供的各种新特性api
    return (
      <div>
        count: {this.state.count}
        <button onClick={() => this.ctx.dispatch('inc')}>inc</button>
        <button onClick={() => this.ctx.dispatch('dec')}>dec</button>
      </div>
    );
  }
}

Поскольку модуль concent имеет такие параметры, как watch, вычисленный и init в дополнение к состоянию, редуктору, и поддерживает вас, чтобы определить его по мере необходимости.

cc-modulepng

Так ли это глобальное потреблениеbusiness model, либо компонент или страница поддерживает себяcomponent modelа такжеpage model, рекомендуется дополнительно записать модель в виде папки, определить состояние, редуктор, вычислить, наблюдать, инициализировать внутри, а затем экспортировать и синтезировать их вместе, чтобы сформировать полное определение модели.

src
├─ ...
└─ page
│  ├─ login
│  │  ├─ model //写为文件夹
│  │  │  ├─ state.js
│  │  │  ├─ reducer.js
│  │  │  ├─ computed.js
│  │  │  ├─ watch.js
│  │  │  ├─ init.js
│  │  │  └─ index.js
│  │  └─ Login.js
│  └─ product ...
│  
└─ component
   └─ ConfirmDialog
      ├─ model
      └─ index.js

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

// code in models/foo/reducer.js
export function changeName(name) {
  return { name };
}

export async function  changeNameAsync(name) {
  await api.track(name);
  return { name };
}

export async function changeNameCompose(name, moduleState, actionCtx) {
  await actionCtx.setState({ loading: true });
  await actionCtx.dispatch(changeNameAsync, name);//基于函数引用调用
  return { loading: false };
}

высокая производительность

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

Затем сравнитеredux, потому что он поддерживает управление детализацией потребления на уровне ключа, и вы знаете, какие экземпляры обновлять с момента отправки статуса, поэтому он может дать вам достаточные гарантии производительности, особенно для сценариев с большим количеством компонентов и сложными моделями данных.cocentЭто должно придать вам достаточно уверенности, чтобы спокойно справиться с этим, давайте посмотрим на сравнениеmbox,concentКакие еще сценарии были изучены.

renderKey, более точное управление диапазоном рендеринга

Контекст экземпляра для каждого компонентаctxимеет соответствующий ему уникальный индекс, называемыйccUniqueKey, каждый компонент передается, если он не отображается при создании экземпляраrenderKeyпереписать, т.renderKeyЗначение по умолчаниюccUniqueKey, когда мы сталкиваемся с тем, что stateKey модуля является списком или картой, каждый дочерний элемент в представлении, созданный путем его обхода, вызывает один и тот жеreducer, через идентификатор для достижения цели изменения только своих собственных данных, но они имеют общийstateKey, так что нужно соблюдать этоstateKeyДругие дочерние элементы также запускаются для избыточного рендеринга, и ожидаемый результат таков: любой, кто изменяет свои данные, только инициирует рендеринг.

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

render-key.png

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

Будь тоsetState,dispatch,ещеinvoke, оба поддерживают входящиеrenderKey.

render-key

Ключ, поставляемый с компонентом реакции, используется для diff v-dom-tree, concentrenderKeyОн используется для управления диапазоном позиционирования экземпляра. Между ними есть существенные различия. Ниже приведен пример кода,Образец онлайн-кода указывает мне проверить

// store的一个子模块描述
{
  book: {
    state: {
      list: [
        { name: 'xx', age: 19 },
        { name: 'xx', age: 19 }
      ],
      bookId_book_: { ... },//map from bookId to book
    },
    reducer: {
      changeName(payload, moduleState) {
        const { id, name } = payload;
        const bookId_book_ = moduleState.bookId_book_;
        const book = bookId_book_[id];
        book.name = name;//change name

        //只是修改了一本书的数据
        return { bookId_book_ };
      }
    }
  }
}

@register('book')
class ItemView extends Component {
  changeName = (e)=>{
    this.props.dispatch('changeName', e.currentTarget.value);
  }
  changeNameFast = (e)=>{
    // 每一个cc实例拥有一个ccUniqueKey 
    const ccUniqueKey = this.ctx.ccUniqueKey;
    // 当我修改名称时,真的只需要刷新我自己
    this.props.dispatch('changeName', e.currentTarget.value, ccUniqueKey);
  }
  render() {
    const book = this.state.bookId_book_[this.props.id];
    //尽管我消费是subModuleFoo的bookId_book_数据,可是通过id来让我只消费的是list下的一个子项

    //替换changeName 为 changeNameFast达到我们的目的
    return <input value={ book.name } onChange = { changeName } />
  }
}

@register('book')
class BookItemContainer extends Component {
  render() {
    const books = this.state.list;
    return (
      <div>
        {/** 遍历生成ItemView */}
        {books.map((v, idx) => <ItemView key={v.id} id={v.id} />)}
      </div >
    )
  }
}

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

lazyDispatch, более точный контроль времени рендеринга

существуетconcentвнутри,reducerфункция иsetStateТочно так же рекомендуется возвращать то, что изменено, и формат написания разнообразный.

  • Может быть обычной чистой функцией
  • возможноgeneratorгенераторная функция
  • возможноasync & awaitфункция Вы можете вернуть частичное состояние, вы можете вызвать другие функции-редьюсеры, а затем вернуть частичное состояние, или вы можете ничего не возвращать, просто объедините другие функции-редьюсеры для вызова. В сравненииreduxилиreduxВ схеме семейства много хлопот всегда синтезировать новое состояние, а чистые функции и функции побочных эффектов уже не трактуются по-разному. functions , просто объявите ее как обычную функцию, если вам нужна функция побочного эффекта, объявите ее как асинхронную функцию, простую и понятную, в соответствии с мышлением при чтении.

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

//reducer fns
export async function updateAge(id){
  // ....
  return {age: 100};
}

export async function trackUpdate(id){
  // ....
  return {trackResult: {}};
}

export async function fetchStatData(id){
  // ....
  return {statData: {}};
}

// compose other reducer fns
export async function complexUpdate(id, moduleState, actionCtx) {
  await actionCtx.dispatch(updateAge, id);
  await actionCtx.dispatch(trackUpdate, id);
  await actionCtx.dispatch(fetchStatData, id);
}

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

Исходный триггер функции редуктора начинается с контекста экземпляра ctx.dispatch или глобального контекста cc.dispatch (или cc.reducer), вызывает функцию редуктора модуля, а затем запускает другие функции редуктора внутри своей функции редуктора. факт, ацепочка вызовов, каждая функция-редуктор в цепочке, которая возвращает значение состояния, вызовет обновление рендеринга.Если в цепочке много функций-редукторов, будет много избыточных обновлений того же представления, что и обычно.

Исходный код, запускающий редюсер

// in your view
<button onClick={()=> ctx.dispatch('complexUpdate', 2)}>复杂的更新</button>

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

dispatch.png

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

увидеть эту функцию,mboxДумал ли пользовательtransactionКонцепция , да, вы правильно понимаете, в определенной степени они служат той же цели, но их проще и элегантнее использовать в контексте.

Теперь вам просто нужно сделать небольшую модификацию источника триггера, используяlazyDispatchзаменятьdispatchВсе, код в редьюсере не нужно корректировать, Concent задержит время, когда все функции редьюсера в цепочке вызовов функций редуктора вызовут обновление ui, и только новая часть возвращаемого ими состояния будет объединена модуль и временно сохраняется Конечный источник Когда вызов функции завершается, он отправляется в хранилище один раз и запускает рендеринг соответствующего экземпляра.

// in your view
<button onClick={()=> ctx.lazyDispatch('complexUpdate', 2)}>复杂的更新</button>

lazy-dispatch

Посмотреть пример кода онлайн

Теперь новый процесс обновления выглядит следующим образом

image.png

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

// in your view
const a=  <button onClick={()=> ctx.dispatch('complexUpdateWithLoading', 2)}>复杂的更新</button>

// in your reducer
export async function complexUpdateWithLoading(id, moduleState, actionCtx) {
  //这里会实时的触发更新
  await actionCtx.setState({ loading: true });

  //从这里开始启用lazy特性,complexUpdate函数结束前,其内部的调用链都不会触发更新
  await actionCtx.lazyDispatch(complexUpdate, id);

  //这里返回了一个新的部分状态,也会实时的触发更新
  return { loading: false };
}

delayBroadcast, более активно снижать частоту времени рендеринга

Для некоторого общего состояния, когда экземпляр часто его меняет, используйтеdelayBroadcastАктивно контролируйте отложенное распространение этого состояния на другие экземпляры, чтобы достичьБолее активно сокращайте время рендеринга

delay

function ImputComp() {
  const ctx = useConcent('foo');
  const { name } = ctx.state;
  const changeName = e=> ctx.setState({name: e.currentTarget.value});
  //setState第四位参数是延迟分发时间
  const changeNameDelay = e=> ctx.setState({name: e.currentTarget.value}, null, null, 1000);
  return (
    <div>
      <input  value={name} onChange={changeName} />
      <input  value={name} onChange={changeName} />
    </div>
  );
}

function App(){
  return (
    <>
      <ImputComp />
      <ImputComp />
      <ImputComp />
    </>
  );
}

Посмотреть пример кода онлайн

усилить реакцию

мы упоминали ранееctxОбъекты — это «герои», которые усиливают реакцию, потому что каждый экземпляр имеетconcentпостроен дляctxObject, очень удобно добавлять под него множество новых функций и новых возможностей.

добавлены новые функции

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

  • computedопределить каждыйstateKeyПри значении функция вычисления должна запускаться, а ее результат кэшироваться, только когдаstateKeyСчетчик сработает только при повторном изменении значения.Узнайте больше о вычисляемых
  • watchопределить каждыйstateKeyФункция обратного вызова, которая будет запущена, когда значениеstateKeyОн будет запущен только тогда, когда значение снова изменится, что обычно используется для обработки некоторых асинхронных задач.Узнать больше о часах. если я изsetStateЧтобы объяснить его природу, вы сможете понять, что эти функции естественным образом предоставляются пользователям.

setStateПереданный параметрpartialState,такconcentЗнайте, какие из них с самого началаstateKeyизменилось, естественно нам нужно только выставить конфигурациюcomputed,watch, то когда экземпляр отправляет новое частичное состояние после улучшенияsetStateЕстественно, он сможет вызвать соответствующий обратный вызов.

enhance set state.png

установка дает компонентам больше возможностей

упомянутый вышеcomputed,watchЗначение для модуля, нам нужно настроить его отдельно для экземпляраcomputed,watchКак с этим бороться?

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

Основываясь на характеристиках времени выполнения настройки, это эквивалентно предоставлению компоненту дополнительного пространства, одновременному определению соответствующей персонализированной конфигурации для компонента, предоставлению компоненту дополнительных возможностей, особенно для функционального компонента, обеспечениюuseConcentкопироватьregisterВсе возможности интерфейса, результаты возврата которого собираются вctx.settingsФункция в этом позволяет функциональным компонентам определять все методы одновременно вsetupТаким образом, устраняется слабость многократного создания временных замыкающих функций во время повторного рендеринга функциональных компонентов, что снижает нагрузку на сборщик мусора.

использоватьuseConcentТак что вы все еще можете использовать классикуdispatch&&reducerрежим для написания основной бизнес-логики и не исключает другие функции хуков инструментов (такие какuseWindowSizeд.), смешиваются между собой.

разрешите намsetupБар! ! ! Посмотрите на магию установки, котораяeffectФункция крюка - идеальная заменаuseEffect.Узнать больше о настройке

const setup = ctx => {
  //count变化时的副作用函数,第二位参数可以传递多个值,表示任意一个发生变化都将触发此副作用
  ctx.effect(() => {
    console.log('count changed');
  }, ['count']);
  //每一轮渲染都会执行
  ctx.effect(() => {
    console.log('trigger every render');
  });
  //仅首次渲染执行的副作用函数
  ctx.effect(() => {
    console.log('trigger only first render');
  }, []);

  //定义实例computed,因每个实例都可能会触发,优先考虑模块computed
  ctx.computed('count', (newVal, oldVal, fnCtx)=>{
    return newVal*2;
  });

 //定义实例watch,区别于effect,执行时机是在组件渲染之前
 //因每个实例都可能会触发,优先考虑模块watch
  ctx.watch('count', (newVal, oldVal, fnCtx)=>{
    //发射事件
    ctx.emit('countChanged', newVal);
    api.track(`count changed to ${newVal}`);
  });

  //定义事件监听,concent会在实例销毁后自动将其off掉
  ctx.on('changeCount', count=>{
    ctx.setState({count});
  });

  return {
    inc: () => setCount({ count: ctx.state.count + 1 }),
    dec: () => setCount({ count: ctx.state.count - 1 }),
  };
}

выгода отsetupатрибуты и все экземпляры concent содержат объект контекстаctx, компоненты классов и компоненты функций обеспечивают 100% унифицированные возможности вызовов API, что означает, что стили кодирования этих двух компонентов в высокой степени согласованы, а стоимость взаимного преобразования равна нулю.

Функциональные компоненты, обеспечивающие доступ к настройке

import { useConcent } from 'concent';

function HooklFnComp() {
  //setup只会在初次渲染前调用一次
  const ctx = useConcent({ setup, module:'foo' });
  const { state , settings: { inc, dec }  } = ctx;

  return (
    <div>
      count: {state.count}
      <button onClick={inc}>+</button>
      <button onClick={dec}>-</button>
    </div>
  );
}

Доступ к классовому компоненту установки

@register('foo')
class ClassComp extends React.Component() {
  ?setup(ctx){
    //复用刚才的setup定义函数, 这里记得将结果返回
    return setup(ctx);
  }

  render(){
    const ctx = this.ctx;
    //ctx.state 等同于 this.state
    const { state , settings: { inc, dec }  } = ctx;

    return (
      <div>
        count: {state.count}
        <button onClick={inc}>+</button>
        <button onClick={dec}>-</button>
      </div>
    );
  }

}

Посмотреть пример кода онлайн

После улучшения способности вы можете свободно выбирать подходящий способ обновления статуса в соответствии со сценой.

@register("foo")
class HocClassComp extends Component {
  render() {
    const { greeting } = this.state; // or this.ctx.state
    const {invoke, sync, set, dispatch} = this.ctx;

    // dispatch will find reducer method to change state
    const changeByDispatch = e => dispatch("changeGreeting", evValue(e));
    // invoke cutomized method to change state
    const changeByInvoke = e => invoke(changeGreeting, evValue(e));
    // classical way to change state, this.setState equals this.ctx.setState
    const changeBySetState = e => this.setState({ greeting: evValue(e) });
    // make a method to extract event value automatically
    const changeBySync = sync('greeting');
    // similar to setState by give path and value
    const changeBySet = e=> set('greeting', evValue(e));

    return (
      <>
        <h1>{greeting}</h1>
        <input value={greeting} onChange={changeByDispatch} /><br />
        <input value={greeting} onChange={changeByInvoke} /><br />     
        <input value={greeting} onChange={changeBySetState} /><br />
        <input value={greeting} onChange={changeBySync} /><br />
        <input value={greeting} onChange={changeBySet} />
      </>
    );
  }
}

Посмотреть пример кода онлайн

На следующем рисунке представлена ​​схема полного жизненного цикла компонента компонента:

ins.png

Поддержка промежуточного программного обеспечения и плагинов

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

image.png

определить промежуточное ПО и использовать

Промежуточное звено — это обычная функция

import { run } from 'concent';
const myMiddleware = (stateInfo, next)=>{
  console.log(stateInfo);
  next();//next一定不能忘记
}

run(
  {...}, //store config
  {
    middlewares: [ myMiddleware ] 
  }
);

определить плагины и использовать

Плагин — это простой объект, который должен содержать метод установки.

import { cst, run } from 'concent';

const myPlugin = {
  install: ( on )=>{
    //监听来自concent运行时的各种信号,并做个性化处理
    on(cst.SIG_FN_START, (data)=>{
      const { payload, sig } = data;
      //code here
    })
  }

  return { name: 'myPlugin' }//必需返回插件名
}

На основе механизма плагинов были предоставлены следующие плагины

  • concent-plugin-loading, плагин для простого управления статусом загрузки концентрированного приложения
  • concent-plugin-redux-devtool, разрешите приложению concent получить доступ к инструменту отладки redux-dev-tool, который удобен и нагляден для отслеживания истории изменения состояния.

image.png

Используйте существующую экосистему реагирования

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

глобальное воздействиеhistoryобъекта, наслаждайтесь программными навигационными переходами.

import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import { ConnectRouter, history, Link } from 'react-router-concent';
import { run, register } from 'concent';

run();

class Layout extends Component {
  render() {
    console.log('Layout Layout');
    return (
      <div>
        <div onClick={() => history.push('/user')}>go to user page</div>
        <div onClick={() => history.push('/user/55')}>go to userDetail page</div>
        {/** 可以基于history主动push,也可以使用Link */}
        <Link to="/user" onClick={to => alert(to)}>to user</Link>
        <div onClick={() => history.push('/wow')}>fragment</div>
        <Route path="/user" component={User_} />
        <Route path="/user/:id" component={UserDetail_} />
        <Route path="/wow" component={F} />
      </div>
    )
  }
}

const App = () => (
  <BrowserRouter>
    <div id="app-root-node">
      <ConnectRouter />
      <Route path="/" component={Layout} />
    </div>
  </BrowserRouter>
)
ReactDOM.render(<App />, document.getElementById('root'));

Нажмите на меня, чтобы увидеть онлайн-пример

Заключение и мысли

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

Основываясь на этом принципе в теории, он может быть использован для другихpull basedПлатформа пользовательского интерфейса механизма обновления реализует управление состоянием и обеспечивает их соответствие возможностям вызовов API и стилям написания кода, таким как小程序изthis.setData,omiизthis.update.

В то же время, потому чтоconcentОбъект контекста экземпляра предоставляетсяctxмодернизировать возможности компонентов, поэтому, если мы поставим перед собой цель: можем сделать响应式а также不可变сосуществование, кажется, работает, просто нужно добавить еще один иstateЭквивалентная наблюдаемая находится вctxна, предполагаяthis.ctx.dataэто наблюдаемая, которую мы строим, а затем упомянутое响应式Необходимо иметь дело с разными платформами в соответствии с разными стратегиями для достижения цели сосуществования.

  • для себя响应式Фреймворкangualrа такжеvue,поставкаthis.ctx.dataНепосредственное изменение состояния эквивалентно соединению исходного механизма обновления, в то время какreducerВозвращенное состояние в конечном итоге падает доthis.ctx.dataизменить для управления рендерингом вида.
  • противpull basedкадр какreact,поставкаthis.ctx.dataэто просто псевдоответчикthis.ctx.dataСобранные изменения по-прежнему попадают вthis.setStateЧтобы управлять обновлением представления, но это заставляет пользователя чувствовать, что это иллюзия, что представление управляется прямым манипулированием данными. Итак, если объединение этого слоя достигнуто, правильно ли это?concentМогут ли все фреймворки пользовательского интерфейса быть написаны одним и тем же способом кодирования?

Конечно, желание великого объединения прекрасно, но нужно ли его реализовывать? Решения по управлению состоянием в каждой структуре очень зрелые, и люди с ограниченной энергией для достижения этого видения должны выбрать самый трудный путь, поэтому здесь просто личное заявление о响应式а также不可变Сосуществование мышления и сортировки предоставляет читателям некоторые справочные мнения, чтобы они могли подумать о тенденции развития между управлением состоянием и инфраструктурой пользовательского интерфейса.

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

Как только золотой ветер и нефритовая роса встретятся, они победят, но в мире бессчетное количество людей.

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

В настоящее времяconcentПока только считайreactЗанимайтесь интеграцией, работайте над улучшением молчаливого взаимопонимания между ними и надейтесь постепенно улучшитьreduxИ второй братmobxпод территорией, занимайте маленькую базу, чтобы выжить, если читателю нравится эта статья, даconcentЗаинтересованы, добро пожаловатьstar, я верю, что огонь революции обязательно продолжится,concentИдея должна идти дальше.