Управление глобальным состоянием апплета WeChat для достижения отзывчивости app.globalData

Апплет WeChat

1. Введение

Мини-программы WeChat во многом похожи на Vue и React, но сама мини-программа не предоставляет глобального инструмента управления состоянием, такого как Vuex или Redux. App.globalData не является реактивным, хотя там застрял app.globalData. То есть изменение app.globalData не может управлять страницей, но данные и свойства в Page и Component могут управлять страницей, поэтому мне интересно, можно ли каким-то образом связать app.globalData и данные или свойства.

Достигаемый конкретный эффект:Свяжите данные на определенной странице или компоненте со значением в app.globalData.Когда данные в app.globalData изменяются, измените значение в data.

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

2. Пример сценария

Предположим, у моего апплета есть индекс страницы, а два компонента — это childA и childB. Вложенная связь между ними выглядит следующим образом:

Эффект, которого я хочу добиться сейчас, заключается в том, чтобы определить поле данных indexData в индексе и передать его дочернему компоненту childA и дочернему компоненту второго уровня childB в виде передачи значения атрибута, и это значение находится на страницах индекса, childA и childB.show. И когда indexData изменяется, все страницы также могут реагировать.

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

// index.js
Page({
  data: {
    indexData: '默认值'
  },
  onLoad: function () {
    setTimeout(() => {
        this.setData({
            indexData: '修改后的值'
        })
    }, 3000)
  }
})
<!--index.wxml-->
<view class="container">
  <childA class="childA" childaProp='{{indexData}}'></childA>
  <view class="usermotto">{{indexData}}</view>
</view>

Сначала объявите поле данных indexData в index.js и передайте его свойству childaProp компонента childA в шаблоне.

// childA.js
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    childaProp: {
      type: String,
      value: ''
    }
  }
})
<!--childA.wxml-->
<view>我在childA:{{childaProp}}</view>
<childB class="childB" childbProp='{{childaProp}}'></childB>

Объявите свойство childaProp в childA.js и передайте его свойству childbProp компонента B в шаблоне.

// childB.js
const app = getApp()

Component({
  /**
   * 组件的属性列表
   */
  properties: {
    childbProp: {
      type: String,
      value: ''
    }
  }
})

<!--childB.wxml-->
<view>我在childB:{{childbProp}}</view>

Таким образом, когда индекс изменяет indexData через 3 секунды, и childaProp, и childbProp могут реагировать и управлять изменениями страницы.

Однако теперь требования изменились, нам нужно изменить childbProp в компоненте «внук» childB, и надеяться, что после изменения childbProp могут измениться и значения его отца и дедушки. Или просто надеяться, что при изменении компонента childB могут реагировать и значения в компонентах-сестрах (таких как named childC), которые не имеют к нему никакого отношения.

Говорить триггеры по одному слишком жалко, офицер, знакомый с глобальным управлением состояниями, может сразу подумать: Эй? Разве не тогда нужен Vuex?

Цитируя оригинальные слова в обзоре Vuex на официальном сайте Vue:

Однако, если вам нужно создать одностраничное приложение среднего или большого размера, вы, вероятно, будете думать о том, как лучше управлять состоянием вне компонентов, и Vuex будет естественным выбором. Цитируя автора Redux Дэна Абрамова Архитектура Flux похожа на очки: вы знаете, когда вам это нужно.

Товарищи. . . Время пришло, но у апплета WeChat нет решения Flux.

3. Вторичная разработка app.js

Ниже приведен процесс захвата данных в объекте app.globalData и обновления всех подписанных данных в режиме публикации-подписки. Эта статья полностью опирается на статью о принципе mvvm, упомянутом в начале, разница в том, что принцип mvvm заключается в том, чтобы подписаться на все узлы dom, которые должны быть привязаны к данным при компиляции шаблона, а здесь — подписаться на все данные или свойства, которые необходимо связать. То есть, когда данные меняются, dom меняется вместе с ним, вот когда меняется app.globalData, указанные данные меняются вместе с ним.

Поменяй суп, но не лекарство~

Первый — это захват данных

App({
  onLaunch: function () {
    //...此处省略很多自己生成的代码
    this.observe(this.globalData.wxMinix)
  },
  Observe: function (data) {
    let _this = this
    for (let key in data) {
      let val = data[key]
      this.observe(data[key])
      let dep = new Dep()
      Object.defineProperty(data, key, {
        configurable: true,
        get() {
          return val
        },
        set(newValue) {
          if (val === newValue) {
            return
          }
          console.log('newValue', newValue)
          val = newValue
          _this.observe(newValue)
        }
      })
    }
  },
  observe: function (data) {
    if (!data || typeof data !== 'object') return   
    this.Observe(data)
  },
  globalData: {
    wxMinix: {
      indexData: ''
    }
  }
})

Настройте свойство wxMinix в объекте globalData. Мы перехватываем объект, соответствующий этому свойству. Целью этого является разделение обязанностей. Мы не перехватываем другие свойства в globalData и по-прежнему можем использовать их как обычное приложение. .

