Giter Site home page Giter Site logo

blog's People

Watchers

 avatar  avatar

blog's Issues

uni-app源码解析一,构建vue-cli

首先,我们来看package.json文件

"scripts": {
  "serve": "vue-cli-service serve",
  "build": "vue-cli-service build"
}

看到,这里明显是使用了vue-cli-service指令来打包;那我们看看vue-cli-service,是如何进行打包的;
众所周知,vue-cli-service是vue-cli的一个插件;vue-cli-service serve 命令主要用于在开发阶段构建项目,包括热加载这一套,下面开始简单分析下整个代码。通过前面对 @vue/cli-service 的整体分析, 可以发现,vue-cli-service 所有的 CLI 命令服务都是动态注册的,包括环境变量文件加载,获取项目配置信息,合并项目配置,加载插件等等,最后执行对应 CLI 命令服务:

async run (name, args = {}, rawArgv = []) {
  ...
  const { fn } = command
  return fn(args, rawArgv)
}

而这个command指令,就是通过 api.registerCommand 来注册命令的:

module.exports = (api, options) => {
  api.registerCommand('serve', {
    // ...
  }, async function serve (args) {
    // 代码较多
  })
}

这里就简单找到了,uni-cli是如何做到全局指令的;而后面就大概梳理一下vue-cli,vue-cli-service和vue-cli-plugin-uni以及其他配置产生的关系:
 
image
 
如上图,我们的uni-cli同样是通过webpack配置来进行项目打包配置的;具体的配置通过了vue-cli-plugin-uni的初始配置./lib/config-wepback.js./lib/chain-webpack.js以及./lib/"platform"的配置;然后再经过vue-cli-service,加入了开发环境的配置,如hot-reload middleware;同时会使用项目中的vue.config.js内的配置去覆盖原本配置。

so,总结结论,配置权重: vue.config.js > vue-cli-service > vue-cli-plugin-uni;但是vue-cli-plugin-uni也会被pages.json和manifest.json影响;

mpvue源码分析

mpvue源码

Vue$3.prototype._initMP = initMP;

Vue$3.prototype.$updateDataToMP = updateDataToMP;
Vue$3.prototype._initDataToMP = initDataToMP;

Vue$3.prototype.$handleProxyWithVue = handleProxyWithVue;
return Vue$3;

整体文件都在为了这个Vue$3构建函数;这个构建函数,请大致看一下Vue.prototype._init部分;

Vue.prototype._init = function (options) {
    ...
    initLifecycle(vm);
    initEvents(vm);
    initRender(vm);
    callHook(vm, 'beforeCreate');
    initInjections(vm); // resolve injections before data/props
    initState(vm);
    initProvide(vm); // resolve provide after data/props
    callHook(vm, 'created');
}

上面就是mpvue的类似constrator的构建函数;
 
 

mpvue生命周期如何和小程序生命周期关联

众所周知,小程序app由Page和Component组成,那是如何和vue进行的绑定呢?

  • 小程序的app,对应vue中的最外层的Vue实例节点,也就是正常的app.vue
  • component,对应被标记了component的Vue实例节点(倒是没有怎么用到这个)
  • 小程序内的page,对应其他Vue实例节点
Vue$3.prototype.$mount = function (el, hydrating) {
  var this$1 = this;
  // 初始化小程序生命周期相关
  var options = this.$options;

  if (options && (options.render || options.mpType)) {
    var mpType = options.mpType; if ( mpType === void 0 ) mpType = 'page';
    return this._initMP(mpType, function () {
      return mountComponent(this$1, undefined, undefined)
    })
  } else {
    return mountComponent(this, undefined, undefined)
  }
};
function initMP (mpType, next) {
  ...
  if (mp.status) {
    // 处理子组件的小程序生命周期
    if (mpType === 'app') {
      callHook$1(this, 'onLaunch', mp.appOptions);
    } else {
      callHook$1(this, 'onLoad', mp.query);
      callHook$1(this, 'onReady');
    }
    return next()
  }
  // mp.registered = true

  mp.mpType = mpType;
  mp.status = 'register';

  if (mpType === 'app') {
    global.App({
      // 页面的初始数据
      globalData: {
        appOptions: {}
      },

      handleProxy: function handleProxy (e) {
        ...
      },

      // Do something initial when launch.
      onLaunch: function onLaunch (options) {
        ...
      },

      // Do something when app show.
      onShow: function onShow (options) {
        ...
      },

      // Do something when app hide.
      onHide: function onHide () {
        ...
      },

    });
  } else if (mpType === 'component') {
    initMpProps(rootVueVM);

    global.Component({
      // 小程序原生的组件属性
      ...
    });
  } else {
    var app = global.getApp();
    global.Page({
      // 页面的初始数据
      ...
    });
  }
}

这里可见,initMP的时候,就会被打上对应的标记mpType,而且不同的mpType会初始化不一样的,分别对应小程序的App,Page和自定义组件Component;并且通过callHook$1来触发mpvue定义的回调(这些生命周期的钩子就是vue的钩子);这就完美地将vue的生命周期和小程序生命周期进行了关联;
 
 

mpvue如何使vue和小程序的数据同步

还是看到最底下的Vue$3构建函数:

Vue$3.prototype._initMP = initMP;

Vue$3.prototype.$updateDataToMP = updateDataToMP;
Vue$3.prototype._initDataToMP = initDataToMP;

Vue$3.prototype.$handleProxyWithVue = handleProxyWithVue;

