Можете ли вы реализовать глобальное управление состоянием апплета без вмешательства?

Апплет WeChat

Здравствуйте, сегодня мы поговорим об управлении состоянием небольших программ~ (Есть ли такое🤔)

чего мы собираемся достичь

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

И я надеюсь попытаться не менять исходную логику кода в этом процессе.

почему

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

Поскольку апплет официально не имеет глобального механизма управления состоянием, если вы хотите использовать глобальные переменные, вы можете вызвать App() только в app.js для создания экземпляра приложения, а затем добавить атрибут globalData. Однако этот globalData не отвечает, то есть, если одно из его значений изменено на определенной странице (если инициализация введена в данные), обновление представления не может быть завершено, не говоря уже об обновлении глобальные страницы и экземпляры компонентов.

Текущая основная практика

Давайте сначала рассмотрим самые популярные решения на данный момент.

В качестве примера возьмем westore. Это решение от Goose Factory, которое охватывает такие функции, как управление состоянием и межстраничная коммуникация. Основной процесс заключается в самостоятельном обслуживании компонента хранилища (похожего на vuex). Собирайте зависимости страниц и обновляйте их вручную. глобальные данные обновляются по мере необходимости. Предоставленный API также очень лаконичен, но если вы его используете, вам необходимо внести некоторые навязчивые изменения в исходный код проекта. НапримерПервый: при создании страницы или компонента это можно сделать только через API фреймворка. Второй: явный вызов this.update() для обновления представления каждый раз, когда изменяется глобальный объект.Вот исходный код

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

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

Прежде чем мы начнем, давайте проясним наши идеи. мы надеемся достичь

  1. Сделайте globalData реактивным.
  2. Соберите соответствующие свойства и обновите представления в data и globalData каждой страницы и компонента.
  3. Уведомляет все собранные страницы и компоненты об обновлении представлений при изменении globalData.

Это будет модель публикации-подписки, если вы этого не помните, то можете прочитать мою предыдущую статью. (Портал:модель публикации-подписки)

Talk is cheap. Show me the code.

Сказав это, пришло время двигаться.

Во-первых, мы определяем диспетчерский центр Observer для сбора зависимостей экземпляров компонентов глобальной страницы, чтобы уведомлять об обновлении при обновлении данных.Но здесь есть проблема: сбор всего экземпляра компонента страницы занимает слишком много памяти и влияет на первоначальный рендеринг (obj ниже), как его оптимизировать?

// 1.Observer.js
export default class Observer {
  constructor() {
    this.subscribers = {};
  }

  add (key, obj) { // 添加依赖 这里存放的obj应该具有哪些东东?
    if (!this.subscribers[key]) this.subscribers[key] = [];
    this.subscribers[key].push(obj);
  }

  delete () { // 删除依赖
    // this.subscribers...
  }

  notify(key, value) { // 通知更新
    this.subscribers[key].forEach(item => {
      if (item.update && typeof item.update === 'function') item.update(key, value);
    });
  }
}

Observer.globalDataObserver = new Observer(); // 利用静态属性创建实例(相当于全局唯一变量)

Я полагаю, что об этом думали многие студенты.На самом деле нам нужно только собрать данные и метод обновления (setData) в компоненте страницы.Подумав об этом,С таким же успехом можно настроить класс Watcher (obj выше), new Watcher() каждый раз, когда инициализируется компонент страницы, и передавать необходимые данные и методы., тогда давайте сначала завершим начальную часть инъекции.

// 2.patcherWatcher.js
// 相当于mixin了Page和Component的一些生命周期方法
import Watcher from './Watcher';
function noop() {}

