一:vue3.0 介绍

1:源码组织方式的改变

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

       

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

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

Options API

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

Composition API

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

 

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

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

    

 3:性能提升

   1: 响应式系统升级

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

      优点:

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

   2:编译升级

   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 【标记动态节点,对比时候只对比动态节点的值是否变化】
  •    缓存事件处理函数【较少了不必要的更新操作】

   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 type="module" src="...."></script> 
  • 支持模块的 script ,并且是默认延迟加载
  1.  类似于 script 标签设置 defer
  2.  在(DOM树创建完毕)文档解析完成 之后,执行
  3.  在触发 DOMContentLoaded 事件  之前   执行
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app">Hello World</div>
  <script>
    window.addEventListener('DOMContentLoaded', () => {
      console.log('DOMContentLoaded')  // 后执行这个
    })

  </script>
  <script type="module" src="./modules/index.js"></script> // 先执行这个
</body>
</html>

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

Vite 和 Vue-Cli  的区别

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

          工作原理: 在开发模式下 ,Vite使用  浏览器原生支持的 ES Module 加载模块,【也是就:import导入模块,支持ES Module的现代浏览器,通过 <script type=“module” src=""></script>的方式加载模块】,因为 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 <project-name>
cd <project-name>
npm install
npm run dev

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

 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】代码演示合集:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app">
    x: {{ x }} <br>
    y: {{ y }}
  </div>
  <script type="module">
    import { createApp, reactive, onMounted, onUnmounted, toRefs } from './node_modules/vue/dist/vue.esm-browser.js' // 加载vue模块
    
    // 提取封装功能模块
    function useMousePosition () {
      // 第一个参数 props
      // 第二个参数 context,attrs、emit、slots
      const position = reactive({  // 使用reactive创建响应式对象
        x: 0,
        y: 0
      })
      const update = e => {
        position.x = e.pageX
        position.y = e.pageY
      }
      // 事件注册函数
      onMounted(() => {
       // 这里可以使用this,因为此时组件已经被创建
        window.addEventListener('mousemove', update)
      })
      // 移除事件,销毁事件
      onUnmounted(() => {
        window.removeEventListener('mousemove', update)
      })
      return toRefs(position)
    }


    const app = createApp({ // createApp创建Vue
      setup () { // setup 是 Composition 的入口
        // 这里不可以用this,因为setup在props解析之后, 组件实例创建之前执行,所以this是undefined
        // const position = useMousePosition()  // 调用封装的功能模块
        // 不可以对响应式对象进行解构
        const { x, y } = useMousePosition()  // 解构对象,通过key获取对象里的value
        return {
          x,
          y
        }
      }
    })
    console.log(app)
    app.mount('#app')
  </script>
</body>
</html>

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

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

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

代码演示: 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <button @click="increase">按钮</button>
    <span>{{ count }}</span>
  </div>
  <script type="module">
    import { createApp, ref } from './node_modules/vue/dist/vue.esm-browser.js' // 加载vue   
    function useCount () {
      const count = ref(0)
      return {
        count,
        increase: () => {
          count.value++
        }
      }
    }
    createApp({
      setup () {
        return {
          ...useCount()
        }        
      }
    }).mount('#app')
  </script>
