vue组件的使用

一、组件的基础

1. 组件的定义

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>

<body>
    <div id="root"></div>
    <script>
        const app = Vue.createApp({
            // 根组件
            template: `<div><hello/><world/></div>`
        });
        // 子组件
        app.component('hello', {
            template: `<div>hello</div>`
        });
        // 子组件
        app.component('world', {
            template: `<div>world</div>`
        })
        const vm = app.mount('#root');
    </script>
</body>

</html>

2. 组件的复用性

  • 组件定义后可以重复使用,且互不影响。
const app = Vue.createApp({
    // 根组件
    template: `<div>
            <counter />
            <counter />
            <counter />
        </div>`
});
// 子组件
app.component('counter', {
    data() {
        return {
            count: 0
        };
    },
    template: `<div @click='count++'>{{count}}</div>`
});

3. 全局组件

  • app.component() 定义的组件都是全局组件。
  • 全局组件,只要定义了,到处可以使用,性能不高,但使用方便。
  • 名字建议,小写字母单词,中间用横线间隔。

4. 局部组件

  • 定义局部组件使用时,需要用到 components 选项。
  • components 用于声明一组可用于组件实例中的组件。
// 子组件
const HelloWorld = {
    template: `<div>hello world</div>`
}
const app = Vue.createApp({
    // components: { 'hello-world': HelloWorld }
    components: { HelloWorld },
    // 根组件
    template: `<div>
            <hello-world>
        </div>`
});

  • 局部组件,定义了,要注册后才能使用,性能较高,使用相对麻烦。
  • 名字建议大写字母开头,驼峰命名。
  • 局部组件使用时,要做一个名字和组件间的映射对象,若不写映射,Vue底层也会尝试帮你做映射。

二、组件间传值与传值校验

1. 组件间传值

  • props 选项是一个用于从父组件接收数据的数组或对象。
  • 结合 v-bind 指令可以传递动态 prop 。
const app = Vue.createApp({
    data() {
        return { message: 'hello' };
    },
    template: `<div>
            <demo v-bind:content='message' />
        </div>`
});
app.component('demo', {
    props: ['content'],
    template: `<div>{{content}}</div>`
});

2. 传值校验

  • 通过将 props 设置为对象,可以对传值进行校验。
  • 可以校验 String、Number、Function、Boolean、Object、Array等类型。
  • 如:使用 content: Number 校验传入的值是否为 Number 类型,若不是控制台中会抛出警告,但是不会报错。
app.component('demo', {
    props: {
        content: Number
    },
    template: `<div>{{typeof content}}</div>`
});

在 props 为对象时,还可以添加下列特殊选项:

  • type:可以是下列原生构造函数中的一种:String、Number、Boolean、Array、Object、Date、Function、Symbol、任何自定义构造函数、或上述内容组成的数组。会检查一个 prop 是否是给定的类型,否则抛出警告。
  • default:any  为该 prop 指定一个默认值。

  • required:Boolean 定义该 prop 是否是必填项。

  • validator:Function 自定义验证函数会将该 prop 的值作为唯一的参数代入。如果该函数返回一个 falsy 的值 (也就是验证失败),一个控制台警告将会被抛出。
props: {
    content: {
        type: Number,
        default: 123,
        required: true,
        validator: (num) => { return num > 1000 }
    }
}

3. 传多个 prop

  • 传多个 prop 时,可以将这些 prop 先存入一个对象中,再将这个对象一起传给子组件。
// v-bind='param' 
// 等价于 v-bind:a='param.a' v-bind:b='param.b' v-bind:c='param.c' v-bind:d='param.d'
const app = Vue.createApp({
    data() {
        return {
            param: {
                a: 'A',
                b: 'B',
                c: 'C',
                d: 'D'
            }
        };
    },
    template: `<div>
            <demo v-bind='param' />
        </div>`
});
app.component('demo', {
    props: ['a', 'b', 'c', 'd'],
    template: `<div>{{a}}--{{b}}--{{c}}--{{d}}</div>`
});

4. 命名问题

  • 通常属性传的时候,使用 content-abc 横线分隔命名,接受的时候,使用 contentAbc 驼峰命名。
const app = Vue.createApp({
    data() {
        return { content: 'hello' };
    },
    template: `<div>
            <demo v-bind:content-abc='content' />
        </div>`
});
app.component('demo', {
    props: ['contentAbc'],
    template: `<div>{{contentAbc}}</div>`
});

5. 单向数据流

  • 单项数据流的概念:子组件可以使用父组件传递过来的数据,但是绝对不能修改传递过来的数据。
  • 直接修改父组件传递过来的值会在控制台发出警告,且值也不会变化
  • 可以将父组件传递的值作为初始值保存在子组件的data对象中进行更新操作
const app = Vue.createApp({
    data() {
        return {
            count: 1
        };
    },
    template: `<div>
            <demo v-bind:count='count' />
        </div>`
});
app.component('demo', {
    props: ['count'],
    data() {
        return {
            myCount: this.count
        };
    },
    template: `<div @click='myCount++'>{{myCount}}</div>`
});

三、Non-Props 属性是什么

  • Non-Props 属性是指没有被 props 接受的属性。
  • 它会被默认继承到子组件上,常用于给子组件添加 class、style 属性。
const app = Vue.createApp({
    template: `
        <div>
            <demo style='color: red' />
        </div>
    `
});
app.component('demo', {
    template: `<div>hello world</div>`
});

inheritAttrs: false

  • 通过 $attrs 可以获取到这些 Non-Props 属性。
// 直接获取全部 Non-Props
template: `<div v-bind='$attrs'>hello world</div>`
// 分别获取每一个 Non-Prop
template: `
    <div :style='$attrs.style'>hello world</div>
    <div :class='$attrs.class'>hello world</div>
`
  • 在生命周期函数中获取 Non-Props 属性。
mounted(){
    console.log(this.$attrs);
}

四、父子组件间如何通过事件进行通信

1. 不传参数

  • 这里要用到实例方法 $emit 用于触发当前实例上的事件。
const app = Vue.createApp({
    data(){
        return {
            count: 1
        }
    },
    methods: {
        // 5. 执行handleAddOne函数count+1
        handleAddOne(){
            this.count += 1
        }
    },
    // 4. addOne事件触发,调用 handleAddOne
    template: `
        <div>
            <demo :count='count' @addOne='handleAddOne'/>
        </div>
    `
});
app.component('demo', {
    props: ['count'],
    methods: {
        // 2. 执行 handleClick 
        handleClick(){
            // 3. $emit 会触发当前实例上的 addOne 事件
            this.$emit('addOne')
        }
    },
    // 1. 点击事件触发,调用 handleClick
    template: `
        <div @click='handleClick'>{{count}}</div>
    `
});

2. 需要传递参数

  • 如果需要还可以传递一些参数,因为 $emit 中附加参数都会传给监听器回调。
const app = Vue.createApp({
    data() {
        return { count: 1 }
    },
    methods: {
        handleAdd(n1, n2, n3) {
            this.count += n1 + n2 + n3;
        }
    },
    template: `
        <div>
            <demo :count='count' @add='handleAdd'/>
        </div>
    `
});
app.component('demo', {
    props: ['count'],
    methods: {
        handleClick() {
            this.$emit('add', 2, 3, 4)
        }
    },
    template: `
        <div @click='handleClick'>{{count}}</div>
    `
});

3. emits 选项的作用

  • emits 可以是数组或对象,从组件触发自定义事件,emits 可以是简单的数组,也可以是对象,后者允许配置事件验证
const app = Vue.createApp({
    data() {
        return { count: 1 }
    },
    methods: {
        handleAdd(count) { this.count = count; }
    },
    template: `<div><demo :count='count' @add='handleAdd'/></div>`
});
app.component('demo', {
    props: ['count'],

    // 数组的形式,注意 emits 的事件名最好和 $emit 的事件名一样
    // 否则会在控制台打印警告信息
    // emits: ['add'],

    // 对象的形式
    emits: {
        // 在 $emit 传入的count小于等于5时,返回false控制台会打印警告信息。
        // 大于5时,返回true,则不会打印警告信息
        add: function (count) {
            if (count > 5) return true;
            return false
        }
    },
    methods: {
        handleClick() {
            this.$emit('add', this.count + 1)
        }
    },
    template: `
        <div @click='handleClick'>{{count}}</div>
    `
});

