初探Vue3.0--响应式api理解~

掘金的第一篇文章献给vue3.0,html

vue3.0的正式版发布有必定的时间了,忙于其余,虽然好奇但没有进行比较和学习,终于不负本身“指望”,打败了“懒惰”,学习相关熟悉的技术新版本,其实入手仍是比较简单的(坐等打脸),本文主要是笔者本身在学习过程当中的探索;vue

组合api(Composition API)

最大的感觉,组合api是将具备独立功能的函数封装组合在一块儿,提升复用性; 后来实际封装了相关功能,变量较多的状况下,设计高内聚的功能是很是必要的react

demo 学生和教师显示

分别用vue2.x以及vue3.x进行了学生列表和老师列表的展现,其代码功能分部图以下图所示es6

vue2.x 学生和老师展现代码功能图 vue 2.x
api

vue 3.x! 学生和老师展现代码功能图 其中不一样颜色表示页面中两个功能的代码分布;markdown

  • 对于vue2.x而言,主要分为三个部分
    • data中的数据定义
    • methods中的方法定义
    • 视图使用
  • 对于Vue3.x而言
    • 学生、老师中的变量和功能定义
    • setup中引用功能
    • 视图使用

根据颜色而言,其实vue2.x的功能相对而言是比较分散的,而vue3.0中的功能和数据定义能够定义在一个函数中,功能比较集中;所以在进行组合性api的是时候是须要思考如何设计是比较合理的;vue3.x在功能上可以实现高度的一个复用,想到vue.mixin的方法,实现代码的复用;数据结构

响应式数据定义

响应式数据是实现mvvm很是重要的一个部分,在vue3.x中,在支持data方式定义响应式的数据外,还提供不少可自定义的api;app

在响应式的数据中值得注意的部分 深层次响应仍是浅层次响应;dom

reactive

定义响应式数据的一种方式,经过 Proxy包装传入的变量,借助Proxy提供的set和get方法,实现响应式数据的逻辑异步

  • vue3中提供实现响应式数据的方法
  • vue2中响应式数据是经过 O b j e c . d e f i n e P r o p e r t y \color{red}{Objec.defineProperty} 来实现的,而在vue3中数据经过es6的 P r o x y \color{red}{Proxy} 来实现的,

reactive使用

setup(props) {
     let state =reactive({
        msg:{
          name:'mafe',
          age:24, 
        },
        time: new Date(), //只能进行 赋值更新
        list:[
          {name:'mfy'},
          {name:'mfy1'},
          {name:'mfy2'},
        ]
     })
     function changeName(){
       //定义的是对象可以直接修改赋值
       state.msg.name = '333'
        //没法更改state中time的赋值对象 
       // state.time.setDate(state.time.getDate()+1)
       let newDate =new Date(state.time.getTime())
       newDate.setDate(state.time.getDate()+1) 
       state.time = newDate ;//不是对象类型 只能修改数据内容
     }
     return  {
       state,
       changeName
     }
  }

复制代码

reactive定义数据

reactive 使用注意事项

  • reactive定义的基础类型数据
    • 使用只能经过state.time = XXX 赋值的方式进行更改;
    • 得到到的只能是当前的值,好比time初始使用new Date() 进行赋值,获得的time的原型并不是是Date的,再次修改time的值的时候不能经过setDate赋值;
  • reactive定义复杂数据类型
    • 对象的每一层都会被包装成一个Proxy的代理,用于页面的响应式数据构建;
    • 更改对象中的某一个属性的值都会所以视图的响应;

ref

也是定义响应式数据的一种,一般用来定义基础类型数据,以及获取dom元素的内容 reactive 一般用于定义对象,在咱们实际的使用过程当中是很是不便捷的,咱们仅仅是想要某个变量实现响应式,ref是对简单的值进行监听;

ref使用

ref的本质仍是reactive,系统会自动根据咱们传入的值转换成ref(xxx)->reactive({value:xx})

setup(props) {
    // 传递一个值 
     let age = ref(33);
     function changeAge(){ 
       age.value +=1;
     }
     return{
       age,
       changeAge
     }
  }
复制代码

ref 定义的内容:

ref的注意点

  • 在Vue中使用ref的值不用经过value获取;
  • 在js中使用ref的值必需要用value获取;
  • 在setup 中定义的变量是没法在method中获取的;

triggerRef 更新变量

ref定义对象类型时候更新问题 ref定义的对象类型,若是是只想更改某一层的变化 须要调用triggerRef进行触发更新

// 只想更改ref中某一层的变化
  msg.value.a = 'myy'
  //增长triggerRef 引发视图的更新 
  // Vue3 只提供了triggerRef 没有提供reactRef相关的
  triggerRef(msg)
