vue3.0 Composition API学习及分享!

正文

  • 大家都知道,现在Vue3的各个版本已经陆续发布了,并且有很多的团队已经着手各个库的开发与Vue2向Vue3的升级,我们当然也不能落后,所以赶紧将跟着本文一起学习新的API吧~

版本

  • 当前处于beta版本, 想要正式使用在项目里还需要一段的时间, 但是结构与api变化应该不大了
  • 对于不理解版本的看下面↓↓
1.alpha	内测版本
2.beta	公测版本
3.Gamma	正式发布的候选版本
4.Final 正式版
5.plus	增强版
6.full	完全版
7.Trial	试用版(一般有时间或者功能限制)

vue3.0 的优势在哪?

  • 性能提升
  • 打包大小减少了 41%
  • 初次渲染快 55%
  • 更新快 133%
  • 内存的使用减少了54%
  • vue3 使用typescript 重写 ,更好的兼容typescript

vue3.0和vue2.0的区别

  • 重构响应式系统,使用Proxy替换Object.defineProperty。
  • 使用Proxy优势:可直接监听数组类型的数据变化
    监听的目标为对象本身,不需要像Object.defineProperty一样遍历每个属性,有一定的性能提升
    可拦截apply、ownKeys、has等13种方法,而Object.defineProperty不行
    直接实现对象属性的新增/删除
  • 新增Composition API,更好的逻辑复用和代码组织
  • 重构 Virtual DOM:模板编译时的优化,将一些静态节点编译成常量
    slot优化,将slot编译为lazy函数,将slot的渲染的决定权交给子组件
    模板中内联事件的提取并重用(原本每次渲染都重新生成内联函数)
  • 代码结构调整,更便于Tree shaking,使得体积更小
  • 使用Typescript替换Flow
  • 当然vue3.0,完全兼容vue2.0,不喜欢新语法的伙伴可以保持使用2.0语法

vue3.0的环境搭建

yarn global add @vue/cli
# OR
npm install -g @vue/cli
  • 具体安装流程

Compsition API :
setup函数

  • 在说setup前,我们先了解一下2.0组件属于选项分离式。
  • 如:我们写代码使用 (data、computed、methods、watch) 组件选项来组织逻辑。然而,当我们的组件开始变得更大时,逻辑关注点的列表也会增长。尤其对于那些一开始没有编写这些组件的人来说,这会导致组件难以阅读和理解。
  • 这种碎片化使得理解和维护复杂组件变得困难。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块。
  • setup 就是解决了以上问题,能够将同一个逻辑关注点相关代码收集在一起会更好。而这正是组合式 API
  • setup 函数也是 Compsition API 的入口函数,我们的变量、方法都是在该函数里定义的
  • setup 也是生命周期函数,代替beforeCreate、created
<template>
  <div>
    <span style="padding: 10px;">{{num}}</span>
    <button @click="btnClickFn">增加</button>
  </div>
</template>

<script>
// ref 是 vue 3.0 的一个重大变化,其作用为创建响应式的值
import { ref } from 'vue'  // 带 ref 的响应式变量
export default {
  name: 'Hone',
  setup(){
    // 定义了一个响应式常量
    const num = ref(0)
    // 定义点击按钮事件改变num值
    const btnClickFn = ()=>{
      // 通过.value 修改值
      num.value +=1
    }
    console.log(this)	// undefined
    // 导出以上定义的模板
    return{
      num,
      btnClickFn
    }
  }
}
</script>
  • 划重点:
  • 上述代码中用到了 ref 函数,下面会详细讲解,在这里你只需要理解它的作用是包装一个响应式的数据即可,并且你可以将 ref 函数包装过的变量看作是Vue2 data 中的变量
    这样就简单实现了一个点击按钮数字加1的功能
  • ref 接收参数并将其包裹在一个带有 value property 的对象中返回,然后可以使用该 property 访问或更改响应式变量的值:
  • 将值封装在一个对象中,看似没有必要,但为了保持 JavaScript 中不同数据类型的行为统一,这是必须的。这是因为在 JavaScript 中,Number 或 String 等基本类型是通过值而非引用传递的。
  • 在Vue2中,我们访问 data 或 props 中的变量,都是通过类似 this.number 这样的形式去获取的,但要特别注意的是,在setup中,this 指向的是 undefined,也就是说不能再向Vue2一样通过 this 去获取变量了
    那么到底该如何获取到 props 中的数据呢?
    其实 setup 函数还有两个参数,分别是 props 、context,前者存储着定义当前组件允许外界传递过来的参数名称以及对应的值;后者是一个上下文对象,能从中访问到 attr 、emit 、slots
    其中 emit 就是我们熟悉的Vue2中与父组件通信的方法,可以直接拿来调用

