红枫心声社区

 找回密码
 立即注册

手机动态码快速登录

手机号快速注册登录

搜索
热搜: 活动
查看: 307|回复: 0

Vue3.0 完全教程#2

[复制链接]

33

主题

33

帖子

219

积分

正式会员

Rank: 3Rank: 3

积分
219

活跃会员

发表于 2022-4-30 10:05:43 | 显示全部楼层 |阅读模式
二、setup 及组合 API 使用详解

为什么要使用setup组合?
Vue3 中新增的 setup,目的是为了解决 Vue2 中“数据和业务逻辑不分离”的问题。

Vue3中使用 setup 是如何解决这一问题的呢?
第1步: 用setup组合API 替换 vue2 中的data/computed/watch/methods等选项;
第2步: 把setup中相关联的功能封装成一个个可独立可维护的hooks。

1、ref
作用:一般用于定义基本数据类型数据,比如 String / Boolean / Number等。
背后:ref 的背后是使用 reactive 来实现的响应式.
语法:const x = ref(100)
访问:在 setup 中使用 .value 来访问。

<template>
  <h1 v-text='num'></h1>
  <!-- 在视图模板中,无须.value来访问 -->
  <button @click='num--'>自减</button>
  <!-- 在setup中,要使用.value来访问 -->
  <button @click='add'>自增</button>
</template>

<script setup>
  import { ref } from 'vue'
  const num = ref(100)
  const add = () => num.value++
</script>


2、isRef
作用:判断一个变量是否为一个 ref 对象。
语法:const bol = isRef(x)

<template>
  <h1 v-text='hello'></h1>
</template>

<script setup>
  import { ref, isRef, reactive } from 'vue'
  const hello = ref('Hello')
  const world = reactive('World')
  console.log(isRef(hello))  // true
  console.log(isRef(world))  // false
</script>


3、unref
作用:用于返回一个值,如果访问的是 ref变量,就返回其 .value值;如果不是 ref变量,就直接返回。
语法:const x = unref(y)

<template>
  <h1 v-text='hello'></h1>
</template>

<script setup>
  import { ref, unref } from 'vue'
  const hello = ref('Hello')
  const world = 'World'
  console.log(unref(hello))  // 'Hello'
  console.log(unref(world))  // 'World'
</script>


4、customRef
作用:自定义ref对象,把ref对象改写成get/set,进一步可以为它们添加 track/trigger。

<template>
  <h1 v-text='num'></h1>
  <button @click='num++'>自增</button>
</template>

<script setup>
  import { customRef, isRef } from 'vue'
  const num = customRef((track, trigger)=>{
    let value = 100
    return {
      get () {
        track()
        return value
      },
      set (newVal) {
        value = newVal
        trigger()
      }
    }
  })
  console.log(isRef(num)) // true
</script>


5、toRef
作用:把一个 reactive对象中的某个属性变成 ref 变量。
语法:const x = toRef(reactive(obj), 'key') // x.value

<template>
  <h1 v-text='age'></h1>
</template>

<script setup>
  import { toRef, reactive, isRef } from 'vue'
  let user = { name:'张三', age:10 }

  let age = toRef(reactive(user), 'age')
  console.log(isRef(age))  // true
</script>


6、toRefs
作用:把一个reactive响应式对象变成ref变量。
语法:const obj1 = toRefs(reactive(obj))
应用:在子组件中接收父组件传递过来的 props时,使用 toRefs把它变成响应式的。

<template>
  <h1 v-text='info.age'></h1>
</template>

<script setup>
  import { toRefs, reactive, isRef } from 'vue'
  let user = { name:'张三', age:10 }
  let info = toRefs(reactive(user))
  
  console.log(isRef(info.age))  // true
  console.log(isRef(info.name))  // true
  console.log(isRef(info))  // true
</script>


7、shallowRef
作用:对复杂层级的对象,只将其第一层变成 ref 响应。 (性能优化)
语法:const x = shallowRef({a:{b:{c:1}}, d:2}) 如此a、b、c、d变化都不会自动更新,需要借助 triggerRef 来强制更新。

<template>
  <h1 v-text='info.a.b.c'></h1>
  <button @click='changeC'>更新[c]属性</button>

  <h1 v-text='info.d'></h1>
  <button @click='changeD'>更新[d]属性</button>
</template>

