小程序里写vue3 vue-mini思路学习

725 阅读3分钟

使用

定义页面

//index.js
import { definePage, ref } from '@vue-mini/wechat'
definePage({
  setup() {
    const count  = ref(0)
    function increment() {
      count.value++
    }
    return {
      count,increment
    }
  },
})

<!-- index.wxml -->
<button bindtap="increment">
  Count is: {{ count }}
</button>

​ 跟写vue3一样,可以轻松的使用vue3响应式相关Api。

​ 无需繁琐的在Page.data里定义数据,更新数据时在setData进行更新。

实现

vue-mini在实现方面并没有做编译相关的工作,底层直接依赖于 @vue/reactivity,那么接下来就看看他是如何实现相关功能的

初始化

如上,我们在使用definePage定义页面时,调用definePage方法,与vue3里defineComponent一样,可以接收对象或函数

通过判断,最终的目的依旧是获取setupoptions

function definePage(optionsOrSetup: any, config?: Config): void {
  let setup: PageSetup
  let options: Options
  if (isFunction(optionsOrSetup)) {
    setup = optionsOrSetup
    options = {}
  } else {
    const { setup: setupOption, ...restOptions } = optionsOrSetup
    setup = setupOption
    options = restOptions
  }
  ...

并创建onLoad函数,等待Page调用onLoad函数

//用户如果有写onLoad方法,就保存该函数,并创建一个新的onLoad函数,等待page调用
const originOnLoad = options[PageLifecycle.ON_LOAD]
options['onLoad'] = function (query) {
      //setup执行就在这里
      ...
}
//创建其他相关生命周期相关函数
...

在拿到setup和创建生命函数后,最后会执行Page方法

function definePage(optionsOrSetup: any, config?: Config): void {
    ...
    ...
    ...
	Page(options)
}

Page是小程序的方法,当我们正常写小程序时,会这么写

Page({
  data:{
    count:0
  },
  increment(){
    let count = ++this.data.count
    this.setData({count:count})
  }
})

options传入,等待小程序执行onLoad函数

options['onLoad'] = function (query) {
    //将currentPage指向当前Page
    setCurrentPage(this)
    //创建上下文
    const context: PageContext = {
      is: this.is,
      route: this.route,
      options: this.options,
      ...
    }
    //执行setup函数,并拿到return后的返回值
    const bindings = setup(query, context)
    if (bindings !== undefined) {
      Object.keys(bindings).forEach((key) => {
        const value = bindings[key]
        //判断值是否是函数,小程序中函数是与page.data同级的
        //而响应式数据是在data内的
        if (isFunction(value)) {
          this[key] = value
          return
        }
        //非函数的数据会执行setData更新,并等待视图刷新
        this.setData({ [key]: deepToRaw(value) })
        //并监听bindings内数据的变化,更新就触发相对应的effect
        deepWatch.call(this, key, value)
      })
    }
    setCurrentPage(null)
    
    //对用户的onLoad方法进行调用
    if (originOnLoad !== undefined) {
      originOnLoad.call(this, query)
    }
}

数据更新

deepWatch实现

function deepWatch( key,value) {
  if (!isObject(value)) {
    return
  }
  //监听数据,一旦变化,就调用setData进行数据变更
  watch(
      isRef(value) ? value : () => value,
      () => {
        this.setData({ [key]: deepToRaw(value) })
      },
      {
        deep: true,
      }
    )
}

watch并非@vue/reactivity提供的而是内部实现的,实现如下

function watch(source,cb,options) {
  return doWatch(source, cb, options)
}

调用doWatch函数

代码比较长,实现进行了简化

doWatch主要的目的就是创建effect函数,并执行自己的scheduler更新调度,最终目的还是执行cb函数

function doWatch(source,cb,{ immediate, deep, flush, onTrack, onTrigger } = {}) {
  let getter
  //通过source类型  对getter进行赋值
  if (isRef(source)) {
    ...
  } else if (isReactive(source)) {
   ...
  } else if (isArray(source)) {
    ...
  } else if (isFunction(source)) {
   ...
  } else {
   ...
  }

  //不采用effect默认的执行方式
  //使用自身的scheduler调度
  let scheduler
  scheduler = () => {
       queueJob(job)
   }
  
  const runner = effect(getter, {
    lazy: true,
    onTrack,
    onTrigger,
    scheduler,
  })

  // Initial runner
  if (cb) {
    oldValue = runner()
  } else {
    runner()
  }
}

结语

​ 可以看到,vue-mini主要依赖于@vue/reactivity和小程序

​ 在使用上,与vue3一样

​ 在实现上,并没有对小程序做相关修改,而是执行vue-mini内方法后,在特定的时机,调用小程序的原生方法