Выпущен собственный фреймворк апплета Tencent OMIX 2.0

JavaScript Апплет WeChat
Выпущен собственный фреймворк апплета Tencent OMIX 2.0

написать впереди

Данные у вас под рукой, статус негде спрятать

характеристика

  • глобальное управление состоянием
  • Представление без сохранения состояния
  • Нулевое вмешательство в мини-программы
  • только один API
  • Поддержка вычисляемых свойств
  • Удобная навигация по малым, средним и крупным проектам
  • Также подходит для мини-игр, да да, используйтеМаленькая игра по разработке программ, во втором случае этой статьи используется OMIX для реализации небольшой игры.
  • [Обновление] Поддержка страницы с сохранением состояния (данные), см. QA в конце статьи.

OMIX 2.0 — это эволюционная версия westore. Westore использует diff до и после изменения данных. Выходные данные diff в формате json — это исправление setData. omix 2.0 использует наблюдатель для отслеживания изменений данных, чтобы получить исправление setData. По сравнению с омиксом, омикс требует больше вычислений при запуске, а для инициализации омикса требуется больше памяти и вычислений, но омикс работает быстрее, чем омикс, при изменении данных.С точки зрения опыта программирования, омикс не требует ручного обновления, а омикс требует ручного обновления.

Есть только один хороший дизайн, и мы думаем, что дизайн OMIX 2.0 в самый раз.

Быстрый старт

API

  • create(store, option)Создайте страницу, хранилище вводится со страницы и может использоваться совместно между страницами и компонентами. Если параметр определяет данные, данные хранилища будут смонтированы вthis.data.$под
  • create(option)Создание компонентов
  • this.store.dataи данные, страница и все компоненты страницы могут быть получены, а рабочие данные автоматически обновят представление

Страницы или компоненты, которые не нужно внедрять в хранилище, используютPageа такжеComponentконструктор,Componentпройти черезtriggerEventОбщайтесь с верхним уровнем или взаимодействуйте с хранилищем верхнего уровня

Просто и практично

Реализовать простое отображение списка журналов

Определите глобальное хранилище:

export default {
  data: {
    logs: []
  }
}

Страница определения:

import create from '../../utils/create'
import util from '../../utils/util'
import store from '../../store'

create(store, {
  // 声明依赖
  use: ['logs'], //也支持复杂路径依赖,比如 ['list[0].name']
  // 计算属性,可以直接绑定在 wxml 里
  computed: {
    logsLength() {
      return this.logs.length
    }
  },
  onLoad: function () {
    // 响应式,自动更新视图
    this.store.data.logs = (wx.getStorageSync('logs') || []).map(log => {
      return util.formatTime(new Date(log))
    })

    setTimeout(() => {
      //响应式,自动更新视图
      this.store.data.logs[0] = 'Changed!'
    }, 1000)

    setTimeout(() => {
      //响应式,自动更新视图
      this.store.data.logs.push(Math.random(), Math.random())
    }, 2000)

    setTimeout(() => {
      //响应式,自动更新视图
      this.store.data.logs.splice(this.store.data.logs.length - 1, 1)
    }, 3000)
  }
})
<view class="container log-list">
  <block wx:for="{{logs}}" wx:for-item="log">
    <text class="log-item">{{index + 1}}. {{log}}</text>
  </block>
</view>

Определите компонент тестового хранилища, компонент также может использовать глобальные журналы в компоненте, исходный код компонента:

import create from '../../utils/create'

create({
  use: ['logs'],
  //计算属性
  computed: {
    logsLength() {
      return this.logs.length
    }
  }
})
<view class="ctn">
  <view>Log Length: {{logs.length}}</view>
  <view>Log Length by computed: {{logsLength}}</view>
</view>

Другие необязательные инструкции по настройке

Измените поле отладки store.js, чтобы включить и отключить отладку журнала:

export default {
  data: {
    motto: 'Hello World',
    userInfo: {},
    hasUserInfo: false,
    canIUse: wx.canIUse('button.open-type.getUserInfo'),
    logs: []
  },
  debug: true, //调试开关,打开可以在 console 面板查看到 store 变化的 log
  updateAll: true //当为 true 时,无脑全部更新,组件或页面不需要声明 use
}

Разработка глобальных обновлений по умолчанию отключена, а переключатель отладки по умолчанию включен.store.dataВсе изменения будут отображаться на панели журнала инструментов разработчика, как показано ниже:

разное

Здесь следует отметить, что изменение длины массива не приведет к обновлению представления, вам нужно использовать метод размера:

