推行 热搜:

在微信小顺序里运用 watch 和 computed

   日期:2018-03-31     阅读:99    批评:0    
中心提示:在开辟 vue 的时分,我们可以运用 watch 和 computed 很方便的检测数据的变革,从而做出相应的改动,但是在小顺序里,只能在数据
在开辟 vue 的时分,我们可以运用 watch 和 computed 很方便的检测数据的变革,从而做出相应的改动,但是在小顺序里,只能在数据改动时手动触发 this.setData(),那么怎样给小顺序也加上这两个功用呢?

我们晓得在 vue 里是经过 Object.defineProperty 来完成数据变革检测的,给该变量的 setter 里注入一切的绑定操纵,就可以在该变质变化时动员别的数据的变革。那么是不是可以把这种办法运用在小顺序上呢?

实践上,在小顺序里完成要比 vue 里复杂,应为关于 data 里工具来说,vue 要递归的绑定工具里的每一个变量,使之呼应式化。但是在微信小顺序里,不论是关于工具照旧根本范例,只能经过 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) } })
  },
})
如今我们要完成 watch 和 computed 办法,使得 test 变革时,test1、test2、test3 也变革,为此,我们添加了一个按钮,当点击这个按钮时,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 的值,这里把该办法包一层,绑定 context。

接着来看 computed,这个略微庞大,由于我们无法得知 computed 里依赖的是 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 外面每个属性第一次的值,也便是上例中的 test2、test3。

computed(this, {
  test2: function() {
    return this.data.test.a + '2222222'
  },
  test3: function() {
    return this.data.test.a + '3333333'
  }
})
这里辨别挪用 test2 和 test3 的值,将前往值与对应的 key 值组分解一个工具,然后再挪用 setData() ,如许就会第一次盘算这两个值,这里运用了 reduce 办法。但是你能够会发明此中这两行代码,它们仿佛都没有被提到是干嘛用的。

  ctx.data.$target = function() {
    ctx.setData({ [next]: obj[next].call(ctx) })
  }
  
  ctx.data.$target = null
可以看到,test2 和 test3 都是依赖 test 的,如许必需在 test 改动的时分在其的 setter 函数中挪用 test2 和 test3 中对应的函数,并经过 setData 来设置这两个变量。为此,需求将 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 中。不晓得列位照旧否记得下面我们说到的在 reduce 里的那两行代码。由于在实行盘算 test1 和 test2 第一次 computed 值的时分,会挪用 test 的 getter 办法,现在便是一个好时机将函数注入到 subs 中,在 data 上声明一个 $target 变量,并将需求实行的函数赋值给该变量,如许在 getter 中就可以判别 data 上有无 target 值,从而就可以 push 进 subs,要留意的是需求立刻将 target 设为 null,这便是第二句的用处,如许就到达了一石二鸟的作用。固然,这实在便是 vue 里的原理,只不外这里没那么庞大。

到此为止曾经完成了 watch 和 computed,但是还没完,有个题目。当同时运用这两者的时分,watch 里的工具的键也同时存在于 data 中,如许就会反复在该变量上挪用 Object.defineProperty ,前面会掩盖后面。由于这里不像 vue 里可以决议两者的挪用次序,因而我们引荐先写 computed 再写 watch,如许可以 watch computed 里的值。如许就有一个题目,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
    },
  })
}
如许,我们就一步一步的完成了所需的功用。完好的代码和例子请戳。

固然颠末了一些测试,但不包管没有别的未知错误,欢送提出题目。
 
打赏
 
更多>同类资讯
0相干批评

引荐图文
引荐资讯
点击排行
网站首页  |  关于我们  |  联络方法  |  运用协议  |  版权隐私  |  网站舆图  |  排名推行  |  告白效劳  |  网站留言  |  RSS订阅  |  违规告发  |  手机娱乐官网鄂ICP备14001892号-2
手机娱乐官网Powered By 真实人网络