复制代码

unRef返回其原始值

若是参数为 ref,则返回内部值,不然返回参数自己。这是 val = isRef(val) ? val.value : val

let age = ref(33);
console.log(age)
console.log(unref(age))
复制代码

ref 和reactive的扩展讲解

ref 和reactive的区别

  • 在template中使用ref类型的数据,vue会自动帮咱们计算.value
  • 在template中使用reactive类型的数据,vue不会自动帮咱们计算.value

vue 是如何决定是否自动添加.value

Vue在解析数据以前,会自动判断这个数据是不是ref类型的,若是是,则自动添加上.value,若是不是,则不会自动添加

vue 是如何判断当前数据是不是ref类型的

  • 经过当前按数据的__v_isRef来进行判断的,
  • 若是有私有的属性,而且取值为true,那么就表明是一个ref类型的数据
console.log("判断是不是ref类型数据",isRef(age))
 console.log("判断是不是isReactive类型数据",isReactive(age))
复制代码

监听层级问题

不管是ref仍是reactive的都是递归监听的过程,也就是不管咱们嵌套了多少层级,其每一层的每一个数据都会发生监听;

递归监听

reactive

let state= reactive({
      a:'2',
      gf:{
        b:'b',
        f:'c',
        c:{
          a:3
        }
      }
    })
  // 多个层级 每一个层级内容改变的时候,都会改变;
    function changeValue(){
      console.log(state)
      console.log(state.gf)
    }
复制代码

打印后能够发如今reactive中定义的对象,发现对象中的每一层对象都是经过Proxy进行代理

ref

let msg = ref({
       a:'2',
        gf:{
          b:'b',
          f:'c',
            c:{
            a:3
          }
        }
    }) 
 function changeRef(){
    //直接更改ref 
    console.log(msg.value)
    console.log(msg.value.gf) 
 }
复制代码

非递归监听

只监听对象的第一层,

  • 只有第一层被包装,第二层未被包装;
  • 第一层的数据发生改变,第一层的ui也会变化
  • 第二层的数据修改,可是不会更新ui
  • shallowRef Vue监听的是.value的变化,并非第一层的变化

利用shallowRef 和shallowReactive可以改变当前对象的监听层级

shallowReactive

let state = shallowReactive({
      a:'2',
      gf:{
        b:'b',
        f:'c',
        c:{
          a:3
        }
      }
    })
  function changeValue(){
      console.log(state)
      console.log(state.gf)
    }
复制代码

第二层的对象不会包装成响应式内容

shallowRef

let msg = shallowRef({
       a:'2',
        gf:{
          b:'b',
          f:'c',
            c:{
            a:3
          }
        }
    })
function changeRef(){ 
      console.log(msg)
      console.log(msg.value)
      console.log(msg.value.gf) 
    }
复制代码

注意内容:

  • 经过ref定义的内容会被reactive包装成一个.value的对象,所以这个对象的第一层的就是value
  • 当访问msg.value的时候,其实已经访问它的值了,天然是没有内容的;

readonly

只读属性的修改,使用readonly防止更改响应式对象

存在一组对象,咱们只能读取,可是不能更改,所以可使用readonly进行包裹;

  • 对于对象深度包裹,递归到最底层的变量
let state = readonly({name:'mfy',age:34,attr:{weight:45,height:1.88}})
  function changeReadonly(){
      state.age = 2; //修改第一层 
      state.attr.weight = 2; //修改深层
      console.log(state)
      console.log(isReadonly(state)) 
    }
复制代码

点击进行修改时候

readonly定义的对象和reactive数据结构的区别

const obj = reactive({name:'33'})
  console.log(obj)
  let state = readonly({name:'mfy',age:34,attr:{weight:45,height:1.88}})
  console.log(state)
复制代码

  • reactive定义的参数在Proxy函数的set支持传入四个参数
  • readonly只能收到两个参数,在set中直接进行警告提示

shallowReadonly

shallowRef以及shallowReactive的性质同样,只能控制第一层的数据是不可修改的;

let state = shallowReadonly({name:'mfy',age:34,attr:{weight:45,height:1.88}})
console.log(state)
 function changeReadonly(){
   state.age = 2; //修改第一层
   state.attr.weight = 2; //修改第二层
   console.log(state) 
   console.log(state.attr)
}
复制代码

  • 第二层数据的对象发生变化
  • 视图没法进行更新,由于第二层的数据不是响应式的
  • shllowReadonly 非递归包装,只包装第一层数据,第二层将不会进行包装

不得不比较const

