组件

组件的注册

vue 中,我们可以通过 new Vue 来创建一个组件,不过通常它是作为整个应用的顶层根组件存在的,我们还可以通过另外的方式来注册一个更为通用的组件

组件

  • 根组件:通过new Vue()来创建的,通常应用中只有一个
  • 可复用性组件:通过Vue.component()来创建
    分为全局组件,局部组件
    component全局不带s
    局部带s ,components

Vue.component()

Vue.component('组件名称', {组件选项})
  • 组件名称遵循自定义组件命名规范:全小写、连字符(虽然驼峰式一般也没问题)
  • 组件选项与 new Vue 选项配置基本一致(也有一些细节的不同)
全局组件与局部组件

通过 Vue.component 注册的组件,我们称为全局组件,因为它可以在任意范围内使用,我们还可以定义局部组件

Vue.component("d-div",{
            template:`
                    <div>
                         <div>div1</div>
                         <p-p-t></p-p-t>
                         <div>div2</div>
                     </div>
            `,
            components:{
                // 组件名称:组件选项
                'p-p-t':{
                    template:`<div>我是一个div</div>`   
                }
            }
        })

在一个组件内部通过 components 选项注册的组件是局部组件,只能在当前 components 选项所在的组件内部使用

注意:局部注册的组件只能中当前注册的组件中使用,不能在它的子组件中使用

局部组件
let app = new Vue({
            el:"#app",
            // 局部注销可复用性组件的方式
            component:{
             }
        });

data

在非 new Vue 的组件中,data 必须为函数,函数返回值必须是一个对象,作为组件的最终 data

<script>
        Vue.component("d-div",{
            template:`
                <div>div1</div>
                <div>div2</div>  
            `,
            data(){
                return{
                    name:"孩子"
                }
            }
        })
        let app = new Vue({
            el:"#app",
            data:{
                par:"父亲"
            }
        })
    </script>

组件的传参(父传子)props

组件中内部私有数据存储中组件 data 中,通过外部传入的数据,则通过 props 选项接收

  • 如果传入的 props 值为一个表达式,则必须使用 v-bind
  • 组件中的 dataprops 数据都可以通过组件实例进行直接访问
  • data 中的 keyprops 中的 key 不能冲突
<div id="app">
        <p v-text = "name"></p>
        <!-- 父传 使用v-bind绑定属性的方式 -->
        <d-div :page = "10"></d-div>
    </div>
    <script>
        // 可复用性组件中:最上层只能有一个元素(根元素)
        Vue.component("d-div",{
            // 子接:使用属性props
            // props:用来存储数据
            //        组件内部使用数据的使用方式和data一致,直接通过this调用
            props:["page"],
            template:`
                <div>
                    <div>{{name}}</div>
                    <div>div2 {{page}}</div>  
                </div>
            `,    
           data(){
               return {
                   name:"儿子"
               }
           }
        })
        let app = new Vue({
            el:"#app",
            data:{
                name:"父亲"
            }
        })
    </script>
组件通讯
  • 父传子:
    父级调用子组件,通过子组件的属性传入数据
    子元素内部通过prop配置项(数组),来接受对应的数据

  • 子传父:
    注意:Vue中的数据默认的单项流动,只能父到子直接传递, 但是子到父不能直接修改

    原因:因为父级的数据,不一定只是某个子级使用,或许还有其他的子级也在使用,那么如果一个子级内部随意去修改了父级的数据,很容易导致数据混乱

组件的传参(子传父) $emit()

vue 为每个组件对象提供了一个内置方法 $emit ,它等同于自定义事件中的 new Event,trigger

this.$emit('自定义事件名称', 事件数据)
  • 事件数据就是中触发事件的同时携带传递的数据 - event

  • 父级在使用该组件的过程中,可以通过 @事件名称 来注册绑定事件函数

  • 事件函数的第一个参数就是事件数据

如果子级想修改数据

1.子级执行 $emit()来触发自定义事件
2.父级监听 子级触发的自定义事件
3.监听到触发 执行父级的回调函数

子级在特定条件下,触发自定义事件来通知父级,父级通过监听接收到这个通知后,自己决定是否改变数据或者说是如何改变