</body>
</html>

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
   }
})

  代码演示: 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <button @click="push">按钮</button>
    未完成:{{ activeCount }}
  </div>
  <script type="module">
    import { createApp, reactive, computed } from './node_modules/vue/dist/vue.esm-browser.js'
    const data = [
      { text: '看书', completed: false },
      { text: '敲代码', completed: false },
      { text: '约会', completed: true }
    ]
    createApp({
      setup () {
        const todos = reactive(data)
        const activeCount = computed(() => {
          return todos.filter(item => !itempleted).length
        })

        return {
          activeCount,
          push: () => {
            todos.push({
              text: '开会',
              completed: false
            })
          }
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

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 不需要,因为他一开始就指定了依赖。

代码演示

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <p>
      请问一个 yes/no 的问题:
      <input v-model="question">
    </p>
    <p>{{ answer }}</p>

    <hr>
    <button @click="increase">increase</button>
    <button @click="stop">stop</button>
    <br>
    {{ count }}
  </div>

  <script type="module">
    // https://www.yesno.wtf/api
    import { createApp, ref, watch, watchEffect } from './node_modules/vue/dist/vue.esm-browser.js'
    createApp({
      setup () {
        // 演示watch 部分
        const question = ref('')
        const answer = ref('')
        const fun = ref(null)
        watch (question, async (newValue, oldValue) => {
          const response = await fetch('https://www.yesno.wtf/api')
          const data = await response.json()
          debounce()
        })
        // 防抖
        debounce(() => {
            if (fun.value !== null){
                clearTimeout(fun)
            }
            fun.value = setTimeout(() => answer.value = data.answer, 1000)
        })

        // 演示watchEffect 部分
        const count = ref(0)
        const stop = watchEffect(() => {
          console.log(count.value)
        })
        return {
          question,
          answer,
          count,
          stop,
          increase: () => {
            count.value++
          }
        }
      }
    }).mount("#app")
  </script>
</body>
</html>

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中需要返回布尔类型的值 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script>
    'use strict'
    解决问题1:proxy的 set 和 deleteProperty中需要返回布尔类型的值
             【默认没有返回,则默认值是undefined,转化为布尔值是false】
              在严格模式下,如果返回 false 的话会出现 Type Error 的异常

    const target = {
      foo: 'xxx',
      bar: 'yyy'
    }
    const proxy = new Proxy(target, {
      get (target, key, receiver) {
        // return target[key], 
        // receiver:是当前的proxy对象,或者继承的proxy对象
        // Reflect是反射的意思,是es6新增的成员:用来设置或者获取对象中的成员如下:
        // Reflect.getPrototypeOf() 类似 Object.getPrototypeOf()
        return Reflect.get(target, key, receiver)
      },
      set (target, key, value, receiver) {
        // target[key] = value
        return Reflect.set(target, key, value, receiver) // 设置成功返回:true; 设置失败返回:false
      },
      deleteProperty (target, key) {
        // delete target[key]
        return Reflect.deleteProperty(target, key) // 删除成功返回:true; 删除失败返回:false
      }
    })

    proxy.foo = 'zzz'
    // delete proxy.foo
  </script>
</body>
</html>

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

<body>
  <script>
    'use strict'
     解决问题2:Proxy 和 Reflect 中使用的 receiver
    // Proxy 中 receiver:是 Proxy 对象 或者继承 Proxy 的对象
    // Reflect 中 receiver:如果 target 对象中设置了 getter,getter 中的 this 指向 receiver

    const obj = {
      get foo() {
        console.log(this)
        return this.bar
      }
    }
    const proxy = new Proxy(obj, {
      get (target, key, receiver) {
        // target[key]
        if (key === 'bar') { 
          return 'value - bar' 
        }
        // return Reflect.get(target, key) // 则foo()里的this指向obj
        // 下面这句会触发执行 proxy的get方法
        return Reflect.get(target, key, receiver) // receiver指向proxy,则foo()里的this指向proxy
      }
    })
    console.log(proxy.foo)
  </script>
</body>

 2:reactive

    1: reactive 原理

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

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

  •  index.html
  • <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <script type="module">
        import { reactive } from './reactivity/index.js'
        const obj = reactive({
          name: 'zs',
          age: 18
        })
        obj.name = 'lisi'
        delete obj.age
        console.log(obj)
      </script>
    </body>
    </html>
  •  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()

4: effect ,track,trigger

<body>
  <script type="module">
    import { reactive, effect } from './reactivity/index.js'
    const product = reactive({
      name: 'iPhone',
      price: 5000,
      count: 3
    })
    let total = 0 
    effect(() => {
      total = product.price * product.count
    })
    console.log(total)

    product.price = 4000
    console.log(total)

    product.count = 1
    console.log(total)

  </script>
</body>

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

5:ref

 reactive vs ref

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

      

<body>
  <script type="module">
    import { reactive, effect, ref } from './reactivity/index.js'

    const price = ref(5000)
    const count = ref(3)
   
    let total = 0 
    effect(() => {
      total = price.value * count.value
    })
    console.log(total)

    price.value = 4000
    console.log(total)

    count.value = 1
    console.log(total)

  </script>
</body>

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

6:toRefs

<body>
  <script type="module">
    import { reactive, effect, toRefs } from './reactivity/index.js'

    function useProduct () {
      const product = reactive({
        name: 'iPhone',
        price: 5000,
        count: 3
      })
      return toRefs(product)
    }

    const { price, count } = useProduct()

    let total = 0 
    effect(() => {
      total = price.value * count.value
    })
    console.log(total)

    price.value = 4000
    console.log(total)

    count.value = 1
    console.log(total)

  </script>
</body>

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

7:computed 

<body>
  <script type="module">
    import { reactive, effect, computed } from './reactivity/index.js'

    const product = reactive({
      name: 'iPhone',
      price: 5000,
      count: 3
    })
    // computed 返回的是一个ref创建的对象,需要用.value来获取
    let total = computed(() => {
      return product.price * product.count
    })
   
    console.log(total.value)

    product.price = 4000
    console.log(total.value)

    product.count = 1
    console.log(total.value)

  </script>
</body>

  代码方法集合演示:【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 实现原理 

更多推荐

vue3.0