Compsition API :
ref 响应式引用

  • 通过ref定义一个count 属性,通过点击实现响应
<template>
  <div id="app">
   {{ count }}
  </div>
  <button @click="btnClickFn()"></button>
</template>
<script>
import {ref} from 'vue'
export default {
  name: 'App',
  setup() {
    const count = ref(0)
    const btnClickFn = ()=>{
		 count.value +=2
	}
    return {
    	count 
    }
  }
}
</script>
  • vue2.0 模板引用怎么用?
<template>
  <div id="app" ref="root">
   {{ count }}
  </div>
  <button @click="btnClickFn()"></button>
</template>
<script>
import {ref} from 'vue'
export default {
  name: 'App',
  setup() {
    const count = ref(0)
    const root = ref(null)
    const btnClickFn = ()=>{
		 count.value +=2
	}
	onMounted(()=>{
	// DOM元素将在初始渲染后分配给ref
      console.log(root) // 
这是根元素
}) return { count, root } } } </script>

Compsition API :
reactive响应式对象

  • 我们已经了解普通响应数据的绑定了。但是,那只是普通数据,我们在实际开发中,用到的对象数据是最多的。这一节,我们就来讲讲响应对象数据的绑定。
<template>
  <div id="app">
   {{ state.count }}
  </div>
</template>

<script>
// 从 vue 中导入 reactive 
import {reactive} from 'vue'
export default {
  name: 'App',
  setup() {
    // 创建响应式的数据对象
    const state = reactive({count: 3})

    // 将响应式数据对象state return 出去,供template使用
    return {state}
  }
}
</script>
  • 划重点:
  • reactive 方法是用来创建一个响应式的数据对象,该API也很好地解决了Vue2通过 defineProperty 实现数据响应式的缺陷

Compsition API :
toRefs

  • 其作用就是将传入的对象里所有的属性的值都转化为响应式数据对象,该函数支持一个参数,即 obj 对象
  • 从上面代码中我们不难发现,使用reactive响应式对象后,template模板中使用必须以{{state.xxx}}来取值,很不方便,我希望的是{{xxx}}来取值,怎么办呢?其实很简单:
// 导入toRefs 
import { reactive, toRefs } from 'vue'
// 改变 return
return {
	...toRefs(state)
}
// 这里要注意,直接...展开的是没有响应普通值,所以我们需要toRefs转换为响应式值;

Compsition API :
生命中周期函数

  • Vue2中有 beforeCreate 、created 、beforeMount 、mounted 、beforeUpdate 等生命周期函数
    而在Vue3中,这些生命周期部分有所变化,并且调用的方式也有所改变,下面放上一张变化图来简单了解一下