const prePage = Page;
Page = function() {
  const obj = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
  const _onLoad = obj.onLoad || noop;
  const _onUnload = obj.onUnload || noop;

  obj.onLoad = function () {
    const updateMethod = this.setState || this.setData; // setState可以认为是diff后的setData
    const data = obj.data || {};
    // 页面初始化添加watcher 传入方法时别忘了绑定this指向
    this._watcher = this._watcher || new Watcher(data, updateMethod.bind(this));
    return _onLoad.apply(this, arguments);
  };
  obj.onUnload = function () {
    // 页面销毁时移除watcher
    this._watcher.removeObserver();
    return _onUnload.apply(this, arguments);
  };
  return prePage(obj);
};
// 。。。下面省略了Component的写法,基本上和Page差不多

Затем по нашему плану завершаем часть Watcher.Здесь входящие данные будут фильтроваться, нам нужно только свойство (reactiveData), соответствующее globalData, и инжектить Observer при инициализации.

// 3.Watcher.js
import Observer from './Observer';
const observer = Observer.globalDataObserver;
let uid = 0; // 记录唯一ID

export default class Watcher {
  constructor() {
    const argsData = arguments[0] ? arguments[0] : {};
    this.$data = JSON.parse(JSON.stringify(argsData));
    this.updateFn = arguments[1] ? arguments[1] : {};
    this.id = ++uid;
    this.reactiveData = {}; // 页面data和globalData的交集
    this.init();
  }

  init() {
    this.initReactiveData();
    this.createObserver();
  }

  initReactiveData() { // 初始化reactiveData
    const props = Object.keys(this.$data);
    for(let i = 0; i < props.length; i++) {
      const prop = props[i];
      if (prop in globalData) {
        this.reactiveData[prop] = getApp().globalData[prop];
        this.update(prop, getApp().globalData[prop]); // 首次触发更新
      }
    }
  }

  createObserver() { // 添加订阅
    Object.keys(this.reactiveData) props.forEach(prop => {
      observer.add(prop, this);
    });
  }

  update(key, value) { // 定义observer收集的依赖中的update方法
    if (typeof this.updateFn === 'function') this.updateFn({ [key]: value });
  }

  removeObserver() { // 移除订阅 通过唯一id
    observer.delete(Object.keys(this.reactiveData), this.id);
  }
}

наконец,Используйте прокси для завершения универсального метода реактивного объекта.

Вот небольшая деталь. При изменении массива set будет запускать некоторые дополнительные записи, такие как длина. Я не буду здесь вдаваться в подробности. Заинтересованные студенты могут узнать, как Youda обрабатывает это в vue3.0 (избегайте множественных триггеров).

// 4.reactive.js
import Observer from './Observer';
const isObject = val => val !== null && typeof val === 'object';

function reactive(target) {
  const handler = {
    get: function(target, key) {
      const res = Reflect.get(target, key);
      return isObject(res) ? reactive(res) : res; // 深层遍历
    },
    set: function(target, key, value) {
      if (target[key] === value) return true;
      trigger(key, value);
      return Reflect.set(target, key, value);
    }
  };
  const observed = new Proxy(target, handler);
  return observed;
}

function trigger(key, value) { // 有更改记录时触发更新 => 会调用所有Watcher中update方法
  Observer.globalDataObserver.notify(key, value);
}

export { reactive };

Наконец, просто укажите его в app.js.

// app.js
require('./utils/patchWatcher');
const { reactive } = require('./utils/Reactive');

App({
  onLaunch: function (e) {
    this.globalData = reactive(this.globalData); // globalData响应式化
    // ...
  },
  // ...
  globalData: { /*...*/ }

Суммировать

В общем, мы шаг за шагом отВнедрение инициализации компонента страницы => определение класса Watcher => сбор Watcher в Observer и запуск обновления здесь => глобальное введение в app.jsЭти шаги завершают отзывчивость globalData. В результате добавление 4 файлов ➕app.js3 строки кода (включая комментарии и т. д., всего более 100 строк кода) завершается практически без вмешательства Таким образом достигается функциональное разделение. Имеет определенную масштабируемость.

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

Спасибо за прочтение!

(Нелегко кодировать слова. Я все это видел здесь. Не так уж сложно попросить Бо понравиться. Закончилось~)