написать впереди
Данные у вас под рукой, статус негде спрятать
характеристика
- глобальное управление состоянием
- Представление без сохранения состояния
- Нулевое вмешательство в мини-программы
- только один 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
Любые комментарии и предложения приветствуются обратной связью.