阅读 264

Vue3初探

Vue3初探

花了大量时间看完了Vue2的核心源码,是时候开始全面接触Vue3了。我们知道Vue3新增了很多特性,Composition ApiTeleportFragmentsEmits Component OptioncreateRenderApi用于创建自定义渲染器。今天我们就来初步探讨一下Componsition Api

Composition API

Composition Api字面意思是组合API,它是为实现基于函数的逻辑复用机制而产生的。为了开始使用Composition Api首先需要一个可以实际使用它的地方----setup

基本使用

<div id="app">
  <h1>Composition API</h1>
  <div>count: {{ state.count }}</div>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
  const { createApp, reactive, computed } = Vue;
  const App = {
    beforeCreate() {
      console.log('beforeCreate');
    },
    setup() {
      console.log('setup');
      const state = reactive({
        count: 0,
        doubleCount: computed(() => state.count * 2)
      })
      return { state }
    },
    created() {
      console.log('created');
    },
  }
  createApp(App).mount('#app')
</script>
复制代码

通过createApp创建组件实例,setup是一个新的组件选项,它是组件内使用Composition API的入口。Vue对外暴露了reactive,这个属性是处理响应式的,接受一个对象,返回一个响应式的代理对象。最后返回的对象将和渲染函数上下文合并。很多文章说setup的执行时间在beforeCreatecreated之间,经过检验,setup的执行时间实际是在beforeCreatecreated之前。

image.png

计算属性
 <div>doubleCount: {{state.doubleCount}}</div>
复制代码
const { computed } = Vue;
setup() {
  const state = reactive({
    count: 0,
    doubleCount: computed(() => state.count * 2)
  })
  return {
    state,
  }
}
复制代码

computed函数传递一个参数,返回一个只读的响应式数据。

事件处理
<div @click="add">count: {{ state.count }}</div>
复制代码
const App = {
 setup() {
     // setup中声明⼀个add函数
     function add() {
         state.count++
     } 
 // 传⼊渲染函数上下⽂
 return { state, add }
 }
}
复制代码

事件的定义和options api还是有很大的区别的,只需要在setup中定义即可,并且需要return

侦听器:watch()
const { watch } = Vue;
const App = {
     setup() {
         // state.count变化cb会执⾏
         watch(() => state.count, (val, oldval) => {
             console.log('count变了:' + val);
         })
     }
}
复制代码
引用对象:单值响应化 ref
<div>counter: {{ counter }}</div>
复制代码
const { ref } = Vue;
const App = {
 setup() {
     // 返回响应式的Ref对象
     const counter = ref(1)
     setTimeout(() => {
     // 要修改对象的value
         counter.value++
     }, 1000);
     // 添加counter
     return { state, add, counter }
 }
}
复制代码

体现可维护性

<div>
    <h1>逻辑组合</h1>
    <div id="app"></div>
  </div>
  <script>
    const { createApp, reactive, onMounted, onUnmounted, toRefs } = Vue;
    // ⿏标位置侦听
    function useMouse() {
      // 数据响应化
      const state = reactive({
        x: 0,
        y: 0
      })
      const update = e => {
        state.x = e.pageX
        state.y = e.pageY
      }
      onMounted(() => {
        window.addEventListener('mousemove', update)
      })
      onUnmounted(() => {
        window.removeEventListener('mousemove', update)
      })
      // 转换所有key为响应式数据
      return toRefs(state)
    }
    // 事件监测
    function useTime() {
      const state = reactive({
        time: new Date()
      })
      onMounted(() => {
        setInterval(() => {
          state.time = new Date()
        }, 1000)
      })
      return toRefs(state)
    }
    // 逻辑组合
    const MyComp = {
      template: `
        <div>x: {{ x }} y: {{ y }}</div>
        <p>time: {{time}}</p>
        `,
      setup() {
        // 使⽤⿏标逻辑
        const { x, y } = useMouse()
        // 使⽤时间逻辑
        const { time } = useTime()
        // 返回使⽤
        return { x, y, time }
      }
    }
    createApp(MyComp).mount('#app')
  </script>
复制代码

从上面的代码可以看出我们把useMouse,useTime函数根据我们需要的业务模块做出了抽离,最后组合到setup中。如果使用options api,则我们的代码量如果过多,业务逻辑变得特别复杂,我们的响应式数据还是需要在data中定义,事件需要在methods中声明,这样就会需要“上下横跳”的现象。此外,这种情况是的理解和维护变得更加的困难。而Composition Api却能够很好的解决这个问题。

响应式

我们知道vue2是通过Object.defineProperty来实现对数据的拦截。但是这个方法是有缺点的。

  • 不能监听数组的变化
  • 必须遍历对象的每个属性
  • 必须深层遍历嵌套的对象
  • 新加或删除属性无法监听

vue2.0对数组做了处理,重写了数组的7个方法。

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})
复制代码

Vue3.0则使用了ES6Proxy特性来很好的解决了这些问题。

function reactive(obj) {
  if (typeof obj !== 'object' && obj != null) {
    return obj
  }
  const observed = new Proxy(obj, {
    get(target, key, receiver) {
      const res = Reflect.get(target, key, receiver)
      console.log(`获取${key}:${res}`)
      return res
    },
    set(target, key, value, receiver) {
      const res = Reflect.set(target, key, value, receiver)
      console.log(`设置${key}:${value}`)
      return res
    },
    deleteProperty(target, key) {
      const res = Reflect.deleteProperty(target, key)
      console.log(`删除${key}:${res}`)
      return res
    }
  })
  return observed
}

const state = reactive({
  foo: 'foo'
 })

 state.foo // yes
 state.foo = 'fooooooo' // yes
 state.dong = 'dong' // yes
 delete state.dong // yes
复制代码

嵌套对象响应式

对于上面的处理方式嵌套的对象不能响应

const state = reactive({
 bar: { a: 1 }
})
// 设置嵌套对象属性
state.bar.a = 10 // no
复制代码

通过递归的方式处理

function isObject(val) {
  if(!val && typeof val === 'object') {
    return true
  }
}
function reactive(obj) {
  if (typeof obj !== 'object' && obj != null) {
    return obj
  }
  const observed = new Proxy(obj, {
    get(target, key, receiver) {
      const res = Reflect.get(target, key, receiver)
      console.log(`获取${key}:${res}`)
       return isObject(res) ? reactive(res) : res
    },
    ···
    ···
  })
  return observed
}
复制代码

以上是对Vue3.0的一个初步了解,后续会对Vue3.0进行全面的学习,目前还只是了解了皮毛,后面会对vue3.0的源码进行解刨,也希望可以通过写文章的形式督促学习。 欢迎留言~~~

文章分类
前端
文章标签