03-Vue中的组件

nobility 发布于 2022-01-18 1199 次阅读


Vue中的组件

组件是可复用的Vue实例,且带有一个名字,因为组件是可复用的Vue实例,所以和new Vue()接收相同的参数,Vue使用Vue.component()注册全局组件,使用components属性注册局部组件

组件的基本使用

  • 由于HTML大小写不敏感,所以定义组件名称时的驼峰命名法在HTML中使用短横线连接
  • 由于HTML规范,比如tbody中只能是tr等,可能导致组件渲染时可能结构就会出问题,可以使用is属性来指定使用的是组件,is属性方式可以使用驼峰命名法,也可使用短横线连接
  • 为了组件的复用性,数据互相独立,要求组件中的data参数必须是一个方法
  • template中必须有一个根元素
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>hello world</title>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><!-- CDN方式引入Vue -->
</head>

<body>
  <div id="app">
    <count-component></count-component> <!-- 使用全局组件 -->
    <count-component></count-component> <!-- 复用组件 -->
    <div is="countComponent"></div> <!-- 使用is方式使用组件,可采用驼峰命名法,也可采用短横线连接 -->
    <hello-component></hello-component> <!-- 使用局部子组件 -->
  </div>
  <script>
    Vue.component("countComponent",{  //注册全局组件
      template: `
        <div>
          <h1 @click="add">{{count}}</h1>
          <strong>测试是否必须有根元素</strong>
        </div>
        `,  //组件模板
      data(){ return { count: 0 } },  //组件绑定数据,data必须是方法
      methods: { add(){ this.count++; } } //组件中的方法
    });
    
    var app = new Vue({ //创建Vue实例
      el: "#app",
      components: { //注册局部组件
        helloComponent: { template: `<h1>hello component</h1>` }
      }
    });
  </script>
</body>

</html>

组件之间的信息传递

父子组件之间的信息传递

  • 父组件通过属性的方式向子组件传递信息,子组件通过props属性进行接收
  • 子组件通过this.$emit("自定义事件名",...参数)事件触发的方式向父组件传递信息,父组件通过监听该事件来接收
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>hello world</title>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><!-- CDN方式引入Vue -->
</head>

<body>
  <div id="app">
    <test-component arg="hello world" @custom="fun"></test-component> <!-- 使用绑定方式传递的是JavaScript代码,所以是数字 -->
  </div>
  <script>
    Vue.component("testComponent", { //注册全局组件
      props: ["arg"], //props用于接收接收父组件传递进来的数据
      template: `<h1 @click="$emit('custom', 'bye world')">{{arg}}</h1>`, //组件模板
    });
    var app = new Vue({ //创建Vue实例
      el: "#app",
      methods: {
        fun(arg){ console.log(arg); } //接收到子组件触发传递的参数
      }
    });
  </script>
</body>

</html>
prop
  • 由于HTML大小写不敏感,所以在HTML中使用短横线连接,在子组件接收属性时使用驼峰命名法
  • 单向数据流,即子组件不能修改父组件传递的信息,因为当传递参数是对象时,是引用传递,当前子组件修改后会影响到其他地方使用该对象
  • props定义为对象,可为传递参数提供一个带有验证需求的对象,就可以对传入
  • 对于未接收的属性,会再子组件的根部添加上该子定义的属性,可在组件中使用inheritAttrs: false对此特性进行关闭
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>hello world</title>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><!-- CDN方式引入Vue -->
</head>

<body>
  <div id="app">
    <test-component :count="10" data-test="test"></test-component> <!-- 使用绑定方式传递的是JavaScript代码,所以是数字 -->
    <test-component count="100" data-test="test"></test-component> <!-- 未使用绑定方式传递的是字符串 -->
    <verify-component
    content-one="100"
    :content-two="100"
    data-test="test"
    ></verify-component>
  </div>
  <script>
    Vue.component("testComponent", { //注册全局组件
      props: ["count"], //字符串数组方式没有任何类型校验
      data() { return { number: this.count } }, //组件绑定数据,不允许子组件进行修改传递的数据,所以拷贝父组件传递的参数
      template: `<h1 @click="number++">{{number}}</h1>` //组件模板
    });
    Vue.component("verifyComponent", { //注册全局组件
      props: {
        "contentOne": String, //此时传递必须为字符串,类型是原生构造函数
        "contentTwo": [Number, String],  //此时传递必须为数字或字符串
        "contentThree": {
          type: String, //参数类型
          required: false,  //是否为必须参数 
          default: "100", //未传入时的默认值
          validator(value){return true} //自定义校验函数,若通过返回true即可,value是传入的参数
        }
      },
      inheritAttrs: false,  //关闭非prop特性
      template: `<h1>{{contentOne}}、{{contentTwo}}、{{contentThree}}</h1>` //组件模板
    });
    var app = new Vue({ //创建Vue实例
      el: "#app"
    });
  </script>
