vue3.0

一:vue3.0 介绍

1:源码组织方式的改变

  • 源码采用 TypeScript 重写【会有格式类型匹配提示】
  • 使用 monorepo 管理项目结构

       vue3.0_第1张图片

2:Composition API 【vue3.0 采用Composition API ,vue2.0采用Options API】

  • RFC(Request For Comments)
  •  https://github.com/vuejs/rfcs
  • Composition API RFC 
  •  https://composition-api.vuejs.org

Options API

  • 包含一个描述组件选项(data, methods, props 等)
  • options API  开发复杂组件,同一个功能逻辑的代码被拆分到不同选项

Composition API

  •   vue3.0  新增的 一组 API
  •    一组  基于函数 的 API
  •   可以更灵活的的 组织 ” 组件“ 逻辑

vue3.0_第2张图片 vue3.0_第3张图片

    2 者 结构对比,如下【相同颜色代表相同功能模块】,

    显然Composition API 可以更好的整理相同模块,使其看起来更加整洁 统一,

    vue3.0_第4张图片

 3:性能提升

   1: 响应式系统升级

  •  vue2.x 响应式系统的核心是 defineProperty【初始化时,通过遍历data里的对象,使用 dedineProperty 把每个对象转化为getter和setter,从而使其变成响应式数据】
  •  vue 3.0 使用 Proxy(es6新属性)对象,重写响应式系统

      优点:

  1.   可以监听动态 "新增" 的属性
  2.   可以监听 "删除" 的属性
  3.   可以监听 数组 的 ”索引 和  length“  属性     

   2:编译升级

vue3.0_第5张图片

   vue2.x 中通过 "标记"  "静态根节点"  来优化 diff 的过程

      回顾vue2.x编译的过程:

  1. 模板首先要编译成 render函数【这个过程一般是在构建的时候完成的】
  2. 在编译的时候,会编译 ”静态根节点【必须是固定的】“ 和 ”静态节点“, 【静态根节点要求必须有一个静态子节点】 
  3. 当组件的状态发生变化时,会通知 watcher ,触发 watcher 的 update
  4. 最终去执行虚拟dom的patch操作,遍历所有的虚拟节点找到差异,并更新到真实dom上
  5. diff 过程中,会去比较整个的虚拟DOM,先对比新旧DIV 以及它的属性,再对比它的子节点

    总结

  • 在vue2.x 中它渲染的最小单位是 “组件”
  • vue2.x 在 diff  过程中会 跳过 静态根节点因为静态根节点的内容不会发生变化】,所以 vue2.x  通过 "标记"  "静态根节点",优化了diff 过程,
  • 在 vue 2.x 中 静态节点 还需要再进行 diff,这个过程  没有被优化

   vue 3.0 中通过 "标记 和 提升"  "所有的静态根节点",diff 时候只需要对比动态节点内容  【大大提升 diff 的性能】

  •    Fragments【片段,不是必须有一个固定的根节点,需要升级 vetur 插件】
  •    静态升级开启hoistStatic,将静态节点(内容是纯文本的节点),在初始化的时候创建(只创建一次),后面的render函数只需要直接调用,已经创建的vnode静态节点
  •    Patch  flag 【标记动态节点,对比时候只对比动态节点的值是否变化】
  •    缓存事件处理函数【较少了不必要的更新操作】

vue3.0_第6张图片

   3:源码体积的优化

  •    vue 3.0 移除了一些 不常用的 API  【 如:inline-template, filter 等】
  •    Tree-shaking 【vue3.0 对它的支持比较好】

    内容拓展

  • Tree-shaking 依赖 ES Modulees6 模块化语法的静态结构:import 和 export】,通过编译阶段的静态分析,找到没有引入的模块,在打包的时候直接过滤掉,让打包后的体积更小
  •  vue3.0 在设计之初就考虑到了,内置的组件如:transition, keep-alive ;内置的指令v-module 等都是按需引入的
  • vue3.0 的很多API 都是支持 Tree-shaking的,如果vue3.0  新增的一些API你没有使用,这部分代码是不会被打包的,只会打包你使用的API,但是 默认vue核心模块都会被打包

 4: Vite 【构建工具】

       背景:

            伴随 Vue 3.0 的推出, Vue3.0 的作者还开发了一个构建工具 Vite

            Vite:这个单词来自于法语【快的意思】, 意味着 Vite 这个工具比过去 vue-cli 【基于webpack的】更快

            彩蛋:vite多久后能干掉webpack?

