vue 3.0 官方文档学习实践与思考

140 阅读2分钟

新特性总结

Fragment 组件,借鉴react Suspense 组件,借鉴react v-model 通过指定具体名可以同时绑定多个

为支持 tree-sharking 引入方式都以模块化引入

功能新特性实例实际应用
指令动态指令名<a :[key]="url"> ... 实际项目作用还未知
$watch可以是函数返回值,相当于将computed后进行监听this.$watch(()=>this.a+this.b,()=>{})props 两个属性具有相同响应方法时,可以 省略computed的合并,直接使用
watch对象值为一个函数名或函数数组函数名 :可以将响应函数写在methods中函数数组:将响应逐一调用函数代码组织上可能更好看些
watchEffect监听内部所涉及到的响应对象,并在页面渲染前就执行。
emits新增实例①验证emit的数据是否达到预期。②使组件代码中的emit事件更加一目了然
v-mode①由原来的:value,@input语法糖,改成:modelValue @update:modelValue即将原来v-bind的sync修饰符与v-model合并②可以指定具体名称③自定义修饰符v-model =‘a’=>{props:['modelValue','modelModifiers'],methods:{emit(){this.$emit('update:modelValue',xxx)}}}v-model:hello.big =‘a’=>{props:['hello','helloModifiers'],methods:{emit(){this.'helloModifiers.big=="big"this.$emit('update:hello',xxx)}}}对组件中的value变量更加容易区分。自定义修饰符是v-model功能更强大,而不需要通过额外的props来控制
provide/inject  可以通过 reactive 或ref函数来实现响应式绑定provide(){return{ f_data:reactive({ a:this.m })}}
defineAsyncComponent新增实例,defineAsyncComponent(()=>import(xx))与Suspense 配合对异步组件的加载和错误捕获进行控制
Suspense新增实例,
refsetup中 ref的实现不再用$refs使用setup

setup函数的注意点:

  • 1、由于在执行 setup函数的时候,还没有执行 Created 生命周期方法,所以在 setup 函数中,无法使用 data 和 methods 的变量和方法  
  • 2、由于我们不能在 setup函数中使用 data 和 methods,所以 Vue 为了避免我们错误的使用,直接将 setup函数中的this修改成了 undefined
  • 3、setup函数只能是同步的不能是异步的

reactive ref 二者的区别,在js中,赋值时 ref定义的变量 需要赋值给其value对象,而template则可以直接赋值

实践总结

  • reactive 是对对象地址做监听处理,若执行过程中改变变量的所指向的引用地址则监听响应被取消。

  • attrs 所对应的地址不能被监听。可以重新解构成新的地址,并赋值给新的变量, 并在onUpdate从新在赋值一遍即可。

  • defineProps 是在编译时就执行,所以不能放在函数中返回

  • ref 作用在组件上时,若v-bind:ref形式 则返回一个参数为当前dom的回调函数 若使用<script setup>则不能获取到子组件的对象实例,

  • $attrs 现在包含元素上所有的 attributes,包括class,style,vue2.0不包括,迁移时可能会有样式问题

  • listeners被移除,以on开头的函数放在listeners 被移除,以on开头的函数放在attrs中。v-bind="attrs"von="attrs" v-on="listeners"统一为 v-bind="$attrs"

  • emits 作用:① 对emit事件返回值做校验。②对为未声明的事件绑定到原生dom上,即原有的native修饰符移除了

  • watchEffect 是对方法内所有的响应属性进行监听。复杂的业务组件中不建议使用,否则会造成溯源困难

  • $nextTick 返回一个promise对象,所以可以配合async await 使用

demo页面

<template>
  <h1>Demo</h1>
  <Suspense>
    <template #default>
      <attrs-value ref="ref_dom"
        v-model:ref_plain="ref_plain" 
         :ref_obj="ref_obj" 
         :reactive_obj="reactive_obj" 
         :ref_self="ref_self" 
         :toRefs_plain="toRefs_plain"
         :readonly_reactive="readonly_reactive" 
         @t_emit="onT_Emit">
        <template #ref_plain>
          <button @click="() => { ref_plain++;} ">up+1</button>
        </template>
        <template #ref_obj>
          <button @click="() => { ref_obj.count++;} ">up+1</button>
        </template>
        <template #reactive_obj>
          <input v-model.number="reactive_obj.count" type="number" />
        </template>
        <template #ref_self>
          <button @click=" () => { ref_self++; }">up+1</button>
        </template>
        <template #toRefs_plain>
          <button @click=" () => { toRefs_plain++; }">up+1</button>
        </template>
        <template #readonly_reactive>
          <button @click=" () => { readonly_reactive.count++; }">up+1</button>
        </template>
      </attrs-value>
    </template>
    <template #fallback>
      <div>Suspense Loading</div>
    </template>
  </Suspense>
  <Teleport to="#Teleport" >
    <div style="text-align:center;">
      <h1>this is Teleport</h1>
    </div>
    <Suspense>
      <attrs-value ref="ref_dom" :ref_self="ref_self">
        <template #ref_self>
          <button @click=" () => { ref_self++; }">up+1</button>
        </template>
      </attrs-value>
    </Suspense>
  </Teleport>
</template>
<script setup>
import {
  customRef, reactive, ref,
  computed,
  onMounted,
  watch,
  toRefs,
  defineProps,
  useContext,
  defineAsyncComponent,
  readonly
} from "vue"
defineAsyncComponent(() => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(import('./attrs-value.vue'))
    }, 2000)
  })
})
const attrsValue = defineAsyncComponent({
  loader: () => new Promise((resolve) => {
    setTimeout(() => {
      resolve(import('./attrs-value.vue'))
    }, 2000)
  }),
  // 加载异步组件时要使用的组件
  loadingComponent() {
    //当组件为纯函数时,return 的就是 render函数
    return "loading"
  },
  errorComponent() {
    //当组件为纯函数时,return 的就是 render函数
    return "error"
  },
  timeout: 1000,

  // 定义组件是否可挂起 | 默认值:true
  // suspensible: false,

})
defineProps(["msg", "test"])
const reactive_obj = reactive({ count: 0 })
const ref_plain = ref(0)
let ref_obj = ref({ count: 1 })
let { toRefs_plain } = toRefs(reactive({ toRefs_plain: 1 }))
const ref_dom = ref(null)
watch(
  ref_obj,
  () => {
    console.log('%c当ref为一个对象时,watch 必须用deep:true方能监听到变化,ref_obj' + ref_obj.value, 'color:red')
  },
  { deep: true, immediate: true }
)
const onT_Emit = (e) => {
  console.log("emit send:%c" + e, "color: red;font-size:20px")
}

