20230717----重返学习-常见面试题-todoList-封装服务相关的组件-vue3生态-vue-router-pinia

day-114-one-hundred-and-fourteen-20230717-常见面试题-todoList-封装服务相关的组件-vue3生态-vue-router-pinia

常见面试题

  • vue2模版编译
  • vue3模版编译
  • 对于一些导入的模块,可以在npm 官网、 github官网找到模块查看原理。

v-if与v-for

  1. vue2模板编译网站
  2. vue3模板编译网站
  3. 查看一些第三方插件,可以通过npm或github来进行查找。
  • v-if 和 v-for的优先级问题

    • vue2 肯定是v-for他的优先级高, 在同一个元素中使用,会发生需要每循环一次就判断一次。 将数据计算出来再循环。 用计算属性来代替 v-for + if

      <div v-for="item of [1,2,3,4,5]"> 
          <template >
              <div v-if="item%2 === 0">div>
          <template>
      div>
      
      • 在vue3 中v-for的优先级低于v-if。 v-if 会被抽离到v-for的上一层元素中,如果有依赖关系,建议提升到计算属性后使用 查看模板编译的结果
        • 如果判断条件需要依赖v-for的结果,应该写成计算属性的⽅式。
  • 在vue3 中v-for的优先级低于v-if。 v-if 会被抽离到v-for的上一层元素中,如果有依赖关系,建议提升到计算属性后使用 查看模板编译的结果

模板编译

  • vue2

    {{ item }}
    function render() {
      with(this) {
        return _c('div', _l(([1, 2, 3, 4, 5]), function (item) {
          return (item % 2 === 0) ? _c('div', [_c('div', [_v(_s(item))])]) :
            _e()
        }), 0)
      }
    }
    
  • vue3

    {{ item }}
    import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, createCommentVNode as _createCommentVNode } from "vue"
    
    export function render(_ctx, _cache, $props, $setup, $data, $options) {
      return (_openBlock(), _createElementBlock("div", null, [
        (_ctx.item % 2 === 0)
          ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, _renderList([1, 2, 3, 4, 5], (item) => {
              return _createElementVNode("div", null, [
                _createElementVNode("div", null, _toDisplayString(item), 1 /* TEXT */)
              ])
            }), 64 /* STABLE_FRAGMENT */))
          : _createCommentVNode("v-if", true)
      ]))
    }
    

v-if与v-show

  • Vue 中的 v-show 和 v-if 怎么理解?

    • v-if使用场景会更多一些, v-if 可以配合 v-else v-else-if (v-if 可以放到template上,但是v-show不行),控制的是display属性, 会选择默认的或者display:none v-show会编译成指令,v-if 会编译成三元表达式 (visibility:hidden opacity)
    • v-if条件不成⽴不会渲染当前指令所在节点的DOM元素;
    • v-show只切换当前dom的显示或者隐藏 display属性;
    • v-if可以阻断内部代码是否执⾏,如果条件成⽴不会执⾏内部逻辑,如果⻚⾯逻辑在第⼀次加载的时候已经确认,后续不会频繁更改则采⽤v-if;
  • 代码:

    • v-show

      import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, vShow as _vShow, withDirectives as _withDirectives, createCommentVNode as _createCommentVNode } from "vue"
      
      export function render(_ctx, _cache, $props, $setup, $data, $options) {
        return (_ctx.item % 2 === 0)
          ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, _renderList([1, 2, 3, 4, 5], (item) => {
              return _createElementVNode("div", null, [
                _withDirectives(_createElementVNode("template", null, [
                  _createElementVNode("div", null, _toDisplayString(item), 1 /* TEXT */)
                ], 512 /* NEED_PATCH */), [
                  [_vShow, false]
                ])
              ])
            }), 64 /* STABLE_FRAGMENT */))
          : _createCommentVNode("v-if", true)
      }
      
      // Check the console for the AST
      
    • v-if

      import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, createCommentVNode as _createCommentVNode, createElementVNode as _createElementVNode } from "vue"
      
      const _hoisted_1 = { key: 0 }
      
      export function render(_ctx, _cache, $props, $setup, $data, $options) {
        return (_ctx.item % 2 === 0)
          ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, _renderList([1, 2, 3, 4, 5], (item) => {
              return _createElementVNode("div", null, [
                false
                  ? (_openBlock(), _createElementBlock("div", _hoisted_1, _toDisplayString(item), 1 /* TEXT */))
                  : _createCommentVNode("v-if", true)
              ])
            }), 64 /* STABLE_FRAGMENT */))
          : _createCommentVNode("v-if", true)
      }
      
      // Check the console for the AST
      