</body>

</html>
自定义事件
  • 由于HTML大小写不敏感,所以事件的命名只能使用短横线连接,不能使用驼峰命名法
  • 默认组件上的事件都是自定义事件,可使用.native事件修饰符可为子组件绑定原生事件
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>hello world</title>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><!-- CDN方式引入Vue -->
</head>

<body>
  <div id="app">
    <test-component @child-event="handle"></test-component>
    <click-component @click="handle"></click-component>
    <native-component @click.native="handle"></native-component>
  </div>
  <script>
    Vue.component("testComponent", { //注册全局组件
      template: `<h1 @click="$emit('child-event')">test component</h1>` //事件名只能使用短横线
    });
    Vue.component("clickComponent", { //注册全局组件
      template: `<h1 @click="$emit('click')">click component</h1>` //必须向父组件传递自定义事件,此时click是自定义事件
    });
    Vue.component("nativeComponent", { //注册全局组件
      template: `<h1>native component</h1>` //使用.native可为组件监听原生事件
    });
    var app = new Vue({ //创建Vue实例
      el: "#app",
      methods: {
        handle(){ console.log("事件触发"); }
      }
    });
  </script>
</body>

</html>

非父子组件之间的信息传递

将Vue实例绑定在Vue的原型上,这样所有创建出的Vue实例都会多带一个辅助的Vue实例,并且所以Vue实例中的辅助Vue实例是同一个,通过该辅助Vue实例进行事件的监听和触发即可达到非父子组件的信息传递,该模式也就是观察者模式,Vue中也叫总线

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

<head>
  <meta charset="UTF-8">
  <title>hello world</title>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><!-- CDN方式引入Vue -->
</head>

<body>
  <div id="app">
    <h1>click 后会将tow中的内容修改</h1>
    <component-one content="one中的内容"></component-one>
    <component-two></component-two>
  </div>
  <script>
    Vue.prototype.bus = new Vue();  //挂载Vue实例到Vue原型上
    Vue.component("componentOne",{  //注册全局组件
      props: ["content"],
      template: `<h1 @click="clickHandle">component one:{{content}}</h1>`,
      methods: {
        clickHandle(){
          this.bus.$emit("click",this.content); //使用bus进行自定义事件的触发
        }
      }
    });

    Vue.component("componentTwo",{  //注册全局组件
      data(){ return {content: "two原来的内容"} },
      template: `<h1>component tow :{{content}}</h1>`,
      mounted(){  //当元素挂在到页面上之后
        this.bus.$on("click",(content)=>{ //监听bus的自定义事件
          this.content = content;
        })
      }
    });
    
    var app = new Vue({ //创建Vue实例
      el: "#app"
    });
  </script>
</body>

</html>

列表渲染组件

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

<head>
  <meta charset="UTF-8">
  <title>hello world</title>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><!-- CDN方式引入Vue -->
</head>

<body>
  <div id="app">
    <table>
      <tbody>
        <tr is="count-component"  v-for="(item,index) in counts" :key="item" @delete="deleteHandle"
        :count="item" :index="index"> <!-- 由于HTML5的规范,这里只能使用is方式才能正确渲染 -->
        </tr> <!-- 列表渲染组件,并传递数组值和索引给子组件 -->
      </tbody>
    </table>
  </div>
  <script>
    Vue.component("countComponent",{  //注册全局组件
      props: ["count","index"], //props用于接收接收父组件传递进来的数据,但是不允许子组件进行修改传递的数据
      template: `<tr><td @click="$emit('delete',index)">{{count}}</td></tr>`,  //组件模板
    });
    var app = new Vue({ //创建Vue实例
      el: "#app",
      data(){ return { counts: [0, 1, 2, 4, 5] } },
      methods: {
        deleteHandle(index){
          this.counts.splice(index,1);  //删除数组中该索引内容
        }
      }
    });
  </script>