Мы перебираем каждый ключ в app.globalData.wxMinix и переопределяем их свойства доступа с помощью метода Object.defineProperty Когда значение свойства все еще является объектом, мы повторяем вышеуказанную операцию до тех пор, пока не будет перехвачен каждый ключ. Это эквивалентно вытягиванию двух строк, когда это свойство принимает значение или присваивает значение, и выполнению того, что мы хотим сделать в эти два момента времени.

В это время присвойте значение app.globalData.wxMinix.indexData в любом месте апплета, и вы можете напечатать «newVlaue+new value» на консоли.

После освобождения подписки

Автор оригинального поста объяснил, что такое публикация и подписка.Подписка фактически добавляет в массив то, что нужно сделать (функции обратного вызова), а публикация выполняет их последовательно. Что нам нужно сделать здесь: подписаться на данные или свойства, которые мы хотим связать с app.globalData.wxMinix.indexData, а затем отслеживать app.globalData.wxMinix.indexData, Когда они изменятся, немедленно уведомить все данные или свойства которые подписаны на книгу. , пусть последуют их примеру.


// 在app.js的全局作用域定义观察者和订阅列表
function Watcher(key, gd, fn) {
  this.key = key
  this.gd = gd
  this.fn = fn

  Dep.target = this
  let arr = key.split('.')
  let val = this.gd
  arr.forEach(key => {
    val = val[key]
  })
  Dep.target = undefined
}

Watcher.prototype.update = function () {
  let arr = this.key.split('.')
  let val = this.gd
  console.log(this.gd)
  arr.forEach(key => {
    val = val[key]
  })
  this.fn(val)
}

function Dep() {
  this.subs = []
}

Dep.prototype = {
  addSubs(watcher) {
    this.subs.push(watcher)
  },
  notify() {
    this.subs.forEach(watcher => {
      watcher.update()
    })
  }
}
// 修改数据劫持中的代码
Observe: function (data) {
    let _this = this
    for (let key in data) {
      let val = data[key]
      this.observe(data[key])
      let dep = new Dep()   //Dep的实例可在set和get中闭包访问
                            //也就是说每个key都有对应的要通知的观察列表
      Object.defineProperty(data, key, {
        configurable: true,
        get() {
          Dep.target && dep.addSubs(Dep.target)     //获取app.globalData.wxMinix对应的值时进行订阅
          return val
        },
        set(newValue) {
          if (val === newValue) {
            return
          }
          console.log('newValue', newValue)
          val = newValue
          _this.observe(newValue)
          dep.notify()      // 当app.globalData.wxMinix对应的值变化时发布
        }
      })
    }
  }

Здесь главное сначала поддерживать конструктор типа Dep, который имеет только одно свойство массива, в его цепочке прототипов есть два метода: addSubs (подписка) и notify (публикация).

globalData: {
    wxMinix: {
      indexData: ''
    }
},
makeWatcher: function (key, gb, fn) {
    new Watcher(key, gb, fn)
}

Наконец, экземпляр наблюдателя создан, и вы можете подписаться на него~

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

Например: я хочу, чтобы indexData в index.js был привязан к app.globalData.wxMinix.indexData, тогда мне просто нужно подписаться на этого наблюдателя в жизненном цикле onLoad index.js.

onLoad: function () {
    let _this = this
    app.makeWatcher('wxMinix.indexData', app.globalData, function(newValue) {
        _this.setData({
            indexData: newValue
        })
    })
  }

В настоящее время, если вы измените значение childbProp в childB.js:

lifetimes: {
    attached: function () {
      let _this = this
      // 在组件实例进入页面节点树时执行
      setTimeout(() => {
        app.globalData.wxMinix.indexData = '从childB中修改后的值'
        console.log(app.globalData.indexData)
      }, 5000)
    },
    detached: function () {
      // 在组件实例被从页面节点树移除时执行
    },

В это время вы обнаружите, что indexData в index.js также изменился~, если вы хотите привязать данные к любому компоненту, вам нужно только подписаться на наблюдателя в жизненном цикле onLoad этой страницы или компонента и изменить это значение в функции обратного вызова Просто отлично.

Уведомление

На этом разработка в основном завершена, и следует отметить два момента:

  • Данные в app.globalData.wxMinix, которые вы хотите отслеживать, должны быть объявлены в объекте globalData заранее, потому что, когда перехват данных выполняется в onLaunch, он захватывает только все ключи в это время и вытаскивает их методы get и set. из темы Давай~ (ps: хотя кажется, что данные в самом app.globalData не могут быть доступны в апплете без предварительного объявления)

  • Если сам indexData также является объектом, также можно просто изменить одно из значений его свойства, но вам нужно объявить соответствующее значение в конкретных данных или свойствах страницы, и вы можете изменить это значение, когда makeWatcher. Или, изменяя app.globalData.wxMinix.indexData, вы можете глубоко скопировать indexData и после изменения одного из значений свойства присвоить новые indexData целиком. Например:

    let temp = JSON.parse(JSON.stringify(app.globalData.wxMinix.indexData))
    temp.name = '哈哈哈'
    app.globalData.wxMinix.indexData = temp

Поверхностное копирование не работает �-_-||.

4. Постскриптум

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

Я чувствую, что публикация и подписка довольно расплывчаты.Если вы этого не понимаете, вы можете прямопортал, что очень ясно, и я буду продолжать пересматривать эту статью.

---------------------------------Разделительная линия--------------- -----------------------

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