v-show源码

  • fang/f20230717/vue3-lan-1/node_modules/vue/dist/vue.runtime.esm-browser.js
const vShow = {
  beforeMount(el, { value }, { transition }) {
    el._vod = el.style.display === "none" ? "" : el.style.display;
    if (transition && value) {
      transition.beforeEnter(el);
    } else {
      setDisplay(el, value);
    }
  },
  mounted(el, { value }, { transition }) {
    if (transition && value) {
      transition.enter(el);
    }
  },
  updated(el, { value, oldValue }, { transition }) {
    if (!value === !oldValue)
      return;
    if (transition) {
      if (value) {
        transition.beforeEnter(el);
        setDisplay(el, true);
        transition.enter(el);
      } else {
        transition.leave(el, () => {
          setDisplay(el, false);
        });
      }
    } else {
      setDisplay(el, value);
    }
  },
  beforeUnmount(el, { value }) {
    setDisplay(el, value);
  }
};
function setDisplay(el, value) {
  el.style.display = value ? el._vod : "none";
}

vue3中组件通信

  • Vue 3 中如何进行组件通信?

    • 父-》子
      • props 属性传递参数,子元素要通过props进行接收。
      • attrs 属性传递参数,子元素不用接受。
      • ref 获取子组件的实例。
      • 可以通过provide 提供属性给儿子。
    • 子-》父
      • 绑定的事件可以通过props来接受,还可以通过attrs.onXxx来执行,emit 父亲给子组件绑定事件,子组件触发绑定的事件。
      • 默认情况下所有的属性都会暴露出来 expose 来暴露属性。
      • $parent
      • 可以通过inject,拿到父组件方法进行通信。
    • 平级传递参数
      • 找到共同的父级。
    • 跨级传递参数
      • provide , inject (不建议在业务代码中使用) 数据来源不明确, 会发生重名的问题。
      • eventBus -> mitt (vue3推荐使用mitt来做通信处理不在支持 eventBus)。
      • vuex来进行数据处理。
  • Vue 3 中如何进⾏组件通信?

    • props
    • $emit
    • $slots
    • $attrs
    • expose / ref
    • v-model
    • provide / inject
    • mitt

$parent通信

vue2的$parent通信

vue3的parent通信

  • 父组件传递给子组件:

    • src/App.vue

      
      
      
      
    • src/components/com.vue

      
      
      
      
  • 子组件传递给父组件:

    • src/App.vue

      
      
      
      
    • src/components/com.vue

      
      
      
      

provide-inject通信

  1. 数据来源不明确,会优先最近的。但直系祖先中,每个组件都可以直接发布一个任意名称的provide,可能会有命名冲突。
  • src/App.vue



  • src/components/com.vue



mitt

  1. 查看一些第三方插件,可以通过npm或github来进行查找。
  2. mitt