</body>

</html>

访问子组件实例或子元素

为子元素或子组件使用ref属性进行标记,之后在父元素或父组件中的this.$refs对象中拿到该子元素或子组件

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

<head>
  <meta charset="UTF-8">
  <title>hello world</title>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><!-- CDN方式引入Vue -->
</head>

<body>
  <div id="app">
    <div ref="hello" @click="clickHandle">hello world</div>
    <hello-component ref="component" @click.native="clickHandle"></hello-component>
  </div>
  <script>
    Vue.component("helloComponent",{  //注册全局组件
      template: `<h1>hello component</h1>`,
    });
    
    var app = new Vue({ //创建Vue实例
      el: "#app",
      methods: {
        clickHandle(){
          console.log(this.$refs.hello);	//子元素
          console.log(this.$refs.component);	//子组件
        }
      }
    });
  </script>
</body>

</html>

插槽

用于向组件中传递DOM元素,提高了组件的多样性

简单插槽使用

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

<head>
  <meta charset="UTF-8">
  <title>hello world</title>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><!-- CDN方式引入Vue -->
</head>

<body>
  <div id="app">
    <solt-component></solt-component> <!-- 未传入DOM元素的插槽将显示插槽中的默认内容 -->
    <solt-component>
      <strong>传入插槽中的内容</strong> <!-- 传入DOM元素的插槽将替换插槽中的默认内容 -->
    </solt-component>
  </div>
  <script>

    Vue.component("soltComponent",{  //注册全局组件
      template: `<p>
        <strong>hello solt</strong>
        <slot><em>default content</em></slot>
      </p>`,
    });
    
    var app = new Vue({ //创建Vue实例
      el: "#app"
    });
  </script>
</body>

</html>

具名插槽

在Vue 2.6.0中,具名插槽和作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)。它取代了slotslot-scope这两个目前已被废弃但未被移除

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

<head>
  <meta charset="UTF-8">
  <title>hello world</title>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><!-- CDN方式引入Vue -->
</head>

<body>
  <div id="app">
    <anonymity-slot>
      <strong>left</strong>
      <strong>right</strong>
    </anonymity-slot>
    <!-- 此时会发现两个标签分别被渲染了两遍,这是因为一个slot中的内容其实是组件包裹的所有DOM元素 -->

    <named-slot>
      <template v-slot:left>  <!-- 具名插槽使用template进行包裹,并使用v-slot:帮i的那个插槽名-->
        <strong>left</strong>
      </template>
      <template v-slot:default> <!-- 对于匿名插槽,Vue会分配一个default的名字,当然不写template会默认渲染到匿名插槽中 -->
        <strong>default</strong>
      </template>
      <template #right> <!-- 也可使用 #插槽名 的简写形式-->
        <strong>right</strong>
      </template>
    </named-slot>
    <!-- 此时会发现正确渲染了,但是这种方式有点繁琐,若对应的插槽名刚好是HTML标签,也可以使用下面方式简写 -->
    <html-slot>
      <header>
        <strong>header</strong>
      </header>
      <strong>default</strong>
      <footer>
        <strong>footer</strong>
      </footer>
    </html-slot>

  </div>
  <script>
    //匿名插槽
    Vue.component("anonymitySlot",{  //注册全局组件,希望外部传递两个slot
      template: `<p>
        <slot></slot>
        <em>hello solt</em>
        <slot></slot>
      </p>`,
    });

    //具名插槽
    Vue.component("namedSlot",{  //注册全局组件,希望外部传递两个slot
      template: `<p>
        <slot name="left"></slot>
        <slot></slot>
        <slot name="right"></slot>
      </p>`,
    });
    Vue.component("htmlSlot",{  //注册全局组件,希望外部传递两个slot
      template: `<p>
        <slot name="header"></slot>
        <slot></slot>
        <slot name="footer"></slot>
      </p>`,
    });
    
    var app = new Vue({ //创建Vue实例
      el: "#app"
    });
  </script>
