vue3学习随便记8

Posted sjg20010414

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue3学习随便记8相关的知识,希望对你有一定的参考价值。

组件基础

我们其实前面已经看过很多组件的例子,都是用 app.component(...) 注册一个全局组件,组件的“视图”是直接用 template 定义的字符串模板。这样的组件主要用来举例和学习原理,实际工程中通常会使用单文件组件,即一个组件是单个文件定义的(在编译构建系统中常常是 .vue 文件)。

组件是自定义元素,可以像元素一样复用,复用时,每个组件都是新的实例,它的属性方法都是它自己的。

组件通常可以嵌套组织成树状。

模板中要使用组件,必须先注册。注册分为全局注册和局部注册。我们用 app.component(...) 注册的都是全局注册,全局注册的组件可以在应用中的任何组件的模板中使用。用 app.component(...) 方法注册的组件是全局组件,但创建app时在其配置 components 中定义的组件是 app 的组件,却不是全局组件(属于局部注册),因为组件在其子组件中不可用。

const app = Vue.createApp(
  components: 
    'component-a': ComponentA,
    'component-b': ComponentB
  
)

上面的代码中,可以在app中使用 ComponentA 和 ComponentB,但不能在 ComponetB 中使用 ComponentA,如果希望 ComponentB 可以使用 ComponentA,必须明确声明

const ComponentA = 
  /* ... */


const ComponentB = 
  components: 
    'component-a': ComponentA
  
  // ...

通过 prop (属性)向子组件传递数据

组件的 prop 就是组件中自定义的 attribute,它是组件向外暴露的通信接口,是外部向子组件传递数据的方式。组件 prop 就是在组件配置对象的配置项 props: [...] 中定义,在组件模板中使用,然后外部像寻常的 attribute 一样绑定数据。

<html>

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

<body>
    <div id="app">
        <blog-post v-for="post in posts" :key="post.id" :title="post.title"></blog-post>
    </div>
    <script>
        const app = Vue.createApp(
            data() 
                return 
                    posts: [
                        id:1, title:'Vue3 之旅',
                        id:2, title:'Vue3 博客',
                        id:3, title:'Vue3 学习之趣'
                    ]
                
            
        )
        app.component('blog-post', 
            props: ['title'],
            template: `<h4> title </h4>`
        )
        const vm = app.mount('#app')
    </script>
</body>

</html>

 监听子组件事件

子组件是自定义的元素,除了可以用 props 扩展属性(attribute),还可以用 emits 扩展事件。我们为 blog-post 组件扩展 enlarge-text事件(在该事件触发后放大所有博文的字号):先给app添加一个属性变量表示博文的字号

            data() 
                return 
                    posts: [
                       /* ... */
                    ],
                    postFontSize: 1
                
            

然后模板中用该变量控制博文的字号,触发 blog-post 的 enlarge-text 事件时,增加该变量的值

    <div id="app">
        <div :style=" fontSize: postFontSize + 'em' ">
            <blog-post v-for="post in posts" :key="post.id" 
                :title="post.title" @enlarge-text="postFontSize += 0.1">
            </blog-post>
        </div>
    </div>

我们来修改 blog-post 组件,它应该向外暴露 enlarge-text 事件,同时,扩展的事件 enlarge-text 不会自动能触发,它要么手动用代码去触发,要么转嫁到组件已有的 HTML 事件上,在 HTML事件中用代码去触发。我们修改组件模板,给组件添加一个按钮,在该按钮的 click 事件中用代码去触发 enlarge-text事件。(在JS代码中,使用camelCased驼峰写法,如大驼峰的组件名BlogPost,小驼峰的变量 enlargeText,在HTML中,使用kebab-case短横分隔写法,如blog-post,enlarge-text )

        app.component('blog-post', 
            props: ['title'],
            emits: ['enlargeText'],
            template: `
                <div class="blog-post">
                    <h4> title </h4>
                    <button @click="$emit('enlargeText')">放大文本</button>
                </div>
            `
        )

 上面的代码中,我们放大文本的步进量是使用组件的一方决定的,如果我们希望这个步进是组件自身决定的,那么我们在用vm的API方法$emit触发enlargeText事件时,可以同时抛出一个值,然后让使用方使用这个值。

<button @click="$emit('enlargeText', 0.1)">放大文本</button>

使用方可以通过 $event 参数访问到抛出的值

            <blog-post v-for="post in posts" :key="post.id" 
                :title="post.title" @enlarge-text="postFontSize += $event">
            </blog-post>

如果事件处理函数是一个方法,那么抛出的值会成为该方法的第一个参数

    <div id="app">
        <div :style=" fontSize: postFontSize + 'em' ">
            <blog-post v-for="post in posts" :key="post.id" 
                :title="post.title" @enlarge-text="onEnlargeText">
            </blog-post>
        </div>
    </div>
    <script>
        const app = Vue.createApp(
            data() 
                return 
                    posts: [
                        /* ... */
                    ],
                    postFontSize: 1
                
            ,
            methods: 
                onEnlargeText(amount) 
                    this.postFontSize += amount
                
            
        )
        app.component('blog-post', 
            /* ... */
        )
        const vm = app.mount('#app')
    </script>

组件作为自定义的元素,也可以是自定义的输入元素,从而可以使用 v-model。首先我们要来了解 v-model 双向绑定的含义:值绑定到vm变量,input事件处理中把vm变量设置为输入元素的值

<input v-model="searchText" />

上述代码等价于

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

当在自定义的组件custom-input上使用 v-model 时,

<custom-input v-model="searchText"></custom-input>

等价于

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

