初识 Vue3.0

1,030 阅读4分钟

这是新入职以来的第一篇技术博客。虽然目前工作中暂时没有用到 Vue3.0,但是还是要和社区保持同步,了解一门新的技术还有它的底层原理是件快乐的事。这个周末宅在家里无所事事,把李南江老师关于 Vue3.0 的讲解视频刷了几遍,鉴于我金鱼般三秒的记性,觉得有必要在此做个记录。冬季的午后暖洋洋,小奶狗躺在我的腿上打鼾,多么美好的周末吖~

Composition API

诞生背景

组合API(Composition API)是 Vue3.0 的一个新特性。它的诞生主要是解决 Vue2.0 存在的问题,即数据和业务逻辑分离的问题。比方说我们要实现一个功能,我们首先要在 data 中初始化相关的数据,然后再找到 methods 方法(有时候是在 computed 或者 watch),在里面书写我们的业务逻辑。这种数据和业务逻辑完全分离的书写方式,在页面代码量大的情况下,阅读起来可是相当费劲呀,而且也不利于代码的管理和维护。

使用方式

组合API是在 setup() 中定义的,下面我们来简单认识下 setup() 的用法。

<template>
  <div>
    <p>{{count}}</p>
    <button @click="myBtn">加法按钮</button>
  </div>
</template>

<script>
//ref函数注意点:
//ref函数只能监听简单类型的变化,不能监听复杂类型的变化
import {ref} from 'vue'

export default {
  name: 'App',
  // setup函数是组合API的入口函数
  setup() {
    // 定义一个count变量,初始值为0
    // ref()函数的作用是
    // 当变量发生改变的时候,
    // Vue会自动更新UI界面
    let count = ref(0)
    // 在组合API中,可以直接定义方法
    function myBtn() {
      count.value++
    }
    // 在组合API中定义的变量和方法,必须暴露出去才能被外界使用
    return {count,myBtn}
  }
}
</script>

初识组合API的魅力之后,我们来看看它到底是如何解决 Vue2.0 将数据和业务逻辑分离的问题。

<template>
  <div>
    <form>
      <input type="text" v-model="state2.dogs.id"/>
      <input type="text" v-model="state2.dogs.name"/>
      <input type="text" v-model="state2.dogs.age"/>
      <input type="submit" @click="addItem"/>
    </form>
    <ul>
      <li v-for="item in state.dogs" :key="item.id" @click="deleteItem(item.id)">
        {{item.name}} - {{item.age}}
      </li>
    </ul>
  </div>
</template>

<script>
//ref函数注意点:
//ref函数只能监听简单类型的变化,不能监听复杂类型的变化
//reactive函数可以监听复杂数据类型的变化
import {ref,reactive} from 'vue'

export default {
  name: 'App',
  // setup函数是组合API的入口函数
  setup() {
    let state = reactive({
      dogs: [{
        id: 1, name: 'Sugar', age: 1
      },{
        id: 2, name: 'Momo', age: 2
      },{
        id: 3, name: 'Bobo', age: 3
      },{
        id: 4, name: 'Kara', age: 4
      }]
    })

    let state2 = reactive({
      dogs: {
        id: '',
        name: '',
        age: ''
      }
    })

    // 点击删除该数据
    function deleteItem (id) {
      state.dogs = state.dogs.filter(item => id !== item.id)
      console.log(state.dogs)
    }

    // 点击添加数据
    function addItem (e) {
      e.preventDefault()
      const dog = Object.assign({}, state2.dogs)
      state.dogs.push(dog)

    }

    return {state,state2,deleteItem,addItem}
  },
}
</script>

当然我们也可以将方法抽取出来

<script>
import {ref,reactive} from 'vue'

export default {
  name: 'App',
  // setup函数是组合API的入口函数
  setup() {
    let {state,deleteItem} = useDeleteDogModule()
    let {state2,addItem} = useAddDogModule(state)
    return {state,state2,deleteItem,addItem}
  },
}

