Vue学习-组件化开发

190 阅读1分钟

Vue学习-组件化开发

认识组件

提供了一种抽象、让我们可以开发出一个个独立可复用的组件来构造我们的应用

任何的应用都会被抽象成一颗组件树

组件的使用三个步骤:

  • 创建组件构造器
  • 注册组件
  • 使用组件

基本使用过程

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <my-cpn></my-cpn>
    </div>

    <script src="../js/vue.js"></script>
    <script>
      // 1. 创建组件构造器对象
      const cpnC = Vue.extend({
        template: `
							<div>
								<h2>我是标题</h2>
								<p>我是内容,哈哈哈</p>
								<p>我是内容,呵呵呵</p>
      				</div>
						`,

      });

      // 2. 注册组件
      Vue.component('my-cpn', cpnC);

      const app = new Vue({
        el: '#app',
        data: {

        },
        methods: {

        }
      })
    </script>
  </body>
</html>

全局组件和局部组件

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <cpn></cpn>
    </div>

    <div id="app2">
      <cpn></cpn>
    </div>

    <script src="../js/vue.js"></script>
    <script>

      // 1. 创建组件构造器
      const cpnC = Vue.extend({
        template: `
<div>
<h2>我是标题</h2>
<p>我是内容,阿哈哈哈</p>
      </div>
`
      });

      // 2. 注册组件(全局组件)
      // 全局组件可以在多个vue实例下面使用
      // Vue.component('cpn', cpnC);

      const app = new Vue({
        el: '#app',
        data: {

        },
        methods: {

        },
        // 注册局部组件
        components: {
          cpn: cpnC
        }
      });

      const app2 = new Vue({
        el: '#app2',
        data: {}
      })
    </script>
  </body>
</html>

父组件和子组件的区分

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <cpn2></cpn2>

      <hr>
      <!-- 不会生效 -->
      <cpn1></cpn1>

    </div>

    <script src="../js/vue.js"></script>
    <script>

      const cpnC1 = Vue.extend({
        template:  `
				<div>
					<h2>title</h2>
					<p>content: hahahaha</p>
      	</div>
			`
      });

      const cpnC2 = Vue.extend({
        template: 
        `
					<div>
						<h2>title</h2>
						<p>content: hehehehe</p>
						<cpn1></cpn1>
     			</div>
				`,
        components: {
          cpn1: cpnC1 
        }
      })

      const app = new Vue({
        el: '#app',
        data: {

        },
        components: {
          cpn2: cpnC2
        }
      })
    </script>
  </body>
</html>

注册组件的语法糖写法

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>

  <body>
    <div id="app">
      <cpn1></cpn1>
      <cpn2></cpn2>
    </div>

    <script src="../js/vue.js"></script>
    <script>
      // 1. 全局组件注册的语法糖
      // 1. 创建组件构造器
      // const cpn1 = Vue.extend()
      // 2. 注册组件
      Vue.component('cpn1', {
        template:
        `
					<div>
						<h2>title</h2>
						<p>content: hahahaha</p>
      		</div>
				`
      })
      const app = new Vue({
        el: '#app',
        data: {

        },
        methods: {

        },
        components: {
          cpn2: {
            template:
            `
							<div>
								<h2>title</h2>
								<p>content: hehehe</p>
      				</div>
						`
          }
        }
      })
    </script>
  </body>

</html>

组件模板的抽离写法

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>

  <body>
    <div id="app">
      <cpn1></cpn1>
      <cpn2></cpn2>
    </div>

    <!-- 方法1 -->
    <script type="text/x-template" id="cpn1">
        <div>
            <h2>title</h2>
            <p>content: hahahaha</p>
      </div>
    </script>

    <!-- 方法2 -->
    <template id="cpn2">
      <div>
        <h2>title</h2>
        <p>content: hehehehe</p>
      </div>
    </template>

    <script src="../js/vue.js"></script>
    <script>

      Vue.component('cpn1', {
        template: '#cpn1'
      })
      const app = new Vue({
        el: '#app',
        data: {

        },
        methods: {

        },
        components: {
          cpn2: {
            template: '#cpn2'
          }
        }
      })
    </script>
  </body>

