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);
  }
}());

組件化類似模塊化的更細粒度,組件充當了基本類庫一樣的東西目的是復用拓展性,模塊主要是以功能區分類別划分盡量隔離其他業務


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM