Vue组件

Vue组件

  • 组件:由html、css、js三部分组成的独立单位,可以类似于变量,重复使用

  • 组件其实就是vue实例(对象),一个组件就是一个vue实例(对象)

  • new Vue() 产生的也是实例(对象),所以也是组件,称之为根组件

    一个页面建议只出现一个根组件(项目开发模式下,一个项目建议只出现一个根组件)

  • 组件的html页面结构由 template 实例成员提供

    template提供的html结构是用来构建虚拟DOM,真实DOM最终会被虚拟DOM替换

    根组件一般不通过template,就由挂载点来提供构建虚拟DOM的页面结构,根组件如果提供了template,还需要设置挂载点作为替换占位。

    template模板有且只有一个根标签。

根组件

通过new Vue()创建的实例就是根组件(实例与组件一一对应,一个实例就是一个组件)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>根组件</title>
</head>
<body>
<div id="app">
    <h1>{{ msg }}</h1>
</div>
<script src="js/vue.js"></script>
<script>
    // 通过new Vue()创建的实例就是根组件(实例与组件一一对应,一个实例就是一个组件)
    // 每个组件均拥有模板,template
    let app = new Vue({
        // 根组件的模板就是挂载点
        el: '#app',
        data: {
            msg: '根组件'
        },
        // 模板:由""包裹的html代码块,出现在组件的内部,赋值给组件的$template变量
        // 显示书写模板,就会替换挂载点,但根组件必须拥有挂载点
        template: "<div>显示模板</div>"
    });
    // app.$template
</script>

</body>
</html>

子组件

在根组件template中加载的组件,称之为根组件的子组件

如何定义子组件

组件就是一个普通对象,内部采用vue语法结构,被vue注册解释后,就会成为vue组件。eg:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>子组件</title>
</head>
<body>
    <!--根组件的template-->
    <div id="app">
        <!--在根组件template中加载的组件,称之为根组件的子组件-->
        <my-tag></my-tag>
        <my-tag></my-tag>
        <my-tag></my-tag>

        <tag></tag>
    </div>
</body>
<script src="js/vue.js"></script>
<script>
    // 1、定义组件
    // 2、注册组件
    // 3、使用组件

    // 如何定义子组件:组件就是一个普通对象,内部采用vue语法结构,被vue注册解释后,就会成为vue组件
    let myTag = {
        template: `
        <div>
            <h3>子组件</h3>
            <p>我是自定义的子组件</p>
        </div>
        `,
    };

    // 了解:全局组件,不要注册就可以直接使用
    Vue.component('tag', {
        template: `
        <div>
            <h3>全局组件</h3>
            <p>我是自定义的全局组件</p>
        </div>
        `,
    });

    new Vue({
        el: '#app',
        components: {
            // 'my-tag': myTag,
            // myTag: myTag,
            myTag,
        }
    })
</script>
</html>

子组件数据局部化

// 子组件
let tag = {
    template: `...`,
    // 能被复用的组件(除了根组件),数据都要做局部化处理,因为复用组件后,组件的数据是相互独立的
    // data的值为绑定的方法的返回值,返回值是存放数据的字典
    data() {
        return {
           	// 数据...
        }
    }
}
// 在哪个组件模板中出现的属性变量和方法变量,都由当前所属组件自己提供

eg:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title></title>
    <style>
        .wrap {
             calc(200px * 4 + 80px);
            margin: 0 auto;
            user-select: none;
        }
        .box {
             200px;
            height: 260px;
            /*border: 1px solid black;*/
            background-color: rgba(10, 200, 30, 0.5);
            border-radius: 10px;
            float: left;
            margin: 10px;
        }
        .box img {
             100%;
            /*height: 200px;*/
            border-radius: 50%;
        }
        .box p {
            text-align: center;
        }
    </style>
</head>
<body>
    <div id="app">
        <div class="wrap">
            <tag></tag>
            <tag></tag>
            <tag></tag>
            <tag></tag>
        </div>
    </div>
</body>
<script src="js/vue.js"></script>
<script>
    let titleTag = {
        template: `
        <p>
            <b>
                这是一种纯二哈
            </b>
        </p>
        `,
    };

    let tag = {
        template: `
        <div class="box">
            <img src="img/001.jpg" alt="">
            <title-tag />
            <p @click="fn">
                锤它:<b>{{ num }}下</b>
            </p>
        </div>
        `,
        // 能被复用的组件(除了根组件),数据都要做局部化处理,因为复用组件后,组件的数据是相互独立的
        // data的值为绑定的方法的返回值,返回值是存放数据的字典
        data () {
            return {
                num: 0
            }
        },
        methods: {
            fn() {
                this.num ++
            }
        },
        components: {
            titleTag,
        }
    };

	new Vue({
		el: '#app',
        components: {
		    tag,
        }
	});

</script>
</html>

父组件传递数据给 子组件

通过绑定属性的方式进行数据传递

  • 数据在父组件中产生
  • 在父组件中渲染子组件,子组件绑定自定义属性,附上父组件中的数据
  • 子组件自定义属性在子组件的props成员中进行声明(采用字符串反射机制)
  • 在子组件内部,就可以用props声明的属性(直接作为变量)来使用父组件中的数据
<div id="app">
    <!-- 在父组件汇总使用子组件-->
	<tag :sub_msg="msg" />
</div>
    
<script>
    // 子组件
let tag = {
    // 在组件内部就可以通过设置的自定义属性sub_msg,拿到外部选择子组件提供给属性的值
    props: ['sub_msg'],
    template: `<div>{{ sub_msg }}</div>`,
}

new Vue({
    el: '#app',
    // 注册子组件
    components: {
        tag,
    },
    data: {
        msg: '父级数据'
    }
})
</script>  

eg:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>父传子</title>
    <style>
        .wrap {
             calc(200px * 4 + 80px);
            margin: 0 auto;
            user-select: none;
        }
        .box {
             200px;
            height: 260px;
            /*border: 1px solid black;*/
            background-color: rgba(10, 200, 30, 0.5);
            border-radius: 10px;
            float: left;
            margin: 10px;
        }
        .box img {
            /* 100%;*/
            height: 160px;
            border-radius: 50%;
            margin: 0 auto;
            display: block;
        }
        .box p {
            text-align: center;
        }
    </style>
</head>
<body>
    <div id="app">
        <div class="wrap">
            <tag v-for="dog in dogs" v-bind:dog="dog" :a="1" :b="2" />
        </div>
    </div>
</body>
<script src="js/vue.js"></script>
<script>
    let dogs = [
        { title: '二哈1号', img: 'img/1.jpg', },
        { title: '二哈2号', img: 'img/2.jpg', },
        { title: '二哈3号', img: 'img/3.jpg', },
        { title: '二哈4号', img: 'img/4.jpg', },
    ];

    let tag = {
        // 在组件内部就可以通过设置的自定义属性,拿到外部选择子组件提供给属性的值
        props: ['dog', 'a', 'b', 'z'],
        template: `
        <div class="box">
            <img :src="dog.img" alt="">
            <p>
                <b>
                    {{ dog.title }}
                </b>
            </p>
            <p @click="fn">
                锤它:<b>{{ num }}下</b>
            </p>
        </div>
        `,
        data () {
            return {
                num: 0,

            }
        },
        methods: {
            fn() {
                this.num ++
            }
        },
    };

	new Vue({
		el: '#app',
        data: {
		    dogs,
        },
        components: {
		    tag,
        }
	});

</script>
</html>

子组件传递数据给父组件

通过发送事件请求的方式进行数据传递

<div id="app">
    <h1> {{ title }} </h1>
    <!-- 组件标签不能添加系统事件,只能添加自定义事件,自定义事件在组件内部通过$emit主动触发 -->
    <tag @self_action="changeTitle"/>
</div>
    
<script>
	let tag = {
        template: `
        <div>
            <input v-model="sub_title" />
        </div>
        `,
        data() {
            return {
                sub_title: ''
            }
        },
        watch: {
            // 监听sub_title属性,值一改变就会触发
            sub_title() {
                // 将sub_title与父级的title建立关联
                // 激活(触发)self_action自定义事件
                this.$emit('self_action', this.sub_title)
            }
        }
    };

    new Vue({
        el: '#app',
        components: {
            tag,
        },
        data: {
            title: '父级初始标题'
        },
        methods: {
            changeTitle(sub_title) {
                this.title = sub_title ? sub_title : '父级初始标题';
            }
        }
    })
</script>  

