vue04-组件化和模块化

四、组件化

什么是组件

借鉴了将一个大的问题拆分成一个个的小问题这种思想 , 就是"基础库"或者“基础组件",意思是把代码重复的部分提炼出一个个组件供给功能使用。

  • 将一个页面拆分成一个个的小组件,可以递归的拆分
  • 每个组件完成自己相关的功能,多个组件共同组成一个页面或者程序
  • 复用性:下次需要同样的功能就可以复用
  • 类似于项目中的一个模块,只不过更加细化了。

组件的使用

  1. 创建组件的构造器
  2. 注册组件
  3. 使用组件

组件必须放在vue管理的作用域内,如果是多个标签必须被一个元素包裹,就是有一个唯一的祖先元素

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script src="../../js/vue.js"></script>
<div id="app">
    <cpt></cpt>
    <cpt></cpt>
    <cpt></cpt>
    <cpt></cpt>
</div>

<script>
    // 1. 创建组件构造器
    const component = Vue.extend({
        template: `
            <div>
                hello
            </div>`,
    });
    // 2. 注册组件 全局组件
    Vue.component('cpt', component);

    const app = new Vue({
        el: "#app",
        data: {
            message: "hello world"
        }
    });

</script>
</body>
</html>

局部组件

<div id="app">11
    <cpt></cpt>
    <cpt></cpt>
</div>

<div id="app2">22
    <cpt></cpt>
</div>
<script>
    // 1. 创建组件构造器
    const component = Vue.extend({
        template: `
            <div>
                hello
            </div>`,
    });

    //局部组件 只在app中的作用域有效
    const app = new Vue({
        el: "#app",
        data: {
            message: "hello world"
        },
        components: {
            cpt: component
        }
    });

    //未注册组件
    const app2 = new Vue({
        el: "#app2",
        data: {
            message: "hello"
        }
    });

</script>

父子组件

<div id="app">11
    <pt></pt>
    <pt></pt>
    <pt></pt>
</div>
<script>

    /*第1个组件构造器*/
    const child = Vue.extend({
        template: `
            <div>
                child
            </div>`
    });
    // 第二创建组件构造器
    const parent = Vue.extend({
        template: `
            <div>
                parent
                <cd></cd>
            </div>`,
        components: {
            cd: child
        }
    });


    //局部组件 只在app中的作用域有效
    const app = new Vue({
        el: "#app",
        data: {
            message: "hello world"
        },
        components: {
            pt: parent
        }
    });

</script>

组件的传递

组件不会向上级作用域传递,只会向下传递,孙子没有在爷爷的作用域注册的话孙子只能在父亲的作用域使用

组件的语法糖

<div id="app">11
  <pt></pt>
  <pt></pt>
  <pt></pt>
</div>
<script>

  //局部组件 只在app中的作用域有效
  const app = new Vue({
    el: "#app",
    data: {
      message: "hello world"
    },
    components: {
      pt: {
        //  语法糖直接可以放在注册的地方
        template: `
            <div>
                hello
            </div>`
      }
    }
  });

</script>

模板的分离

<script src="../../js/vue.js"></script>
<div id="app">11
  <pt></pt>
  <pt></pt>
  <pt></pt>
</div>
<!--<script type="text/x-template" id="pt">
  <div>
    <div>我是标题</div>
  </div>
</script>-->

<template id="pt">
  <div>
    <div>我是tempalte</div>
  </div>

</template>
<script>

  //局部组件 只在app中的作用域有效
  const app = new Vue({
    el: "#app",
    data: {
      message: "hello world"
    },
    components: {
      pt: {
        //  语法糖直接可以放在注册的地方
        template: "#pt"
      }
    }
  });

</script>

组件访问数据

  • 组件不能访问实例中的数据
  • 只能访问自己的数据
  • 在子组件中data属性是一个function不是对象,可以返回一个数据对象供它访问
  • 组件也有method属性,它的原型实际上是指向vue的实例的
<div id="app">11
  <pt></pt>
  <pt></pt>
  <pt></pt>
</div>

<template id="pt">
  <div>
    <div>我是{{title}}</div>
  </div>
</template>
<script>

  //局部组件 只在app中的作用域有效
  const app = new Vue({
    el: "#app",
    data: {
      message: "hello world"
    },
    components: {
      pt: {
        
        template: "#pt",
        //是一个函数,且只能访问自己的数据
        data(){
          return {title:"title"};
        }
      }
    }
  });

</script>


组件的data必须是函数

  • 如果写属性的话,很容易造成多个组件的数据引用指向同一块内存,会相互影响
  • 用function的话你只要每次返回一个匿名对象,他是没有公共引用指向的所以不会影响,如果需要的话你自己可以return 一个公用的引用就会相互影响的
  • 所以为了避免这种bug,data不是function就会报错
  • 必须return 一个对象{}