其中,观察initDataToMP和updateDataToMP:

function initDataToMP () {
  var page = getPage(this);
  if (!page) {
    return
  }

  var data = collectVmData(this.$root);
  page.setData(data);
}

initDataToMP调用了collectVmData,递归得到当前组件和及其子组件下的所有数据(包括props,data,computed,watch)。
这里大家可以看看实际得到的一个数据:
image
然后再通过节流函数以及setData做数据更新;

ok,至此,mpvue的数据转化成小程序的数据,已经清晰了;但是,mpvue是如何做到跟踪数据的变化呢?
还是利用了发布订阅模式;
发布订阅模式比较常见,这里仅介绍一下了;具体的函数,可以见initState;这里只看ovserve:

function observe (value, asRootData, key) {
  if (!isObject(value)) {
    return
  }
  var ob;
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__;
  } else if (
    observerState.shouldConvert &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value, key);
  }
  if (asRootData && ob) {
    ob.vmCount++;
  }
  return ob
}

里面Observer的观察者:

var Observer = function Observer (value, key) {
  this.value = value;
  this.dep = new Dep();
  this.vmCount = 0;
  if (key) {
    this.key = key;
  }
  def(value, '__ob__', this);
  if (Array.isArray(value)) {
    var augment = hasProto
      ? protoAugment
      : copyAugment;
    augment(value, arrayMethods, arrayKeys);
    this.observeArray(value);
  } else {
    this.walk(value);
  }
};

其中,关键核心在于augment(value, arrayMethods, arrayKeys);this.observeArray(value);;将数据全部推送给观察者;

function defineReactive$$1 (
  obj,
  key,
  val,
  customSetter,
  shallow
) {
  var dep = new Dep();

  var property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  var getter = property && property.get;
  var setter = property && property.set;

  var childOb = !shallow && observe(val, undefined, key);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val;
      if (Dep.target) {
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
        }
        if (Array.isArray(value)) {
          dependArray(value);
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      var value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }

      /* eslint-enable no-self-compare */
      if ("production" !== 'production' && customSetter) {
        customSetter();
      }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal, undefined, key);
      dep.notify();

      if (!obj.__keyPath) {
        def(obj, '__keyPath', {}, false);
      }
      obj.__keyPath[key] = true;
      if (newVal instanceof Object && !(newVal instanceof Array)) {
        // 标记是否是通过this.Obj = {} 赋值印发的改动,解决少更新问题#1305
        def(newVal, '__newReference', true, false);
      }
    }
  });
}

上面是使用典型的getter和setter的劫持

在挂载组件的时候,Vue$3.prototype.$mount的时候,就创建了一个watcher的观察器;

vm._watcher = new Watcher(vm, updateComponent, noop);
....
this.value = this.lazy
  ? undefined
  : this.get();

watcher观察器中的get方法:

Watcher.prototype.get = function get () {
  pushTarget(this);
  var value;
  var vm = this.vm;
  try {
    value = this.getter.call(vm, vm);
  } catch (e) {
    if (this.user) {
      handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
    } else {
      throw e
    }
  } finally {
    // "touch" every property so they are all tracked as
    // dependencies for deep watching
    if (this.deep) {
      traverse(value);
    }
    popTarget();
    this.cleanupDeps();
  }
  return value
};

image
其中,Dep是一个观察者数组,可以通过addSub,removeSub增加和删减需要观察的字段,另外提供notify进行发布的通知;

Dep.prototype.notify = function notify () {
  // stabilize the subscriber list first
  var subs = this.subs.slice();
  for (var i = 0, l = subs.length; i < l; i++) {
    subs[i].update();
  }
};

所以,当set方法调用的时候,会调用notify,来通知Dep实例上加入的订阅者watcher,最后调用watcher的update方法;

Watcher.prototype.update = function update () {
  /* istanbul ignore else */
  if (this.lazy) {
    this.dirty = true;
  } else if (this.sync) {
    this.run();
  } else {
    queueWatcher(this);
  }
};

继续看queueWatcher

function queueWatcher (watcher) {
  var id = watcher.id;
  if (has[id] == null) {
    has[id] = true;
    if (!flushing) {
      queue.push(watcher);
    } else {
      var i = queue.length - 1;
      while (i > index && queue[i].id > watcher.id) {
        i--;
      }
      queue.splice(i + 1, 0, watcher);
    }
    // queue the flush
    if (!waiting) {
      waiting = true;
      nextTick(flushSchedulerQueue);
    }
  }
}

没什么疑问,这里将观察者推入执行队列中,并且做了短时间内的去重优化(具有重复ID的作业将被跳过)
紧接着

function flushSchedulerQueue () {
  ...
  callUpdatedHooks(updatedQueue);
}

function callUpdatedHooks (queue) {
  var i = queue.length;
  while (i--) {
    var watcher = queue[i];
    var vm = watcher.vm;
    if (vm._watcher === watcher && vm._isMounted) {
      callHook(vm, 'updated');
    }
  }
}

最后通过watcher内的调用Vue.protoype._update,再调用patch方法;

function patch () {
  corePatch.apply(this, arguments);
  this.$updateDataToMP();
}

function updateDataToMP () {
  var page = getPage(this);
  if (!page) {
    return
  }

  var data = formatVmData(this);
  diffData(this, data);
  throttleSetData(page.setData.bind(page), data);
}

从而完成了数据的观察的更新;

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.