知识回顾:浏览器使用 ES Module  的方式

  • 现代浏览器都支持  ES Module 【IE不支持】
  • 通过下面的方式加载模块【使用es module方式加载模块,默认开启严格模式 use strict 】
  1.   
  • 支持模块的 script ,并且是默认延迟加载
  1.  类似于 script 标签设置 defer
  2.  在(DOM树创建完毕)文档解析完成 之后,执行
  3.  在触发 DOMContentLoaded 事件  之前   执行



  
  
  Document


  
Hello World
// 先执行这个

   Vite的快:就是使用浏览器支持的ES Module方式,避免 开发环境下打包,从而提升开发速度

Vite 和 Vue-Cli  的区别

  •   Vite 在 开发模式下  不需要打包,可以直接运行

          工作原理: 在开发模式下 ,Vite使用  浏览器原生支持的 ES Module 加载模块,【也是就:import导入模块,支持ES Module的现代浏览器,通过 的方式加载模块】,因为 Vite 不需要打包项目【因为把所有模块的请求都交给服务器来处理,在服务器端处理浏览器不能识别的模块,再把编译的结果返回给浏览器】, 因此Vite在开发模式下,打开页面是秒开的。

  •   Vue-CLI  开发模式下,必须 对 项目打包 才可以 运行

          Vue-CLI在开发模式下,会先打包整个项目,如果项目比较大,打包会特别慢

  •    Vite  在 生产模式下 使用  Rollup 打包

           Rollup是基于浏览器原生的ES Module 打包的,它不需要使用 babel 将 import 转换为require,以及相应的一些辅助函数,因此会比 webapck 打包的 体积更小【现代浏览器都已经支持ES Module的方式加载模块】

  •    Vue-CLI 生产模式下 使用  webpack 打包

  Vite的特点

  • 快速冷启动 【因为不需要打包】
  • 按需编译 【只有代码在当前需要加载时才会编译,不需要在开启整个开发服务器的时候,等待整个项目被打包,等到项目比较大时,该特征就会更明显 】
  • 模块热更新【模块热更新的性能 和 模块总数无关,无论你有多少模块,HRM的速度始终比较快】

Vite 创建项目【基本使用】

 1: Vite 创建项目 

npm init vite-app 
cd 
npm install
npm run dev

创建成功后的目录结构如下: 

vue3.0_第7张图片

 2:基于模板 创建项目【可以让它支持其他框架】

npm init vite-app --template react // react 是要使用的框架
npm init vite-app --template preact

二:Composition API

1:Composition API

  •  createAPP: 用来创建Vue
  •  setup:Composition 的入口 【setup 是 在 props 解析之后,组件实例 创建之前 执行,所以它内部不能使用 this,this 值是 undefined】【vue2.x版本中 beforeCreate和 created
  •  reactive: 创建响应式对象,也是代理对象,返回process对象

  不可以对响应式对象进行解构

 const position = reactive({
    x: 0,
    y: 0
 })

  因为 如上position 调用reactive 方法将对象转换为process对象, 此时position是 process对象。

 当访问 position 对象的   x  和 y 时候, 会调用代理对象中的  gettrer  拦击 收集依赖

 当 x, y 变化的时候,会调用 代理对象中的  setter  进行拦截,  触发 跟新

const { x, y } = position

   如上: 当解构 代理对象 position 时, 就相当于 定义了 2个变量 x 和 y 来接收 position.x  和 position.y 【基本类型赋值:就是把内存中的值复制一份】,所以这里的 x 和 y 就是基本类型变量,跟代理对象无关,当重新给 x 和 y 赋值的时候,也不会调用代理对象的 setter,无法 "触发更新" 的操作,所以我们不能对响应式对象进行解构。。。  

   知识拓展:

什么是process对象?

  • process对象是一个Global全局对象,你可以在任何地方(所有模块中)都能使用它,而无需 require【如:process.env: 代表全局环境】
  • 作用域是全局的
    • 比如 export 也是在所有模块中都能使用,但是作用域是当前模块

