美文网首页vue
vue解析4-组件

vue解析4-组件

作者: 百里哈哈 | 来源:发表于2020-04-12 13:23 被阅读0次

    组件

    组件本身是来自Vue的继承, 在Vue初始化的时候会对component类型进行函数注册。


    QQ20200412-0.png

    继承函数 extend代码如下

    Vue.extend = function (extendOptions) {
        extendOptions = extendOptions || {};
        var Super = this;
        var SuperId = Super.cid;
        var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
        if (cachedCtors[SuperId]) {
          return cachedCtors[SuperId]
        }
    
        var name = extendOptions.name || Super.options.name;
        if (process.env.NODE_ENV !== 'production' && name) {
          validateComponentName(name);
        }
    
        var Sub = function VueComponent (options) {
          this._init(options);
        };
        Sub.prototype = Object.create(Super.prototype);
        Sub.prototype.constructor = Sub;
        Sub.cid = cid++;
        Sub.options = mergeOptions(
          Super.options,
          extendOptions
        );
        Sub['super'] = Super;
    
        // For props and computed properties, we define the proxy getters on
        // the Vue instances at extension time, on the extended prototype. This
        // avoids Object.defineProperty calls for each instance created.
        if (Sub.options.props) {
          initProps$1(Sub);
        }
        if (Sub.options.computed) {
          initComputed$1(Sub);
        }
    
        // allow further extension/mixin/plugin usage
        Sub.extend = Super.extend;
        Sub.mixin = Super.mixin;
        Sub.use = Super.use;
    
        // create asset registers, so extended classes
        // can have their private assets too.
        ASSET_TYPES.forEach(function (type) {
          Sub[type] = Super[type];
        });
        // enable recursive self-lookup
        if (name) {
          Sub.options.components[name] = Sub;
        }
    
        // keep a reference to the super options at extension time.
        // later at instantiation we can check if Super's options have
        // been updated.
        Sub.superOptions = Super.options;
        Sub.extendOptions = extendOptions;
        Sub.sealedOptions = extend({}, Sub.options);
    
        // cache constructor
        cachedCtors[SuperId] = Sub;
        return Sub
      };
    }
    

    render生成vdom时 component的元素会挂载Vue的子类, patch的过程中执行子类的init方法,从而递归的一层层组件的渲染。

    props

    遵循数据单向的原则, 如果在子组件中试图改变props, vue会有报错提示,然而并不阻止数据的变更。
    其判断逻辑代码如下

    defineReactive$$1(props, key, value, function () {
            if (!isRoot && !isUpdatingChildComponent) {
              warn(
                "Avoid mutating a prop directly since the value will be " +
                "overwritten whenever the parent component re-renders. " +
                "Instead, use a data or computed property based on the prop's " +
                "value. Prop being mutated: \"" + key + "\"",
                vm
              );
            }
          });
    

    测试示例如下
    compTest.vue

    <template>
        <div>
            my name is {{name}}
            <p>
                the msg is {{msg}}
                <span @click="changeMsg"> change the msg</span>
                <span @click="changeName"> try change prop</span>
            </p>
        </div>
    </template>
    <script>
    export default {
        props: {
            name: {
                type: String,
                default: null
            }
        },
        data() {
            return {
                msg: 'ddd'
            }
        },
        methods: {
            changeMsg() {
                this.msg = 'two' + Date.now();
            },
            changeName() {
                this.name = 'haha' + Date.now()
            }
        }
    }
    </script>
    

    slot

    在Vue渲染过程中,牢记其核心规则父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
    匿名插槽示例
    app.vue

    <compTest :name="name">
          <div>the top </div>
        </compTest>
    

    compTest.vue

    <template>
        <div>
            <slot></slot>
            my name is {{name}}
            <p>
                the msg is {{msg}}
                <span @click="changeMsg"> change the msg</span>
                <span @click="changeName"> try change prop</span>
            </p>
        </div>
    </template>
    

    编译后的代码
    父元素的render

    return e("div", { attrs: { id: "app" } }, [e("compTest", { attrs: { name: this.name } },
                            [e("div", [this._v("the top ")])]), this._v(" "), e("div", { on: { click: this.change } }, [this._v("click me")])], 1)
    
    
    

    子元素的render

    return t("div", [n._t("default"), n._v("\n    my name is " + n._s(n.name) + "\n    "), 
                        t("p", [n._v("\n        the msg is " + n._s(n.msg) + "\n        "), t("span", { on: { click: n.changeMsg } }, 
                        [n._v(" change the msg")]), n._v(" "), t("span", { on: { click: n.changeName } }, [n._v(" try change prop")])])], 2)
    
    

    通过生成的render函数我们可知,slot内部的元素仍然是在父作用域下进行vnode的生成。在子元素中可通过.slots获取的插槽信息, 从而实现在子元素中的渲染

    function resolveSlots (
      children,
      context
    ) {
      if (!children || !children.length) {
        return {}
      }
      var slots = {};
      for (var i = 0, l = children.length; i < l; i++) {
        var child = children[i];
        var data = child.data;
        // remove slot attribute if the node is resolved as a Vue slot node
        if (data && data.attrs && data.attrs.slot) {
          delete data.attrs.slot;
        }
        // named slots should only be respected if the vnode was rendered in the
        // same context.
        if ((child.context === context || child.fnContext === context) &&
          data && data.slot != null
        ) {
          var name = data.slot;
          var slot = (slots[name] || (slots[name] = []));
          if (child.tag === 'template') {
            slot.push.apply(slot, child.children || []);
          } else {
            slot.push(child);
          }
        } else {
          (slots.default || (slots.default = [])).push(child);
        }
      }
      // ignore slots that contains only whitespace
      for (var name$1 in slots) {
        if (slots[name$1].every(isWhitespace)) {
          delete slots[name$1];
        }
      }
      return slots
    }
    

    其函数的调用轨迹 this._init ——>initRender——>resolveSlots

    作用域插槽
    示例代码
    app.vue

    <template>
      <div id="app">
        <slotsTest>
          <template v-slot:default="slotProps">
            {{slotProps.user.firstName}}
          </template>
        </slotsTest>
      </div>
    </template>
    

    slotTest.vue

    <template>
        <div>
            <slot :user=user>
            </slot>
            {{user.lastName}}
        </div>
    </template>
    <script>
    export default {
        data() {
            return {
                user:{
                    firstName: 'haha',
                    lastName: 'ali'
                }
            }
        }
    }
    </script>
    

    build后的代码
    父元素的render

    return t("div", { attrs: { id: "app" } }, [t("slotsTest", { scopedSlots: n._u([{ key: "default", fn: function (e) { return [n._v("\n      " + n._s(e.user.firstName) + "\n    ")] } }]) })], 1)
    
    
    
    

    子元素的render

    return (this._self._c || n)("div", [this._t("default", null, { user: this.user }), this._v("\n    " + this._s(this.user.lastName) + "\n")], 2)
    
    
    
    

    有上面代码可知,slot的编译过程仍是在父元素中完成。只不过生成的是个function 方法, 在子元素render的过程中会调用该方法,其对应的作用域data会作为参数传入。

    函数式组件

    函数式组件是一个接受一些 props , 无状态无实例,故而没有生命周期。
    简单示例
    app.vue

    <funCompJS :name="name"></funCompJS>
    

    funCompJS.jsx

     const funCompJS = {
        functional: true,
        props: {
            name: {
                type: String,
                default: null
            }
        },
        render (h, context) {
            return (
                <div>
                    the content is {context.props.name}
                </div>
            );
        }
    }
    export default funCompJS
    

    在函数式组件中可通过context获取上下文

    函数式组件createFunctionalComponent方法

    function createFunctionalComponent (
      Ctor,
      propsData,
      data,
      contextVm,
      children
    ) {
      var options = Ctor.options;
      var props = {};
      var propOptions = options.props;
      if (isDef(propOptions)) {
        for (var key in propOptions) {
          props[key] = validateProp(key, propOptions, propsData || emptyObject);
        }
      } else {
        if (isDef(data.attrs)) { mergeProps(props, data.attrs); }
        if (isDef(data.props)) { mergeProps(props, data.props); }
      }
    
    // 函数式render上下文
      var renderContext = new FunctionalRenderContext(
        data,
        props,
        children,
        contextVm,
        Ctor
      );
    
      var vnode = options.render.call(null, renderContext._c, renderContext);
    
      if (vnode instanceof VNode) {
        return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options, renderContext)
      } else if (Array.isArray(vnode)) {
        var vnodes = normalizeChildren(vnode) || [];
        var res = new Array(vnodes.length);
        for (var i = 0; i < vnodes.length; i++) {
          res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options, renderContext);
        }
        return res
      }
    }
    

    调用轨迹
    createComponent——>createFunctionalComponent——>var vnode = options.render.call(null, renderContext._c, renderContext);

    异步组件

    在代码优化中有些可以通过采用异步方案来实现优化的目的。
    其中异步组件就是我们所需要考虑的
    简单是示例
    可以在components中进行异步引入
    方法一

    'async-comp': () => import('./asyncComp.vue')
    

    方法二

    'async-comp': function (resolve) {
          require(['./asyncComp.vue'], resolve)
        }
    

    在createComponent方法中会对异步组件进行处理,异步组件首先会创建一个comment node的节点进行占位,等待异步组件加载完成后进行渲染替换

    // async component
      var asyncFactory;
      if (isUndef(Ctor.cid)) {
        asyncFactory = Ctor;
        Ctor = resolveAsyncComponent(asyncFactory, baseCtor);
        if (Ctor === undefined) {
          // return a placeholder node for async component, which is rendered
          // as a comment node but preserves all the raw information for the node.
          // the information will be used for async server-rendering and hydration.
          return createAsyncPlaceholder(
            asyncFactory,
            data,
            context,
            children,
            tag
          )
        }
      }
    

    加载异步组件方法resolveAsyncComponent

    function resolveAsyncComponent (
      factory,
      baseCtor
    ) {
      if (isTrue(factory.error) && isDef(factory.errorComp)) {
        return factory.errorComp
      }
    
      if (isDef(factory.resolved)) {
        return factory.resolved
      }
    
      var owner = currentRenderingInstance;
      if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
        // already pending
        factory.owners.push(owner);
      }
    
      if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
        return factory.loadingComp
      }
    
      if (owner && !isDef(factory.owners)) {
        var owners = factory.owners = [owner];
        var sync = true;
        var timerLoading = null;
        var timerTimeout = null
    
        ;(owner).$on('hook:destroyed', function () { return remove(owners, owner); });
    
        var forceRender = function (renderCompleted) {
          for (var i = 0, l = owners.length; i < l; i++) {
            (owners[i]).$forceUpdate();
          }
    
          if (renderCompleted) {
            owners.length = 0;
            if (timerLoading !== null) {
              clearTimeout(timerLoading);
              timerLoading = null;
            }
            if (timerTimeout !== null) {
              clearTimeout(timerTimeout);
              timerTimeout = null;
            }
          }
        };
    
        var resolve = once(function (res) {
          // cache resolved
          factory.resolved = ensureCtor(res, baseCtor);
          // invoke callbacks only if this is not a synchronous resolve
          // (async resolves are shimmed as synchronous during SSR)
          if (!sync) {
            forceRender(true);
          } else {
            owners.length = 0;
          }
        });
    
        var reject = once(function (reason) {
          process.env.NODE_ENV !== 'production' && warn(
            "Failed to resolve async component: " + (String(factory)) +
            (reason ? ("\nReason: " + reason) : '')
          );
          if (isDef(factory.errorComp)) {
            factory.error = true;
            forceRender(true);
          }
        });
    
        var res = factory(resolve, reject);
    
        if (isObject(res)) {
          if (isPromise(res)) {
            // () => Promise
            if (isUndef(factory.resolved)) {
              res.then(resolve, reject);
            }
          } else if (isPromise(res.component)) {
            res.component.then(resolve, reject);
    
            if (isDef(res.error)) {
              factory.errorComp = ensureCtor(res.error, baseCtor);
            }
    
            if (isDef(res.loading)) {
              factory.loadingComp = ensureCtor(res.loading, baseCtor);
              if (res.delay === 0) {
                factory.loading = true;
              } else {
                timerLoading = setTimeout(function () {
                  timerLoading = null;
                  if (isUndef(factory.resolved) && isUndef(factory.error)) {
                    factory.loading = true;
                    forceRender(false);
                  }
                }, res.delay || 200);
              }
            }
    
            if (isDef(res.timeout)) {
              timerTimeout = setTimeout(function () {
                timerTimeout = null;
                if (isUndef(factory.resolved)) {
                  reject(
                    process.env.NODE_ENV !== 'production'
                      ? ("timeout (" + (res.timeout) + "ms)")
                      : null
                  );
                }
              }, res.timeout);
            }
          }
        }
    
        sync = false;
        // return in case resolved synchronously
        return factory.loading
          ? factory.loadingComp
          : factory.resolved
      }
    }
    

    有代码可知,可为异步组件设置loading的展示组件,如果有loading时。首先render的为loading样式,而非commont占位节点。
    可使用下面代码进行测试

    'async-comp': () => ({
          component: import('./asyncComp.vue'),
          loading: LoadingComp,
          delay: 200
        })
    

    相关文章

      网友评论

        本文标题:vue解析4-组件

        本文链接:https://www.haomeiwen.com/subject/mesvmhtx.html