<div id="app">
        <!-- 父级监听自定义事件 -->
        <d-div @:d-button = "fn"></d-div>
        <p>我是子元素传来的{{name}}</p>
    </div>
    <script>
        // 子传父  传参
        Vue.component("d-div",{
            data(){
                return{
                    name:"姓名"
                }
            },
            template:`
                    <div>
                        <p>我是第一段</p>
                        <p>我是第二段</p>
                        <button @click = "go">按钮</button>
                    </div>  
            `,
            // 传参
            methods:{
                // 函数
                go(){
                    // if(){
                        // 参数1:自定义事件 (自定义事件名称不能使用 大写字母)
                        // 参数2:传递的参数
                        this.$emit("d-button",this.name)
                    // }
                    
                }
            }               
        })
        let app = new Vue({
            el:"#app",
            data:{
                name:""
            },
            methods:{
                fn(n){
                    console.log(n);
                    this.name = n;
                }
            }
        })
    </script>

组件双绑的实现

虽然并不推荐在组件内部修改 props ,但是,有的时候确实希望组件内部状态变化的时候改变 props ,我们可以通过子组件触发事件,父级监听事件来达到这个目的,不过过程会比较繁琐,vue 提供了一些操作来简化这个过程

v-model

v-modelvue 提供的一个用于实现数据双向绑定的指令,用来简化 props 到 datadata 到 props 的操作流程

通过v-model双项绑定数据

  • 父传子:
    1.子组件绑定父组件属性
    2.子组件通过props接收使用
    3.model中prop指定属性

  • 子传父:
    1.子组件触发自定义事件
    2.model中指定自定义事件
    3.自动监听,自动回调函数,自动赋值

v-model:不推荐使用v-model来传参,因为它隐藏了太多细节

  • 1.会让我们使用的时候,操作空间变小
  • 2.出现错误的时候,不容易排查
  • 3.绑定不了多个prop
    推荐使用:.sync修饰符
<div id="app">
        <!-- 通过v-bind单项绑定数据 -->
        <d-div :mm="msg" @g = "fn"></d-div>
        <p>{{msg2}}</p>
        <hr>
        
        <!-- 通过v-model双项绑定数据 -->
        <mc-m v-model = "rootmsg"></mc-m>
        <p>我是父组件的:{{rootmsg}}</p>

    </div>
    <script>      
        Vue.component("mc-m",{
            props:["mm","rootmsg"],
            // model选项:就是用来指定绑定的属性和绑定的事件
            model:{
                // prop用来告诉v-model绑定的prop是那个
                prop:'rootmsg',
                // event:告诉v-model触发什么事件的时候,自动去修改绑定的值
                event:'gofather'  //封装了监听和回调,以及赋值
            },
            data(){
                return {
                    name:"子级信息"
                }
            },
            template:`
                <div>{{mm}}
                    <button @click="go">按钮</button>
                    <p>我是子组件的:{{rootmsg}}</p>
                </div>
            `,
            methods:{
                go(){
                    this.$emit("gofather",this.name)
                }
            }
        })
        let app = new Vue({
            el:"#app",
            data:{
                msg:"父级信息",
                msg2:"",
                rootmsg:"父级2"
            },
            methods:{

                fn(n){
                    this.msg2 = n;
                }
            }
        })
    </script>
.sync

通过 v-model 来进行双向绑定,会给状态维护带来一定的问题,因为修改比较隐蔽,同时只能处理一个 prop 的绑定,我们还可以通过另外一种方式来达到这个目的

 <div id="app">
        <p>val1: {{val1}}</p>
        <p>val2: {{val2}}</p>
        <hr>

        <kkb-radio :checked.sync="val1" :disabled.sync="val2"></kkb-radio>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        const kkbRadio = {
            props: ['checked', 'disabled'],
            data() {
                return {
                    status: this.checked,
                    dis: this.disabled
                }
            },
            template: `
                <div class="kkb-radio" :class="{'checked': status, 'disabled': dis}" @click="changeDis" @mouseover="setChecked" @mouseout="removeChecked"></div>
            `,
            methods: {
                setChecked() {
                    this.status = true;
                    this.$emit('update:checked', this.status);
                },
                removeChecked() {
                    this.status = false;
                    this.$emit('update:checked', this.status);
                },
                changeDis() {
                    this.dis = !this.dis;
                    this.$emit('update:disabled', this.dis);
                }
            }
        };
        let vm = new Vue({
            el: '#app',
            data: {
                val1: false,
                val2: false
            },
            components: {
                'kkb-radio': kkbRadio
            },
            methods: {
                
            }
        });
    </script>