vue3模板优化

  1. vue2模板编译网站
  2. vue3模板编译网站
  • vue3 模版优化有哪些?

    • vue3中模版编译时会新增block节点 (收集动态节点的, 按照数组的维度来更新 靶向更新)

      <template v-if="false">
        <div>[1, 2, 3, 4, 5]div>
      template>
      <div>1div>
      <div>1div>
      <div>1div>
      
      const { openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, Fragment: _Fragment } = Vue
      
      return function render(_ctx, _cache, $props, $setup, $data, $options) {
        return (_openBlock(), _createElementBlock(_Fragment, null, [
          false
            ? (_openBlock(), _createElementBlock("div", { key: 0 }, "[1, 2, 3, 4, 5]"))//新增block节点,收集动态节点的
            : _createCommentVNode("v-if", true),
          _createTextVNode(),
          _createElementVNode("div", null, "1"),
          _createTextVNode(),
          _createElementVNode("div", null, "1"),
          _createTextVNode(),
          _createElementVNode("div", null, "1")
        ], 64 /* STABLE_FRAGMENT */))
      }
      
      // Check the console for the AST
      
    • vue3模版编译中会对动态节点做标识 patchFlags 标记哪些属性是动态的,更新时只更新动态属性

      <template v-if="false">
        <div :style="{coloe:isTrue?'red':'blue'}" class="fang">[1, 2, 3, 4, 5]div>
      template>
      
      const { normalizeStyle: _normalizeStyle, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = Vue
      
      return function render(_ctx, _cache, $props, $setup, $data, $options) {
        return false
          ? (_openBlock(), _createElementBlock("div", {
              key: 0,
              style: _normalizeStyle({coloe:_ctx.isTrue?'red':'blue'}),
              class: "fang"
            }, "[1, 2, 3, 4, 5]", 4 /* STYLE */))//patchFlags 标记哪些属性是动态的,更新时只更新动态属性;
          : _createCommentVNode("v-if", true)
      }
      
      // Check the console for the AST
      
    • vue3编译的时候会对函数进行缓存优化,不用每次都创建

      <div v-for="item in arr">
        <div @click="handleClick">88div>
      div>
      
      const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = Vue
      
      return function render(_ctx, _cache, $props, $setup, $data, $options) {
        return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.arr, (item) => {
          return (_openBlock(), _createElementBlock("div", null, [
            _createElementVNode("div", {
              onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.handleClick && _ctx.handleClick(...args)))//对函数进行缓存优化
            }, "88")
          ]))
        }), 256 /* UNKEYED_FRAGMENT */))
      }
      
  • vue3编译的时候会将静态节点做静态提升,后续直接采用提升后的结果

    <div v-for="item in arr">
      <div>88div>
    div>
    
    const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = Vue
    
    const _hoisted_1 = /*#__PURE__*/_createElementVNode("div", null, "88", -1 /* HOISTED */)//将静态节点做静态提升
    const _hoisted_2 = [
      _hoisted_1
    ]
    
    return function render(_ctx, _cache, $props, $setup, $data, $options) {
      return (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.arr, (item) => {
        return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
      }), 256 /* UNKEYED_FRAGMENT */))
    }
    
    // Check the console for the AST
    
  • vue3中会对同一个静态节点超过20个 会转化成字符串来渲染

    <div>
      <div>88div>
      <div>88div>
      <div>88div>
      <div>88div>
      <div>88div>
      <div>88div>
      <div>88div>
      <div>88div>
      <div>88div>
      <div>12div>
      <div>88div>
      <div>88div>
      <div>88div>
      <div>88div>
      <div>88div>
      <div>88div>
    div>
    
    const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
    
    const _hoisted_1 = /*#__PURE__*/_createStaticVNode("
    88
    88
    88
    88
    88
    88
    88
    88
    88
    12
    88
    88
    88
    88
    88
    88
    "
    , 16)//会转化成字符串来渲染 const _hoisted_17 = [ _hoisted_1 ] return function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createElementBlock("div", null, _hoisted_17)) } // Check the console for the AST

v-model的原理

  • v-model的原理
    • 响应式原理 (Object.defineProperty , proxy) 和 双向绑定原理是一个东西么? (表单元素绑定v-model)
    • 对于表单元素而言,input 的v-model 不能直接等于 value + input (内部会根据类型触发不同的事件 input,change ,对于输入框来说,做了中文处理)
    • v-model 可以用在组件 (vue2中v-model是value + input)的语法糖。 vue3中名字被修改了.默认值 modelValue + update:modelValue 可以通过:的方式来修改名字。 .sync语法废弃了

响应式原理

  • vue2中的响应式就是Object.defineProperty。vue3就是Proxy。

  • 说说你对双向绑定的理解,以及它的实现原理吗?

    1. 双向绑定的概念: vue 中双向绑定靠的是指令 v-model,可以绑定⼀个动态值到视图上,同时修改视图能改变数据对应的值(能修改的视图就是表单组件)
    2. 表单元素中的 v-model:
      • 内部会根据标签的不同解析出不同的语法。并且这⾥有“额外”的处理逻辑;
        • 例如 ⽂本框会被解析成 value + input 事件
        • 例如 复选框会被解析成 checked + change 事件
    3. 组件中的 v-model
      • 组件上的 v-model 默认会利⽤名为 modelValue 的 prop 和名为 onUpdate:modelValue 的事件。对于组件⽽⾔ v-model就是个语法糖。可⽤于组件中数据的双向绑定。

      • 绑定的名字也可以修改:

        <my v-model:a="a" v-model:b="b" v-model:c="c"> my>
        

双向数据绑定

  • vue中的双向数据绑定就是对表单元素做了一些常见的数据绑定及事件绑定。具体来说主要就是v-model了。

  • v-model="state"类似于:valule="state" @input="e=>state=e.target.value";

  • 代码示例:

    • src/App.vue

      
      
      
      
    • src/components/com.vue

      
      
      
      

模板中对比v-model的绑定及编译

