10Vue-组件化开发(一)

172 阅读1分钟

localhost地址 = 127.0.0.1

组件拆分与嵌套

图片.png

插件: vetur:提供vue语句、显示vue语句高亮效果; Vue VSCode Snippets:帮我们写好了代码片段 Vue 3 Snippets:也是代码片段

代码片段:vue-base-css(vbase)

在App.vue引入组件(全局组件/局部组件) 在main.js引入 App.vue

App.vue引入

//3.使用
<template>
    <Header/>
    <Main/>
    <Footer/>
</template>

//1.引入
import Header from './Header.vue'
import Main from './Main'
import Footer from './Footer'

export default{
    //2.注册
    components:{
        Header,
        Main,
        Footer
    }
}

这里不用写Header.vue,因为cli基于webpack,帮我们在extensions中配置过了 但是最好加后缀,这样点击ctrl可以进入页面,并且引入组件时会有代码提示

在第3步使用的时候,按照组件的命名规范,应该是用小写的header和footer,但是会和html中命名冲突,所以用大写的。MainBanner = main-banner

组件的css作用域:scoped

HelloWorld.vue

<h2>Hello,World</h2>

App.vue

<style scoped>
    h2{
        color:red;
    }
</scoped>

实际上HelloWorld.vue和App.vue的标签都会变成red;明明我已经加上了scoped属性,为什么还会造成

实际上HelloWorld的组件就是h2标签 图片.png

图片.png 1.但是在HelloWorld.vue上,给h2再套一层div,就可以解决问题,也许是vue-loader的bug

<div>
    <h2>HelloWorld</h2>
</div>

2.不要直接给一个标签样式,而是给他一个类

<h2 class="title"></h2>

组件的通信(父子间传递数据)

图片.png

图片.png

父子组件如何进行通信?

1.父-->子:通过props属性

图片.png

父组件App.vue

3.使用组件
<template>
    <div>
        <show-message title="哈哈哈" content="嘿嘿嘿"/>
        
        注:使用data中的数据,要用v-bind
        <show-message :title="title" content=":content"/>
    
        注:使用data中对象的数据
        <show-message :title="message.title" :content="message.content"/>
        
        注:直接使用对象
        <show-message v-bind="message"/>
        
    </div>
</template>

1.引入组件
import ShowMessage from './ShowMessage.vue';

2.注册组件
export default{
    components:{
        ShowMessage
    },
    data(){
        return{
            title:"嘻嘻嘻",
            content:"我是嘻嘻嘻",
            message:{
                title:"嘿嘿",
                content:"我是嘿嘿",
            }
        }
    }
}

子组件ShowMessage.vue

<template>
    <div>
        2.使用父组件的attribute中传过来的值
        <h2>{{title}}</h2>
        <p>{{content}}</p>
    </div>
</template>

<script>
    export default{
    1.接收父组件传来的数据
        props:['title','content']
    }
</script>

props:可以是数组,也可以是对象

props:用来接收父组件传来的数据,名字要和标签名一样。

因为props是在组件上自定义的attribute,父组件给attribute赋值,子组件通过attribute名称获取到对应的值。

props是对象的话,可以用来限定传来的值
export default{

    props:{
    //1.希望得到title属性中,传来的值是string类型的
        title:String,
        
    //2.这里也可以写成对象形式
        content:{
        //必传且类型为String,也可以设置默认值
            type:String,
            required:true,
            //default:"123"
            
        }
        
    //3.也可以是多个数据类型
        counter:[String,Number],
        
    //4.当限制类型是Object时,default的写法
        info:{
        //对象或数组默认值必须从一个工厂函数获取
            type:Object,
            default(){
                return {message:"hello"}
            }
        }
         
    }
}

图片.png

4.为什么类型是Object的时候会要这样写?

答:因为当我多个组件使用info数据时,我其中某一个组件相对info对象中的message属性进行修改:info.message="123"。这时,其实修改的不是这个组件中的message组件,而是把公共的组件给修改了,其他组件使用message属性的时候就会发现值被改变了。

所以需要使用default(),限定某个组件需要的返回的具体数值

props补充

props命令规则和组件的命名规则一样

图片.png

非Prop的Attribute

图片.png

在App.vue中

//就是我show-message组件自己使用了class的 不传递数据的属性时
<show-message class = "why"/>

分为3中情况来进行处理

(1):当组件有单个根节点时,非Prop的Attribute将自动添加到根节点的Attribute中。

App.vue

<div>
    <show-message class = "why"/>
</div>

实质上在ShowMessage.vue中是这样显示的(被继承到根组件了)

<div class="why">
    {title}
</div>

(2):禁用Attribute继承和多根节点

图片.png

在ShowMessage.vue

export default{
    inheritAttrs:false,
    props:{...}
}

此时App.vue中书写的非prop,就不会继承到ShowMessage的根组件上