父子组件通信

父传子
  • props属性 : 可以写成数组或者对象,对象可以限制类型,对象更好点,也可以类型写成对象添加更多的限制、给默认值
  • 给默认值的时候如果是对象或者是数组,不能直接用{}、[] 需要用工厂(default(){})来创建
  • 自定义validator
  • 可以自定义一个类作为类型
<div id="app">
  <pt :msg="msg" :title="title"></pt>
</div>

<template id="pt">
  <div>
    <div>{{title}}</div>
    <div>{{msg}}</div>
  </div>
</template>
<script>
  // 1.注册组件
  const pt = {
    template:"#pt",
    data() {
      return {};
    },
    methods: {},
    // props:["title","msg"] 可以写成数组或者对象,对象可以限制类型,对象更好点
    props:{
      // title:Array,
      title:{
        type: Array,
        default(){
          return [];
        }
      },
      //也可以写成对象的添加更多的限制、给默认值
      msg:{
        type:String,
        default:"",
        required:true,
        //自定义validator 这个待查阅
        validator: function (val) {
          return val == "hello worl";
        }
      }
    }
  }

  //局部组件 只在app中的作用域有效
  const app = new Vue({
    el: "#app",
    data: {
      msg: "hello world",
      title:["aaa","bbb","ccc"]
    },
    //字面量简写  pt可替换pt:pt
    components:{pt}
  });

</script>

子传父|自定义事件
  • v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent——导致 myEvent 不可能被监听到。
  • $emit --》this.$emit('myevent')会传递给父组件的监听事件要同名
  • 推荐你始终使用 kebab-case 的事件名 my-event
  • 子组件尽量和自己的data属性去绑定
<div id="app">
  <!--  不写参数会默认将$emit事件后传的参数【可多个】传出来,写了参数报错-->
  <pt @child-click="parentClick"></pt>
</div>

<template id="pt">
  <div>
    <button v-for="item in categories" @click="btnClick(item)">{{item.name}}</button>
  </div>
</template>
<script>
  // 1.注册组件
  const pt = {
    template: "#pt",
    data() {
      return {
        categories: [
          {id: "aaa", name: "aaa"},
          {id: "bbb", name: "bbb"},
          {id: "ccc", name: "ccc"},
          {id: "ddd", name: "ddd"}
        ]
      };
    },
    methods: {
      btnClick(ite) {
        // js中这样写不能驼峰,vue可以
        this.$emit('child-click', ite,1);
      }
    }
  };

  //局部组件 只在app中的作用域有效
  const app = new Vue({
    el: "#app",
    data: {
      msg: "hello world",
      title: ["aaa", "bbb", "ccc"]
    },
    components: {pt},
    methods: {
      parentClick(obj,a) {
        console.log(obj,a);
      }
    }
  });

</script>
练习
  1. num1、num2从父组件传递过来
  2. 修改num1,dnum1也变,同时传dnum1给父组件,父组件改变num1,也改变了prop1
  3. dnum2一直是dnum1的1%
<!--1. num1、num2从父组件传递过来
2. 修改num1,dnum1也变,同时传dnum1给父组件,父组件改变num1,也改变了prop1
3. dnum2一直是dnum1的1%-->
<div id="app">
  <pt :cnum1="num1" :cnum2="num2"
      @change1="cc1"
      @change2="cc2"
  ></pt>
</div>

<template id="pt">
  <div>
    <p>props:{{cnum1}}</p>
    <p>data:{{dnum1}}</p>
    cnum1<input type="text" :value="dnum1" @input="changeProp1"><br>
    <p>props:{{cnum2}}</p>
    <p>data:{{dnum2}}</p>
    cnum2<input type="text" :value="dnum2" @input="changeProp2">
  </div>
</template>
<script>

  //局部组件 只在app中的作用域有效
  const app = new Vue({
    el: "#app",
    data: {
      num1: 1,
      num2: 2
    },
    methods: {
      cc1(eve1) {
        this.num1 = eve1;
      },
      cc2(eve2) {
        this.num2 = eve2;
      }
    },
    components: {
      pt: {
        template: "#pt",
        props: {
          cnum1: {
            type: Number,
            default: 3
          },
          cnum2: {
            type: Number,
            default: 4
          }
        },
        data() {
          return {
            dnum1: this.cnum1,
            dnum2: this.cnum2,
          };
        }, 
        methods: {
          changeProp1(event1) {
            this.dnum1 = event1.target.value;
            console.log(this.dnum1)
            if (this.dnum1) {
              this.dnum1 = parseInt(this.dnum1)
              this.dnum2 = this.dnum1 / 100;
              this.$emit('change1', this.dnum1);
            } else {
              this.dnum2 = "";
            }

          },
          changeProp2(event2) {
            this.dnum2 = event2.target.value;
            this.$emit('change2', parseInt(this.dnum2));
          }

        }
      }
    }
  });

