vue3 文档总结 -深入组件

本文详细介绍了Vue3中的组件注册,包括Vue.definedComponent和vue.createComponent().component。讨论了Props的使用,强调了非字符串类型参数需通过`:props`传递。讲解了单向数据流、非prop属性的处理,以及如何避免根节点继承额外属性。自定义事件、v-model的多种用法以及插槽的使用也做了阐述。此外,还深入探讨了提供注入,包括传递静态和动态数据。最后,提到了动态组件、异步组件的定义和Suspense、Fragment组件的使用,以及在vue3中ref的选择元素生效时机。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

组件注册

组件注册的方法从 Vue.component 迁移到了 Vue.definedComponent或者 vue.createComponent().component(name,conffig)

Props

当向子组件传递一个非字符串类型的参数时,即使这个参数是静态的,也要通过:props。否则传入的参数将会被当做静态字符串处理。对于字符串则可以直接传入

单向数据流

prop 只能从父传递到子。每次父组件发生变更的时候,子组件中所有的 prop 都将会刷新称为最新的值。如果想将这个数据当做本地的数据来使用:

  • 定义一个本地的数据作为其初始值
props: ['initProps']
setup(props) {
  const {initProps} = props
  return {
    initProps
  }
}

  • 如果需要转换,则定义一个计算属性。但是子组件中如果要把计算属性写在 setup 中,那么
  setup(props) {
    const {t} = props
    const upper = Vue.computed(() => {
      return t.toUpperCase();
    });
    return { upper };
  },

以上这种写法是达不到想要的效果的,会提示

Getting a value from the `props` in root scope of `setup()` will cause the value to lose reactivity

即在子组件中获取参数值的时候,是会使得这个参数丢失响应式属性的,要通过 props 来直接引用,像下面这样(官方提供有 toRefs 方法可以转化为响应式数据。可以直接用这个方法转化)

// 子组件中
props: ["t"],
 setup(props) {
    const upper = Vue.computed(() => {
      return props.t.toUpperCase();
    });
    return { upper };
}

非 prop 的属性

  • 非参数属性,指的是传向一个组件,但是该组件并没有相应的 props 或者 emits 定义的 attribute
  • 常见的包括 class\style\id
  • 这些属性将自动添加到根节点的 attribute 中
  • 如果不希望根组件继承这些属性,可以在组件的选项中设置 inheritAttrs: false

自定义事件

  • 自定义事件,不存在跟 props 那样的 key-base 转换
  • 使用对象语法在 emits 选项里对提交的事件做验证
