最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • vue3.0 初体验 && 响应式原理模拟实现

    正文概述 掘金(linh0801)   2020-12-19   558

    创建VUE3 项目

    环境搭建

    npm install -g @vue/cli # OR yarn global add @vue/cli
    vue create todolist-vue3
    # select vue 3 preset
    

    我在搭建的过程中,由于我本地安装的是vue-cli 版本是 3.x,查看vue-cli 命令:

    #--注意V 是大写--
    vue -V
    

    因此,先需要全局更新vue-cli版本到最新:

    npm install -g @vue/cli
    # OR
    yarn global add @vue/cli
    

    成功更新,运行 vue -V, 结果版本还是我原先的版本,怎么办呢?网上找了好多方法,一顿折腾后,还是没有解决问题,时间也不早,关机睡觉。 重新开机后,重新运行vue -V 结果终于显示了预期的了。

    添加待办事项

    vue3.0 初体验 && 响应式原理模拟实现

    JS 的逻辑代码:

    import { ref } from 'vue'
    import './assets/index.css'
     // 1. 添加代办事件
    const useAdd = todos => {
        //  ref 返回一个响应式的对象
        const input = ref('') 
        // 文本框中输入的值添加到待办列表中
        const addTodo = () => {
          // 输入空格,直接返回
          const text = input.value && input.value.trim()
          if (text.length === 0) return 
          // 将输入的待办事件,添加到第一个
          todos.value.unshift({
            text,
            completed: false
          })
          input.value = ''
          console.log(todos.value)
        }
        return {
          input,
          addTodo
        }
      
    }
    export default {
      name: 'App',
      setup () {
        const todos = ref([])
        return {
         // 这个对象中的数据供模板使用
         todos,
         ...useAdd(todos)
        }
      }
    }
    

    template 模板代码:

    <section id="app" class="todoapp">
        <header class="header">
          <h1>todos</h1>
          <input
            class="new-todo"
            placeholder="What needs to be done?"
            autocomplete="off"
            autofocus
            v-model="input"
            @keyup.enter="addTodo"
            >
        </header>
        <section class="main">
          <ul class="todo-list">
            <input id="toggle-all" class="toggle-all" type="checkbox">
            <label for="toggle-all">Mark all as complete</label>
              <li v-for="todo of todos" :key="todo.text">
                {{todo.text}}
              </li>
          </ul>  
         </section> 
      
      
      </section>
    

    删除待办事项

    js 代码:

    // 2. 定义删除待办事件函数
    const useRemove = todos => {
      const remove = todo => {
        // 获取todo的索引
        let index = todos.value.indexOf(todo)
        todos.value.splice(index, 1)
      }
      return {
        remove
      }
    }
    //setup 中调用
    
    setup () {
       ....
        return {
         ..省略..
         ...useRemove(todos)
        }
      }
    

    template 调用删除的代码:

    <button class="destroy" @click="remove(todo)"></button>
    

    编辑待办事件

    这个功能比较复杂,我们可以先将要实现的功能列举出来:

    • 双击待办项,展示编辑文本框
    • 按回车或者编辑文本框失去焦点,修改数据
    • 按esc取消编辑
    • 把编辑文本框清空按回车,删除这一项
    • 显示编辑文本款的时候获取焦点

    js代码:

    // 3. 编辑待办项
    const useEdit = remove => {
                                                                                                                   
      // 记录原有的数据
      let beforEditText = ''
      // 标识编辑的状态
      const editingToDo = ref(null)
      // 开始编辑,需要记录编辑之前的文本数据
      const editTodo = todo => {
        beforEditText = todo.text
        editingToDo.value = todo
      }
      // 完成编辑
      const doneEdit = todo => {
        if (!editingToDo.value) return
    
        todo.text = todo.text.trim()
        // 如果文本框的值为空,则删除这一项
        todo.text || remove(todo)
        // 取消状态状态
        editingToDo.value = null
      }
      // 取消编辑
      const cancelEdit = todo => {
        // 取消状态状态
        editingToDo.value = null
        // 恢复文本数据
        todo.text = beforEditText
      } 
      return {
        editingToDo,
        editTodo,
        doneEdit,
        cancelEdit
      }
    }
    
    export default {
      name: 'App',
      setup () {
        const todos = ref([
          {
            text: '吃饭',
            completed: false,
          },
          {
            text: '睡觉',
            completed: false,
          },
          {
            text: '打豆豆',
            completed: false,
          },
        ])
        const { remove } = useRemove(todos)
        return {
         // 这个对象中的数据供模板使用
         todos,
         remove,
         ...useAdd(todos),
         ...useEdit(remove)
        }
      },
      directives: {
        editingFocus: (el, binding) => {
          binding.value && el.focus()
        }
      }
    }
    

    tempalte 代码:

    <section id="app" class="todoapp">
        <header class="header">
          <h1>todos</h1>
          <input
            class="new-todo"
            placeholder="What needs to be done?"
            autocomplete="off"
            autofocus
            v-model="input"
            @keyup.enter="addTodo"
            >
        </header>
        <section class="main">
          <ul class="todo-list">
            <input id="toggle-all" class="toggle-all" type="checkbox">
            <label for="toggle-all">Mark all as complete</label>
              <li v-for="todo of todos" 
              :key="todo"
              :class="{editing: todo === editingToDo}">
                <div class="view">
               <!-- <input class="toggle" type="checkbox" v-model="todo.completed"> -->
                 <label @dblclick="editTodo(todo)">{{ todo.text }}</label>
                <button class="destroy" @click="remove(todo)"></button>
              </div>
              <input
                class="edit"
                type="text"
                v-editing-focus="todo === editingToDo"
                v-model="todo.text"
                @keyup.enter="doneEdit(todo)"
                @blur="doneEdit(todo)"
                @keyup.esc="cancelEdit(todo)"
                >
              </li>
    
          </ul>  
         </section> 
      
      
      </section>
    

    切换待办事项状态

    • 点击checkbox, 改变所有的待办事项的状态
    • All/Active/Completed, 切换查看不同状态的事项
    • 其它
      • 显示未完成待办项个数
      • 移除所有完成的项目
      • 如果没有待办项隐藏main 和 footer

    点击checkbox, 改变所有的待办事项的状态

    首先我们分析一下实现这一步,需要考虑到两种情况

    1.点击全选框,所有待办选项都要被勾选

    1. 当选项全被选上时,全选框要被勾选

    因此,我们可以定义一个计算属性allDone,绑定全选框。

    • 通过get来获取所有项的状态,当所有的选项状态为已完成时,全选框被选中。
    • 通过 set 来设置将所有的选项状态为 完成 状态

    实现代码:

    const allDone = computed({
       get () {
         // 所有事项都已完成的,返回true
         return !todos.value.filter(todo => !todo.completed).length
       },
       set (value) {
         todos.value.forEach(todo => {
           todo.completed = value
         })
       }
     })
     //---模板中代码---
     <input id="toggle-all" v-model="allDone" class="toggle-all" type="checkbox">
    

    实现效果: vue3.0 初体验 && 响应式原理模拟实现

    All/Active/Completed, 切换查看不同状态的事项

    vue3.0 初体验 && 响应式原理模拟实现

    我们通过点击;页面上这三个链接,来获取相对应状态的事项:

     <ul class="filters">
            <li><a href="#/all">All</a></li>
            <li><a href="#/active">Active</a></li>
            <li><a href="#/completed">Completed</a></li>
          </ul>
    

    在点击链接的时候hash 值会发生变化,因此,我们以这一点作为切入点,首先我们开启hash监听

    onMounted(() => {
        window.addEventListener('hashchange', onHashChange)
        onHashChange()
      }) 
    

    onHashChange, 就是我们要实现功能的核心处理函数,我第一反应就是按照下面的逻辑来实现:

    const onHashChange = () => {
        // 获取hash 值
        const hash = window.location.href.replace('#/', '')
        // type.value = hash
        if (hash === 'all') {
    	// 此处处理 all 的逻辑
        } else if (hash === 'active') {
    	// 此处处理 active 的逻辑
        } else if (hash === 'completed') {
    	// 此处处理 completed 的逻辑	
        }
    

    这样写也并没有什么问题,就是可能会引入比较多的if...else... 逻辑判断的代码,后面我转换思路,优化了这部分逻辑:

    // 定义一个对象用于存放获取all, active, completed三种不同状态的事项方法
    const filter = {
        all: list => list,
        active: list => list.filter(todo => !todo.completed),
        completed: list => list.filter(todo => todo.completed)
      }
    // 定义一个存放当前状态的响应对象,默认“显示全部”
      const type = ref('all')
      // 计算属性依赖type , 调用filter对象中对应的处理函数
      const filterTodos = computed(() => filter[type.value](todos.value))
      // 定义一个用于处理hash 变化的处理函数
      const onHashChange = () => {
        // 获取hash 值
        const hash = window.location.hash.replace('#/', '')
        if (filter[hash]) {
           type.value = hash
        }
    
      }
    

    移除事件:

    onUnmounted (() => {
        window.removeEventListener('hashchange', onHashChange)
      })
    
    

    其它功能点

    • 显示未完成的待办项个数
    // template 代码
    <strong>{{remainTodoCount}}</strong> {{ remainTodoCount > 1 ? 'items': 'item' }} left
    // JS 代码
    // 未完成的事项个数
      const remainTodoCount = computed(() => filter.active(todos.value).length)
    

    vue3.0 初体验 && 响应式原理模拟实现

    • 移除所有完成的事项

    我们将这个方法放到删除待办事件模块中去,统一管理

    // 2. 删除待办事件
    const useRemove = todos => {
      const remove = todo => {
        // 获取todo的索引
        let index = todos.value.indexOf(todo)
        todos.value.splice(index, 1)
      }
      // 删除已经完成的事项
      const removeCompleted = () => {
        todos.value = todos.value.filter(todo => !todo.completed)
      }
      return {
        remove,
        removeCompleted
      }
    }
    

    添加一个删除全部已完成的待办的按钮,这个按钮只有当未完成的待办的个数 小于 全部待办的个数时才出现这个按钮,否则隐藏

    <button class="clear-completed" @click="removeCompleted" v-show="count > remainTodoCount">
             Clean completed
    </button>
    

    存储待办事项

    到目前为止,每次添加好待办事项后,刷新页面,发现原本的数据没有了。因此,我们还需要将待办事项存储到localstorage中去。

    我们都知道,storage 数据的存取在很多场合下都要使用到,因此,我们需要做一个统一的模块来封装这部分逻辑。 在/src/utils下创建了一个名为localStorage.js:

    function parse (str) {
      let value = null
      try{
        value = JSON.parse(str)
      } catch {
        value = null
      }
      return value
    }
    
    function stringify (obj) {
      let value = null
      try {
        value = JSON.stringify(obj)
      } catch {
        value = null
      }
      return value
    }
    
    export default function useLocalStorage () {
      function setItem (key, value) {
        value = stringify(value)
        window.localStorage.setItem(key, value)
      }
      function getItem (key) {
        
        let value = window.localStorage.getItem(key)
        if (value) {
          value = parse(value)
        }
        return value
      }
      return {
        setItem,
        getItem
      }
    }
    

    在 APP.vue 中导入该模块

    import useLocalStorage from './utils/localstorage'
    

    模块导入完毕之后,记住一定要先调用一下, 我原先直接按照以下的错误方法调用

    const key ="TODOSKEY"
    useLocalStorage.getItem(key)
    
    

    正确的做法应该是

    // 先调用 useLocalStorage
    const storage = useLocalStorage()
    

    接着创建一个调用storage 中的存储数据的方法:

    // 5. localstorage存储待办事项
    const useStorage = () => {
      const KEY = 'TODOSKEY'
      const todos = ref(storage.getItem(KEY) || [])
      // 待办事项列表一发生改变就触发set
      watchEffect (() => storage.setItem(KEY, todos.value))
      return todos
    }
    

    到此以完成了所有的功能。具体代码可以访问链接

    Vue3.0 响应式原理

    proxy 使用注意的问题

    我们都知道Vue3.0 响应式原理主要借助的是ES6 中的Proxy ,因此,我们首先要对Proxy 对象有个认识,可以参考链接,这里我们不探讨它的用法,我们来介绍使用Proxy 需要注意的两个问题。

    Q1: set 和 deleteProperty 中需要返回布尔类型的值在严格模式下,如果返回 false 的话会出现 Type Error 的异常,看下面的具体代码 非严格模式:

    const target = {
          foo: 'xxx',
          bar: 'yyy'
        }
      
        const proxy = new Proxy(target, {
          get (target, key, receiver) {
            // return target[key]
           return  Reflect.get(target, key, receiver)
          },
          set (target, key, value, receiver) {
            // 
              Reflect.set(target, key, value, receiver)
          },
          deleteProperty (target, key) {
            // delete target[key]
            return Reflect.deleteProperty(target, key)
          }
        })
    
        proxy.foo = 'zzz'
        console.log(proxy.foo)
        delete proxy.bar
        console.log(proxy.bar) 
    
    

    运行结果:

    vue3.0 初体验 && 响应式原理模拟实现

    此时,在非严格模式下,set 和 deleteProperty 并没有 return 语句,正常打印了结果出来。再来看严格模式,抛出typeError 的异常了 vue3.0 初体验 && 响应式原理模拟实现

    可能还是不太理解什么意思,再开看下面代码:

     set (target, key, value, receiver) {
            // target[key] = value
            
            // Reflect.set(target, key, value, receiver)
            return false 
          },
    

    我们可以直接将set,返回一个false,结果呢? vue3.0 初体验 && 响应式原理模拟实现 和上面没有return 一样. 再来看下严格模式下的正常情况:

     'use strict'
        const target = {
          foo: 'xxx',
          bar: 'yyy'
        }
        // Reflect.getPrototypeOf()
        // Object.getPrototypeOf()
        const proxy = new Proxy(target, {
          get (target, key, receiver) {
            // return target[key]
           return  Reflect.get(target, key, receiver)
          },
          set (target, key, value, receiver) {
            // target[key] = value
            
            return Reflect.set(target, key, value, receiver)
          },
          deleteProperty (target, key) {
            // delete target[key]
             return Reflect.deleteProperty(target, key)
          }
        })
    
        proxy.foo = 'zzz'
        console.log(proxy.foo)
        delete proxy.bar
        console.log(proxy.bar) 
    
    

    结果: vue3.0 初体验 && 响应式原理模拟实现

    最后我们可以知道:在严格模式下,Proxy 的set和deleteProperty需要返回一个true,否则会报TypeError

    Q2: Proxy 和 Reflect 中使用的 receiver

    看代码:

     const obj = {
          get foo() {
            console.log(this)
            return this.bar
          }
        }
    
        const proxy = new Proxy(obj, {
          get (target, key) {
            if (key === 'bar') { 
              return 'value - bar' 
            }
            return Reflect.get(target, key)
          }
        })
        console.log(proxy.foo)
    
    

    运行结果:

    vue3.0 初体验 && 响应式原理模拟实现 分析: 代码在执行到console.log(proxy.foo)时,访问的是obj 中的foo() 此时 this 打印出来的是obj , foo() 中返回的 this.bar ,bar在obj 中未定义,因此 最终foo() 返回undefined

    接下来引入receiver :

     const obj = {
          get foo() {
            console.log(this)
            return this.bar
          }
        }
    
        const proxy = new Proxy(obj, {
          get (target, key, receiver) {
            if (key === 'bar') { 
              return 'value - bar' 
            }
            return Reflect.get(target, key, receiver)
          }
        })
        console.log(proxy.foo)
    

    运行结果:

    vue3.0 初体验 && 响应式原理模拟实现

    分析:

    代码在执行到console.log(proxy.foo)时,访问的是obj 中的foo() 此时 this 打印出来的是Proxy 对象,在foo() 中访问了 Proxy 中的get 方法, 返回了 value-bar ,最终打印出来

    因此可以得出:

    • Proxy 中 receiver:Proxy 或者继承 Proxy 的对象
    • Reflect 中 receiver:如果 target 对象中设置了 getter,getter 中的 this 指向 receiver

    Rective 的模拟实现

    我们先要明确一下要实现的效果,先看下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 Person = reactive({
          name: {
            firstName: 'Lin',
            lastName: 'Huan'
          },
          age: 29,
          height: 165,
          tel: 18649713338
          })
        
          console.log(Person.name)
          console.log(Person.name.firstName)
          
          Person.age = 18
          console.log(Person.age)
    
          delete Person.height
          console.log(Person)
      </script>
      
    </body>
    </html>
    

    按照代码的执行顺序,我们来分析:

    1. console.log(Person.name) 结果:这是一个Proxy 代理对象

    vue3.0 初体验 && 响应式原理模拟实现

    1. console.log(Person.name.firstName) 结果打印: Linh

    2. 改变age 的值,结果打印: 18

    3. 删除 heigth, 打印Person 对象:

    vue3.0 初体验 && 响应式原理模拟实现

    通过以上,我们要明确reactive 能实现一下几个功能:

    • 返回的是一个Proxy 对象
    • 需要对嵌套属性实现响应式
    • 只有对对象进行响应式处理
    • 在调用,修改,删除代理对象的值时,可以拦截数据

    接下来看代码。

    // 公共工具代码
    const isObject = value => value !== null && typeof value === 'object' 
    const convert = target => isObject(target) ? reactive(target): target
    const hasOwnProperty = Object.prototype.hasOwnProperty
    const hasOwn = (target, key) => hasOwnProperty.call(target, key)
    
    export default function reactive (target) {
      // 如果target不是对象,原样返回
      if (!isObject(target)) return target
    
      // 定义一个处理器
      const handler = {
        get (target, key, receiver) {
          // 依赖收集在这里进行
          console.log(`get:${key}`)
          const result = Reflect.get(target, key, receiver)
          // 是对象的回归调用reactivity()
          return convert(result)
        },
        set (target, key, value, receiver) {
          let result = true
          // target 中是否存在该属性
          // 注意坑:此处不需要判断属性是否存在 error:Uncaught ReferenceError: Cannot access 	  //	'hasOwn' before initialization
          // const hasOwn = hasOwn(target, key)
          // 获取旧值,用于判断新旧值是否一样,不一样重新复值
          const oldValue = Reflect.get(target, key)
          if (hasOwn && value !== oldValue) {
            console.log(`set: ${key}-${value}`)
            result = Reflect.set(target, key, value, receiver)
            // 触发更新在这里进行
          }
          return result
        },
        deleteProperty (target, key) {
          let result = true
          const hasKey = hasOwn(target, key)
          if (hasKey) {
             result = Reflect.deleteProperty(target, key)
            //  触发更新
          }
          return result
        }
      }
      return new Proxy (target, handler)
    }
    
    

    运行结果:

    vue3.0 初体验 && 响应式原理模拟实现 从结果可见,我们实现了,上文提到的要实现的几个功能点

    effect 模拟实现watchEffect 的实现

    首先我们明确下effect 的功能点:

    • 传入的回调函数要立即执行
    • 将回调函数缓存起来,提供给依赖收集的时候使用

    具体代码:

    let activeEffect = null
    export function effect (callback) {
      activeEffect = callback
      // 立即执行一次回调函数,访问响应式对象的属性 触发依赖收集
      callback()
      // 为了防止嵌套属性,造成死递归
      activeEffect = null
    }
    
    

    调用代码:

            import {reactive, effect} from './reactivity/index.js'
        const Person = reactive({
          name: {
            firstName: 'Lin',
            lastName: 'Huan'
          },
          age: 29,
          height: 165,
          tel: 18649666638
          })
          let name = null
          effect (() =>{
            name = Person.name.firstName + '-' + Person.name.lastName
          })
          console.log('effect调用:')
          console.log(name)
         
          Person.name.firstName = 'zhang'
          console.log('修改后')
          console.log( 'fistName:',Person.name.firstName)
          console.log("name:", name)
    

    运行结果:

    vue3.0 初体验 && 响应式原理模拟实现

    结果分析:

    • 目前的effect 函数,只是具有了访问响应式对象属性的能力
    • 对内部依赖的属性,还没有具备监听的能力

    依赖收集

    在上文中,我们的effect函数,只是实现了基本的回调函数的调用。接下来, 我们来实现一下依赖收集的过程。首先,我们来看一下依赖收集实现的内部数据结构的关系,如下图: vue3.0 初体验 && 响应式原理模拟实现

    延续上文中的例子,我们现在开始实现依赖收集:

    //  依赖收集
    let targetMap = new WeakMap()
    function track (target, key) {
      debugger
      // 当前没有需要收集的依赖,直接返回
      if (!activeEffect) return
      let depsMap = targetMap.get(target)
      if (!depsMap) {
      
        targetMap.set(target, (depsMap = new Map()))
      }
      let dep = depsMap.get(key)
      if (!dep) {
        depsMap.set(key, (dep = new Set()))
      } 
      dep.add(activeEffect)
    }
    
    

    触发更新:

    // 触发更新
    function trigger (target, key) {
      const depsMap = targetMap.get(target)
      if (!depsMap) return
      const dep = depsMap.get(key)
      if (dep) {
        // 遍历触发所有依赖当前属性的effect函数
        dep.forEach(effect => {
          effect()
        });
      } 
    }
    
    

    测试调用:

    import {reactive, effect} from './reactivity/index.js'
        const Person = reactive({
          name: {
            firstName: 'Lin',
            lastName: 'Huan'
          },
          age: 90,
          height: 165,
          tel: 18649666638
          })
          let name = null
          effect (() =>{
            name = Person.name.firstName + '-' + Person.name.lastName
          })
          console.log('effect调用:')
          console.log(name)
         
          Person.name.firstName = 'zhang'
          console.log('修改后')
          console.log( 'fistName:',Person.name.firstName)
          console.log("name:", name)
          
      </script>
    
    

    运行结果:

    vue3.0 初体验 && 响应式原理模拟实现

    ref 函数模拟实现

    ref函数实现功能点分析:

    • 接收 一个参数,可以是原始值,也可以是对象
    • 参数的情况不同,对应的处理方式:
      • 是ref创建的对象,直接返回
      • 是普通对象,内部调用reactive
      • 原始值,创建一个只有value属性的响应式对像,并返回
    • 依赖收集
    • 触发更新

    具体实现代码:

    // ref
    export function ref (raw) {
      // ref创建的对象直接返回
       if (isObject(raw) && raw.__v__isRef__) return
      //  普通对象转化成响应式对象
      let value = convert(raw)
      const r = {
        __v__isRef__: true, // 标记是否是ref 创建的对象
        get value () {
          // 依赖收集
          track(r, 'value')
          return value
        },
        set value (newValue) {
          if (newValue !== value) {
            raw = newValue
            value = convert(raw)
            trigger(r, 'value')
          }
        }
      }
      return r
    }  
    

    测试代码:

     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)
    

    运行结果:

    vue3.0 初体验 && 响应式原理模拟实现

    从结果中可以看出,通过ref, 我们可以将原始值转化为响应式对象,这个值被存放到该对象内部的value属性中,需要使用.value 的方式才能取到这个值,当然Vue3,在模板中直接使用,无须使用这种方式。

    toRefs 函数模拟实现

    ref函数实现功能点分析:

    • 接收一个Reactive 返回的Proxy对象作为参数,否则直接返回
    • 把传入对象的所有属性,转换成类似于 ref 函数返回的对象,并把转换后的对象挂载到新的对象中返回

    具体实现代码:

    // toRefs
    export function toRefs (proxy) {
      if ( isObject(proxy) && !proxy.__v__isProxy__ ) {
        console.log('需要传入一个响应式对象')
        return
      }   
      const ret = Array.isArray(proxy) ? new Array(proxy.length) : {}
      // 遍历所有的属性,转换成ref 对象
      for (var key in proxy) {
        ret[key] = toProxyRefs(proxy, key)
      }
      return ret
    }
    function toProxyRefs (proxy, key) {
      const r = {
        __v__isRef__: true,
        get value () {
          // 此处可以不用做依赖收集,因为proxy是个响应式的对象
    
          return proxy[key] 
        },
        set value (newValue) {
          if (newValue !== proxy[key]) {
            proxy[key] = newValue  
          }
        }
      }
      return r
    }
    
    

    需要修改reactive 返回的Proxy 对象

    // return new Proxy(target, handler)
    return new Proxy(Object.assign(target, {__v__isProxy__: true,}), handler)
    

    测试代码:

    import { reactive, effect, toRefs } from './reactivity/index.js'
        toRefs({})
        function useProduct () {
          const product = reactive({
            name: 'iPhone',
            price: 5000,
            count: 3
          })
          
          return toRefs(product)
        }
        console.dir(useProduct())
        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)
    
    

    运行结果: vue3.0 初体验 && 响应式原理模拟实现

    从结果中我们可以看出,toRefs将Reactive的对象中的每一个属性都转换成了一个 ref 的响应式对象,这样我们就可以解构出Reactive 中的属性了。

    computed 函数 模拟实现

    computed 函数实现的功能分析:

    • 接收一个getter 回调函数
    • 定义一个ref 对象,ref.value 用于存放getter 函数的返回值

    具体代码:

    // computed 
    export function computed (getter) {
      // 创建一个value 为 undefined 的对象
      const res = ref()
      effect(() => {
        res.value = getter()
      })
      return res
    }
    
    

    测试代码:

    import { reactive, effect, computed } from './reactivity/index.js'
    
        const product = reactive({
          name: 'iPhone',
          price: 5000,
          count: 3
        })
        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)
    

    运行结果: vue3.0 初体验 && 响应式原理模拟实现


    起源地下载网 » vue3.0 初体验 && 响应式原理模拟实现

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元