// 删除模块
function useDeleteDogModule () {
  let state = reactive({
    dogs: [{
      id: 1, name: 'Sugar', age: 1
    },{
      id: 2, name: 'Momo', age: 2
    },{
      id: 3, name: 'Bobo', age: 3
    },{
      id: 4, name: 'Kara', age: 4
    }]
  })
  // 点击删除该数据
  function deleteItem (id) {
    state.dogs = state.dogs.filter(item => id !== item.id)
    console.log(state.dogs)
  }
  return {state,deleteItem}
}
// 新增模块
function useAddDogModule (state) {
  let state2 = reactive({
    dogs: {
      id: '',
      name: '',
      age: ''
    }
  })

  // 点击添加数据
  function addItem (e) {
    e.preventDefault()
    const dog = Object.assign({}, state2.dogs)
    state.dogs.push(dog)
  }
  return {state2,addItem}
}
</script>

这样的话就实现了数据和业务逻辑在同一个模块中,便于后续的管理和维护。 (此图来自李南江老师的视频 www.bilibili.com/video/BV14k…)

关于 setup() 函数注意点

  • setup 函数只能是同步的不能是异步的;
  • setup 函数的执行时机是在 beforeCreate 和 cerated 的生命周期期间,所以在 setup 函数中无法获取 data 和 methods 中的数据,setup 函数中的 this 为 undefined。

响应式原理

我们都知道 Vue2.0 的响应式原理是利用 Object.defineProperty,它有很多弊端,比如不能监听新增和删除属性的变化。而 Vue3.0 则是利用 ES6 的 Proxy。Proxy 的性能和功能都要比 Object.defineProperty 更好。Vue3.0 在实现响应式功能方面封装了好几个 API,下面我们来了解一下。

  • shallowReactive 和 shallowRef 的区别:都是只监听第一层数据的变化(浅监听)。shallowRef 只能监听简单数据的变化,它的底层也是调用 shallowReactive 。shallowReactive 可以监听复杂数据的变化。
  • reactive 和 ref 的区别:递归监听每一层数据的变化。ref 只能监听简单数据的变化,它的底层也是调用 reactive 。reactive 可以监听复杂数据的变化。
  • shallowReadonly 和 readonly 的区别:表示数据不可修改。shallowReadonly 使得第一层数据是可读的。readonly 递归遍历每一层数据,使得每一层数据都是可读的。
好了,废话不多说,直接上代码,相信你们很快就可以掌握啦。

手写 shallowReactive

/**
 * 
 * @param {*} obj 
 * 该API只会监听到复杂数据第一层数据的变化
 * 所以只会打印一次 更新UI界面
 */
function shallowReactive(obj) {
  return new Proxy(obj, {
    get(obj, key) {
      return obj[key]
    },
    set(obj, key,val) {
      obj[key] = val
      console.log('更新UI界面')
      return true
    }
  })
}

// test
let obj = {
  a: 'a',
  b: {
    c: 'c',
    d: {
      e: 'e',
      f: {
        g: 'g'
      }
    }
  }
}

let state = shallowReactive(obj)

state.a = '1'
state.b.c = '2'
state.b.d.e = '3'
state.b.d.f.g = '4'
 
// 更新UI界面

手写 shallowRef

/**
 * 
 * @param {*} obj 
 * 该API只会监听到复杂数据第一层数据的变化
 * 所以只会打印一次 更新UI界面
 */
function shallowReactive(obj) {
  return new Proxy(obj, {
    get(obj, key) {
      return obj[key]
    },
    set(obj, key,val) {
      obj[key] = val
      console.log('更新UI界面')
      return true
    }
  })
}

/**
 * 
 * @param {*} obj 
 * 该API底层是走shallowReactive方法
 * 只会监听value属性的变化
 */
function shallowRef(val) {
  return shallowReactive({value: val})
}