emits: {
  click: ({payload}) => {
    if (payload < 0) {
      return console.log('error return')
    }
    return true
  }
,
template: `
  <button
    @click="$emit('click', {payload: -1})"
  >
    click
  </button>
  `,

组件上的 v-model -2

  1. 组件上使用 v-model 的默认情况:子组件内用 modelValue 接受参数,用 update:modelValue 触发事件
<div id="app">
  <input type="text" v-model="text" />
  <child-component v-model="text" />
</div>
const childComponent = Vue.defineComponent({
  props: ["modelValue"],
  emits: ["update:modelValue"],
  template: `
          <input type="text" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
          `,
  setup(props, { emit }) {},
});
const app = Vue.createApp({
  el: "#app",
  name: "App",
  components: { childComponent },
  setup() {
    const text = Vue.ref("a");
    return {
      text,
    };
  },
}).mount("#app");
  1. 也可以传入自定义参数,进行多个 v-model 绑定
<div id="app">
  <div class="wrap">
    <input type="text" v-model="text" />
    <input type="text" v-model="search" /> <br />
  </div>
  <hr />
  <child-component v-model:title="text" v-model:search="search" />
</div>
const childComponent = Vue.defineComponent({
  props: ["title", "search"],
  emits: ["update:title", "update:search"],
  template: `
          <input 
            type="text"
            :value="title" 
            @input="$emit('update:title', $event.target.value)" />
          
          <input type="text" 
            :value="search"
             @input="$emit('update:search', $event.target.value)" />
          `,
  setup(props, { emit }) {},
});
const app = Vue.createApp({
  el: "#app",
  name: "App",
  components: { childComponent },
  setup() {
    const text = Vue.ref("a");
    const search = Vue.ref("b");
    return {
      text,
      search,
    };
  },
}).mount("#app");
  1. vue3 中新增的 v-model 自定义修饰符
<div class="wrap">
  <input type="text" v-model="text" />
</div>
<hr />
<child-component v-model:title.upper="text" />
const childComponent = Vue.defineComponent({
  props: ["title", "titleModifiers"], // 自定义修饰符相当于参数
  emits: ["update:title"],
  template: `
            <input
              type="text"
              :value="title"
              @input="up" />
            `,
  setup(props, { emit }) {
    // 对于自定义修饰符是静态的,所以无需用 toRefs 转化
    const { titleModifiers } = props;
    const up = ($event) => {
      if (titleModifiers.upper) {
        const s = $event.target.value.toUpperCase();
        return emit("update:title", s);
      } else {
        return emit("update:title", $event.target.value);
      }
    };
    return { up };
  },
});

插槽

  • 作用域插槽
<div id="app">
  <child-component v-slot:default="slotProps">
    {{slotProps.data}}
    <p>p</p>
  </child-component>
</div>
const childComponent = Vue.defineComponent({
  template: `
           <div>
              <slot name="default" :data = "data"></slot>
            </div>
            `,
  setup(props, { emit }) {
    const data = Vue.ref(1);
    let timer = null;
    Vue.onMounted(() => {
      timer = setInterval(() => {
        data.value++;
      }, 1000);
    });
    Vue.onUnmounted(() => {
      clearInterval(timer);
    });

    return { data };
  },
});
const app = Vue.createApp({
  el: "#app",
  name: "App",
  components: { childComponent },
  setup() {},
}).mount("#app");

提供注入

  • 适用于深层嵌套的组件结构
  • 父组件通过 provider 提供数据
  • 子组件通过 inject 来使用数据

传递一个非组件内部的数据(一般是简单的静态字符串或者数字)- 直接使用 provide 提供,使用 inject 接收

// 顶层父组件中
  provide: {
    data: "i am the props",
  },
// 最底层子组件中
  inject: ["data"],
     template: `
        <div>
            {{data}}
        </div>
            `,

传递一个组件内部的数据(静态,不会变化的数据)- 将 provider 转化为返回对象的函数

父组件中

provide() {
  return {
       number: this.number
  };
},
data() {
  return {
    number: 1
  }
},

传递一个组件内部的会变化的数据(同样是响应式的数据)

  • 将 provider 转化为返回对象的函数
  • 给响应式数据提供一个组合式 API-computed

父组件中

 provide() {
    return {
      number: Vue.computed(() =>this.number),
    };
  },
  data() {
     return {
        number: 1
    }
  },
  mounted() {
    setInterval(() => this.number++, 1000)
  },

子组件中

<span>{{number.value}}</span>

另外 setup 钩子先于 beforeCreate 执行,相同 key,比 data 选项里定义的数据优先级高

setup(props) {
  let number = Vue.ref(10000);
  return { number };
},
data() {
  return {
    number: 1,
  };
},
beforeCreate() {
  console.log("beforeCreate", this.number);
},
created() {
  console.log("created", this.number);
},
mounted() {
  console.log("mounted", this.number);
},

输出:

1.beforeCreate 10000
2.created 10000
3.mounted 10000

动态组件使用 keep-alive 可以缓存状态

vue3 使用 defineAsyncComponent(Func) 方法定义异步组件

异步组件的定义:将应用分割成小一些的代码块,并且在需要的时候才从服务器上加载一个模块

  • 注册方法 vue2
// Promise 写法
Vue.component([name], (resolve, reject) => resolve([component]))


// 结合 ES6,webpack 的 import 语法
Vue.component([name], () => import([component]))


// 局部组件注册方法
components: {
  [name]: () => import([component])
}
  • 注册方法 vue3
// Promise 写法- resolve 一个组件的选项
const AsyncComponent = Vue.defineAsyncComponent(
  () => new Promise((resolve = resolve(component)))
);

// 结合 ES6,webpack 的 import 语法-import 的参数是一个路径
const AsyncComponent = Vue.defineAsyncComponent(() => {
  () => import([path]);
});

// 局部组件注册方法
components: {
  AsyncComponent: Vue.defineAsyncComponent(() => import([path]));
}

Suspense 组件

  1. suspense 组件用于在等待某个异步组件解析时候,显示的后备内容
  2. suspense 不需要导入
<Suspense>
  <template #default>
    <Child />
  </template>
  <template #fallback> Loading ... </template>
</Suspense>
const childComponent1 = Vue.defineComponent({
  template: `
           <div>
            i am the asyncComponent
            </div>
            `,
});

const app = Vue.createApp({
  el: "#app",
  name: "App",
  components: {
    Child: Vue.defineAsyncComponent(
      () =>
        new Promise((resolve, reject) => {
          setTimeout(() => {
            resolve(childComponent1);
          }, 2000);
        })
    ),
  },
}).mount("#app");

Fragment

  • 类似于一个 虚拟标签,但是不会渲染出来。不必引入,也不必写标签。文档中看不到标签,但是解决了 vue2 中每个组件都得有一个根元素的问题
<template>
  <Child />
  <Child />
  <Child />
</template>

ref 选择元素时 只会在组件渲染完成后生效(mounted 之后)

  • vue2 中的写法,通过 this.$refs 获取
    父组件中:
<template>
  <Child ref="myRef" />
  <div ref="setRef">div</div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import Child from "./components/HelloWorld.vue";
export default defineComponent({
  name: "App",
  components: {
    Child,
  },

  mounted() {
    this.$refs.myRef.printer(); // 获取子组件内方法
    this.$refs.setRef.style.color = "red"; // 操作元素样式
  },
});
</script>

子组件中

<template>
  <div class="hello">component</div>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  name: "HelloWorld",
  props: {},
  methods: {
    printer() {
      console.log("child methods");
    },
  },
});
</script>
  • vue3 中 setup 中没有权限访问 this,所以 setup 中也没有办法通过 vue2 的方式去获取元素
    1.vue3-静态 $refs
    父组件