const也是定义的变量不能在修改,可是对于对象除外;

  • const 和 readonly的区别
  • const 赋值保护 不能给变量从新赋值
  • readonly 属性保护,不能给属性从新赋值

toRef

在实际的使用中,咱们可能不只仅是进行一整个属性进行监听,有多是单个的属性进行监听,所以toRef等给咱们提供了固定属性进行监听;

let obj1 = {age:34,name:'mfy'} 
  let state2 = toRef(obj1,'name')
 
  function changeRef(){
     state2.value = 333
     console.log('obj1',obj1)
     console.log('state2',state2) 
   }
复制代码

toRef修改的值不只仅使定义的值发生了改变,其原始值也发生了改变;

  • 利用toRef将某一个对象的属性变成响应式的,此时修改是会影响到原始值
  • 若是响应数据是经过toRef变化的,是不会触发页面更新的

ref和toRef

  • ref
    • 复制,修改响应式数据不会影响到之前的数据
    • 数据发生改变,ui会自动更新
  • toRef
    • 引用 修改响应式数据会影响到之前怼数据
    • 数据发生改变,ui不会自动更新

toRefs

将响应式对象转化为普通对象,其中结果对象的每一个 property 都是指向原始对象相应 propertyref

定义多个属性的响应式

let state3 = toRefs(obj1,'name','age')   
复制代码

转化reactive定义的响应式

let obj = {age:34,name:'mfy'} 
 let state =reactive(obj);
 console.log(state) 
 let state2 = toRefs(state)
 console.log(state2) 
复制代码

打印出来statestate2

state2中的引用和原始数据类型以及经过reactive定义的类型都是属于同一引用关系;即修改objstatestate2中的任何一个元素属性值,都会发生改变;

let obj = {age:34,name:'mfy'} 
   let state =reactive(obj);
   console.log(state) 
   let state2 = toRefs(state)
   console.log(state2) 
  
   function changeRef(){  
     //修改值 
     obj.age = 232323;
     console.log('state2',state2)
     console.log('state',state)
     console.log('obj',obj)

   }
复制代码

使用场景

使用reactive定义的对象是不具备响应式的,所以在使用的时候没法进行结构出来使用,而toRefs能够定义每一个属性都是响应式的,

function useFeatureX() {
  const state = reactive({
    foo: 1,
    bar: 2
  }) 
  // 返回时转换为ref
  return toRefs(state)
}
export default {
  setup() {
    // 能够在不失去响应性的状况下破坏结构
    const { foo, bar } = useFeatureX() 
    return {
      foo,
      bar
    }
  }
}
复制代码

总结内容

找到原始数据

被定义的响应式数据,如何找到原始数据呢?

toRaw

reactive或者ref中获得原始数据,主要是作一些不想被监听的事情(提高性能),每次修改都会被追踪,都会被更新UI界面,可是是很是消耗性能的,因此有一些操做咱们不须要追踪,不须要更新ui界面,此时能够经过 toRaw方法拿到原始数据,对原始数据进行修改,这样不会被追踪,不会更新ui界面

追踪reactive的特性进行定义响应式

//默认状况下不是一个响应式数据
   let obj = {age:34,name:'mfy'}
   let state  =reactive(obj); 
   //把建立时候的参数分解出来
   let obj2 = toRaw (state); // 和obj是一个样子
   function changeRef(){ 
     //引用修改的内容,页面不会自动更新 只有经过修改包装的方法才能更新 
      obj2.name = 'myy'  
      //state和obj是引用关系,其中的一个值更改时候,另一个值也会更改
      console.log(state)
      console.log(obj) 
      console.log(obj2) 
   }
复制代码

  • toRaw获取到的变量,是一个引用的关系
  • 修改每个值的是时候,都会影响到所依赖的值

追踪ref定义的数据

对于ref定义的值,必需要准确的告诉toRaw.value 若是经过toRaw 获取ref的数据,必须明确告诉ref.value的值 通过Vue处理以后,.value中保存的是当初建立时传入的那个原始数据;

let obj = {age:34,name:'mfy'} 
   let state  =ref(obj); 
   //把建立时候的参数分解出来
   let obj2 = toRaw(state.value); // 必须指定定义的ref内部变量的value
   function changeRef(){ 
      obj2.name = 'myy'   
      console.log('state',state)
      console.log('obj',obj) 
      console.log('obj2',obj2) 
   }
复制代码

markRaw

markRaw 永远不想被追踪

let obj = {age:34,name:'mfy'}
   //告诉这个对象 永远不能被监听
   obj = markRaw(obj) 
   let state =reactive(obj);  
   function changeRef(){
     state.name ='mfy';//监听到这个的变化
     console.log(state)
   }
