Skip to main content

框架知识学习概览

· 12 min read
LIU

Vue2

组件化基础

  • 数据驱动视图 (MVVM,setState)
  • MVVM
    • M => data
    • V => view
    • VM => 一个抽象的层级,Vue提供的能力,提供 V 和 M 连接的一部分

响应式

  • 组件 data 的数据一旦变化,会触发视图的更新
  • Object.defineProperty (Vue3.0启用 proxy)
    • 监听对象,监听数组
    • 复杂对象,深度监听
    • image-20220525161254722
  • Object.defineProperty缺点:
    • 深度监听,需要递归到底,一次性计算量较大
    • 无法监听新增属性、删除属性 (Vue.set / Vue.delete)
    • 无法原生监听数组,需要特殊处理
  • Proxy 兼容性不好,且无法 polyfill

组件渲染和更新的过程

虚拟 DOM (Virtual DOM) 和 Diff

  • vdom 是实现 vue 和 React 的重要基石
  • diff 算法 是 vdom 中最核心、最关键的部分
  • DOM 操作非常耗费性能
  • vdom - 用 JS 模拟 DOM 结构,新旧 vnode对比,计算出最小的变更,最后更新 DOM
  • vdom 核心概念:h、vnode、patch、diff、key 等
  • vdom 存在的价值:数据驱动视图,控制 DOM 操作等

image-20220524232902156

snabbdom

  • github.com/snabbdom/snabbd om

  • 简洁强大的 vdom 库

  • Vue 参考它实现的 vdom 和 diff

  • Vue3 重写了 vdom 的代码,优化了性能,但是 vdom 的基本理念不变

  • h函数、vnode数据结构、patch函数

  • patchVnode函数:新旧都有 children,执行update。新有旧无,执行add。新无旧有,执行remove

  • updateChildren函数

    • image-20220525101858219
    • image-20220525101730643

h 函数

第一个参数:标签

第二个参数:属性

第三个参数:子元素

patch 函数 渲染 vnode

const vnode = h('ul#list',{},[
h('li.item',{},'Item1'),
h('li.item',{},'Item2')
])
const = document.getElementById('container')

patch(container,vnode) //初次渲染

const newVnode = h('ul#list',{},[
h('li.item',{},'Item')
])

patch(vnode,newVnode) //dom更新

diff 算法

  • diff 算法是 vdom 中最核心、最关键的部分
  • diff 即对比,是一个广泛的概念,如 linux diff 命令,git diff 等
  • 两个 JS 对象也可以做 diff :jiff 库
  • 树的 diff 的时间复杂度是 O(n^3)
    • 遍历 tree1
    • 遍历 tree2
    • 排序
    • 1000个节点,要计算一亿次,算法不可用
  • 优化时间复杂度到 O(n)
    • 只比较同一层级,不跨级比较
    • tag 不同,则直接删掉重建,不再深度比较
    • tag 和 key ,两者都相同,则认为是相同节点,不再深度比较