</html>

为什么组件data必须是函数

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <cpn></cpn>
      <cpn></cpn>
      <cpn></cpn>
      <cpn></cpn>
    </div>

    <template id="cpn">
      <div>
        <h2>当前计数: {{ counter }}</h2>
        <button @click="increment">+</button>
        <button @click="decrement">-</button>
      </div>
    </template>

    <script src="../js/vue.js"></script>
    <script>

      Vue.component('cpn', {
        template: '#cpn',
        data() {
          // 每次都返回一个新的对象,多个组件之间的数组不会相互影响
          return {
            counter: 0
          }
        },
        methods: {
          increment() {
            this.counter++;
          },
          decrement() {
            this.counter--;
          }
        },
      })
      const app = new Vue({
        el: '#app',
        data: {

        }
      })
    </script>
  </body>
</html>

父子组件通信

父传子-props

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <cpn v-bind:cmovies="movies" :cmessage="message"></cpn>
    </div>

    <template id="cpn">
      <div>
        <p>{{ cmovies }}</p>
        <h2>{{ cmessage }}</h2>
      </div>
    </template>

    <script src="../js/vue.js"></script>
    <script>
      const cpn = {
        template: '#cpn',
        // props方式1:数组
        // props: [
        //     'cmovies',
        //     'cmessage'
        // ],
        // props方式2: 对象
        props: {
          // 1. 类型限制
          // cmovies: Array,
          // cmessage: String

          // 2. 提供一些默认值
          cmessage: {
            type: String,
            default: 'default messgae'
          },
          cmovies: {
            type: Array,
            default: [],
            // 必须传值
            required: true
          }
          // 支持多种数据类型以及自定义验证
        }
      }

      const app = new Vue({
        el: '#app',
        data: {
          movies: ['aaa', 'bbb', 'ccc'],
          message: 'hello'
        },
        components: {
          cpn
        }
      })
    </script>
  </body>
</html>

父传子-驼峰标识

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <!-- 这里不能使用驼峰 -->
      <cpn :c-info="info"></cpn>
    </div>

    <template id="cpn">
      <div>   
        {{ cInfo }}
      </div>
    </template>

    <script src="../js/vue.js"></script>
    <script>

      const cpn = {
        template: '#cpn',
        props: {
          cInfo: {
            type: Object,
            default() {
              return {}
            }
          }
        }
      }

      const app = new Vue({
        el: '#app',
        data: {
          info: {
            name: 'xx',
            age: 18,
            height: 1.88
          }
        },
        components: {
          cpn
        }
      })
    </script>
  </body>
</html>

子传父-自定义事件

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <cpn v-on:item-click="cpnClick"></cpn>
    </div>

    <template id="cpn">
      <div>
        <button v-for="item in categories" :key="item.id" @click="handleClick(item)">{{ item.name }}</button>
      </div>
    </template>

    <script src="../js/vue.js"></script>
    <script>
      const cpn = {
        template: '#cpn',
        data() {
          return {
            categories: [
              { id: 'aaa', name: '111' },
              { id: 'bbb', name: '222' },
              { id: 'ccc', name: '333' },
              { id: 'ddd', name: '444' },
              { id: 'eee', name: '555' }
            ]
          }
        },
        methods: {
          handleClick(item) {
            this.$emit('item-click', item);
          }   
        }
      }
      const app = new Vue({
        el: '#app',
        data: {

        },
        components: {
          cpn
        },
        methods: {
          cpnClick(item) {
            console.log(item.name);
          }
        }
      })
    </script>
  </body>
</html>

