webpack学习:uni运行时代码解读二 (页面的交互)

页面点击触发vm(vue实例)的方法

uni里的vue代码

<template>
        <view
                class="content"
                @click="qq"
                @longpress='fq'
        >
                3345
        </view>
</template>

经过uni转换后的小程序wxml代码

<view data-event-opts="{{[['tap',[['qq',['$event']]]],['longpress',[['fq',['$event']]]]]}}"
 class="content" bindtap="__e" bindlongpress="__e">3345</view>

微信的小程序实例methods里只有2个方法(上文有写),__l是用来关联vm的父子关系的,__e是用来和data-event-opts属性一起使用,关联vm与小程序事件的。__e的代码如下,
写的挺长,其实不看无所谓,你看上文data-event-opts属性的内容就大概知道uni是怎么让小程序的页面交互触发到vm的方法里去了

function handleEvent (event) {
  event = wrapper$1(event);
  // [['tap',[['handle',[1,2,a]],['handle1',[1,2,a]]]]]
  const dataset = (event.currentTarget || event.target).dataset;
  if (!dataset) {
    return console.warn('事件信息不存在')
  }
  const eventOpts = dataset.eventOpts || dataset['event-opts']; // 支付宝 web-view 组件 dataset 非驼峰
  if (!eventOpts) {
    return console.warn('事件信息不存在')
  }

  // [['handle',[1,2,a]],['handle1',[1,2,a]]]
  const eventType = event.type;

  const ret = [];

  eventOpts.forEach(eventOpt => {
    let type = eventOpt[0];
    const eventsArray = eventOpt[1];

    const isCustom = type.charAt(0) === CUSTOM;
    type = isCustom ? type.slice(1) : type;
    const isOnce = type.charAt(0) === ONCE;
    type = isOnce ? type.slice(1) : type;

    if (eventsArray && isMatchEventType(eventType, type)) {
      eventsArray.forEach(eventArray => {
        const methodName = eventArray[0];
        if (methodName) {
          let handlerCtx = this.$vm;
          if (handlerCtx.$options.generic) { // mp-weixin,mp-toutiao 抽象节点模拟 scoped slots
            handlerCtx = getContextVm(handlerCtx) || handlerCtx;
          }
          if (methodName === '$emit') {
            handlerCtx.$emit.apply(handlerCtx,
              processEventArgs(
                this.$vm,
                event,
                eventArray[1],
                eventArray[2],
                isCustom,
                methodName
              ));
            return
          }
          const handler = handlerCtx[methodName];
          if (!isFn(handler)) {
            throw new Error(` _vm.${methodName} is not a function`)
          }
          if (isOnce) {
            if (handler.once) {
              return
            }
            handler.once = true;
          }
          let params = processEventArgs(
            this.$vm,
            event,
            eventArray[1],
            eventArray[2],
            isCustom,
            methodName
          );
          params = Array.isArray(params) ? params : [];
          // 参数尾部增加原始事件对象用于复杂表达式内获取额外数据
          if (/=\s*\S+\.eventParams\s*\|\|\s*\S+\[['"]event-params['"]\]/.test(handler.toString())) {
            // eslint-disable-next-line no-sparse-arrays
            params = params.concat([, , , , , , , , , , event]);
          }
          ret.push(handler.apply(handlerCtx, params));
        }
      });
    }
  });

  if (
    eventType === 'input' &&
    ret.length === 1 &&
    typeof ret[0] !== 'undefined'
  ) {
    return ret[0]
  }
}

vm修改了data如何响应到页面

vue代码

 data() {
           return {
                  title: "Hello"
          };
  },
  methods: {
          qq() {
                  this.title = 'oo'
          }
  },

在vm的qq方法改变了data里的title的值,就会触发vue的依赖更新,然后会执行vue的patch方法,代码如下,uni是修改过vue的patch方法的,
其中cloneWithData和diff是比较关键的代码,比较长就不贴出来了,关于diff方法不是vue原生的算法,代码很简单,
但是为什么这么处理我就不知道了,有一点比较特别假如说原来data的title是一个数组[1,2,35],
然后你重新赋值一个新的数组[3,5,67],只有数组长度一样,uni会setData{title[0]: 3, title[1]: 5, title[2]: 67},
不知道uni这么做出于什么考虑,这样可能会快一些吧。

var patch = function(oldVnode, vnode) {
    var this$1 = this;
  
    if (vnode === null) { //destroy
      return
    }
    if (this.mpType === 'page' || this.mpType === 'component') {
      var mpInstance = this.$scope;
      var data = Object.create(null);
      try {
          // 将你的vm里的data赋值到一个新的对象
        data = cloneWithData(this);
      } catch (err) {
        console.error(err);
      }
      data.__webviewId__ = mpInstance.data.__webviewId__;
      // mpdata 就是小程序实例mp的data里的值
      var mpData = Object.create(null);
      Object.keys(data).forEach(function (key) { //仅同步 data 中有的数据
        mpData[key] = mpInstance.data[key];
      });
      // vm和mp里的data两者比较算出差别来,然后通过mp的setData赋值给mp的data里然后页面就修改了
      var diffData = this.$shouldDiffData === false ? data : diff(data, mpData);
      if (Object.keys(diffData).length) {
        if (process.env.VUE_APP_DEBUG) {
          console.log('[' + (+new Date) + '][' + (mpInstance.is || mpInstance.route) + '][' + this._uid +
            ']差量更新',
            JSON.stringify(diffData));
        }
        this.__next_tick_pending = true;
        mpInstance.setData(diffData, function () {
          this$1.__next_tick_pending = false;
          flushCallbacks$1(this$1);
        });
      } else {
        flushCallbacks$1(this);
      }
    }
  };

用一张图来快速理解

需要注意的地方:

一 mp和vm这2个实例是分别有自己的data

原文地址:https://www.cnblogs.com/wzcsqaws/p/15769068.html