// shallowRef test
let obj = {
  a: 'a',
  b: {
    c: 'c',
    d: {
      e: 'e',
      f: {
        g: 'g'
      }
    }
  }
}

let state = shallowRef(obj)
// 注意这里的修改方式
state.value = {
  a: '1',
  b: {
    c: '2',
    d: {
      e: '3',
      f: {
        g: '4'
      }
    }
  }
}
// 更新UI界面

// 写成这样则无法修改
// state.value.a = '1'

手写 reactive

/**
 * @param {*} obj 
 * 该API可以监听到复杂数据每一层数据的变化
 * 所以只会打印四次 更新UI界面
 */
function reactive(obj) {
  if (typeof obj === 'object') {
    if (obj instanceof Array) {
      obj.forEach((item, index) => {
        if (typeof item === 'object') {
          obj[index] = reactive(item)
        }
      })
    } else {
      for (key in obj) {
        const item = obj[key]
        if (typeof item === 'object') {
          obj[key] = reactive(item)
        }
      }
    }
  } else {
    console.warn('必须传入对象')
  }
  return new Proxy(obj, {
    get(obj, key) {
      return obj[key]
    },
    set(obj, key,val) {
      obj[key] = val
      console.log('更新UI界面')
      return true
    }
  })
}

// test
let obj = {
  a: 'a',
  b: {
    c: 'c',
    d: {
      e: 'e',
      f: {
        g: 'g'
      }
    }
  }
}

let state = reactive(obj)
state.a = '1'
state.b.c = '2'
state.b.d.e = '3'
state.b.d.f.g = '4'

//更新UI界面
//更新UI界面
//更新UI界面
//更新UI界面

手写 ref

/**
 * @param {*} obj 
 * 该API底层是走reactive方法
 * 同shallowReactive和shallowRef的区别
 */
function ref(val) {
  return reactive({value:val})
}

手写 shallowReadonly

/**
 * 
 * @param {*} obj 
 * 该API表示数据第一层是可读的,不可修改
 * 其他层的数据可以修改
 */
function shallowReadonly(obj) {
  return new Proxy(obj, {
    get(obj, key) {
      return obj[key]
    },
    set(obj, key,val) {
      // 此处不给赋值
      console.warn(`${key}是只读的。`)
    }
  })
}

// test
let obj = {
  a: 'a',
  b: {
    c: 'c',
    d: {
      e: 'e',
      f: {
        g: 'g'
      }
    }
  }
}

let state = shallowReadonly(obj)

state.a = '1'
state.b.c = '2'
state.b.d.e = '3'
state.b.d.f.g = '4'
console.log(state)

// a是只读的。
// { a: 'a', b: { c: '2', d: { e: '3', f: [Object] } } }

手写 readonly

/**
 * @param {*} obj 
 * 该API保证每层的数据都是只读的
 */
function readonly(obj) {
  if (typeof obj === 'object') {
    if (obj instanceof Array) {
      obj.forEach((item, index) => {
        if (typeof item === 'object') {
          obj[index] = readonly(item)
        }
      })
    } else {
      for (key in obj) {
        const item = obj[key]
        if (typeof item === 'object') {
          obj[key] = readonly(item)
        }
      }
    }
  } else {
    console.warn('必须传入对象')
  }
  return new Proxy(obj, {
    get(obj, key) {
      return obj[key]
    },
    set(obj, key,val) {
      console.warn(`${key}是只读的。`)
    }
  })
}

// test
let obj = {
  a: 'a',
  b: {
    c: 'c',
    d: {
      e: 'e',
      f: {
        g: 'g'
      }
    }
  }
}

let state = readonly(obj)
state.a = '1'
state.b.c = '2'
state.b.d.e = '3'
state.b.d.f.g = '4'

// a是只读的。
// c是只读的。
// e是只读的。
// g是只读的。

so easy too happy ~!