this.store.data.arr.size(2) //会触发视图更新
this.store.data.arr.length = 2 //不会触发视图更新

this.store.data.arr.push(111) //会触发视图更新
//每个数组的方法都有对应的 pure 前缀方法,比如 purePush、pureShift、purePop 等
this.store.data.arr.purePush(111) //不会触发视图更新

this.store.set(this.store.data, 'newProp', 'newPropVal')  //会触发视图更新
this.store.data.newProp = 'newPropVal' //新增属性不会触发视图更新,必须使用 create.set

вычисляемое свойство

  use: [
    'motto',
    'userInfo',
    'hasUserInfo',
    'canIUse'
  ],
  computed: {
    reverseMotto() {
      return this.motto.split('').reverse().join('')
    }
  }

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

прослушиватель изменений хранилища

const handler = function (evt) {
  console.log(evt)
}
//监听,允许绑定多个
store.onChange(handler)
//移除监听
store.offChange(handler) 

Комплексное управление магазином апплетов

Когда апплет становится очень сложным, однофайловое хранилище становится очень раздутым.Есть два решения:

  • Разделить один магазин на несколько файлов
  • Разделить один магазин на несколько магазинов

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

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

store-a.js:

export const data = {
  name: 'omix'
}

export function changeName(){
  data.name = 'Omix'
}

store-b.js:

export const data = {
  name: 'omix',
  age: 2
}

export function changeAge(){
  data.age++
}

store.js объединяет все вложенные магазины в соответствующие модули (a, b):

import { data as dataA, changeName } from 'store-a.js'
import { data as dataB, changeAge } from 'store-b.js'

const store = {
  data:{
    a: dataA,
    b: dataB
  },
  a: { changeName },
  b: { changeAge }
}

export default store

Привязка данных:

<view>
  <text>{{a.name}}</text>
  <text>{{b.name}}-{{b.age}}</text>
</view>

Использование данных:

import create from '../../utils/create'
import store from '../../store/store'

create(store, {
  //声明依赖
  use: ['a.name', 'b'],
  onLoad: function () {
    setTimeout(_ => {
      store.a.changeName()
    }, 1000)

    setTimeout(_ => {
      store.b.changeAge()
    }, 2000)
  }
})

Полный случай внедрения в несколько магазинов может бытькликните сюда

Разделить один магазин на несколько магазинов

Page A:

import create from '../../utils/create'
import store from '../../store/store-page-a.js'

create(store, {
 
})

Page B:

import create from '../../utils/create'
import store from '../../store/store-page-b.js'

create(store, {
 
})

Магазин страницы А и страницы Б — это два совершенно разных магазина.

Правило попадания пути

когдаstore.dataВ случае изменения будут обновлены зависимые компоненты, например, правило попадания пути:

Путь наблюдателя (сгенерированный изменениями данных) путь в использовании обновлять ли
abc abc возобновить
abc[1] abc возобновить
abc.a abc возобновить
abc abc.a не обновлять
abc abc[1] не обновлять
abc abc[1].c не обновлять
abc.b abc.b возобновить

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

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

Змея игра реальный бой

Дизайн модели предметной области

  • Извлечение основных объектов, таких как (змея, игра)
  • Подвел итоги конкретного метода атрибута бизнес-сущности от существительных,
    • змея
      • Содержит направление движения, атрибуты тела
      • Содержит методы перемещения и поворота.
    • игра
      • Включая состояние конечной паузы, карту, счет, частоту кадров, главного героя игры, еду
      • Содержит методы для запуска игры, приостановки игры, завершения игры, производства еды, сброса игры и т. д.
  • Установите связи между методами атрибутов объекта
    • Единственный главный герой игры — змея.
    • Змея ест еду, счет игры увеличивается
    • Еда исчезает, игра снова отвечает за производство еды
    • Змея ударяется о стену или ударяется о себя, состояние игры окончено.
  • конструкция основной петли
    • Определить, есть ли еда, и произвести ее, если нет (низкая частота кадров)
    • Обнаружение столкновения змеи с самой собой
    • Обнаружение столкновения змеи и препятствия
    • Обнаружение столкновения змеи и еды
    • змеиный ход

Используйте код для описания объектов-змей

class Snake {
  constructor() {
    this.body = [3, 1, 2, 1, 1, 1]
    this.dir = 'right'
  }

  move(eating) {
    const b = this.body
    if (!eating) {
      b.pop()
      b.pop()
    }

    switch (this.dir) {
      case 'up':
        b.unshift(b[0], b[1] - 1)
        break
      case 'right':
        b.unshift(b[0] + 1, b[1])
        break
      case 'down':
        b.unshift(b[0], b[1] + 1)
        break
      case 'left':
        b.unshift(b[0] - 1, b[1])
        break
    }
  }