<template>
  <Child ref="setRef" />
</template>

<script lang="ts">
import { defineComponent, ref, onMounted } from "vue";
import Child from "./components/HelloWorld.vue";
export default defineComponent({
  name: "App",
  components: {
    Child,
  },
  setup(props) {
    const setRef = ref(null);
    // 页面挂载之后才会有 ref
    onMounted(() => {
      // 通过 value 调用
      setRef.value.printer();
    });
    return {
      setRef,
    };
  },
});
</script>

子组件

<template>
  <div class="hello">component</div>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  name: "HelloWorld",
  props: {},
  methods: {
    printer() {
      console.log("child methods");
    },
  },
});
</script>
  1. vue3 动态$refs
    父组件
<template>
  <div v-for="(item, index) in arr" :key="item">
    <Child :ref="(el) => setRef(el, index)" />
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, onBeforeMount, onMounted } from "vue";
import Child from "./components/HelloWorld.vue";
export default defineComponent({
  name: "App",
  components: {
    Child,
  },
  setup(props) {
    const arr = ref([0, 1, 2, 3]);
    const divForRef = ref([]);
    //  保证每次更新之前清空上次的收集的ref
    onBeforeMount(() => {
      divForRef.value = [];
    });
    const setRef = (el, i) => {
      divForRef.value[i] = el;
    };

    onMounted(() => {
      // 使用 ref 拿到元素,调用子组件内的方法
      divForRef.value[2].printer("i am the dynamic $refs - 2");
    });
    return {
      arr,
      setRef,
    };
  },
});
</script>

子组件

<template>
  <div class="hello">component</div>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  name: "HelloWorld",
  props: {},
  methods: {
    printer(args) { // 接受一个参数便于识别
      console.log("child methods say: " + args);
    },
  },
});
</script>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值