模板编译

  • JS 的 with 语法

    • 它改变了 {} 内自由变量的查找规则,当做 obj 属性来查找
    • 如果找不到匹配 obj 下的属性,就会报错
    • with 要慎用,它打破了作用域规则,易读性变差
    • image-20220525105152755
  • vue template complier 将模板编译为 render 函数

  • 执行 render 函数生成 vnode

  • vue的模板不是 html,有指令,插值,js表达式,判断,循环

  • 因此,模板一定是转换为某种 js 代码,即编译模板

  • vue组件可以用 render 代替 template

  • vue-template-compiler

    • 模板 通过 compiler 编译成 render 函数,执行函数生成 vnode,再到渲染和更新

    • 基于 vnode 再执行 patch 和 diff

    • 这里面的 _c 等同于 createElement 等同于 h函数

    • const compiler = require('vue-template-compiler')

      // 插值
      const template = `<p>{{message}}</p>`
      with(this){return createElement('p',[createTextVNode(toString(message))])}
      // h -> vnode
      // createElement -> vnode

      // 表达式
      const template = `<p>{{flag ? message : 'no message found'}}</p>`
      with(this){return _c('p',[_v(_s(flag ? message : 'no message found'))])}

      // 属性和动态属性
      const template = `
      <div id="div1" class="container">
      <img :src="imgUrl"/>
      </div>
      `
      with(this){return _c('div',
      {staticClass:"container",attrs:{"id":"div1"}},
      [
      _c('img',{attrs:{"src":imgUrl}})])}

      // 条件
      const template = `
      <div>
      <p v-if="flag === 'a'">A</p>
      <p v-else>B</p>
      </div>
      `
      with(this){return _c('div',[(flag === 'a')?_c('p',[_v("A")]):_c('p',[_v("B")])])}

      // 循环
      const template = `
      <ul>
      <li v-for="item in list" :key="item.id">{{item.title}}</li>
      </ul>
      `
      with(this){return _c('ul',_l((list),function(item){return _c('li',{key:item.id},[_v(_s(item.title))])}),0)}

      // 事件
      const template = `
      <button @click="clickHandler">submit</button>
      `
      with(this){return _c('button',{on:{"click":clickHandler}},[_v("submit")])}

      // v-model
      const template = `<input type="text" v-model="name">`
      // 主要看 input 事件
      with(this){return _c('input',{directives:[{name:"model",rawName:"v-model",value:(name),expression:"name"}],attrs:{"type":"text"},domProps:{"value":(name)},on:{"input":function($event){if($event.target.composing)return;name=$event.target.value}}})}

      // render 函数
      // 返回 vnode
      // patch

      // 编译
      const res = compiler.compile(template)
      console.log(res.render)

      // 从 vue 源码中找到缩写函数的含义
      function installRenderHelpers (target) {
      target._o = markOnce;
      target._n = toNumber;
      target._s = toString;
      target._l = renderList;
      target._t = renderSlot;
      target._q = looseEqual;
      target._i = looseIndexOf;
      target._m = renderStatic;
      target._f = resolveFilter;
      target._k = checkKeyCodes;
      target._b = bindObjectProps;
      target._v = createTextVNode;
      target._e = createEmptyVNode;
      target._u = resolveScopedSlots;
      target._g = bindObjectListeners;
      target._d = bindDynamicKeys;
      target._p = prependModifier;
      }

组件 渲染/更新/过程

  • 初次渲染过程
    • 解析模板为 render 函数 (或在开发环境已完成,vue-loader)
    • 触发响应式,监听 data 属性 getter setter
    • 执行 render 函数,生成 vnode,patch(elem,vnode)
    • 执行 render 函数会触发 getter
  • 更新过程
    • 修改 data,触发 setter ( 此前在getter 中已被监听 )
    • 重新执行 render 函数
    • patch (vnode, newVnode)
  • 异步渲染
    • $nextTick 待 DOM 渲染完再回调
    • 页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次
    • 汇总 data 的修改,一次性更新视图
    • 减少 DOM 操作次数,提高性能

image-20220525123729108

Vue3

Vue3对比Vue2的优势

  • 性能更好
  • 体积更小
  • 更好的ts支持
  • 更好的代码组织
  • 更好的逻辑抽离
  • 更多新功能

Options API 生命周期

  • destroyed 改为 unmouted
  • beforeDestroy 改为 beforeUnmount
  • 其他沿用 Vue2 的声明周期
//使用setup() 替代
beforecreate -> use setup()
created -> use setup()

//前面加on
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onbeforeUpdate
updated -> onUpdated
activated -> onActivated
deactivated -> onDeactivated
errorCaptured -> onErrorCaptured

//更改名称
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted

//新加入,主要用于开发阶段调试使用
onRenderTracked
onRenderTriggered

Composition API 对比 Options API

  • Composition API
    • 更好的代码组织
    • 更好的逻辑复用
    • 更好的类型推导

image-20220524193550744