2: 生命周期函数

  • onMounted 【组件挂载】生命周期
  • onUnmounted 【组件卸载销毁】生命周期

3:reactive,toRefs,ref

   reactive:把 对象 转化为响应式对象,也是代理对象,返回process对象 【不可解构

   toRefs: 可以把响应式对象中的每一个属性,都转换为响应式的 数据 【可解构

实现原理:toRefs( 接收的参数必须是代理对象 ),它内部会创建一个新的对象,然后 遍历 这个传入的 代理对象 的 所有属性,然后把 所有属性的值 都转化为 响应式对象

function useMousePosition () {
   const position = reactive({
      x: 0,
      y: 0
   })
   return toRefs(position)
}

const { x, y } = useMousePosition()

     如上:toRefs 把 position 这个对象里面的所有属性的值,都转化为 响应式对象,然后挂载到新创建的对象上, 然后再把这个新创建的对象返回;

     它内部会为 代理对象的每一个属性 创建一个具有value属性的对象,该对象是响应式对象,value属性具有getter 和 setter 属性, getter里面返回 代理对象 对应属性 的 值 ,setter中给 代理对象 赋值,所以返回的每一个属性都是响应式的

 【1-3】代码演示合集:




  
  
  Document


  
x: {{ x }}
y: {{ y }}

    ref:响应式API 【 把 “基本类型数据” 转化为 “响应式对象”】

实现原理:ref(参数) ,参数分为2种类型

  •  对象类型:内部会调用 reactive 来转化为 响应式对象
  •  基本数据类型:内部会 创建一个只有value属性的对象,该对象是响应式对象,该对象的 value 属性具有 getter 和 setter 属性, getter 里面收集依赖【返回 数据 的值】 ,setter中 触发更新【给 数据 赋值】

代码演示: 




  
  
  Document


  
{{ count }}

4:computed 【计算属性 】

  computed概念:

    计算属性 【简化模板中代码,可以缓存计算的结果,当数据变化后才会重新计算】

  computed 使用方法:

// 用法1
computed(()=> count.value + 1)

// 用法2
const count = ref(1)
const plusOne = computed({
   get: () => count.value + 1,
   set: val => {
     count.value = val - 1
   }
})

  代码演示: 




  
  
  Document


  
未完成:{{ activeCount }}

5:watch 和 watchEffect

 概念:

   watch: 监听响应式数据

    watcheffect: 简化版的watch,也用来监听数据的变化【watchEffect 会返回一个用于停止这个监听的函数

 watch的使用:

watch有3个参数:

  1.  参数1:要监听的数据
  2.  参数2:监听到数据变化后要执行的函数,这个函数有2个参数,分别是 新值 和 旧值
  3.  参数3:选项对象 【 deep immediate

watch 的返回值

  •  取消监听的函数

watcheffect 有1个参数

  • 参数:接收一个函数作为参数,监听函数内响应式数据的变化

watchEffect与 watch 有什么不同?

  • 第一点我们可以看到 watchEffect 不需要指定监听的属性,他会自动的收集依赖, 只要我们回调中引用到了 响应式的属性, 那么当这些属性变更的时候,这个回调都会执行,而 watch 只能监听指定的属性而做出变更(v3开始可以同时指定多个)。
  • 第二点就是 watch 可以获取到新值与旧值(更新前的值),而 watchEffect 是拿不到的。
  • 第三点是 watchEffect 如果存在的话,在组件初始化的时候就会执行一次用以收集依赖(与computed同理),而后收集到的依赖发生变化,这个回调才会再次执行,而 watch 不需要,因为他一开始就指定了依赖。

代码演示




  
  
  Document


  

请问一个 yes/no 的问题:

{{ answer }}



{{ count }}

6: 案例演示 todolist 

   1:todolist 功能列表

  • 添加待办事项
  • 删除待办事项
  • 编辑待办事项
  • 切换待办事项
  • 存储待办事项 【存在localstorege】

   2:  todolist 案例 git 代码地址

三: Vue.js 3.0 响应式系统原理