const ref_debounc_customRef = (value, daytime = 500) =>
  customRef((track, trigger) => {
    let time = null
    return {
      get() {
        track()
        return value
      },
      set(newvalue) {
        if (time) clearTimeout(time)
        time = setTimeout(() => {
          value = newvalue
          trigger()
        }, daytime)
      }
    }
  })

const ref_self = ref_debounc_customRef(10)

const readonly_reactive = readonly(reactive_obj)
</script>
<style lang='less' scoped>
</style>

attrs-value

<template>
  <table style="width: 100%;">
    <tr>
      <td>变量名</td>
      <td>当前值</td>
      <td>操作</td>
    </tr>
    <tr
      v-for="(value, key) in list"
      :key="key"
    >
      <td>{{ value.label }}</td>
      <td>{{ value.value }}</td>
      <td>
        <slot :name="value.label" />
      </td>
    </tr>
  </table>
</template>

<script setup>
/* 
   该组件以表格的形式显示父组件传入的属性对象值因操作后的变化。
   同时为每个属性对象提供与其变量名一致的slot。
 */
import {
  defineEmit,
  useContext,
  computed,
  reactive,
  onUpdated,
} from "vue";

const context = useContext();
const { Event, Attrs } = Object.keys(context.attrs).reduce((obj, key) => {
  obj[key.indexOf("on") === 0 ? 'Event' : 'Attrs'][key] = context.attrs[key]
  return obj
}, { Event: {}, Attrs: {} })
const attrs = reactive(Attrs);
onUpdated(() => {
  Object.keys(attrs).forEach((v) => {
    attrs[v] = context.attrs[v];
  });
});
const emit = defineEmit(["t_emit"]);
emit("t_emit", "hello! this is  table-item dom");
const list = computed(() => {
  const Obj = { ...attrs };
  return Object.keys(Obj).reduce((arr, attr) => {
    let obj = {
      label: attr,
      value: Obj[attr],
    };
    arr.push(obj);
    return arr;
  }, []);
});
</script>
<style lang='less' scoped>
</style>