上面的代码意味着,自定义元素(组件)必须向外暴露一个名为 modelValue 的 prop(属性),因为这是一个输入元素,所以,组件内部的 input 元素的 value 属性(attribute)应该绑定到 modelValue 属性上。自定义元素(组件)必须向外暴露一个名为 update:modelValue 的事件,组件内部 input 的 input 事件中,用vm API方法 $emit(...) 发射 update:modelValue 事件。即组件应定义如下

app.component('custom-input', 
  props: ['modelValue'],
  emits: ['update:modelValue'],
  template: `
    <input
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
    >
  `
)

我们观察模板部分,value值绑定 和 input事件,这本就是 v-model 的含义,我们可以换一种方式,使用 计算属性 及其 getter/setter 来定义组件

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)
      
    
  
)

通常计算属性是getter,对上述组件的 getter,动作路径是:使用者视图输入-> modelValue -> 内部 value,而内部value变化影响到使用者视图输入框则是 setter,动作路径是:内部value -> modelValue -> 使用者视图输入

通过slot插槽分发内容

props 是组件这个自定义元素的属性,它是变量的概念,我们有时候希望和HTML元素一样,把自定义元素的内容(元素的innerHTML)从外部传递到组件内部, 这是静态内容概念。实现内容从外到内的传递,可以使用 slot插槽,即 innerHTML  --->   <slot></slot>

我们给前面的blog-post组件插入slot,用来存放正文内容

    <div id="app">
        <div :style=" fontSize: postFontSize + 'em' ">
            <blog-post v-for="post in posts" :key="post.id" 
                :title="post.title" @enlarge-text="onEnlargeText">
                <div> post.body </div>
            </blog-post>
        </div>
    </div>
    <script>
        const app = Vue.createApp(
            data() 
                return 
                    posts: [
                        id:1, title:'Vue3 之旅', body: 'Vue3 之旅正文',
                        id:2, title:'Vue3 博客', body: 'Vue3 博客开启新篇章',
                        id:3, title:'Vue3 学习之趣', body: 'Vue3 学习之趣无穷无尽'
                    ],
                    postFontSize: 1
                
            ,
            methods: 
                onEnlargeText(amount) 
                    this.postFontSize += amount
                
            
        )
        app.component('blog-post', 
            props: ['title'],
            emits: ['enlargeText'],
            template: `
                <div class="blog-post">
                    <h4> title </h4>
                    <slot></slot>
                    <button @click="$emit('enlargeText', 0.1)">放大文本</button>
                </div>
            `
        )
        const vm = app.mount('#app')
    </script>

红色部分是 slot 分发的内容

动态组件

所谓动态组件,就是一个组件容器,可以动态确定要渲染的具体组件,一般用 :is 属性指定组件名(或者一个组件的选项对象,即组件的完整配置)。下面的例子,根据用按钮选择的当前的tab名称,动态渲染对应组件

    <div id="app">
        <button v-for="tab in tabs" :key="tab.name"
            :class="['tab-button', active: currentTab === tab.name]"
            @click="currentTab = tab.name">
             tab.text 
        </button>
        <component :is="currentTabComponent" class="tab"></component>
    </div>
    <script>
        const app = Vue.createApp(
            data() 
                return 
                    currentTab: 'Home',
                    tabs: [
                        name:'Home', text:'首页',
                        name:'Posts', text:'帖子',
                        name:'Archive', text:'归档'
                    ]
                
            ,
            computed: 
                currentTabComponent() 
                    return 'tab-' + this.currentTab.toLowerCase()
                
            
        )
        app.component('tab-home', 
            template: `<div class="demo-post">首页内容</div>`
        )
        app.component('tab-posts', 
            template: `<div class="demo-post">帖子……</div>`
        )
        app.component('tab-archive', 
            template: `<div class="demo-post">(归档)</div>`
        )
        const vm = app.mount('#app')
    </script>

 

Vue解析DOM模板的注意事项

如果想在 DOM 中直接书写 Vue 模板, Vue 就不得不从 DOM 中获取字符串,这时,会因为原生 HTML的解析行为带来一些小问题。这句话的意思是,我们在以下情况使用组件时,组件都是一个独立的整体:

  • 注册组件时使用字符串模板(例如 template: `...`)
  • 使用单文件组件
  • 使用 <script type="text/x-template">定义的组件

Vue 解析模板没有问题。而原生的 HTML DOM 和 Vue模板混在一起, Vue解析时势必要从混合物中捞出Vue模板,这样,Vue模板必须迁就原生HTML的一些特性。哪些特性呢?

元素位置受限

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

<table>内部允许的元素是有限制的,这样,上述代码中的<blog-post-row>会被认为是无效的内容提升到外部,导致渲染结果是错误的。这种情况,可以使用 is 属性来变通:

<table>
  <tr is="vue:blog-post-row"></tr>
</table>

这里is的值必须用 vue: 开头,表示这个不是HTML原生的自定义元素,而是Vue组件。

和这个例子类似的元素还有 <ul>、<ol>、<select>,而 <li>、<tr>、<option>只能出现在特定元素内部。

大小写不敏感

HTML 属性名不区分大小写的,从而浏览器将所有大写字母解释为小写,这个问题带来的一点就是JS代码中组件名称之类的是有大小写驼峰写法,驼峰写法的组件名不能直接在HTML中使用(TabA组件和taba组件会被认为是相同组件),解决办法我们前面已经看到过了,就是HTML中用驼峰写法等价的kebab-cased(短横分隔)写法(TabA对应tab-a)

以上是关于vue3学习随便记8的主要内容,如果未能解决你的问题,请参考以下文章

vue3学习随便记11

vue3学习随便记1

vue3学习随便记1

vue3学习随便记5

vue3学习随便记5

vue3学习随便记12