eg:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>子传父</title>
    <style>
        ul {
            list-style: none;
        }
        .d-btn {
            font-size: 12px;
             15px;
            display: inline-block;
        }
        .d-btn:hover {
            color: red;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <div id="app">
        <input type="text" v-model="msg">
        <button @click="send_comment">留言</button>
        <ul>
            <tag v-for="(v, i) in comments" :msg="v" :index="i" @f1="deleteMsg"/>
        </ul>
    </div>
</body>
<script src="js/vue.js"></script>
<script>
    let tag = {
        props: ['msg', 'index'],
        template: `
        <li>
            <i class="d-btn" @click="fn">x</i>
            <b>{{ msg }}</b>
        </li>
        `,
        methods: {
            fn () {
                // 点击子集,要告诉父级删除第几条数据,因为comments在父级中
                // 需要通知父级,f1是事件,事件才能绑定方法,绑定父级的方法deleteMsg,才能接收到传过去的数据
                this.$emit('f1', this.index);
            }
        }
    };

    new Vue({
        el: '#app',
        data: {
            msg: '',
            comments: localStorage.comments ? JSON.parse(localStorage.comments) : [],
        },
        components: {
            tag,
        },
        methods: {
            send_comment() {
                if (this.msg) {
                    this.comments.push(this.msg);
                    this.msg = '';
                    localStorage.comments = JSON.stringify(this.comments);
                }
            },
            deleteMsg(index) {
                this.comments.splice(index, 1);
                localStorage.comments = JSON.stringify(this.comments);
            }
        }
    })
</script>
<script>
    // localStorage,sessionStorage不能直接存储数组和对象,需要序列化为json
    localStorage.arr = JSON.stringify([1, 2, 3]);  // 前端操作json序列化
    let res = JSON.parse(localStorage.arr);        // 前端解析json数据
    console.log(res, res[2]);
</script>
</html>

综合应用练习

有以下广告数据(实际数据命名可以略做调整)
ad_data = {
tv: [
{img: 'img/tv/001.png', title: 'tv1'},
{img: 'img/tv/002.png', title: 'tv2'},
{img: 'img/tv/003.png', title: 'tv3'},
{img: 'img/tv/004.png', title: 'tv4'},
],
phone: [
{img: 'img/phone/001.png', title: 'phone1'},
{img: 'img/phone/002.png', title: 'phone2'},
{img: 'img/phone/003.png', title: 'phone3'},
{img: 'img/phone/004.png', title: 'phone4'},
]
}

i) 有两个大标题,电视和手机,点击对应的标题,渲染对应的数据
ii) 一个字典作为一个显示单位,定义一个子组件进行渲染(涉及父子组件传参)

在上一题基础上,页面最下方有一个 h2 标签,用来渲染用户当前选择的广告(点击哪个广告就是选中哪个广告)
i)当没有点击任何广告,h2 标签显示:未选中任何广告
ii)当点击其中一个广告,如tv1,h2 标签显示:tv1被选中

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title></title>
    <style>
        .wrap {
             calc(200px * 4 + 80px);
            margin: 0 auto;
            user-select: none;
        }

        .box {
             200px;
            height: 260px;
            /*border: 1px solid black;*/
            background-color: rgba(10, 200, 30, 0.5);
            border-radius: 10px;
            float: left;
            margin: 10px;
            overflow: hidden;
        }

        .box img {
            /* 100%;*/
            height: 160px;
            /*border-radius: 50%;*/
            margin: 0 auto;
            display: block;
        }

        .box p {
            text-align: center;
        }
        .action{
            background-color: pink;
        }
    </style>
</head>
<body>
<div id="app">
    <div class="wrap">
        <p>
            <button :class="{action: role === 'tv'}" @click="show('tv')">点击展示电视</button>
            <button :class="{action: role === 'phone'}" @click="show('phone')">点击展示手机</button>
        </p>
        <div v-if="role === 'tv'">
            <tag v-for="(tv, i) in tv" :data="tv" :index="i" @f1="choice"></tag>
        </div>
        <div v-else-if="role === 'phone'">
            <tag v-for="(phone, i) in phone" :data="phone" :index="i" @f1="choice"></tag>
        </div>
    </div>
    <div>
        <h2>{{ msg }}</h2>
    </div>
</div>
</body>
<script src="js/vue.js"></script>
<script>
    let tv = [
        {img: 'img/tv/001.jpg', title: 'tv1'},
        {img: 'img/tv/002.jpg', title: 'tv2'},
        {img: 'img/tv/003.jpg', title: 'tv3'},
        {img: 'img/tv/004.jpg', title: 'tv4'},
    ];

    let phone = [
        {img: 'img/phone/001.jpg', title: 'phone1'},
        {img: 'img/phone/002.jpg', title: 'phone2'},
        {img: 'img/phone/003.jpg', title: 'phone3'},
        {img: 'img/phone/004.jpg', title: 'phone4'},
    ];

    let tag = {
        props: ['data', 'index'],
        template: `
        <div class="box" @click="fn">
            <p>
                <b>{{ data.title }}</b>
            </p>
            <img :src="data.img" alt="">
        </div>
        `,
        methods: {
            fn(){
                this.$emit('f1', this.index);
            }
        }
    };

    new Vue({
        el: '#app',
        data: {
            tv,
            phone,
            role: 'tv',
            msg: '未选中任何广告',
        },
        components: {
            tag,
        },
        methods: {
            show(role) {
                this.role = role;
            },
            choice(index) {
                // console.log(111);
                let obj = this.role==='tv'? this.tv : this.phone;
                this.msg = obj ? obj[index]['title'] +'被选中' : this.msg;
            },
        }
    });
</script>
</html>
原文地址:https://www.cnblogs.com/zhuangyl23/p/11861965.html