  turnUp() {
    if (this.dir !== 'down')
      this.dir = 'up'
  }
  turnRight() {
    if (this.dir !== 'left')
      this.dir = 'right'
  }
  turnDown() {
    if (this.dir !== 'up')
      this.dir = 'down'
  }
  turnLeft() {
    if (this.dir !== 'right')
      this.dir = 'left'
  }
}

В повороте змеи есть логика, то есть она не может отступить в обратную сторону, например, движется вверх, а не может сразу повернуться вниз.turnUp,turnRight,turnDown,turnLeftИмеются соответствующие условные суждения.

Используйте код для описания игровых объектов

import Snake from './snake'

class Game {
  constructor() {
    this.map = []
    this.size = 16
    this.loop = null
    this.interval = 500
    this.paused = false
    this._preDate = Date.now()
    this.init()
  }

  init() {

    this.snake = new Snake

    for (let i = 0; i < this.size; i++) {
      const row = []
      for (let j = 0; j < this.size; j++) {
        row.push(0)
      }
      this.map.push(row)
    }
  }

  tick() {

    this.makeFood()
    const eating = this.eat()
    this.snake.move(eating)
    this.mark()

  }

  mark() {
    const map = this.map
    for (let i = 0; i < this.size; i++) {
      for (let j = 0; j < this.size; j++) {
        map[i][j] = 0
      }
    }

    for (let k = 0, len = this.snake.body.length; k < len; k += 2) {
      this.snake.body[k + 1] %= this.size
      this.snake.body[k] %= this.size

      if (this.snake.body[k + 1] < 0) this.snake.body[k + 1] += this.size
      if (this.snake.body[k] < 0) this.snake.body[k] += this.size
      map[this.snake.body[k + 1]][this.snake.body[k]] = 1
    }
    if (this.food) {
      map[this.food[1]][this.food[0]] = 1
    }
  }

  start() {
    this.loop = setInterval(() => {
      if (Date.now() - this._preDate > this.interval) {
        this._preDate = Date.now()
        if (!this.paused) {
          this.tick()
        }
      }
    }, 16)
  }

  stop() {
    clearInterval(this.loop)
  }

  pause() {
    this.paused = true
  }

  play() {
    this.paused = false
  }

  reset() {
    this.paused = false
    this.interval = 500
    this.snake.body = [3, 1, 2, 1, 1, 1]
    this.food = null
    this.snake.dir = 'right'
  }

  toggleSpeed() {
    this.interval === 500 ? (this.interval = 150) : (this.interval = 500)
  }

  makeFood() {
    if (!this.food) {
      this.food = [this._rd(0, this.size - 1), this._rd(0, this.size - 1)]
      for (let k = 0, len = this.snake.body.length; k < len; k += 2) {
        if (this.snake.body[k + 1] === this.food[1]
          && this.snake.body[k] === this.food[0]) {
          this.food = null
          this.makeFood()
          break
        }

      }
    }
  }

  eat() {
    for (let k = 0, len = this.snake.body.length; k < len; k += 2) {
      if (this.snake.body[k + 1] === this.food[1]
        && this.snake.body[k] === this.food[0]) {
        this.food = null
        return true
      }
    }
  }

  _rd(from, to) {
    return from + Math.floor(Math.random() * (to + 1))
  }
}

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

[
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]

Таким образом, это изображение представляет собой змею длиной 5 и 1 едой, можете ли вы найти ее на картинке выше?

определить магазин

import Game from '../models/game'

const game = new Game
const { snake, map } = game

game.start()

class Store {
  data = {
    map,
    paused: false,
    highSpeed: false
  }
  turnUp() {
    snake.turnUp()
  }
  turnRight() {
    snake.turnRight()
  }
  turnDown() {
    snake.turnDown()
  }
  turnLeft() {
    snake.turnLeft()
  }
  pauseOrPlay = () => {
    if (game.paused) {
      game.play()
      this.data.paused = false
    } else {
      game.pause()
      this.data.paused = true
    }
  }
  reset() {
    game.reset()
  }
  toggleSpeed() {
    game.toggleSpeed()
    this.data.highSpeed = !this.data.highSpeed
  }
}

export default new Store

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

визуализация игровой панели

WXML:

<view class="game">
  <view class="p" wx:for="{{map}}" wx:for-item="row" wx:for-index="index">
    <block wx:for="{{row}}" wx:for-item="col">
      <block wx:if="{{col}}">
        <view class="b s"></view>
      </block>
      <block wx:else>
        <view class="b"></view>
      </block>
    </block>
  </view>