</script>

watch

  • watch监听对象不能直接监听,可以用computed代替对象

  • 语法:

    watch:{
    	监听的属性名(newValue, oldValue){
    
    	}
    }
    
<script src="../../js/vue.js"></script>
<div id="app">
  {{message}}
  <input type="text" v-model="message">
  {{demo.name}}
  <input type="text" v-model="demo.name">
</div>

<template id="cd">
  <div>
    aaaaa
  </div>

</template>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      message: "hello world",
      demo: {
        name: "nameObj"
      }
    },
    computed:{
      demoName(){
        return this.demo.name;
      }
    },
    watch: {
      message(newVal, oldVal) {
        console.log(newVal, oldVal);
      },
      //不能直接监听对象
      // demo(val) {
      //   console.log(val);
      // }
      demoName(val) {
        console.log(val);
      }
    },
    components: {
      cd: {
        template: "#cd"
      }
    }
  });

</script>

  • 如果是键的路径需要用引号包裹

  • 也可以外部调用

  • immediate和handler

    watch有一个特点,就是当值第一次绑定的时候,不会执行监听函数,只有值发生改变才会执行。如果我们需要在最初绑定值的时候也执行函数,则就需要用到immediate属性。

    比如当父组件向子组件动态传值时,子组件props首次获取到父组件传来的默认值时,也需要执行函数,此时就需要将immediate设为true。

  • **deep: **当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听

<div id="app">
  {{demo1.name}}
  <input type="text" v-model="demo1.name">
  {{demo.name}}
  <input type="text" v-model="demo.name">
  <input type="text" v-model="demo2">
</div>

<script>
  const app = new Vue({
    el: "#app",
    data: {
      message: "hello world",
      demo: {
        name: "nameObj"
      },
      demo1: {
        name: "nameObj"
      },
      demo2:"qweqw"
    },
    computed: {
      demoName() {
        return this.demo.name;
      }
    },
    watch: {
      //如果是键的路径需要用引号包裹
      "demo.name": function (val) {
        console.log(val);
      },
        
      // childrens: {  //监听的属性的名字
      //   handler:function(val){
      //     console.log(val.name);
      //   },
      //   deep: true, //可以监听到一个对象的内部属性变化
       //  immediate: true
      // },
      // "childrens.name":function (val) {
      //   console.log(val);
      // }
    }
  });
  //外部调用
  app.$watch("demo2",function (val) {
    console.log(val)
  })
</script>

访问子组件实例 $children和$refs

  • 一般不会用$children来取子组件
  • $refs.refName | $refs['refName']
    • 如果多个相同的引用会取最后一个
    • 如果绑定的是一个普通标签拿到的就是一个dom对象
<div id="app">
  <tmp ref="a"></tmp>
  <tmp ref="a"></tmp>
  <tmp ref="b"></tmp>
  <button @click="btnClick">打印子组件</button>
</div>
<template id="tmp">
  <div>
    <p>哈哈哈</p>
  </div>
</template>

<script>
  const app = new Vue({
    el: "#app",
    data: {
      message: "hello world"
    },
    methods:{
      btnClick(){
        //1. 一般不会用$children来取子组件
        // console.log("第一个子组件:",this.$children[0]);
        // console.log("所有子组件:",this.$children);

        // 2.$refs.refName|['refName']
        console.log("所有组件有ref属性的组件:",this.$refs);
        //如果多个相同的引用会取最后一个
        console.log("取得固定的ref的元素:",this.$refs["a"]);
        console.log("取得固定的ref的元素:",this.$refs.b);
      }
    },
    components: {
      tmp: {
        template: "#tmp"
      }
    },

  });

</script>

访问父组件实例

  • 不建议使用this.$parent,会让组件的耦合增强不够独立
  • 祖先组件this.$root
<div id="app">
  <tmp></tmp>
</div>
<template id="tmp">
  <div>
    <p>哈哈哈</p>
    <button @click="btnClick">打印父组件</button>
  </div>
</template>

<script>
  const app = new Vue({
    el: "#app",
    data: {
      message: "hello world"
    },
    components: {
      tmp: {
        template: "#tmp",
        methods: {
          btnClick() {
            //1. 不建议使用,会让组件的耦合增强不够独立
            console.log("打印直系父组件:", this.$parent);
            //祖先组件
            console.log("打印root组件:", this.$root);
          }
        }
      },
    },

  });