插槽

默认情况下,组件模板解析后会替换整个组件内容,如果我们想在组件引用被包含的内容,可以通过 vue 提供的内置组件 slot 来获取

<div id="app">
  <kkb-dialog title="标题">
    <p>这是内容</p>
  </kkb-dialog>
</div>
const Dialog = {
  props: ['title'],
  template: `
    <div class="dialog">
    	<div class="dialog_header">
    		<span class="dialog_title">{{title}}</span>
    	</div>
    	<div class="dialog_content">
    	    //插槽写入
    		<slot></slot>
    	</div>
    </div>
	`
	};
具名插槽
<div id="app">
  <kkb-dialog>
    
      <h1 slot="title">这是标题</h1>
      <p slot="default">这是内容</p>

  </kkb-dialog>
</div>
const Dialog = {
  props: ['title'],
  template: `
    <div class="dialog">
    	<i class="dialog_close_btn"></i>
    	<div class="dialog_header">
    		<slot name="title"></slot>
    	</div>
    	<div class="dialog_content">
    		<slot></slot>
    	</div>
    </div>
	`
};
v-slot

使用内置组件 templatev-slot 指令进行配置,用来命名插槽,在组件模板中,通过 <slot name="插槽名字"> 来使用

作用域插槽

组件内部与组件包含的内容属于不同的作用域(被包含的内容是组件父级下的作用域)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<style>
    a{   
        color: black; 
        text-decoration: none;        
        border: 1px solid yellowgreen;             
        padding: 10px;
        margin: 10px;
    }
   .act{
      background: #118888;
      color: white;
   }
</style>
<body>
    <div id="app">
        <ul>
            <li v-for = "user of showUsers ">{{user.name}}</li>
        </ul>
        <!--                                  父级监听子级函数 -->
        <d-div :pages="uPages" :page="uPage" @changpage = "changpage"> 
            <template v-slot:header="pro">
                <span>当前页数{{pro.page}}</span>
            </template>          
           
            <template v-slot:footer="pro">
                <span>一共{{pro.pages}}</span>
            </template>
            
            <!-- <span slot ="default">我是默认的</span> -->
        </d-div>

        
    </div>
    <script>
        
        Vue.component("d-div",{
            props:["pages","page"],
            template:`
                <div>
                    <slot name="header" :page="page"></slot>
                    <a href="JavaScript:;" @click="prev">上一页</a>
                    <a href="JavaScript:;" 
                        v-for="p of pages"
                        @click="gotopage(p)"
                        :class="{act:page===p}"
                    >{{p}}</a>

                    <slot name="default"></slot>

                    <a href="JavaScript:;" @click="next">下一页</a>
                    <slot name="footer" :pages="pages"></slot>
                </div>
            `,
            methods:{
                gotopage(p){
                    this.$emit("changpage",p)
                },
                prev(){
                    if(this.page-1>0){
                        // 子传父  参数1:自定义事件  参数2:传递的参数
                        this.$emit("changpage",this.page-1);
                    }                    
                },
                next(){
                    if(this.page+1>this.page){
                        this.$emit("changpage",this.page+1);
                    }                    
                }
            }
        })
        
        let app = new Vue({
            el:"#app",
            data:{
                users:[
                    {id:1,name:"张三"},
                    {id:2,name:"李四"},
                    {id:3,name:"王五"},
                    {id:4,name:"小明"},
                    {id:5,name:"小红"},
                    {id:6,name:"小哦"},
                    {id:7,name:"小屏"},
                ],
                uPage:1,
                pagePage:2,
            },
            computed:{
                // 总页数 = 数据的个数 / 每页的数量
                // 
                uPages(){
                    return Math.ceil(this.users.length / this.pagePage);
                },
               showUsers(){
                     //数组截取的第一个参数
                    let start = (this.uPage - 1) * this.pagePage;
                    return this.users.slice(start,start+this.pagePage);
               }
            },
            // 父组件接收子组件传递过来的参数
            methods:{
                    changpage(pa){
                        console.log("我是子组件传过来的"+pa)
                        this.uPage = pa;
                    }
                }
        })
    </script>
</body>
</html>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值