</body>

</html>

作用域插槽

在Vue 2.6.0中,具名插槽和作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)。它取代了slotslot-scope这两个目前已被废弃但未被移除

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

<head>
  <meta charset="UTF-8">
  <title>hello world</title>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><!-- CDN方式引入Vue -->
</head>

<body>
  <div id="app">

    <scope-slot>
      <template #default="slotProps"><!-- 绑定slotProps对象,插槽绑定数据会挂在该对象上 -->
        <li>{{slotProps}}</li>
      </template>
    </scope-slot>
  </div>
  <script>
    Vue.component("scopeSlot",{  //注册全局组件
      data(){ return { array: [1, 2, 3] } },  //插槽绑定的数据,父级可通过绑定对象方式访问到
      template: `<ul>
        <slot v-for="item in array" :item="item"></slot>
        </ul>`,
    });
    var app = new Vue({ //创建Vue实例
      el: "#app"
    });
  </script>
</body>

</html>

Vue中自带组件

component

component是Vue自带的组件,用于做动态组件,使用is属性进行绑定组件名称(不仅使用component标签可实现动态组件,任意标签都可以,但是component更加语义化)

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

<head>
  <meta charset="UTF-8">
  <title>hello world</title>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><!-- CDN方式引入Vue -->
</head>

<body>
  <div id="app">
    <component :is="componentType"></component> <!-- 不仅使用component标签可实现动态组件,任意标签都可以,但是component更加语义化 -->
    <p>keep-alive组件可使组件不会直接销毁再重建,而是会缓存在内存中,所以输入框中内容还在</p>
    <keep-alive>
      <component :is="componentType"></component>
    </keep-alive>
    <button @click="clickHandle">切换</button>
  </div>
  <script>
    Vue.component("componentOne",{  //注册全局组件
      template: `<div>componentOne:<input type="text"></div>`,
    });
    Vue.component("componentTwo",{  //注册全局组件
      template: `<div>componentTwo</div>`,
    });
    var app = new Vue({ //创建Vue实例
      el: "#app",
      data(){ return { componentType: "componentTwo" } },
      methods: {
        clickHandle(){
          this.componentType = this.componentType === "componentOne" ? "componentTwo" : "componentOne";
        }
      }
    });
  </script>
</body>

</html>

keep-alive

keep-alive是Vue自带的组件,用于缓存DOM或组件,可接收以下两个属性,可是数组、正则和逗号分隔的字符串

  • include:匹配组件会被缓存
  • exclude :匹配组件不会会被缓存

于此同时,使用keep-alive组件包裹的组件会多出以下两个生命周期钩子

  • activated:被keep-alive缓存的组件激活时调用
  • deactivated:被keep-alive缓存的组件停用时调用
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>hello world</title>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><!-- CDN方式引入Vue -->
</head>

<body>
  <div id="app">
    <h2>使用keep-alive缓存的组件mounted只会执行一次,destroyed不会执行,因为被缓存了并不会频繁创建和销毁</h2>
    <h2>activated和deactivated会被频繁执行</h2>
    <keep-alive>
      <component :is="componentType"></component>
    </keep-alive>
    <keep-alive exclude="componentOne"> <!-- 排除componentOne的缓存 -->
      <component :is="componentType"></component>
    </keep-alive>
    <button @click="clickHandle">切换</button>
  </div>
  <script>
    Vue.component("componentOne",{  //注册全局组件
      template: `<div>componentOne:<input type="text"></div>`,
        activated(){console.log("activated");},
        deactivated(){console.log("deactivated");},
        mounted(){console.log("mounted");},
        destroyed(){console.log("destroyed");}
    });
    Vue.component("componentTwo",{  //注册全局组件
      template: `<div>componentTwo</div>`,
    });
    var app = new Vue({ //创建Vue实例
      el: "#app",
      data(){ return { componentType: "componentTwo" } },
      methods: {
        clickHandle(){
          this.componentType = this.componentType === "componentOne" ? "componentTwo" : "componentOne";
        }
      }
    });
  </script>
</body>

</html>
此作者没有提供个人介绍
最后更新于 2022-01-18