<script setup>
  import { shallowRef, triggerRef, isRef } from 'vue'

  let info = shallowRef({a:{b:{c:1}}, d:2})

  console.log(isRef(info.value.a.b.c)) // false
  console.log(isRef(info)) // true
  console.log(isRef(info.a)) // false
  console.log(isRef(info.d)) // false

  const changeC = () => {
    info.value.a.b.c++
    triggerRef(info) // 强制渲染更新
  }

  const changeD = () => {
    info.value.d++
    triggerRef(info) // 强制渲染更新
  }
</script>

8、triggerRef
作用:强制更新一个 shallowRef对象的渲染。
语法:triggerRef(shallowRef对象)
参考代码:见上例。

9、reactive
作用:定义响应式变量,一般用于定义引用数据类型。如果是基本数据类型,建议使用ref来定义。
语法:const info = reactive([] | {})

<template>
  <div v-for='(item,idx) in list'>
    <span v-text='idx'></span> -
    <span v-text='item.id'></span> -
    <span v-text='item.label'></span> -
    <span v-text='item.tab'></span>
  </div>
  <button @click='addRow'>添加一行</button>
</template>

<script setup>
  import { reactive } from 'vue'
  const list = reactive([
    { id:1, tab:'good', label:'精华' },
    { id:2, tab:'ask', label:'问答' },
    { id:3, tab:'job', label:'招聘' },
    { id:4, tab:'share', label:'分享' }
  ])
  const addRow = () => {
    list.push({idate.now(),tab:'test',label:'测试'})
  }
</script>


10、readonly
作用:把一个对象,变成只读的。
语法:const rs = readonly(ref对象 | reactive对象 | 普通对象)

<template>
  <h1 v-text='info.foo'></h1>
  <button @click='change'>改变</button>
</template>

<script setup>
  import { reactive, readonly } from 'vue'
  const info = readonly(reactive({bar:1, foo:2}))
  const change = () => {
    info.foo++  // target is readonly
  }
</script>

11、isReadonly
作用: 判断一个变量是不是只读的。
语法:const bol = isReadonly(变量)

<script setup>
  import { reactive, readonly, isReadonly } from 'vue'

  const info = readonly(reactive({bar:1, foo:2}))
  console.log(isReadonly(info)) // true

  const user = readonly({name:'张三', age:10})
  console.log(isReadonly(user)) // true
</script>


12、isReactive
作用:判断一变量是不是 reactive的。
注意:被 readonly代理过的 reactive变量,调用 isReactive 也是返回 true的。

<script setup>
  import { reactive, readonly, isReactive } from 'vue'

  const user = reactive({name:'张三', age:10})
  const info = readonly(reactive({bar:1, foo:2}))

  console.log(isReactive(info)) // true
  console.log(isReactive(user)) // true
</script>


13、isProxy
作用:判断一个变量是不是 readonly 或 reactive的。

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

  const user = readonly({name:'张三', age:10})
  const info = reactive({bar:1, foo:2})
  const num = ref(100)

  console.log(isProxy(info)) // true
  console.log(isProxy(user)) // true
  console.log(isProxy(num))  // false
</script>


14、toRaw
作用:得到返回 reactive变量或 readonly变量的"原始对象"。
语法::const raw = toRaw(reactive变量或readonly变量)
说明:reactive(obj)、readonly(obj) 和 obj 之间是一种代理关系,并且它们之间是一种浅拷贝的关系。obj 变化,会导致reactive(obj) 同步变化,反之一样。

<script setup>
  import { reactive, readonly, toRaw } from 'vue'

  const uu = {name:'张三', age:10}
  const user = readonly(uu)
  console.log(uu === user) // false
  console.log(uu === toRaw(user)) // true

  const ii = {bar:1, foo:2}
  const info = reactive(ii)
  console.log(ii === info) // false
  console.log(ii === toRaw(info)) // true
</script>


15、markRaw
作用:把一个普通对象标记成"永久原始",从此将无法再变成proxy了。
语法:const raw = markRaw({a,b})

<script setup>
  import { reactive, readonly, markRaw, isProxy } from 'vue'

  const user = markRaw({name:'张三', age:10})
  const u1 = readonly(user)  // 无法再代理了
  const u2 = reactive(user)  // 无法再代理了

  console.log(isProxy(u1))  // false
  console.log(isProxy(u2))  // false
</script>

16、shallowReactive
作用:定义一个reactive变量,只对它的第一层进行Proxy,,所以只有第一层变化时视图才更新。
语法:const obj = shallowReactive({a:{b:9}})

