• Vue3手册译稿


    基础示例

    一个Vue组件示例:

    // 创建一个Vue应用
    const app = Vue.createApp({})
    
    // 定义一个叫`button-counter`的全局组件
    app.component('button-counter', {
      data() {
        return {
          count: 0
        }
      },
      template: `
        <button @click="count++">
          你点击我 {{ count }} 次.
        </button>`
    })
    

    [info]提示
    这是一个简单的组件示例。在典型的 Vue应用程序中,组件一般都是在独立的文件中。你可以通过单文件Vue组件了解更多内容。

    组件可以根据组件名在一个实例里复用,这个示例中就是button-counter。可以当成一个定制组件在根节点下使用:

    <div id="components-demo">
      <button-counter></button-counter>
    </div>
    
    app.mount('#components-demo')
    

    因为组件是可复用的实例,它可以使用根实例相同的选项属性,例如data,computed,watch,methods和生命周期勾子。

    可复用组件

    组件可以根据你的需要多次复用:

    <!DOCTYPE html>
    <html lang="en-US">
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <title>resuing component</title>
    	<script src="https://unpkg.com/vue@next"></script>
       </head>
       <body>
    		<div id="app">
    			<button-counter></button-counter>
    			<button-counter></button-counter>
    			<button-counter></button-counter>
    			<button-counter></button-counter>
    			<button-counter></button-counter>
    			<button-counter></button-counter>
    		</div>
       </body>
       <script type="text/javascript">
    	const data = {
    		data() {
    			return {
    			}
    		},
    		methods: {
    		}
    	}
    	const app = Vue.createApp(data)
    	app.component('button-counter',{
    		data() {
    			return {
    				count: 0
    			}
    		},
    		template: `
    			<button @click="count++">你点击了我{{ count }} 次</button>
    		`
    	})
    	
    	app.mount("#app")
       </script>
    </html>
    

    注意每次点击按钮,每个按钮只增加自己的点击次数,count是分离的。这是因为每次使用一个组件,就相当于创建一个新的实例。

    组件组织

    通用作法是把组件组织成嵌套的组件树(组件套组件):
    image
    例如,一个应用你可能需要一个页对组件、导航栏组件、内容组件,每个组件内部又包含导航链接、博客列表组件等。


    使用组件前必须在Vue里先注册。有全局本地两种注册方式。刚才我们是使用`component选项在当前实例中注册了一个全局组件。在应用内部可以反复调用。
    如果你对组件注册仍然有点疑惑,建议阅读组件注册章节。

    使用props向子组件传递数据

    前面我们提到一个博客列表的组件,但我们需要传递博客标题、内容等我们想展示的字段给这个组件。因此props粉墨登场。
    props是一个可以注册到组件的定制属性, 是一个数组,可以接收多个参数。比如我们要传递博客标题到子组件,我们只需要把title包含在props属性列表里即可:

    const app = Vue.createApp({})
    
    app.component('blog-post', {
      props: ['title'],
      template: `<h4>{{ title }}</h4>`
    })
    
    app.mount('#blog-post-demo')
    

    一旦你把一个值传递给子组件,这个值就是子组件的一个属性,这个属性可以在子组件模板里同其他属性一样访问。
    一个组件可以有多个props,默认可以传递任何值给props
    一旦props注册成功,你就可以像定制属性一样传递数据给它了,示例:

    <!DOCTYPE html>
    <html lang="en-US">
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <title>resuing component</title>
    	<script src="https://unpkg.com/vue@next"></script>
       </head>
       <body>
    		<div id="app">
    			<blog
    				v-for="(item,index) in blogs"
    				:key="index"
    				:title="item.title"
    				:date="item.date"
    			>
    			</blog>
    		</div>
       </body>
       <script type="text/javascript">
    	const data = {
    		data() {
    			return {
    				blogs: [
    					{"title":"这是博客标题1",date:"2021年1月1日"},
    					{"title":"这是博客标题2",date:"2021年1月2日"},
    					{"title":"这是博客标题3",date:"2021年1月3日"},
    					{"title":"这是博客标题4",date:"2021年1月4日"},
    					{"title":"这是博客标题5",date:"2021年1月5日"}
    				]
    			}
    		}
    	}
    	const app = Vue.createApp(data)
    	app.component('blog',{
    		props: ['title','date'],
    		template: `
    			<div class="title">{{ title }}</div>
    			<div class="date">发表于:{{ date }}</div>
    			<hr />
    		`
    	})
    	
    	app.mount("#app")
       </script>
       <style type="text/css">
    		.title {font-size:25px;font-weight:bold;}
    		.date {font-size:18px;color:#cccccc;padding-left:10px;}
       </style>
    </html>
    

    运行结果如下:
    image

    上面示例展示了,通过v-bind(缩写:)传递动态数据给子组件。
    如果还是有点不理解,建议阅读pros章节。

    监听子组件事件

    上面我们定义了一个blog的组件,有些功能需要与父容器进行通讯。不用官网的例子了吧,我们来实现一个删除博客的功能。子组件调用父容器的methods,要用$emit发射出来,组件实例的地方使用属性接收,就可以调用父容器任何methods了:

    <!DOCTYPE html>
    <html lang="en-US">
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <title>resuing component</title>
    	<script src="https://unpkg.com/vue@next"></script>
       </head>
       <body>
    		<div id="app">
    			<blog
    				v-for="(item,index) in blogs"
    				:key="index"
    				:title="item.title"
    				:date="item.date"
    				:index="index"
    				@del="delblog($event)"
    			>
    			</blog>
    		</div>
       </body>
       <script type="text/javascript">
    	const data_and_methods = {
    		data() {
    			return {
    				blogs: [
    					{"title":"这是博客标题1",date:"2021年1月1日"},
    					{"title":"这是博客标题2",date:"2021年1月2日"},
    					{"title":"这是博客标题3",date:"2021年1月3日"},
    					{"title":"这是博客标题4",date:"2021年1月4日"},
    					{"title":"这是博客标题5",date:"2021年1月5日"}
    				]
    			}
    		},
    		methods: {
    			delblog(item_index) {
    				if(confirm("确实要删除这条博客吗?")){
    					this.blogs.splice(item_index,1)
    				}
    			}
    		}
    	}
    	const app = Vue.createApp(data_and_methods)
    	app.component('blog',{
    		props: ['title','date','index'],
    		template: `
    			<div class="title">{{ title }}</div>
    			<div class="date">发表于:{{ date }}</div>
    			<button @click="$emit('del',index)">删除</button>
    			<hr />
    		`
    	})
    	
    	app.mount("#app")
       </script>
       <style type="text/css">
    		.title {font-size:25px;font-weight:bold;}
    		.date {font-size:18px;color:#cccccc;padding-left:10px;}
       </style>
    </html>
    

    这个示例把后面的参数传递也包含了,后面内容不译了。

    组件中使用v-model

    记住,定制事件也可以用来创建一个与v-model一起使用的定制输入控件:

    <input v-model="searchText" />
    

    一样样的:

    <input :value="searchText" @input="searchText = $event.target.value" />
    

    使用该组件时,v-model被替换成这样:

    <custom-input
      :model-value="searchText"
      @update:model-value="searchText = $event"
    ></custom-input>
    

    [waring]警告
    注意到这里model-value使用了短横线命名法是因为我们使用在内联 DOM模板中。你可以在DOM模板解析警告章节中发现短横线和驼峰命名法详细解释。
    要想让这个组件正常使用,组件内的<input>必须要:

    • 绑定value属性到modelValueprops
    • 在input上要把新的录入内容使用update:modelValue发射出来
      看一个完整的例子吧:
    <!DOCTYPE html>
    <html lang="en-US">
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <title>component v-model</title>
    	<script src="https://unpkg.com/vue@next"></script>
       </head>
       <body>
    		<div id="app">
    			<custom-input v-model="searchText"></custom-input>
    			<p>{{ searchText }}</p>
    		</div>
       </body>
       <script type="text/javascript">
    	const data_and_methods = {
    		data() {
    			return {
    				searchText: ''
    			}
    		},
    		methods: {
    			
    		}
    	}
    	const app = Vue.createApp(data_and_methods)
    	app.component('custom-input',{
    		props: ['modelValue'],
    		emits: ['update:modelValue'],
    		template: `
    			<input :value="modelValue" @input="$emit('update:modelValue',$event.target.value)" />
    		`
    	})
    	
    	app.mount("#app")
       </script>
    </html>
    

    另一种实现组件v-model的方法是使用计算属性,为计算属性定义一个getsetget方法返回modelValue属性,set方法负责发射方法。

    app.component('custom-input', {
      props: ['modelValue'],
      emits: ['update:modelValue'],
      template: `
        <input v-model="value">
      `,
      computed: {
        value: {
          get() {
            return this.modelValue
          },
          set(value) {
            this.$emit('update:modelValue', value)
          }
        }
      }
    })
    

    看完上面仍有疑惑,读下定制事件吧!

    使用插槽实现内容分布

    像HTML标签一样,我们有时候需要传递内容到组件内部:

    <alert-box>
      出现了一些错误
    </alert-box>
    

    自定义组件内部的自定义内容像是有一个占位符,从组件实例里传递进来,替换占位符的内容。这就是插槽<slot>

    app.component('alert-box', {
      template: `
        <div class="demo-alert-box">
          <strong>Error!</strong>
          <slot></slot>
        </div>
      `
    })
    

    如有困惑,请阅读slot章节。
    插槽部分让我们稍微延伸下,制作一个APP的头部(使用了具名插槽):
    image

    <!DOCTYPE html>
    <html lang="en-US">
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <title>component slot</title>
    	<link href="https://cdn.bootcss.com/font-awesome/5.13.0/css/all.css" rel="stylesheet">
    	<script src="https://unpkg.com/vue@next"></script>
       </head>
       <body>
    		<div id="app">
    			<custom-header>
    				<template v-slot:leftcont><i class="fa fa-reply"></i></template>
    				<template v-slot:title>博客</template>
    				<template v-slot:rightcont><i class="fa fa-plus"></i></template>
    			</custom-header>
    		</div>
       </body>
       <script type="text/javascript">
    	const data = {
    		data() {
    			return {
    			}
    		},
    		methods: {
    			
    		}
    	}
    	const app = Vue.createApp(data)
    	app.component('custom-header',{
    		template: `
    			<div class="cust-container">
    				<div class="cust-left"><slot name="leftcont"></slot></div>
    				<div class="cust-mid"><slot name="title"></slot></div>
    				<div class="cust-right"><slot name="rightcont"></slot></div>
    			</div>
    		`
    	})
    	
    	app.mount("#app")
       </script>
       <style type="text/css">
    		body {margin:0px;padding:0px;color:#fff;}
    		.cust-container {display:flex;display: -webkit-flex;flex-direction:row;justify-content:space-between;background: gold;transition: 600ms ease-in-out;box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);  
                border-radius: 5px;  }
    		.cust-container div {padding:10px;}
    		.cust-mid {font-weight:bold;font-size:18px;}
       </style>
    </html>
    

    动态组件

    有时为不同组件设置一个动态开关是有用处的,类似tab页切换:

    
    <html lang="en-US">
        <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width,initial-scale=1">
          <title>vue  动态组件</title>
          <script src="https://unpkg.com/vue@next"></script>
         </head>
         <body>
            <div id="app" class="demo">
                <button
                    v-for="tab in tabs"
                    :key="tab"
                    :class="['tab-button',{active:currentTab === tab}]"
                    @click="currentTab = tab"
                >{{ tab }}</button>
                <component
                    :is="currentTabComponent"
                    class="tab"
                ></component>
            </div>
            <script type="text/javascript">
                const app = Vue.createApp({
                    data() {
                        return {
                            currentTab: 'Home',
                            tabs: ['Home','Posts','Archive']
                        }
                    },
                    computed: {
                        currentTabComponent(){
                            return 'tab-' + this.currentTab.toLowerCase()
                        }
                    }
                })
                app.component('tab-home',{
                    template: `
                        <div class="demo-tab">Home component</div>
                    `
                })
                app.component('tab-posts',{
                    template: `
                        <div class="demo-tab">Posts component</div>
                    `
                })
                app.component('tab-archive',{
                    template: `
                        <div class="demo-tab">Archive component</div>
                    `
                })
                app.mount('#app')
            </script>
            <style type="text/css">
                .demo {font-family: sans-serif;border: 1px solid #eee;border-radius: 2px;padding: 20px 30px;margin-top: 1em;margin-bottom: 40px;user-select: none;overflow-x: auto;}
                .tab-button {padding: 6px 10px;border-top-left-radius: 3px;border-top-right-radius: 3px;border: 1px solid #ccc;cursor: pointer;background: #f0f0f0;margin-bottom: -1px;margin-right: -1px;}
                .tab-button:hover {background: #e0e0e0;}
                .tab-button.active {background: #e0e0e0;}
                .demo-tab {border: 1px solid #ccc;padding: 10px;}
            </style>
        </body>
    </html>
    

    DOM模板解析提示

    有些HTML标签如<ul>,<table>,<ol>,<select>对于他内部的元素有严格的限制,而有些元素如,<li>,<tr>,<option>等只能出现在某些特定的元素内。
    在这些有严格限制的元素同组件一起使用时,会导致一些问题:

    <table>
      <blog-post-row></blog-post-row>
    </table>
    

    这个定制组件<blog-post-row>在渲染时因错误提升到外部。我们有一个v-is指令变通方法:

    <table>
      <tr v-is="'blog-post-row'"></tr>
    </table>
    

    [warning] v-is被当作JavaScript表达式,所以组件名需要放到引号内:

    <!-- 不正确,不会渲染任何内容 -->
    <tr v-is="blog-post-row"></tr>
    <!-- 正确 -->
    <tr v-is="'blog-post-row'"></tr>
    

    同时,HTML标签不区分大小写,所以浏览器会将大写解释为小写。这意味着你使用内联DOM模板,props和处理事件参数用驼峰命名会与短横线命相对就:

    // JavaScript中驼峰命名
    app.component('blog-post', {
      props: ['postTitle'],
      template: `
        <h3>{{ postTitle }}</h3>
      `
    })
    
    <!-- HTML中使用短横线 -->
    <blog-post post-title="hello!"></blog-post>
    

    需要注意的是,使用字符串模板时以下几种情况下这些限制是不存在的:

    本节示例代码:

    https://github.com/zhouyu629/vue3-demo/tree/main/compent-basic

  • 相关阅读:
    bzoj4152-[AMPPZ2014]The_Captain
    bzoj3209-花神的数论题
    [模板] 数位dp
    [西安交大附中集训] 自积
    [模板] 后缀数组
    [模板] 哈希表
    [西安交大附中集训] d6 删边(cip)
    java 发布订阅
    Spring Boot使用@Async实现异步调用:自定义线程池
    上传文件到服务器或者直接输出到输出流
  • 原文地址:https://www.cnblogs.com/zhouyu629/p/14510573.html
Copyright © 2020-2023  润新知