</view>

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

Соответствует js:

import create from '../../utils/create'

create({
  use: ['map']
})

mapПредставляет зависимость от store.data.map, и обновления карты будут автоматически обновлять представление.

Управление основной панелью интерфейса

<view>
  <game />
  <view class="ctrl">
    <view class="btn cm-btn cm-btn-dir up" bindtap="turnUp"><i></i><em></em><span>上</span></view>
    <view class="btn cm-btn cm-btn-dir down" bindtap="turnDown"><i></i><em></em><span>下</span></view>
    <view class="btn cm-btn cm-btn-dir left" bindtap="turnLeft"><i></i><em></em><span >左</span></view>
    <view class="btn cm-btn cm-btn-dir right" bindtap="turnRight"><i></i><em></em><span >右</span></view>
    <view class="btn cm-btn space" bindtap="toggleSpeed"><i></i><span >{{highSpeed? '减速': '加速'}}</span></view>
    <view class="btn reset small" bindtap="reset"><i ></i><span >重置</span></view>
    <view class="btn pp small" bindtap="pauseOrPlay"><i></i><span >{{paused ? '继续' : '暂停'}}</span></view>
  </view>
</view>

В основном интерфейсе используется страница, ссылающаяся на компонент:

{
  "usingComponents": {
    "game": "/components/game/index"
  }
}

Соответствует JS:

import create from '../../utils/create'
import store from '../../store/index'

create(store, {
  use: ['paused', 'highSpeed'],
  turnUp() {
    store.turnUp()
  },
  turnDown() {
    store.turnDown()
  },
  turnLeft() {
    store.turnLeft()
  },
  turnRight() {
    store.turnRight()
  },
  toggleSpeed() {
    store.toggleSpeed()
  },
  reset() {
    store.reset()
  },
  pauseOrPlay() {
    store.pauseOrPlay()
  }
})

контроль частоты кадров

Как контролировать основную частоту кадров и локальную частоту кадров. В общем, мы считаем 60 FPS плавными, поэтому наш интервал таймера составляет 16 мс, Чем меньше количество вычислений в основном цикле, тем ближе к 60 FPS:

this.loop = setInterval(() => {
  //
}, 16)

Но некоторые вычисления не нужно производить каждые 16 мс, что уменьшит частоту кадров, поэтому можно записывать время последнего выполнения для контроля частоты кадров:

this.loop = setInterval(() => {
  //执行在这里是大约 60 FPS
  if (Date.now() - this._preDate > this.interval) {
    //执行在这里是大约  1000/this.interval FPS
    this._preDate = Date.now()
    //暂停判断
    if (!this.paused) {
      //核心循环逻辑
      this.tick()
    }
  }
}, 16)

Поскольку апплет JCore не поддерживаетrequestAnimationFrame, поэтому здесь используется setInterval. Конечно, вы также можете использоватьraf-intervalВыполнить тик в цикле:

this.loop = setRafInterval(() => {
  //执行在这里是大约 60 FPS
  if (Date.now() - this._preDate > this.interval) {
    //执行在这里是大约  1000/this.interval FPS
    this._preDate = Date.now()
    //暂停判断
    if (!this.paused) {
      //核心循环逻辑
      this.tick()
    }
  }
}, 16)

Использование такое же, как setInterval, но setTimeout используется внутренне и, если поддерживаетсяrequestAnimationFrameбудет использоваться в первую очередьrequestAnimationFrame.

→ Исходный код змеи

Змеиная Архитектура

Итак, весь проект — это MVC, MVP или MVVM?

Из исходного кода Snake видно: представления (компоненты, страницы) и модели (модели) разделены и не имеют взаимозависимости, но в MVC представление зависит от модели, и связь слишком высока, что приводит к отличная переносимость представления.Ниже, поэтому это не должно быть архитектура MVC.

В паттерне MVP представление не зависит напрямую от модели, а за завершение взаимодействия между Моделью и Представлением отвечает Презентатор. MVVM и MVP похожи по шаблону. ViewModel играет роль Presenter и предоставляет источник данных, требуемый представлением пользовательского интерфейса, вместо того, чтобы напрямую разрешать представлению использовать источник данных модели, что значительно улучшает переносимость представления и модели.Например, одна и та же модель переключается с использованием Flash, HTML, рендеринг WPF, например, одно и то же представление с использованием разных моделей, если модель и модель представления хорошо сопоставлены, представление может быть изменено незначительно или нет.