父子组件通信-结合双向绑定

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <cpn :number1="num1" :number2="num2"
           @num1change="num1change"
           @num2change="num2change"></cpn>
    </div>

    <template id="cpn">
      <div>
        <h2>props: {{number1}}</h2>
        <h2>data: {{dnumber1}}</h2>
        <!-- <input type="text" v-model="dnumber1"> -->
        <input type="text" :value="dnumber1" @input="num1Input">
        <h2>props: {{number2}}</h2>
        <h2>data: {{dnumber2}}</h2>          
        <!-- <input type="text" v-model="dnumber2"> -->
        <input type="text" :value="dnumber2" @input="num2Input">
      </div>
    </template>

    <script src="../js/vue.js"></script>
    <script>
      const app = new Vue({
        el: '#app',
        data: {
          num1: 1,
          num2: 0,
        },
        components: {
          cpn: {
            template: '#cpn',
            props: {
              number1: Number,
              number2: Number
            },
            data() {
              return {
                dnumber1: this.number1,
                dnumber2: this.number2
              }
            },
            methods: {
              num1Input(event) {
                this.dnumber1 = event.target.value;
                this.$emit('num1change', this.dnumber1);
                this.dnumber2 = this.number1 * 100;
                this.$emit('num2change', this.dnumber2);
              },
              num2Input() {
                this.dnumber2 = event.target.value;
                this.$emit('num2change', this.dnumber2);

                this.dnumber1 = this.dnumber2 / 100;
                this.$emit('num1change', this.dnumber1);
              }
            }
          }
        },
        methods: {
          num1change(value) {
            this.num1 = parseInt(value);
          },
          num2change(value) {
            this.num2 = parseInt(value);
          }
        }
      })
    </script>
  </body>
</html>

watch实现

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <cpn :number1="num1" :number2="num2"
           @num1change="num1change"
           @num2change="num2change"></cpn>
    </div>

    <template id="cpn">
      <div>
        <h2>props: {{number1}}</h2>
        <h2>data: {{dnumber1}}</h2>
        <input type="text" v-model="dnumber1">
        <h2>props: {{number2}}</h2>
        <h2>data: {{dnumber2}}</h2>          
        <input type="text" v-model="dnumber2">
      </div>
    </template>

    <script src="../js/vue.js"></script>
    <script>
      const app = new Vue({
        el: '#app',
        data: {
          num1: 1,
          num2: 0,
        },
        components: {
          cpn: {
            template: '#cpn',
            props: {
              number1: Number,
              number2: Number
            },
            data() {
              return {
                dnumber1: this.number1,
                dnumber2: this.number2
              }
            },
            watch: {
              dnumber1(newValue) {
                this.dnumber2 = newValue * 100;
                this.$emit('num1change', newValue);
              },
              dnumber2(newValue) {
                this.dnumber1 = newValue / 100;
                this.$emit('num2change', newValue);
              }
            }
          }
        },
        methods: {
          num1change(value) {
            this.num1 = parseInt(value);
          },
          num2change(value) {
            this.num2 = parseInt(value);
          }
        }
      })
    </script>
  </body>
</html>

父访问子-children-refs

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <cpn></cpn>
      <cpn></cpn>
      <cpn ref="aaa"></cpn>
      <cpn></cpn>
      <button @click="btnClick">按钮</button>
    </div>

    <template id="cpn">
      <div>

      </div>
    </template>
    <script src="../js/vue.js"></script>
    <script>
      const app = new Vue({
        el: '#app',
        data: {

        },
        components: {
          cpn: {
            template: '#cpn',
            methods: {
              showMessage() {
                console.log('showMessage');
              }
            }
          }
        },
        methods: {
          btnClick() {
            // 1. 一般不使用,当html结构发生变化的时候,下标要重新获取
            // console.log(this.$children[0].showMessage());

            // 2. refs方法
            console.log(this.$refs.aaa);
          }
        }
      })
    </script>
  </body>
</html>