插槽slot

  • 拓展组件像回调函数一样,usb接口一样
  • 插槽的基本使用
  • 插槽的默认值 默认值
<!--1. 插槽的基本使用 <slot></slot>-->
<!--2. 插槽的默认值 <slot>默认值</slot>-->
<div id="app">
  <tmp></tmp><br>
  <tmp></tmp><br>
  <tmp></tmp><br>
  <tmp><div>我是插槽</div></tmp>
  <tmp><i>我是插槽i</i></tmp>
</div>
<template id="tmp">
  <div>
    <p>哈哈哈</p>
    <slot><p>我是默认值*******</p></slot>
    <p>娃娃</p>
  </div>
</template>

<script>
  const app = new Vue({
    el: "#app",
    data: {
      message: "hello world"
    },
    components: {
      tmp: {
        template: "#tmp"
      },
    }

  });

</script>
具名插槽
<div id="app">
  <tmp ><a slot="right" href="#">我替换右边</a></tmp><br>
  <tmp ><a slot="left" href="#">我替换左边</a></tmp><br>
  <tmp><a href="#">我替换没名字的</a></tmp><br>
</div>
<template id="tmp">
  <div>
    <slot name="left"><p>我是默认值left</p></slot>
    <slot name="center"><p>我是默认值center</p></slot>
    <slot name="right"><p>我是默认值right</p></slot>
    <slot><p>我是默认值没有名字</p></slot>
  </div>
</template>

<script>
  const app = new Vue({
    el: "#app",
    data: {
      message: "hello world"
    },
    components: {
      tmp: {
        template: "#tmp"
      },
    }

  });

编译作用域

  • 始终使用自己组件中的变量
<div id="app">
<!--    在谁的作用域用谁的变量-->
  <cp v-show="isShow"></cp>
</div>
<template id="cp">

  <div v-show="isShow"><!-- div父元素初始化的时候不受影响 -->
    <a href="">aaa</a>
    <button v-show="isShow">按钮</button>
  </div>
</template>

<script>
  const app = new Vue({
    el: "#app",
    data: {
      message: "hello world",
      isShow: true
    },
    components: {
      cp: {
        template: "#cp",
        data() {
          return {
            isShow: false
          };
        }
      }
    }
  });

</script>
作用域插槽
  • 父组件想要替换子组件的插槽的数据,数据的具体值还是由子组件来决定
  • slot-scope="slotData",类似于该组件的对象,2.5之前要用template标签
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
<script src="../../js/vue.js"></script>
<div id="app">
  <cp>
    <!--    slotData:类似于该组件的对象,2.5之前要用template-->
    <template slot-scope="slotData">
      <!--      取得绑定在组件中的数据-->
      <span v-for="item in slotData.datas">{{item}}-</span>
    </template>
  </cp>

  <cp>
    <template slot-scope="slotData">
      <!--      join方法将数组拼接成字符串-->
      <span>{{slotData.datas.join(' * ')}}</span>
    </template>
  </cp>
</div>
<template id="cp">

  <div>
    <!--    作为传递的数据-->
    <slot :datas="languages">
      <ul>
        <li v-for="item in languages">{{item}}</li>
      </ul>
    </slot>
  </div>
</template>

<script>
  const app = new Vue({
    el: "#app",
    data: {
      message: "hello world",
    },
    components: {
      cp: {
        template: "#cp",
        data() {
          return {
            languages: ['java', 'javascript', 'css', 'html', 'vb', 'python']
          };
        }
      }
    }
  });

</script>
</body>
</html>

五、es6模块化

把功能进行划分,将同一类型的代码整合在一起,所以模块的功能相对复杂,但都同属于一个业务

为什么有模块化

  • js是按顺序加载的,所以一般相互依的js是具有强制性的
  • 多个js文件定义的引用会污染全局变量,多人协作开发可能会有冲突
  • 可以用闭包

闭包解决多人协作开发

  • 只需要写好自己的模块化的命名,就可以很好的避免冲突了,相当于把所有的容错点都聚焦在一个点上,犯错的机会就少了,
  • 但是代码的复用性还是很差
// ;是为了防止其他的导入js相互影响
;var xm01 = (function xiaoming01() {
  return {
    aa:"asdas",
    flag: true
  };
}())


//js文件2
;(function () {
  if (xm01.flag) {
    alert("xm01.flag:" + xm01.flag);
  }
}());

组件化类似模块化的更细粒度,组件充当了基本类库一样的东西目的是复用拓展性,模块主要是以功能区分类别划分尽量隔离其他业务

posted @ 2020-02-14 21:06  zpyu521  阅读(1304)  评论(0编辑  收藏  举报