vue2 vue3
beforeCreate setup()
created setup()
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
beforeDestroy onBeforeUnmount
destroyed onUnmounted
errorCaptured onErrorCaptured
activated onActivated
deactivated onUpdated
updated onDeactivated
  • 划重点:
  • beforeDestroy、destroyed 被替换为了onBeforeUnmount、onUnmounted
  • 去掉了beforeCreate、created请直接使用setup
  • 新增了onRenderTracked、onRenderTriggered(两个钩子都收到DebuggerEvent类似于onTrack和onTrigger观察者的选项
  • 要特别说明一下的就是,setup 函数代替了 beforeCreate 和 created 两个生命周期函数,因此我们可以认为它的执行时间在beforeCreate 和 created 之间

computed(计算函数)

  • 具有缓存机制
 // 计算属性
  setup() {
    const state = reactive({
      num: 0
    })
  
    // 计算属性
    const newNum = computed(()=>{
      return state.num * 2
    })
    return {
      newNum,
      ...toRefs(state),
    };
  },
  • 计算属性默认只有 getter,不过在需要时你也可以提供一个 setter:
  setup() {
    const state = reactive({
      num: 0
    })
    // 计算属性
    const newNum = computed({
      get (){
        return state.num * 2
      },
      set (newVal){
        console.log(newVal)
        return newVal * 2
      }
    })
    return {
      newNum,
      ...toRefs(state),
    };
  },

Compsition API :
watch && watchEffect (监听)

  • watch 和 watchEffect 都是用来监视某项数据变化从而执行指定的操作的,但用法上还是有所区别
    watch:watch( source, cb, [options] )
    参数说明:
    source:可以是表达式或函数,用于指定监听的依赖对象
    cb:依赖对象变化后执行的回调函数
    options:可配置参数,可以配置的属性有 immediate(立即触发回调函数)、deep(深度监听)
  • 当监听 ref 类型时:
<script>
import {ref, watch} from 'vue'
export default {
    setup() { 
        const state = ref(0)

        watch(state, (newValue, oldValue) => {
          console.log(`原值为${oldValue}`)
          console.log(`新值为${newValue}`)
          /* 1秒后打印结果:
                  原值为0
                  新值为1
          */
        })

        // 1秒后将state值+1
        setTimeout(() => {
          state.value ++
        }, 1000)
    }
}
</script>
  • 当监听 reactive 类型时:
<script>
import {reactive, watch} from 'vue'
export default {
    setup() { 
        const state = reactive({count: 0})

        watch(() => state.count, (newValue, oldValue) => {
          console.log(`原值为${oldValue}`)
          console.log(`新值为${newValue}`)
          /* 1秒后打印结果:
                  原值为0
                  新值为1
          */
        })

        // 1秒后将state.count的值+1
        setTimeout(() => {
          state.count ++
        }, 1000)
    }
}
</script>
  • 当同时监听多个值时:
<template>
  <div>
  </div>
</template>

<script>
import {reactive, watch} from 'vue'
export default {
    setup() { 
        const state = reactive({ count: 0, name: 'zs' })

         watch(
            [() => state.count, () => state.name], 
            (newval,oldval) => {	// ([newcount,newname],[oldcound,oldname]) 对应取值
             console.log(newval)  // 通过下标获取对应的值
             console.log(oldval)  // 通过下标获取对应的值
            }
          )
          
          setTimeout(() => {
            state.count ++
            state.name = 'ls'
          }, 1000)
    }
}
</script>
  • watch方法会返回一个stop方法,若想要停止监听,便可直接执行该stop函数
  • 接下来再来聊聊 watchEffect,它与 watch 的区别主要有以下几点:
    1、不需要手动传入依赖
    2、每次初始化时会执行一次回调函数来自动获取依赖
    3、无法获取到原值,只能得到变化后的值
  • 来看一下该方法如何使用:
<script>
import {reactive, watchEffect} from 'vue'
export default {
    setup() { 
          const state = reactive({ count: 0, name: 'zs' })

          watchEffect(() => {
          console.log(state.count)
          console.log(state.name)
          /*  初始化时打印:
                  0
                  zs

            1秒后打印:
                  1
                  ls
          */
          })

          setTimeout(() => {
            state.count ++
            state.name = 'ls'
          }, 1000)
    }
}
</script>
  • 从上述代码中可以看出,我们并没有像 watch 方法一样先给其传入一个依赖,而是直接指定了一个回调函数
  • 当组件初始化时,将该回调函数执行一次,自动获取到需要检测的数据是 state.count 和 state.name
  • 根据以上特征,我们可以自行选择使用哪一个监听器

watchEffect 侦听模板引用

<template>
  <div ref="root">This is a root element</div>
</template>

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

  export default {
    setup() {
      const root = ref(null)

      watchEffect(() => {
        // 这个副作用在 DOM 更新之前运行,因此,模板引用还没有持有对元素的引用。
        console.log(root.value) // => null
      })

      return {
        root
      }
    }
  }
</script>
  • 因此,使用模板引用的侦听器应该用 flush: ‘post’ 选项来定义,这将在 DOM 更新后运行副作用,确保模板引用与 DOM 保持同步,并引用正确的元素。
<template>
  <div ref="root">This is a root element</div>
</template>

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

  export default {
    setup() {
      const root = ref(null)

      watchEffect(() => {
        console.log(root.value) // => 
}, { flush: 'post' // 如果没有配置,默认DOM 更新之前运行 }) return { root } } } </script>

Compsition API :
toRaw

  • toRaw 方法是用于获取 ref 或 reactive 对象的原始数据的
<template>
 <p>{{ state.name }}</p>
 <p>{{ state.age }}</p>
 <button @click="change">改变</button>
</template>

<script>
import {reactive} from 'vue'
export default {
    setup() {
        const obj = {
          name: '小明',
          age: 22
        }

        const state = reactive(obj) 

        function change() {
          state.age = 90
          console.log(obj); // 打印原始数据obj
          console.log(state);  // 打印 reactive对象
        }

        return {state, change}
    }
}
</script>
  • 我们改变了 reactive 对象中的数据,于是看到原始数据 obj 和被 reactive 包装过的对象的值都发生了变化,由此我们可以看出,这两者是一个引用关系
  • 那么此时我们就想了,那如果直接改变原始数据 obj 的值,会怎么样呢?答案是:reactive 的值也会跟着改变,但是视图不会更新
  • 由此可见,当我们想修改数据,但不想让视图更新时,可以选择直接修改原始数据上的值,因此需要先获取到原始数据,我们可以使用 Vue3 提供的 toRaw 方法
  • toRaw 接收一个参数,即 ref 对象或 reactive 对象
<script>
import {reactive, toRaw} from 'vue'
export default {
    setup() {
        const obj = {
          name: '前端印象',
          age: 22
        }

        const state = reactive(obj) 
        const raw = toRaw(state)

        console.log(obj === raw)   // true
    }
}
</script>
  • 划重点:
  • 上述代码就证明了 toRaw 方法从 reactive 对象中获取到的是原始数据,因此我们就可以很方便的通过修改原始数据的值而不更新视图来做一些性能优化了
  • 注意: 补充一句,当 toRaw 方法接收的参数是 ref 对象时,需要加上 .value 才能获取到原始数据对象

Compsition API :
markRaw

  • markRaw 方法可以将原始数据标记为非响应式的,即使用 ref 或 reactive 将其包装,仍无法实现数据响应式,其接收一个参数,即原始数据,并返回被标记后的数据
<template>
 <p>{{ state.name }}</p>
 <p>{{ state.age }}</p>
 <button @click="change">改变</button>
</template>

<script>
import {reactive, markRaw} from 'vue'
export default {
    setup() {
        const obj = {
          name: '前端印象',
          age: 22
        }
        // 通过markRaw标记原始数据obj, 使其数据更新不再被追踪
        const raw = markRaw(obj)   
        // 试图用reactive包装raw, 使其变成响应式数据
        const state = reactive(raw) 

        function change() {
          state.age = 90
          console.log(state);
        }

        return {state, change}
    }
}
</script>
  • 划重点:
  • 我们修改了值也不会更新视图了,即没有实现数据响应式

Compsition API :
provide && inject

  • 与 Vue2中的 provide 和 inject 作用相同,只不过在Vue3中需要手动从 vue 中导入
  • provide :向子组件以及子孙组件传递数据。接收两个参数,第一个参数是 key,即数据的名称;第二个参数为 value,即数据的值
  • inject :接收父组件或祖先组件传递过来的数据。接收一个参数 key,即父组件或祖先组件传递的数据名称,第二个参数:默认值 (可选)
  • 假设这有三个组件,分别是 A.vue 、B.vue 、C.vue,其中 B.vue 是 A.vue 的子组件,C.vue 是 B.vue 的子组件
// A.vue
<script>
import {provide} from 'vue'
export default {
    setup() {
        const obj= {
          name: '前端印象',
          age: 22
        }

        // 向子组件以及子孙组件传递名为info的数据
        provide('info', obj)
    }
}
</script>

// B.vue
<script>
import {inject} from 'vue'
export default {
    setup() { 
        // 接收A.vue传递过来的数据
        inject('info')  // {name: '前端印象', age: 22}
    }
}
</script>

// C.vue
<script>
import {inject} from 'vue'
export default {
    setup() { 
        // 接收A.vue传递过来的数据
        inject('info')  // {name: '前端印象', age: 22}
    }
}
</script>
  • 注意:
  • 为了增加 provide 值和 inject 值之间的响应性,我们可以在 provide 值时使用 ref 或 reactive。
  • 当使用响应式 provide / inject 值时,建议尽可能将对响应式 property 的所有修改限制在定义 provide 的组件内部。
  • 然而,有时我们需要在注入数据的组件内部更新 inject 的数据。在这种情况下,我们建议 provide 一个方法来负责改变响应式 property。
# 父组件
export default {
  name: "Home",
  setup() {
    const state = reactive({
      num: 0,
      updateFn(){
        state.num +=1
      }
    })
    const data  = toRefs(state) // 响应式数据
    provide('user',data.num) 
    provide('update',data.updateFn)	// 父组件定义修改事件,子组件来接受调用
    return {
      ...toRefs(state),
    };
  },
};

# 子组件
  setup(props,context) {
   
    const num = inject('user')
    const updata = inject('update') // 注意inject 不能定义在reactive 对象里!
    const state = reactive({
      addClickFn(){
        console.log(updata) // 返回一个property 对象
        console.log(updata.value()) // 调用修改方法
      }
    })
    
    return {
      num,
      ...toRefs(state)
    }
  }

Compsition API :
getCurrentInstance

  • 我们都知道在Vue2的任何一个组件中想要获取当前组件的实例可以通过 this 来得到,而在Vue3中我们大量的代码都在 setup 函数中运行,并且在该函数中 this 指向的是 undefined,那么该如何获取到当前组件的实例呢?
  • 这时可以用到另一个方法,即 getCurrentInstance
// min.js
app.config.globalProperties.$hello = () => {
    console.log('hi~hello')
}
<template>
 <p>{{ num }}</p>
</template>
<script>
import {ref, getCurrentInstance} from 'vue'
export default {
    setup() { 
        const num = ref(3)
        const instance = getCurrentInstance() // 这里面可以看到你全局定义的属性
        console.log(instance)
		// 如果熟悉里面的数据可以写成: 
		// const { ctx ,proxy }= getCurrentInstance()
		// conosle.log(proxy.$hello)
		// 你会发现,ctx 和 proxy  里面没有$hello属性,但是,proxy 可以访问到
        return {num}
    }
}
</script>
  • 划重点:
  • 我们重点来看一下打印出来的ctx 和 proxy,因为这两个才是我们想要的 this 的内容
  • 可以看到 ctx 和 proxy 的内容十分类似,只是后者相对于前者外部包装了一层 proxy,由此可说明 proxy 是响应式的
  • **注意:**看尤大的意思好像是不建议这么用了。但好像大家一时半会也没办法直接从vue2的模式跳出来。具体请移驾:vue/rfcs
    VUE3不推荐在原型链上挂载一些东西这种适用方法,而是推荐使用依赖注入:provide和inject。

Compsition API :
ref(获取标签元素)

  • 在Vue2中,我们获取元素都是通过给元素一个 ref 属性,然后通过 this.$refs.xx 来访问的,但这在Vue3中已经不再适用了
<template>
  <div>
    <div ref="el">div元素</div>
  </div>
</template>

<script>
import { ref, onMounted } from 'vue'
export default {
  setup() {
    // 定义或创建一个DOM引用,名称必须与元素的ref属性名相同
    const el = ref(null)

    // 在挂载后才能通过 el 获取到目标元素
    onMounted(() => {
      el.value.innerHTML = '内容被修改'
    })

    // 把创建的引用 return 出去
    return {el}
  }
}
</script>
  • 划重点:
  • 先给目标元素的 ref 属性设置一个值,假设为 el
  • 然后在 setup 函数中调用 ref 函数,值为 null,并赋值给变量 el,这里要注意,该变量名必须与我们给元素设置的 ref 属性名相同
  • 把对元素的引用变量 el 返回(return)出去
  • 当然如果我们引用的是一个组件元素,那么获得的将是该组件的实例对象,这里就不做过多的演示了
  • 注意:设置的元素引用变量只有在组件挂载后才能访问到,因此在挂载前对元素进行操作都是无效的

未完待续~~请关注vue3.0基础开发使用及分享(如:组件通信、状态管理Vuex 、路由router等)

你可能感兴趣的:(vue3.0,学习,typescript,javascript)