Как видно из исходного кода Snake, View(components) напрямую использует атрибут данных Presenter(stores) для рендеринга.Атрибут данных берется из атрибута Model(models), и нет сопоставления между Model и ViewModel. . Так что это не должна быть архитектура MVVM.

Итак, жадная змея выше принадлежитMVPЭто просто эволюционная версия MVP, потому что смена карты в M будет настроена как View, цикл из M->P->V автоматизирован, и никакой логики в коде не видно. Просто объявите зависимости:

use: ['map']

Это также позволяет обойти самую большую проблему с MVVM: накладные расходы на сопоставление M-to-VM.

Преимущества усовершенствованного MVP

1. Повторное использование

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

2. Гибкость

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

3. Тестируемость

Учитывая тесную связь между представлением и моделью, было бы невозможно протестировать и модель, и представление, пока они не будут разработаны одновременно. Модульное тестирование представления или модели затруднено по той же причине. Теперь шаблон MVP решает все проблемы. В шаблоне MVP нет прямой зависимости между представлением и моделью, и разработчики могут тестировать любую из сторон, внедряя фиктивные объекты.

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

Например, слой просмотра реакции:

import React from 'react'
import Game from '../game'

import store from '../../stores/index'
import { $ } from 'omis'
require('../../utils/css').add(require('./_index.css'))

export default $({
  render() {
    const { store } = $
    const { paused } = store.data
    return <div className="container">
      <h1>[P]REACT + OMIS SNAKE</h1>

      <Game></Game>

      <div className="ctrl">
        <div className="btn cm-btn cm-btn-dir up" onClick={store.turnUp}><i></i><em></em><span>Up</span></div>
        <div className="btn cm-btn cm-btn-dir down" onClick={store.turnDown}><i></i><em></em><span>Down</span></div>
        <div className="btn cm-btn cm-btn-dir left" onClick={store.turnLeft}><i></i><em></em><span >Left</span></div>
        <div className="btn cm-btn cm-btn-dir right" onClick={store.turnRight}><i></i><em></em><span >Right</span></div>
        <div className="btn cm-btn space" onClick={store.toggleSpeed}><i></i><span >Gear</span></div>
        <div className="btn reset small" onClick={store.reset}><i ></i><span >Reset</span></div>
        <div className="btn pp small" onClick={store.pauseOrPlay}><i></i><span >{paused ? 'Play' : 'Pause'}</span></div>
      </div>
    </div>
  },
![](https://user-gold-cdn.xitu.io/2019/10/31/16e1f8959beb0fea?w=1344&h=950&f=png&s=110201)
  useSelf: ['paused'],
  store
})

Q & A

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

Чистые компоненты не нужно создавать без создания, и они используются внутри компонента.triggerEventУведомите родительский компонент об изменении store.data или вызовите метод хранилища для связи с внешним миром.

  • Есть ли версия TypeScript?

Примеры версии TypeScript нажмите здесьomix-ts

  • Сомнения в том, что омикс не поддерживает определение приватных данных

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

Если определение приватных данных не поддерживается, то при использовании омикса на проекте нужно ли сливать исходные данные всех страниц в магазин?

Когда страниц слишком много, я думаю, это более болезненная вещь.

Сравнивая omix, omix-v1, westore и dd-store, мы обнаружили, что последние три поддерживают определение личных данных.

Я хотел бы спросить, что заставило omix отказаться от функции omix-v1, поддерживающей определение приватных данных?

Нарушает ли это функцию нулевого вторжения Omix для небольших программ?

О: На страницах поддержки есть личные данные:

Например:

create(store, {
  use: [
    'motto',
    'userInfo',
    'hasUserInfo',
    'canIUse'
  ],
  computed: {
    reverseMotto() {
      return this.motto.split('').reverse().join('')
    }
  },
  data: {
    name: 'omix'
  },

В это время данные глобального хранилища будут висеть вdata.$, поэтому нужно добавить префикс при привязке wxml, например:

<!--index.wxml-->
<view class="container">
  <view class="userinfo">
    <button wx:if="{{!$.hasUserInfo && $.canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button>
    <block wx:else>
      <image bindtap="bindViewTap" class="userinfo-avatar" src="{{$.userInfo.avatarUrl}}" mode="cover"></image>
      <text class="userinfo-nickname">{{$.userInfo.nickName}}</text>
    </block>
  </view>
  <view class="usermotto">
    <text class="user-motto">{{$.motto}}-{{reverseMotto}}-{{name}}</text>
  </view>
  <test-store />
</view>

Обратите внимание, что данные и вычисляемые свойства не нуждаются в префиксе $.

Github

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