复制代码

经过markRaw定义的属性再次被定义成响应式的时候,不管怎样对state进行赋值,是没法更改其内容的;

Composition API 的入口 (setup)

setupvue3.x中加入的一个新的生命周期,也是组合api的入口,因为在执行 setup 时还没有建立组件实例,所以在 setup 选项中没有this。这意味着,除了props 以外,你将没法访问组件中声明的任何属性——本地状态、计算属性或方法。

执行时间

setup ->beforeCreate -> created

  • 因为setup在执行的时候是在beforeCreate以前,所以是没法进行获取到this
  • setup中只能执行同步的,不能是异步的
setup(props) { 
    //定义数据
    let age = ref(33) 
    
     //定义函数
    function changeAge(){
      age.value = 333
    }
    // 必须将变量暴露出去才能进行使用
    return{
      age,
      changeAge
    } 
  }
复制代码

setup中生命周期

在setup的函数中,其内部提供了不少的可进行兼容的方法

setup响应式数据监控方法

computed

用于使用计算属性,能够直接获取到当前拼接出来的值

let firstName = ref("马");
let lastName = ref("fyy") 
let fullName = computed(()=> firstName.value + lastName.value)
console.log(fullName)  
复制代码

若想要直接修改computed的变量,可使用set的方法,将依赖的属性的值进行修改;

let age = ref(12)
 let ageCom = computed({
      get: () => age.value + 1,
      set: val => {
        age.value = val - 1
      }
 })
复制代码

watchEffect

在响应式地跟踪其依赖项时当即运行一个函数,并在更改依赖项时从新运行它。

let firstName = ref("马");
let lastName = ref("fyy") 
let fullName = computed(()=> firstName.value + lastName.value) 
watchEffect(() => { 
  console.log('%c 内容发生改变了','color:yellow',fullName.value)
})
function changeName(){
  firstName.value = 'Ming'+Math.random()   
}
复制代码


每次watchEffect里面的内容发生改变的时候都会触发;

watch

watch API 与选项式 API this.$watch (以及相应的 watch 选项) 彻底等效。watch 须要侦听特定的 data 源,并在单独的回调函数中反作用。默认状况下,它也是惰性的——即,回调是仅在侦听源发生更改时调用。 与 watchEffect 比较,watch 容许咱们:

  • 惰性地执行反作用;
  • 更具体地说明应触发侦听器从新运行的状态;
  • 访问侦听状态的先前值和当前值。

侦听一个数据源

const state = reactive({ count: 0 }) 
 watch(
   ()=>state.count,
   (count,preCount)=>{
     console.log('%c count的值发生修改了','color:yellow','count='+count,' preCount='+preCount)
   }
 ) 
function changeCount(){
  state.count +=1  
}

复制代码

侦听多个数据源

let name = ref('myy')
     let age = ref(22)
     watch([name,age],([name,age],[prevName,prevAge])=>{
       console.log("%c --姓名和年龄更改--",'color:yellow')
       console.log("%c 姓名更改",'color:green','prevName='+prevName,' name='+name )
       console.log("%c 年龄更改",'color:green','prevAge='+prevAge,' age='+age )
     })
     function changeCount(){
       name.value = 'myyyyy'
       age.value +=1  
     }
复制代码

setup 中定义的参数

基础类型 (最准确的来讲 是使用ref定义的基础类型)

  • setup中定义的参数,只能在setup的做用域下进行修改
  • 在外部的method中进行修改,则会报错
  • 在setup暴露出去后,其暴露的就是当前变量的值;
changeValue(){ 
      console.log(this.isShowAddModal) //false
      this.isShowAddModal.value =3
    }
复制代码

复杂数据类型(使用reactive)

在外部的method中进行修改数据 可以修改为功

changeValue(){ 
    console.log(this.heorData)
    this.heorData.list=[] 
  }
//setup内部的监听函数是否会触发
  watch(heorData,()=>{
    console.log("watch:: heroData--数据修改了",heorData)
  }) 
复制代码

调用setup内部暴露的函数,在setup内部是能够被检测到的

  • ref定义的参数,没法在setup外部进行参数的修改
  • reactive定义的参数可以进行修改,而且可以被watch监听到

总结

本身经过文档+视频+实际操做对Vue3.x有了一点点了解,有些内容其实不是不够深刻的,比较好奇的点会经过本身想要了解的内容去实际操做一波;

  • Composition API 的使用
  • 响应式数据的定义
    • 引用类型、基础类型定义
    • 递归响应和非递归响应数据的定义
    • 只读响应数据
  • setup函数的使用

参考文档

官方文档api b站vue3.0入门讲解视频