4. 使用 v-model 实现父子组件通信

  • 默认情况下,使用 modelValue 作为 prop 和 update:modelValue 作为事件
const app = Vue.createApp({
    data() {
        return { count: 1 }
    },
    // 1. 通过 v-model 绑定 count 的值,并将其传递给子组件
    template: `<demo v-model='count'/>`
});
app.component('demo', {
    // 2. 子组件通过名为 modelValue 的属性进行接受
    props: ['modelValue'],
    methods: {
        handleClick() {
            // 3. 通过传递的参数更新modelValue与count
            this.$emit('update:modelValue', this.modelValue + 1)
        }
    },
    template: `
        <div @click='handleClick'>{{modelValue}}</div>
    `
});

  • 若想要使用其它的属性名,需要使用 v-model:one='count' 的写法。
const app = Vue.createApp({
    data() {
        return { count: 1 }
    },
    template: `<demo v-model:one='count'/>`
});
app.component('demo', {
    props: ['one'],
    // 这里同样可以设置 emits 选项,同样要注意名字要一致
    emits: ['update:one'],
    methods: {
        handleClick() {
            this.$emit('update:one', this.one + 1)
        }
    },
    template: `
        <div @click='handleClick'>{{one}}</div>
    `
});

  • v-model 绑定多个属性 。
const app = Vue.createApp({
    data() {
        return { 
            count: 1,
            count1: 1 
        }
    },
    template: `<demo v-model:one='count' v-model:two='count1'/>`
});
app.component('demo', {
    props: ['one', 'two'],
    methods: {
        handleClick() {
            this.$emit('update:one', this.one + 1)
        },
        handleClick1() {
            this.$emit('update:two', this.two + 1)
        }
    },
    template: `
        <div @click='handleClick'>{{one}}</div>
        <div @click='handleClick1'>{{two}}</div>
    `
});
  • 自定义修饰符,注意这里只能使用modelValue的情况。
  • 添加到组件 v-model 的修饰符将通过 modelModifiers prop 提供给组件,同时会对修饰符进行校验,没有传修饰符时返回空对象,确保程序不会报错。
const app = Vue.createApp({
    data() {
        return {
            count: 'a',
        }
    },
    template: `<demo v-model.uppercase='count'/>`
});
app.component('demo', {
    props: {
        modelValue: String,
        modelModifiers: {
            // 返回空对象外面需要加()
            default: () => ({})
        }
    },
    methods: {
        handleClick() {
            let newValue = this.modelValue + 'b';
            if (this.modelModifiers.uppercase) {
                newValue = newValue.toUpperCase();
            }
            this.$emit('update:modelValue', newValue)
        },
    },
    template: `
        <div @click='handleClick'>{{modelValue}}</div>
    `
});

五、使用插槽和具名插槽解决组件内容传递问题

1. slot 插槽

  • <slot> 元素作为组件模板之中的内容分发插槽。<slot> 元素自身将被替换。
  • slot 中使用数据,作用域的问题:
  • 父模板里调用的数据属性,使用的都是父模板里的数据。
  • 子模版里调用的数据属性,使用的都是子模版里的数据。
const app = Vue.createApp({
    data() {
        return {
            text: '提交',
        }
    },
    template: `
        <demo><button>{{text}}</button></demo>
    `
});
app.component('demo', {
    template: `
        <div>
            <input />
            <slot></slot>
        </div>
    `
});

  • 若模板组件中没有添加内容,可以在插槽中设置默认值。
<demo></demo>
app.component('demo', {
    template: `
        <div>
            <input />
            <slot><div>hello world</div></slot>
        </div>
    `
});

2. 具名插槽

  • 想要有选择性的接受插槽中的内容,需要使用具名插槽。
  • 通过 v-slot 指令可以设置插槽的名称,<slot>通过name属性接受需要插入的内容。
  • 通常需要在插槽内容外包裹一个<template>标签。
  • 注意:v-slot 后面用 “ : ” 而不是 “ = ” ,v-slot可以缩写为 “ # ”。
const app = Vue.createApp({
    template: `
        <layout>
            <template v-slot:header>
                <header>header</header>
            </template>
            <template v-slot:footer>
                <footer>footer</footer>
            </template>
        </layout>
    `
});
app.component('layout', {
    template: `
        <div>
            <slot name='header'></slot>
            <main>main</main>
            <slot name='footer'></slot>
        </div>
    `
});