这种场景一般发生在:我写的非prop是想给组件的,不是给组件的根标签的,就会使用禁用,然后通过$attrs来访问非props的attribute

在ShowMessage.vue

<div>
    //从attrs中,取到class的属性
    <h2 :class="$attrs.class">{{title}}</h2>
    //当atts中的非props属性过多时
    <h2 v-bind="$attrs">{{title}}</h2>
</div>

(3):多个根节点的Attribute 在App.vue

//这是id就找不到到底让哪个根标签继承,需要手工指定
<muilt-root id = "abc" />

在MuiltRoot.vue中,有多个根标签

//手动指定绑定
<h2 :id="$attrs.id"></h2>
<h2></h2>
<h2></h2>
<h2></h2>

2.子-->父:通过$emit触发事件

图片.png

父组件触发子组件的+1和-1,当触发的时候,我父组件也会执行一些相应的方法

在App.vue

<template>
  <div>
    <h2>当前计数: {{counter}}</h2>
//3.监听事件,得到触发过来的事件以及数据,使用v-on(@),才可以调用事件方法,因为这不是内置的click方法。将触发过来的事件和这里的进行绑定。
    <counter-operation @add="addOne" 
                       @sub="subOne"
                       @addN="addNNum">
    </counter-operation>
  </div>
</template>

<script>
  import CounterOperation from './CounterOperation.vue';

  export default {
    components: {
      CounterOperation
    },
    data() {
      return {
        counter: 0
      }
    },
    methods: {
      addOne() {
        this.counter++
      },
      subOne() {
        this.counter--
      },
      addNNum(num, name, age) {
        console.log(name, age);
        this.counter += num;
      }
    }
  }
</script>

在CounterOperation.vue

<template>
  <div>
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>

    <input type="text" v-model.number="num">
    <button @click="incrementN">+n</button>
  </div>
</template>

<script>
  export default {
// 1.告诉App.vue,我要触发add、sub、addN事件
    // emits: ["add", "sub", "addN"],
// 对象写法的目的是为了进行参数的验证
// null表示不用参数验证
    emits: {
      add: null,
      sub: null,
//payload:就是要传递的数据们,具体写就是num、name、age
      addN: (num, name, age) => {
        console.log(num, name, age);
        if (num > 10) {
          return true
        }
        return false;
      }
    },
    data() {
      return {
        num: 0
      }
    },
    methods: {
      increment() {
        console.log("+1");
        
//2. 触发事件
        this.$emit("add");
      },
      decrement() {
        console.log("-1");
        
//2. 触发事件        
        this.$emit("sub");
      },
      
//2. 触发事件,并传递数据      
      incrementN() {
        this.$emit('addN', this.num, "why", 18);
      }
      
    }
  }
</script>

综合练习(商品页面切换)

App.vue

<template>
3.使用传过来的事件
    <tab-control :titles="titles" 
    @titleClick="titleClick">  
    </tab-control>
    <h2>{{contents[currentIndex]}}</h2>
</template>

<script>
    import TabControl from './TabControl.vue'
    
    export default{
        components:{
            TabControl,
        },
        data(){
            titles:['衣服','鞋子','裤子'],
            contents:['衣服界面','鞋子页面','裤子页面'],
            currentIndex:0
        },
        methods:{
            titleClick(index){
                this.currentIndex = index
            }
        }
    }
</script>

TabControl.vue

<template>
    <div class="tab-control">
        <div 
        class=tab-control-item
        :class="{active: currentIndex === index}" 
        v-for="(title,index) in titles" 
        :key="title"
        @click="itemClick(index)"
        >
            <span>{{title}}</span>
        </div>
    </div>
</template>

<script>
    export default{
    //1.我在点击title的时候,下面显示数据,告诉App我要触发此事件
        emits:["titleClick"],
        props:{
            titles:{
                type:Array,
                default(){
                    return []
                }
            }
        },
        data(){
            return{
                currentIndex:0
            }
        },
        methods:{
            itemClick(index){
                this.currentIndex = index;
            //2.将事件数据传给App    
                this.$emit('titleClick',index)
            }
        }
    }
</script>

<style scpoed>
    .tab-control-item.active{
        color:red
    }
</style>

demo05.gif

总结emits: 我希望在子组件中点击东西的时候,父组件也可以有相应的反应,所以诞生了emits,用来子组件传递父组件。

1.在子组件中书写要触发的事件是什么:emit['事件名']

2.在子组件中想要让父组件执行操作的方法里,传递事件以及相应数据。表示,当我子组件执行这个方法时,我想要让父组件也做出相应的操作,操作的一些相关数据来源于我子组件里

3.父组件收到相应的事件后,@emit的事件名="父组件要做的方法名",这个时候父组件要做的方法里,可以使用传过来的数据了,并且在子组件执行时,父组件也会相应的执行,并返回具体的操作结果。