WeChatアプリでwatchとcomputedを使う方法


vueを開発する時、ウォッチとcomputtedを使ってデータの変化を測定して、それに応じて変化をすることができますが、小さいプログラムではデータが変わる時に手動でthis.setData() を触発するしかありません。この二つの機能をどのように小さなプログラムに追加しますか?
私たちは、vueではObject.defineProperty を通じてデータ変化の検出を実現し、その変数のsetterにすべてのバインディング操作を注入すれば、その変数の変化によって他のデータの変化を駆動することができることを知っています。この方法を小さいプログラムに応用してもいいですか?
実際には、小さなプログラムでvueよりも簡単に実現するためには、dataのオブジェクトに対して、vueが再帰的にバインディングされるオブジェクト内の各変数に応答して式化する必要があります。しかし、WeChatウィジェットでは、対象に対しても基本タイプに対しても、this.setData() でしか変更できません。そうすると、私たちはdataの中のkey値の変化を検出するだけで、key値の中のkeyを検出する必要はありません。
先にテストコードを付けます

<view>{{ test.a }}</view>
<view>{{ test1 }}</view>
<view>{{ test2 }}</view>
<view>{{ test3 }}</view>
<button bindtap="changeTest">change</button>

const { watch, computed } = require('./vuefy.js')
Page({
 data: {
  test: { a: 123 },
  test1: 'test1',
 },
 onLoad() {
  computed(this, {
   test2: function() {
    return this.data.test.a + '2222222'
   },
   test3: function() {
    return this.data.test.a + '3333333'
   }
  })
  watch(this, {
   test: function(newVal) {
    console.log('invoke watch')
    this.setData({ test1: newVal.a + '11111111' })
   }
  })
 },
 changeTest() {
  this.setData({ test: { a: Math.random().toFixed(5) } })
 },
})
今はウォッチとcomputedの方法を実現して、testが変わる時、test 1、test 2、test 3も変化します。そのために、ボタンを追加しました。このボタンを押すと、testが変わります。
watch方法は比較的簡単な点で、まず関数を定義して変化を検出します。

function defineReactive(data, key, val, fn) {
 Object.defineProperty(data, key, {
  configurable: true,
  enumerable: true,
  get: function() {
   return val
  },
  set: function(newVal) {
   if (newVal === val) return
   fn && fn(newVal)
   val = newVal
  },
 })
}
そして、watch関数を巡回して、各キーにこの方法を呼び出します。

function watch(ctx, obj) {
 Object.keys(obj).forEach(key => {
  defineReactive(ctx.data, key, ctx.data[key], function(value) {
   obj[key].call(ctx, value)
  })
 })
}
ここにはfnというパラメータがあります。つまり、上のwatch方法でtestの値です。ここでこの方法を1つ包んで、contextを結合します。
続いてcomputtedを見てみます。これはちょっと複雑です。computtedに依存するのはdataの中のどの変数かは分かりませんので、dataの中の各変数を遍歴するしかないです。

function computed(ctx, obj) {
 let keys = Object.keys(obj)
 let dataKeys = Object.keys(ctx.data)
 dataKeys.forEach(dataKey => {
  defineReactive(ctx.data, dataKey, ctx.data[dataKey])
 })
 let firstComputedObj = keys.reduce((prev, next) => {
  ctx.data.$target = function() {
   ctx.setData({ [next]: obj[next].call(ctx) })
  }
  prev[next] = obj[next].call(ctx)
  ctx.data.$target = null
  return prev
 }, {})
 ctx.setData(firstComputedObj)
}
このコードを詳しく説明して、まずdataの各属性にdefineReactive メソッドを呼び出す。次に、computedの各属性の最初の値を計算します。つまり、上記の例のtest 2、test 3です。

computed(this, {
 test2: function() {
  return this.data.test.a + '2222222'
 },
 test3: function() {
  return this.data.test.a + '3333333'
 }
})
ここでは、test 2とtest 3の値をそれぞれ呼び出し、対応するkey値とを1つのオブジェクトに結合し、 setData() を呼び出し、この2つの値を初めて計算することができる。ここではreduce 方法を使用する。しかし、この2つの行のコードが発見されるかもしれません。どちらも何のために使われていますか?

 ctx.data.$target = function() {
  ctx.setData({ [next]: obj[next].call(ctx) })
 }
 
 ctx.data.$target = null
test 2とtest 3は共にtestに依存しています。このようにtestが変更された時にそのsetter関数からtest 2とtest 3の対応する関数を呼び出して、set Dataによってこの2つの変数を設定しなければなりません。そのためには、defineReactiveを変更する必要があります。

function defineReactive(data, key, val, fn) {
 let subs = [] //   
 Object.defineProperty(data, key, {
  configurable: true,
  enumerable: true,
  get: function() {
   //   
   if (data.$target) {
    subs.push(data.$target)
   }
   return val
  },
  set: function(newVal) {
   if (newVal === val) return
   fn && fn(newVal)
   //   
   if (subs.length) {
    //   setTimeout      this.data     
    setTimeout(() => {
     subs.forEach(sub => sub())
    }, 0)
   }
   val = newVal
  },
 })
}
以前に比べて、いくつかの行のコードが追加されました。変更時に実行すべき関数をすべて保存する変数を宣言しました。set時に各関数を実行します。このとき、this.data.test の値はまだ変化していないので、setTimeoutを使って次のラウンドで再実行します。今はもう一つの問題があります。どうやって関数をsubsに追加しますか?皆さんは上記のコードを覚えていますか?test 1とtest 2を計算して初めてcomputted値を計算する時、testのgetterメソッドを呼び出すので、今は良い機会です。subsに関数を注入して、data上で1つの$target変数を宣言して、実行したい関数を変数に割り当てます。このようにgetterでdataの上にtaがtaの価値があるかどうかを判断できます。注意したいのは、すぐにタロットをnullにすることです。これは第二句の用途で、一石二鳥の役割を果たします。もちろん、これはvueの原理ですが、ここはそんなに複雑ではありません。
これまでWatchとcomputedを実現しましたが、まだ終わっていません。問題があります。この2つを同時に使用すると、ウオッチ内のオブジェクトのキーも同時にdataに存在し、このように変数上で Object.defineProperty を呼び出し、後に前面を覆う。ここはvueの中では両者の呼出順を決めることができませんので、まずcomputtedを書いてからウォッチを書くことをオススメします。このように一つの問題があります。computedは上書きによって無効になります。
なぜですか?
明らかに、この時は以前のsubsが空配列として再宣言されたからです。この時、私達は簡単な方法として、前のcomputedのsubsを場所に置いて、次回defineReactive を呼び出した時に対応するkeyがすでにsubsがあるかどうかを見て、問題を解決できます。コードを修正します。

function defineReactive(data, key, val, fn) {
 let subs = data['$' + key] || [] //   
 Object.defineProperty(data, key, {
  configurable: true,
  enumerable: true,
  get: function() {
   if (data.$target) {
    subs.push(data.$target)
    data['$' + key] = subs //   
   }
   return val
  },
  set: function(newVal) {
   if (newVal === val) return
   fn && fn(newVal)
   if (subs.length) {
    //   setTimeout      this.data     
    setTimeout(() => {
     subs.forEach(sub => sub())
    }, 0)
   }
   val = newVal
  },
 })
}
このように、私達は一歩ずつ必要な機能を実現しました。完全なコードと例押してください
いくつかのテストを経ましたが、他の未知のエラーがないとは保証できません。質問を歓迎します。
以上が本文の全部です。皆さんの勉強に役に立つように、私たちを応援してください。