组件注册
组件注册的方法从 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
- 组件上使用 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");
- 也可以传入自定义参数,进行多个 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");
- 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 组件
- suspense 组件用于在等待某个异步组件解析时候,显示的后备内容
- 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>
- 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>