3. 作用域插槽

  • 作用域插槽:可以让插槽内容能够访问子组件中才有的数据。
const app = Vue.createApp({
    // v-slot='pList' 会接受所有的prop
    // 使用 v-slot={ item } 解构的方式可以接受特定的prop
    template: `
        <list v-slot='pList'>
            <span>{{pList.item}}</span>
        </list>
    `
});
app.component('list', {
    template: `
        <div>
            <slot v-for='item in 3' :item='item'></slot>
        </div>
    `
});

六、动态组件和异步组件

1. 动态组件

  • 动态组件:根据数据的变化,结合 component 这个标签,来动态切换组件。
  • component:渲染一个“元组件”为动态组件。依 is 的值,来决定哪个组件被渲染。
const app = Vue.createApp({
    data() {
        return { currentItem: 'input-item' }
    },
    methods: {
        handleClick() {
            if (this.currentItem === 'input-item') {
                this.currentItem = 'common-item'
            } else {
                this.currentItem = 'input-item'
            }
        }
    },
    template: `
        <component :is='currentItem'></component>
        <button @click='handleClick'>切换</button>
    `
});
app.component('input-item', {
    template: `
        <input />
    `
});
app.component('common-item', {
    template: `
        <div>hello</div>
    `
});

  • 使用<keep-alive> 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
<keep-alive>
    <component :is='currentItem'></component>
</keep-alive>

2. 异步组件

  • defineAsyncComponent:可以创建一个只有在需要时才会加载的异步组件。
  • 参数为一个返回 Promise 的工厂函数。
const app = Vue.createApp({
    template: `
        <div>
            <common-item />
            <async-common-item />
        </div>
    `
});
app.component('common-item', {
    template: `<div>hello</div>`
});
app.component('async-common-item', Vue.defineAsyncComponent(() => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve({
                template: `<div>this is an async component</div>`
            })
        }, 2000)
    })
}));

七、基础语法知识点查缺补漏

1. v-once 指令

  • v-once:不需要表达式,只渲染元素和组件一次
const app = Vue.createApp({
    data() {
        return { count: 1 }
    },
    methods: {
        handleClick() {
            this.count++;
            console.log(this.count);
        }
    },
    template: `<div @click='handleClick' v-once>{{ count }}</div>`
});

2. 特殊属性 ref

  • ref 被用来给元素或子组件注册引用信息。
  • 引用信息将会被注册在父组件的 $refs 对象上。
  • 如果在普通的 DOM 元素上使用,引用指向的就是那个 DOM 元素;
  • 如果用在子组件上,引用就指向组件实例。

使用 ref 和 $refs 获取 dom 节点:

  • 注意:获取 dom 节点,需要在 mounted 生命周期之后(包括mounted)。
const app = Vue.createApp({
    data() {
        return { count: 1 }
    },
    mounted() {
        // console.log(this.$refs.count);
        // 获取到 dom 节点后,可以修改其中的内容
        this.$refs.count.innerHTML = 'hello';
    },
    template: `<div ref='count' >{{ count }}</div>`
});

使用 ref 和 $refs 获取子组件注册引用信息

const app = Vue.createApp({
    data() {
        return { count: 1 }
    },
    mounted() {
        // 调用子组件的sayHello方法
        this.$refs.item.sayHello();
    },
    template: `<item ref='item'/>`
});
app.component('item', {
    methods: {
        sayHello() { console.log('hello'); }
    },
    template: `<div>hello</div>`
});

3. provide / inject 组合式API

  • provide / inject :可以跨组件,进行多层组件之间的传递。
  • 但是传递的值不是响应式的,是一次性的数据。
const app = Vue.createApp({
    data() {
        return { count: 1 }
    },
    methods: {
        handleClick() {
            console.log(this.count);
            this.count++;
        }
    },
    provide() {
        return {
            count: this.count
        }
    },
    template: `<item1 @click='handleClick'/>`
});
app.component('item1', {
    template: `<item2 />`
});
app.component('item2', {
    inject: ['count'],
    template: `<div>{{count}}</div>`
});

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值