vue3.0 响应式回顾

  • proxy 实现响应式监听【提升 和 标记 所有的静态跟节点】
  • 多层属性嵌套,在访问属性过程,中处理下一级属性【通过reactive处理】
  • 默认监听动态添加的属性
  • 默认监听属性的删除操作
  • 默认监听 数组索引 和 length属性
  • 可以作为单独的模块使用

核心方法

  •   reactive,ref,toRefs,computed
  •   effect【是watch 和 watchEffect 底层的核心方法】
  •   track 【收集依赖的函数】
  •   trigger 【触发更新的函数】

1:proxy响应式系统

 代码演示

        解决问题1:proxy的 set 和 deleteProperty中需要返回布尔类型的值 




  
  
  Document


  

        解决问题2:Proxy 和 Reflect 中使用的 receiver 


  

 2:reactive

    1: reactive 原理

  •  接受一个参数,判断这个参数是否是对象
  •  创建拦截器对象handler,设置get、set,deleteProperty
  •  返回Proxy对象

    2:手写 reactive() 方法: 代码实现

  •  index.html
  • 
    
    
      
      
      Document
    
    
      
    
    
  •  reactive.js
  • ====定义全局方法
    // 1: 是否是对象的方法,返回布尔值
    const isObject = val => val !== null && typeof val === 'object'
    // 2:判断是否是多层属性嵌套
    const convert = target => isObject(target)? reactive(target) : target
    // 3: 判断对象中是否存在某个key
    const hasOwnProperty = Object.prototype.hasOwnProperty
    const hasOwn = (target, key) => hasOwnProperty.call(target, key)
    
    
    export default function reactive (target) {
      // 1: 接收一个参数,判断这个参数是否是对象
      if (!isObject(target)) return target // 该值不是对象,则直接返回这个值
      
      // 2: 创建拦截对象handler【proxy的 set 和 deleteProperty中需要返回布尔类型的值】
      const handler = {
        get (target, key, receiver) {
        // 收集依赖
    
        // 获取值
        const result = Reflect.get(target, key, receiver)
        
        // 判断result是否是多层属性嵌套,然后用reactive处理
        return convert(result)
        },
        set (target, key, value, receiver) {
          // 获取旧的值
          const oldValue = Reflect.get(target, key, receiver)
          
          let result = true
    
          // 判断新旧值是否相同
          if (!oldValue === value) {
          // Reflect.set() 设置成功返回true, 失败返回false
          result = Reflect.set(target, key, value, receiver)
    
          // 触发更新
          }
          return result // 手动添加return
        },
        deleteProperty (target, key) {
          const hadKey = hasOwn(target, key)
          const result =  Reflect.deleteProperty(target, key)
          if (hadKey && result) {
          // 触发更新
          }
          return result
        }
      }
      return new Proxy(target, handler)
    }

         代码方法集合演示:【reactive】 

3:收集依赖 

知识拓展

get() 依赖收集 过程原理

    当访问 代理对象的属性时,会执行该属性的get方法,在get方法中会 ”收集依赖“ 【在代理对象的get方法中会存储:target【目标代理对象】  ,key【目标对象对应的属性】, 回调函数 (该key属性改变后的回调函数) 】

set() 触发跟新 过程原理

当重新给属性赋值时,会执行属性对应的set方法,在set方法中会 ”触发更新“【就是找到依赖收集过程中存储的 属于 对应的 回调函数】,找到这个函数后,会立即执行

  •  new WeakMap()
  •  new Map()
  •  new Set()

vue3.0_第8张图片

4: effect ,track,trigger


  

  代码方法集合演示:【effect,tract,trigger】 

5:ref

 reactive vs ref

  •  ref 可以把基本数据类型数据,转成 响应式对象
  •  ref 返回的对象,重新赋值成对象,也是,响应式的
  •  reactive返回的对象,重新赋值  丢失 ”响应式“
  •  reactive返回的对象,不可解构

      vue3.0_第9张图片


  

  代码方法集合演示:【ref 】 

6:toRefs


  

  代码方法集合演示:【toRefs】 

7:computed 


  

  代码方法集合演示:【computed


代码方法集合演示:【reactive,ref,toRefs,computed,tract,trigger】