<input type="text" v-model="val"/>
const { vModelText: _vModelText, withDirectives: _withDirectives, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue

return function render(_ctx, _cache, $props, $setup, $data, $options) {
  return _withDirectives((_openBlock(), _createElementBlock("input", {
    type: "text",
    "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => ((_ctx.val) = $event))
  }, null, 512 /* NEED_PATCH */)), [
    [_vModelText, _ctx.val]
  ])
}

// Check the console for the AST

v-model源码

  • fang/f20230717/vue3-lan-1/node_modules/vue/dist/vue.runtime.esm-browser.js
const vModelText = {
  created(el, { modifiers: { lazy, trim, number } }, vnode) {
    el._assign = getModelAssigner(vnode);
    const castToNumber = number || vnode.props && vnode.props.type === "number";
    addEventListener(el, lazy ? "change" : "input", (e) => {
      if (e.target.composing)
        return;
      let domValue = el.value;
      if (trim) {
        domValue = domValue.trim();
      }
      if (castToNumber) {
        domValue = looseToNumber(domValue);
      }
      el._assign(domValue);
    });
    if (trim) {
      addEventListener(el, "change", () => {
        el.value = el.value.trim();
      });
    }
    if (!lazy) {
      addEventListener(el, "compositionstart", onCompositionStart);
      addEventListener(el, "compositionend", onCompositionEnd);
      addEventListener(el, "change", onCompositionEnd);
    }
  },
  // set value on mounted so it's after min/max for type="range"
  mounted(el, { value }) {
    el.value = value == null ? "" : value;
  },
  beforeUpdate(el, { value, modifiers: { lazy, trim, number } }, vnode) {
    el._assign = getModelAssigner(vnode);
    if (el.composing)
      return;
    if (document.activeElement === el && el.type !== "range") {
      if (lazy) {
        return;
      }
      if (trim && el.value.trim() === value) {
        return;
      }
      if ((number || el.type === "number") && looseToNumber(el.value) === value) {
        return;
      }
    }
    const newValue = value == null ? "" : value;
    if (el.value !== newValue) {
      el.value = newValue;
    }
  }
};
const vModelCheckbox = {
  // #4096 array checkboxes need to be deep traversed
  deep: true,
  created(el, _, vnode) {
    el._assign = getModelAssigner(vnode);
    addEventListener(el, "change", () => {
      const modelValue = el._modelValue;
      const elementValue = getValue(el);
      const checked = el.checked;
      const assign = el._assign;
      if (isArray(modelValue)) {
        const index = looseIndexOf(modelValue, elementValue);
        const found = index !== -1;
        if (checked && !found) {
          assign(modelValue.concat(elementValue));
        } else if (!checked && found) {
          const filtered = [...modelValue];
          filtered.splice(index, 1);
          assign(filtered);
        }
      } else if (isSet(modelValue)) {
        const cloned = new Set(modelValue);
        if (checked) {
          cloned.add(elementValue);
        } else {
          cloned.delete(elementValue);
        }
        assign(cloned);
      } else {
        assign(getCheckboxValue(el, checked));
      }
    });
  },
  // set initial checked on mount to wait for true-value/false-value
  mounted: setChecked,
  beforeUpdate(el, binding, vnode) {
    el._assign = getModelAssigner(vnode);
    setChecked(el, binding, vnode);
  }
};
function setChecked(el, { value, oldValue }, vnode) {
  el._modelValue = value;
  if (isArray(value)) {
    el.checked = looseIndexOf(value, vnode.props.value) > -1;
  } else if (isSet(value)) {
    el.checked = value.has(vnode.props.value);
  } else if (value !== oldValue) {
    el.checked = looseEqual(value, getCheckboxValue(el, true));
  }
}
const vModelRadio = {
  created(el, { value }, vnode) {
    el.checked = looseEqual(value, vnode.props.value);
    el._assign = getModelAssigner(vnode);
    addEventListener(el, "change", () => {
      el._assign(getValue(el));
    });
  },
  beforeUpdate(el, { value, oldValue }, vnode) {
    el._assign = getModelAssigner(vnode);
    if (value !== oldValue) {
      el.checked = looseEqual(value, vnode.props.value);
    }
  }
};
const vModelSelect = {
  //  relies on its children
  // 
  mounted(el, { value }) {
    setSelected(el, value);
  },
  beforeUpdate(el, _binding, vnode) {
    el._assign = getModelAssigner(vnode);
  },
  updated(el, { value }) {
    setSelected(el, value);
  }
};
function setSelected(el, value) {
  const isMultiple = el.multiple;
  if (isMultiple && !isArray(value) && !isSet(value)) {
    warn(
      `