理解 ref / toRef / toRefs

  • ref

    • 生成值类型的响应式数据
    • 可用于模板和 reactive
    • 通过 .value 修改值
  • toRef

    • 针对一个响应式对象(reactive 封装)的 prop

    • 创建一个 ref,具有响应式

    • 两者保持引用关系

    • toRef 如果用于普通对象(非响应式对象),产出的结果不具备响应式

    • const state = reactive({
      age:10,
      name:'xx'
      })
      const ageRef = toRef(state,'age') //单个
      //修改其一都会联动
      state.age = 20
      ageRef.value = 30
  • toRefs

    • 将响应式对象 (reactive 封装)转换为普通对象

    • 对象的每个 prop 都是对应的 ref

    • 两者保持引用关系

    • //不能直接结构使用,需要 toRefs
      const state = reactie({
      age:10,
      name:'xx'
      })
      const stateAsRefs = toRefs(state) //整个

      const {age:ageRef,name:nameRef} = stateAsRefs //每个属性 都是ref响应式对象
      return {
      ageRef,
      nameRef
      }
      return stateAsRefs
      return {
      ...stateAsRefs
      }
      return toRefs(state)
    • image-20220524225231538

  • 最佳使用方式

    • 用 reactive 做对象的响应式,用 ref 做值类型响应式
    • setup 中返回 toRefs(state),或者 toRef(state,'xx')
    • ref 的变量命名都用 xxxRef
    • 合成函数返回响应式对象时,使用 toRefs,有助于使用方对数据进行结构

6-9

路由

image-20220525135916734

路由模式

  • hash

    • hash 变化会触发网页跳转,即浏览器的前进和后退

    • hash 变化不会刷新页面,SPA 必需的特点

    • hash 永远不会提交到 server 端

    • window.onhashchange

    • window.onhashchange = (event) =>{
      console.log('old url:',event.oldURL)
      console.log('new url:',event.newURL)
      console.log('hash:',event.hash)
      }
  • H5 history

    • 用 url 规范的路由,但跳转时不刷新页面

    • history.pushState 向浏览器历史添加了⼀个状态(增加⼀个记录)

    • history.replaceState 把当前的页⾯的历史记录替换掉

    • window.onpopstate

    • // 页面初次加载,获取 path
      document.addEventListener('DOMContentLoaded', () => {
      console.log('load', location.pathname)
      })

      // 打开一个新的路由
      // 用 pushState 方式,浏览器不会刷新页面
      document.getElementById('btn1').addEventListener('click', () => {
      const state = { name: 'page1' }
      console.log('切换路由到', 'page1')
      history.pushState(state, '', 'page1') // 重要
      })

      // 监听浏览器前进、后退
      window.onpopstate = (event) => { // 重要
      console.log('onpopstate', event.state, location.pathname)
      }

总结

v-show 和 v-if

  • v-show 通过 CSS display 控制显示和隐藏
  • v-if 组件真正的渲染和销毁
  • 频繁的切换显示状态用 v-show,否则用 v-if

v-for 中的 key

  • 必须用 key,且不能是 index 和 random
  • diff 算法中 通过 tag 和 key 来判断 ,是否是sameNode
  • 减少渲染次数,提升渲染性能

Vue组件中生命周期(父子组件)

  • 单组件生命周期图
  • 父子组件生命周期关系

Vue组件通信

  • 父子组件 props 和 $emit
  • 自定义事件 event.$on 、event.$emit、event.$off
  • Event.Bus
  • vuex

v-model实现原理

  • input 元素的 value = this.name
  • 绑定 input 事件 this.name = $event.target.value
  • data 更新 触发 re-render
  • image-20220525183151761

computed 特点

  • 缓存,data 不变不会重新计算
  • 提高性能

ajax请求放在哪个生命周期

  • mounted
  • 放在 mounted 之前没用,只会让逻辑更加混乱

将组件所有props传递给子组件

  • $props
  • <User v-bind="$props" />

beforeDestory

  • 解除自定义事件 event.$off
  • 清除定时器
  • 解绑自定义的 DOM 事件,如 window,scroll 等

作用域插槽

  • 将子组件的 data 传递给父组件,子组件:<slot :user="user">
  • 父组件通过 <template v-slot='slotProps'> 接收

Vuex中 action 和 mutation

  • action 支持异步,mutation 不支持
  • Mutation 做原子操作(每次只做一个操作)
  • action 可以整合多个 mutation

Vue-router 异步加载

  • component:()=>import('./xxxxx')

Vue监听数组变化

  • Object.definedProperty 不能监听数组变化
  • 重新定义原型,重写 push pop 等方法,实现监听
  • Proxy 可以原生支持监听数组变化

diff算法过程