子访问父-parent-root

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <cpn></cpn>
    </div>

    <template id="cpn">
      <div>
        <h2>我是子组件</h2>
        <ccpn></ccpn>
        <button @click="btnclick">按钮</button>
      </div>
    </template>

    <template id="ccpn">
      <div>
        <h2>我是子子组件</h2>
        <button @click="ccbtnclick">子子按钮</button>
      </div>
    </template>
    <script src="../js/vue.js"></script>
    <script>
      const app = new Vue({
        el: '#app',
        data: {

        },
        components: {
          cpn: {
            template: '#cpn',
            data() {
              return {
                name: '我是cpn组件的name'
              }
            },
            methods: {
              btnclick() {
                console.log(this.$parent);
              }
            },
            components: {
              ccpn: {
                template: '#ccpn',
                methods: {
                  ccbtnclick() {
                    console.log(this.$parent);
                    console.log(this.$parent.name);

                    console.log(this.$root);
                  }
                }
              }
            }
          }
        }
      })
    </script>
  </body>
</html>

插槽

插槽的基本使用

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <cpn>
        <button>按钮</button>
      </cpn>
      <cpn>
        <span>哈哈哈</span>
      </cpn>
      <cpn>
        <i>呵呵呵</i>
      </cpn>
      <cpn>

      </cpn>
    </div>

    <template id="cpn">
      <div>
        <h2>我是组件</h2>
        <p>我是组件,哈哈哈</p>
        <slot>
          <button>默认值</button>
        </slot>
      </div>
    </template>

    <script src="../js/vue.js"></script>
    <script>
      const app = new Vue({
        el: '#app',
        data: {

        },
        components: {
          cpn: {
            template: '#cpn',

          }
        }
      })
    </script>
  </body>
</html>

具名插槽的使用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <cpn>
            <span slot="left">返回</span>
            <span slot="center">标题</span>
        </cpn>        
    </div>

    <template id="cpn">
        <div>
            <h2>我是组件</h2>
            <p>我是组件,哈哈哈</p>
            <slot name="left"><span>左边</span></slot>
            <slot name="center"><span>中间</span></slot>
            <slot name="right"><span>右边</span></slot>
        </div>
    </template>

    <script src="../js/vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {

            },
            components: {
                cpn: {
                    template: '#cpn',

                }
            }
        })
    </script>
</body>
</html>

编译作用域的概念

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <cpn v-show="isShow"></cpn>
    </div>

    <template id="cpn">
      <div>
        <h2>我是子组件</h2>
        <p>我是内容,哈哈哈</p>
      </div>
    </template>

    <script src="../js/vue.js"></script>
    <script>
      const app = new Vue({
        el: '#app',
        data: {
          isShow: true
        },
        components: {
          cpn: {
            template: '#cpn',
            data() {
              return {
                isShow: false
              }
            }
          }
        }
      })
    </script>
  </body>
</html>

作用域插槽的使用

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <body>
    <!-- 父组件替换插槽的标签,但是内容由子组件来提供 -->
    <div id="app">
      <cpn></cpn>
      <cpn>
        <template slot-scope="slot">
          <!-- <span v-for="item in slot.data">{{item}}-</span>
-->
          <span>{{slot.data.join('-')}}</span>
        </template>
      </cpn>
      <cpn></cpn>
      <cpn></cpn>
    </div>

    <template id="cpn">
      <div>
        <slot :data="pLanguages">
          <ul>
            <li v-for="item in pLanguages">{{item}}</li>
          </ul>
        </slot>     
      </div>
    </template>

    <script src="../js/vue.js"></script>
    <script>
      const app = new Vue({
        el: '#app',
        data: {

        },
        components: {
          cpn: {
            template: '#cpn',
            data() {
              return {
                pLanguages: ['js', 'java', 'c++', 'go']
              }
            } 
          }
        }
      })
    </script>
  </body>
</html>