<template>
  <h1 v-text='info.a.b.c'></h1>
  <h1 v-text='info.d'></h1>
  <button @click='change'>改变</button>
</template>

<script setup>
  import { shallowReactive, isProxy } from 'vue'
  const info = shallowReactive({a:{b:{c:1}}, d:2})

  const change = () => {
    info.d++  // 只改变d,视图自动更新
    info.a.b.c++ // 只改变c,视图不会更新
    // 同时改变c和d,二者都更新
  }

  console.log(isProxy(info))  // true
  console.log(isProxy(info.d))  // false
</script>

17、shallowReadonly
作用:定义一个reactive变量,只有第一层是只读的。
语法:const obj = shallowReadonly({a:{b:9}})

<template>
  <h1 v-text='info.a.b.c'></h1>
  <h1 v-text='info.d'></h1>
  <button @click='change'>改变</button>
</template>

<script setup>
  import { reactive, shallowReadonly, isReadonly } from 'vue'
  const info = shallowReadonly(reactive({a:{b:{c:1}}, d:2}))

  const change = () => {
    info.d++  // d是读的,改不了
    info.a.b.c++ // 可以正常修改,视图自动更新
  }
  console.log(isReadonly(info))  // true
  console.log(isReadonly(info.d))  // false
</script>

18、computed
作用:对响应式变量进行缓存计算。
语法:const c = computed(fn / {get,set})

<template>
  <div class='page'>
    <span
      v-for='p in pageArr'
      v-text='p'
      @click='page=p'
      :class='{"on":p===page}'
    >
    </span>
  </div>

  <!-- 在v-model上使用computed计算属性 -->
  <input v-model.trim='text' /><br>
  你的名字是:<span v-text='name'></span>
</template>

<script setup>
  import { ref, computed } from 'vue'
  const page = ref(1)
  const pageArr = computed(()=>{
    const p = page.value
    return p>3 ? [p-2,p-1,p,p+1,p+2] : [1,2,3,4,5]
  })

  const name = ref('')
  const text = computed({
    get () { return name.value.split('-').join('') },
    // 支持计算属性的setter功能
    set (val) {
      name.value = val.split('').join('-')
    }
  })
</script>

<style lang='scss' scoped>
  .page {
    &>span {
      display:inline-block; padding:5px 15px;
      border:1px solid #eee; cursor:pointer;
    }
    &>span.on { color:red; }
  }
</style>

19、watch
作用:用于监听响应式变量的变化,组件初始化时,它不执行。
语法:const stop = watch(x, (new,old)=>{}),调用stop() 可以停止监听。
语法:const stop = watch([x,y], ([newX,newY],[oldX,oldY])=>{}),调用stop()可以停止监听。

<template>
  <h1 v-text='num'></h1>
  <h1 v-text='usr.age'></h1>
  <button @click='change'>改变</button>
  <button @click='stopAll'>停止监听</button>
</template>

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

  // watch监听ref变量、reactive变量的变化
  const num = ref(1)
  const usr = reactive({name:'张三',age:1})
  const change = () => {
    num.value++
    usr.age++
  }
  const stop1 = watch([num,usr], ([newNum,newUsr],[oldNum,oldUsr]) => {
    // 对ref变量,newNum是新值,oldNum是旧值
    console.log('num', newNum === oldNum) // false
    // 对reactive变量,newUsr和oldUsr相等,都是新值
    console.log('usr', newUsr === oldUsr) // true
  })

  // watch还可以监听计算属性的变化
  const total = computed(()=>num.value*100)
  const stop2 = watch(total, (newTotal, oldTotal) => {
    console.log('total', newTotal === oldTotal) // false
  })

  // 停止watch监听
  const stopAll = () => { stop1(); stop2() }
</script>

20、watchEffect
作用:相当于是 react中的 useEffect(),用于执行各种副作用。
语法:const stop = watchEffect(fn),默认其 flush:'pre',前置执行的副作用。
watchPostEffect,等价于 watchEffect(fn, {flush:'post'}),后置执行的副作用。
watchSyncEffect,等价于 watchEffect(fn, {flush:'sync'}),同步执行的副作用。
特点:watchEffect 会自动收集其内部响应式依赖,当响应式依赖发变化时,这个watchEffect将再次执行,直到你手动 stop() 掉它。

<template>
  <h1 v-text='num'></h1>
  <button @click='stopAll'>停止掉所有的副作用</button>
</template>

<script setup>
  import { ref, watchEffect } from 'vue'
  let num = ref(0)

  // 等价于 watchPostEffect
  const stop1 = watchEffect(()=>{
    // 在这里你用到了 num.value
    // 那么当num变化时,当前副作用将再次执行
    // 直到stop1()被调用后,当前副作用才死掉
    console.log('---effect post', num.value)
  }, { flush:'post'} )

  // 等价于 watchSyncEffect
  const stop2 = watchEffect(()=>{
    // 在这里你用到了 num.value
    // 那么当num变化时,当前副作用将再次执行
    // 直到stop2()被调用后,当前副作用才死掉
    console.log('---effect sync', num.value)
  }, { flush:'sync'})

  const stop3 = watchEffect(()=>{
    // 如果在这里用到了 num.value
    // 你必须在定时器中stop3(),否则定时器会越跑越快!
    // console.log('---effect pre', num.value)
    setInterval(()=>{
      num.value++
      // stop3()
    }, 1000)
  })

  const stopAll = () => {
    stop1()
    stop2()
    stop3()
  }
</script>


21、生命周期钩子
选项式的 beforeCreate、created,被setup替代了。setup表示组件被创建之前、props被解析之后执行,它是组合式 API 的入口。
选项式的 beforeDestroy、destroyed 被更名为 beforeUnmount、unmounted。
新增了两个选项式的生命周期 renderTracked、renderTriggered,它们只在开发环境有用,常用于调试。
在使用 setup组合时,不建议使用选项式的生命周期,建议使用 on* 系列 hooks生命周期。

<template>
  <h1 v-text='num'></h1>
  <button @click='num++'>自增</button>
</template>

<script setup>
  import {
    ref, onBeforeMount, onMounted,
    onBeforeUpdate, onUpdated,
    onBeforeUnmount, onUnmounted,
    onRenderTracked, onRenderTriggered,
    onActivated, onDeactivated,
    onErrorCaptured
  } from 'vue'

  console.log('---setup')
  const num = ref(100)
  // 挂载阶段
  onBeforeMount(()=>console.log('---开始挂载'))
  onRenderTracked(()=>console.log('---跟踪'))
  onMounted(()=>console.log('---挂载完成'))

  // 更新阶段
  onRenderTriggered(()=>console.log('---触发'))
  onBeforeUpdate(()=>console.log('---开始更新'))
  onUpdated(()=>console.log('---更新完成'))

  // 销毁阶段
  onBeforeUnmount(()=>console.log('---开始销毁'))
  onUnmounted(()=>console.log('---销毁完成'))

  // 与动态组件有关
  onActivated(()=>console.log('---激活'))
  onDeactivated(()=>console.log('---休眠'))
  
  // 异常捕获
  onErrorCaptured(()=>console.log('---错误捕获'))
</script>


22、provide / inject
作用:在组件树中自上而下地传递数据.
语法:provide('key', value)
语法:const value = inject('key', '默认值')

# App.vue

<script setup>
  import { ref, provide } from 'vue'
  const msg = ref('Hello World')
  // 向组件树中注入数据
  provide('msg', msg)
</script>

# Home.vue

<template>
  <h1 v-text='msg'></h1>
</template>
<script setup>
  import { inject } from 'vue'
  // 消费组件树中的数据,第二参数为默认值
  const msg = inject('msg', 'Hello Vue')
</script>


23、getCurrentInstance
作用:用于访问内部组件实例。请不要把它当作在组合式 API 中获取 this 的替代方案来使用。
语法:const app = getCurrentInstance()
场景:常用于访问 app.config.globalProperties 上的全局数据。
<script setup>
  import { getCurrentInstance } from 'vue'
  const app = getCurrentInstance()
  // 全局数据,是不具备响应式的。
  const global = app.appContext.config.globalProperties
  console.log('app', app)
  console.log('全局数据', global)
</script>


24、关于setup代码范式(最佳实践)
只使用 setup 及组合API,不要再使用vue选项了。
有必要封装 hooks时,建议把功能封装成hooks,以便于代码的可维护性。
能用 vite就尽量使用vite,能用ts 就尽量使用ts。
您需要登录后才可以回帖 登录 | 立即注册 手机动态码快速登录

本版积分规则

快速回复 返回顶部 返回列表