// 1: 定义全局方法,是否是对象的方法,返回布尔值
const isObject = val => val !== null && typeof val === 'object'
// 2:判断是否是多层属性嵌套
const convert = target => isObject(target)? reactive(target) : target
// 3: 判断对象中是否存在某个key
const hasOwnProperty = Object.prototype.hasOwnProperty
const hasOwn = (target, key) => hasOwnProperty.call(target, key)


// =======【 reactive 】
export default function reactive (target) {
  // 1: 接收一个参数,判断这个参数是否是对象
  if (!isObject(target)) return target // 该值不是对象,则直接返回这个值
  
  // 2: 创建拦截对象handler【proxy的 set 和 deleteProperty中需要返回布尔类型的值】
  const handler = {
    get (target, key, receiver) {
    // 收集依赖
    track(target, key)
    // 获取值
    const result = Reflect.get(target, key, receiver)
    
    // 判断result是否是多层属性嵌套,然后用reactive处理
    return convert(result)
    },
    set (target, key, value, receiver) {
      // 获取旧的值
      const oldValue = Reflect.get(target, key, receiver)
      
      let result = true

      // 判断新旧值是否相同
      if (!oldValue === value) {
        // Reflect.set() 设置成功返回true, 失败返回false
        result = Reflect.set(target, key, value, receiver)

        // 触发更新
        trigger(target, key)
      }
      return result // 手动添加return
    },
    deleteProperty (target, key) {
      const hadKey = hasOwn(target, key)
      const result =  Reflect.deleteProperty(target, key)
      if (hadKey && result) {
        // 触发更新
        trigger(target, key)
      }
      return result
    }
  }
  return new Proxy(target, handler)
}



// 全局的是否有依赖收集 标识
const activeEffect = null

export function effect (callback) {
  activeEffect = callback
  callback() // 访问响应式对象时候,触发的回调,收集依赖
  activeEffect = null
}

let targetMap = new WeakMap()

// 收集依赖
export function track (target, key) {
  // 如果没有依赖,则不需要收集
  if (!activeEffect) return
  
  // target:目标对象key, depsMap:目标对象target的value
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }

  // key:目标对象的 属性名, dep:目标对象 的 属性 对应的 值value, 是集合【effect回调函数集合】
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  dep.add(activeEffect)
}

// 触发更新
export function trigger (target, key) {
  // 获取目标对象
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  
  // 获取,目标对象下 对应属性的值
  const dep = depsMap.get(key)
  if (dep) {
    // 遍历属性值dep 里的所有 effect集合
    dep.forEach(effect => {
      effect()
    })
  }
}

// =======【 ref 】
export function ref (raw) {
  // 判断 rwa 是不是 ref创建的对象,是就直接返回,不做转换了
  if (raw._v_isRef && isObject(raw)) {
    return
  }
  let value = convert(raw)
  // 1: 生成新的对象
  const r = {
    _v_isRef: true, // 是否是ref的标识
    get value () { // 创建一个get 属性名字叫value
      // 收集依赖
      track(r, value)
      return value
    },
    set value (newValue) { // 创建一个set 属性名字叫value
      if (value !== newValue) {
        raw = newValue
        value = convert(raw)
        // 触发更新
        trigger(r, value)
      }
    }
  }
   // 2: 返回新的对象
   return r
}

// =======【 toRefs】
// 作用:把reactive返会的每一个属性,转化为类似于ref返回的对象,这样就可以对reactive返回的对象进行解构
export function toRefs (proxy) {
  // 设置一个新的对象遍历,并设置初始值
  const ret = proxy instanceof Array ? new Array(proxy.length) : {}
  
  for (const key in proxy) {
    // 遍历代理对象,将代理对象的每一个属性,转化为响应式 对象,并挂载给新对象
    ret[key] = toProxyRef(proxy, key)
  }
  return ret
}
export function toProxyRef (proxy, key) {
  const r = {
    _v_isRef: true,
    get value () {
      return proxy[key] 
    },
    set value (newValue) {
      if (newValue !== proxy[key]) {
        proxy[key] = newValue
      }
    }
  }
  return r
}

// =======【 computed 】
export function computed (getter) {
  const result = ref()
  // effect 内部可以监听getter内部属性的变化
  effect(() => result.value = getter)
  return result
}

四:Vite 实现原理 

你可能感兴趣的:(vue,vue.js,vue,前端,前端框架)