• Vue学习-组件和生命周期


    前言

    本人准备大四,做一些项目学习一些新的技能,如果文章有写的不好的或者不对的地方欢迎评论或者私信指出,谢谢!
    欢迎参观本人的个人博客:https://linzyblog.netlify.app/

    一、组件基础

    通常一个应用会以一棵嵌套的组件树的形式来组织:
    在这里插入图片描述
    例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。

    1、为什么我们要用组件

    了解传统方式到组件化方式的更新。
    在这里插入图片描述

    传统方式编写网页,会导致依赖关系混乱,代码复用率低,所以我们需要用到组件,不同组件的用来实现局部功能的代码和资源。

    在这里插入图片描述

    2、基本使用

    为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册和局部注册。至此,我们的组件都只是通过 component 方法全局注册的:

    1)全局注册

    组件是用来实现局部功能的代码和资源的集合,是带有名称的可复用实例

    const app = Vue.createApp({})
    
    app.component('my-component-name', {
      // ... 选项 ...
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5

    全局注册的组件可以在应用中的任何组件的模板中使用。

    2)局部注册

    父组件 App.vue
    子组件 header.vue、message.vue、bottom.vue

    • header.vue
    <script>
    export default {
        data() {
            return {
                msg: "这里是头部子组件"
            }
        }
    }
    script>
    
    <template>
        <h1>{{ msg }}h1>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • message.vue
    <script>
    export default {
        data() {
            return {
                msg: "这里是内容子组件"
            }
        }
    }
    script>
    
    <template>
        <h1>{{ msg }}h1>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • bottom.vue
    <script>
    export default {
        data() {
            return {
                msg: "这里是底部子组件"
            }
        }
    }
    script>
    
    <template>
        <h1>{{ msg }}h1>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • App.vue
    <script>
    //声明式渲染,可以提高开发效率
    //导入各个组件
    import heade from "./components/header.vue"
    import bottom from "./components/bottom.vue"
    import message from "./components/message.vue"
    export default {
      components: {
        heade,
        bottom,
        message,
      }
    };
    script>
    
    <template>
      <div>
        <heade>heade>
      div>
      <div>
        <message>message>
      div>
      <div>
        <bottom>bottom>
      div>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在这里插入图片描述

    3、通过 Prop 向子组件传递数据

    Prop 是你可以在组件上注册的一些自定义 attribute。为了给hello子组件传递一个message,我们可以用 props 选项将其包含在该组件可接受的 prop 列表中:

    1)传递值

    App.vue

    <script>
    //声明式渲染,可以提高开发效率
    import hello from './components/hello.vue'
    export default {
      data() {
        return {
        }
      },
      components: {
        hello
      }
    };
    script>
    
    <template>
      <div>
        
        <hello title="我是头部">hello>
        <hello title="我是内容">hello>
        <hello title="我是底部">hello>
      div>
    template >
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    hello.vue

    <script>
    export default {
        props: ['title'],
    }
    script>
    <template>
        <div>
            <h1>{{ title }}h1>
        div>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    当一个值被传递给一个 prop attribute 时,它就成为该组件实例中的一个 property。该 property 的值可以在模板中访问,就像任何其他组件 property 一样。
    一个组件可以拥有任意数量的 prop,并且在默认情况下,无论任何值都可以传递给 prop。

    2)用 v-bind 来动态传递 prop

    App.vue

    <script>
    //声明式渲染,可以提高开发效率
    import hello from './components/hello.vue'
    export default {
      data() {
        return {
          message: "hello vue",
        }
      },
      components: {
        hello
      }
    };
    script>
    
    <template>
      <div>
        <hello :msg="message">hello>
        <h1>父组件中h1>
        <input type="text" v-model="message">
      div>
    template >
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    hello.vue

    <script>
    export default {
        props: ['msg'],
    }
    script>
    <template>
        <div>
            <h1>hello子组件中h1>
            <h1>{{ msg }}h1>
        div>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

    我们传递给子组件的值是双向绑定的,我在父组件更新内容,子组件也会同样获取更新后的内容

    4、监听子组件事件

    我们在开发子组件时,它的一些功能可能需要与父级组件进行沟通,例如我在子组件改变特定值时,我们想要父组件也同样接收到,就需要父组件去监听子组件事件

    父组件去监听子组件事件

    1. 在子组件中设置方法,通过$emit来触发事件
    2. 在父组件中,通过v-on监听子组件中自定义的事件,接收子组件传来的值

    App.vue

    <script>
    import Content from './components/Content.vue'
    //声明式渲染,可以提高开发效率
    export default {
      data() {
        return {
          message: "hello vue",
        }
      },
      components: {
        Content
      },
      methods: {
        getMsg: function (msg) {
          console.log(msg);
          this.message = msg
        }
      }
    };
    script>
    
    <template>
      <div>
        
        
        <content @injectMsg="getMsg">content>
        <h1>{{ message }}h1>
      div>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    父级组件可以像处理原生 DOM 事件一样通过 v-on 或 @ 监听子组件实例的任意事件

    content.vue

    <script>
    import Hello from './HelloWorld.vue'
    export default {
        data() { //让每个组件都返回一个新的对象,不会造成数据污染
            //局部变量
            return {
                msg: "我是content",
                list: [1, 2, 3, 4]
            }
        },
        components: {
            Hello
        },
        methods: {
            //1、在子组件中,通过$emit来触发事件
            sendParent: function () {
                // this.emit('自定义事件名称', '发送参数')
                this.$emit('injectMsg', this.msg)
            }
        }
    }
    script>
    
    <template>
        <div>
            
            <Hello :message="msg" aaa="123" :list="list">Hello>
            <h2>我是content组件h2>
            <h2>{{ msg }}h2>
            <button @click="msg = '你好'">改变msgbutton>
            
            <button @click="sendParent">发送数据到父组件button>
        div>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    同时子组件可以通过调用内建的 $emit 方法并传入事件名称来触发一个事件

    helloworld.vue

    <script>
    export default {
      data() {
        return {
          msg: "我是hello"
        }
      },
      props: {//对象
        //1、类型限制
        message: String,
        //2、设置默认值、必须传值
        message: {
          type: String,
          default: "我是linzy",
          required: true,
        },
        list: {
          type: Array,
          default: [],
        }
      },
      mounted() {
        console.log(this.$parent);
        console.log(this.$root);
      }
    }
    script>
    
    <template>
      <div>
        <h4>helloh4>
        <h1>{{ message }}h1>
        { aaa }} -->
        <h1>{{ list }}h1>
      div>
    template>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    我们可以把props变为对象形式,这样我们在接收到的值进行条件限定,可以限定类型、设置默认值、必须传入值等。

    在这里插入图片描述
    在这里插入图片描述

    5、插槽

    和 HTML 元素一样,我们经常需要向一个组件传递内容,就是我们使用 < slot> 作为我们想要插入内容的占位符

    1)插槽内容

    App.vue

    <script>
    //声明式渲染,可以提高开发效率
    import Content from './components/Content.vue'
    export default {
      data() {
        return {
          message: "hello vue",
        }
      },
      components: {
        Content
      }
    };
    script>
    
    <template>
      <div>
        
        <Content>我是插槽Content>
        
        <Content><button>按钮button>Content>
        
        <Content><input type="text">Content>
      div>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    content.vue

    <template>
        <div>
            <h1>我是Content组件h1>
            <div>
                <slot>slot>
            div>
        div>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    如果有多个值,同时放入组件进行替换时,一起作为替换元素

    2)渲染作用域

    父级模板里的所有内容都在父级作用域中编译 子模板里的所有内容都是在子作用域中编译

    <todo-button>
      Delete a {{ item.name }}
    todo-button>
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    3)备用内容

    有时为一个插槽指定备用 (也就是默认的) 内容是很有用的,它只会在没有提供内容的时候被渲染。例如在一个 组件中:

    <button type="submit">
      <slot>Submitslot>
    button>
    
    • 1
    • 2
    • 3

    现在当我们在一个父级组件中使用 并且不提供任何插槽内容时

    <submit-button>submit-button>
    
    • 1

    备用内容“Submit”将会被渲染:

    <button type="submit">
      Submit
    button>
    
    • 1
    • 2
    • 3

    但是如果我们提供内容,则这个提供的内容将会被渲染从而取代备用内容

    4)具名插槽

    有时我们需要多个插槽,具名插槽可以根据slot的name进行分配,< slot> 元素有一个特殊的 attribute:name。通过它可以为不同的插槽分配独立的 ID,也就能够以此来决定内容应该渲染到什么地方。

    <template>
        <div>
            <h1>我是Content组件h1>
            <div>
                <slot>slot>
                <slot name="header">slot>
                <slot name="main">slot>
                <slot name="bottom">slot>
            div>
        div>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    一个不带 name 的 出口会带有隐含的名字“default”

    在向具名插槽提供内容的时候,我们可以在一个 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:

    <script>
    //声明式渲染,可以提高开发效率
    import Content from './components/Content.vue'
    export default {
      components: {
        Content
      }
    };
    script>
    
    <template>
      <div>
        <Content>
          
          <template v-slot:default>
            <p>我不是具名插槽.p>
          template>
          <template v-slot:header><button>header按钮button>template>
          <template v-slot:main><input type="text" value="main文本框">template>
          <template v-slot:bottom>
            <h1>我是bottom插槽h1>
          template>
        Content>
      div>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在这里插入图片描述

    注意,v-slot 只能添加在 < template> 上

    5)作用域插槽

    有时让插槽内容能够访问子组件中才有的数据是很有用的。当一个组件被用来渲染一个项目数组时,这是一个常见的情况,我们希望能够自定义每个项目的渲染方式。

    简而言之,可能每个页面插槽渲染的数据可能会不同,所以我们通过访问子组件中的数据来渲染,来实现相同的效果

    App.vue

    <script>
    //声明式渲染,可以提高开发效率
    import Content from './components/Content.vue'
    export default {
      components: {
        Content
      }
    };
    script>
    
    <template>
      <div>
        
        <Content>
          <template v-slot:default="slotProps">
            <ul>
              <li v-for="item in slotProps.item" :key="item">{{ item }}li>
            ul>
          template>
        Content>
        
        <Content>
          <template v-slot:default="slotProps">
            <ol>
              <li v-for="item in slotProps.item" :key="item">{{ item }}li>
            ol>
          template>
        Content>
      div>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    content.vue

    <script>
    export default {
        data() {
            return {
                arr: [1, 3, 6, 1, 7]
            }
        }
    
    }
    script>
    
    <template>
        <div>
            <h1>我是Content组件h1>
            <div>
                <slot :item="arr">slot>
            div>
        div>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述
    在这里插入图片描述

    6、跨级通信Provide / Inject

    **通常,当我们需要从父组件向子组件传递数据时,我们使用 props。**想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 prop 沿着组件链逐级传递下去,可能会很麻烦。

    对于这种情况,我们可以使用一对 provide 和 inject。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。这个特性有两个部分:父组件有一个 provide 选项来提供数据,子组件有一个 inject 选项来开始使用这些数据。
    在这里插入图片描述
    例如,我们有这样的层次结构:

    Root
    └─ TodoList
       ├─ TodoItem
       └─ TodoListFooter
          ├─ ClearTodosButton
          └─ TodoListStatistics
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果要将 todo-items 的长度直接传递给 TodoListStatistics,我们要将 prop 逐级传递下去:TodoList -> TodoListFooter -> TodoListStatistics。通过 provide/inject 的方式,我们可以直接执行以下操作:

    1. 祖先组件用provide值传递
    2. 子孙组件用inject接收值传递

    1)值传递/引用传递

    App.vue

    <script>
    import Content from "./components/Content.vue"
    //声明式渲染,可以提高开发效率
    export default {
      data() {
        return {
          message: "hello vue",
          obj: {
            message: "hello linzy",
          }
        }
      },
      components: {
        Content
      },
      //provide/inject并不是响应式的
      // provide: { message: this.message }
      //如果想去访问组件实例的属性
      provide() {
        return {
          //值传递
          message: this.message,
          //引用传递 
          //1、响应式对象方式
          obj: this.obj
        }
      }
    };
    script>
    
    <template>
      <div>
        <h1>HomeView----{{ message }}h1>
        <h1>HomeView----{{ obj.message }}h1>
        <p>--------------------p>
        <Content>Content>
        <button @click="message = '勇敢牛牛'">改变message按钮button>
        <button @click="obj.message = '哈哈哈'">改变对象里的message按钮button>
      div>
    template>
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    content.vue

    <script>
    export default {
        //值传递
        inject: ['message'],
        //引用传递
        inject: ['obj']
    }
    script>
    
    <template>
        <h2>hello---{{ obj.message }}h2>
        <h1>{{ message }}h1>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述
    注意:值传递就相当于,在子组件开辟了新的属性变量,跟父组件的属性变量没有关系了,引用传递,可以看做c语言里的指针的概念,传递了一个地址,都是属于一个内存空间的,所以子组件里对象的属性变量改变了,父组件的也会改变

    父组件不需要知道哪些子组件使用了它 provide 的 property
    子组件不需要知道 inject 的 property 来自哪里

    2)处理响应性

    这是因为默认情况下,provide/inject 绑定并不是响应式的
    我们可以通过传递一个 ref property 或 reactive 对象给 provide 来改变这种行为。在我们的例子中,如果我们想对祖先组件中的更改做出响应。
    App.vue

    <script>
    import Content from "./components/Content.vue"
    //声明式渲染,可以提高开发效率
    export default {
      data() {
        return {
          message: "hello vue",
          obj: {
            message: "hello linzy",
          }
        }
      },
      components: {
        Content
      },
      //provide/inject并不是响应式的
      // provide: { message: this.message }
      //如果想去访问组件实例的属性
      provide() {
        return {
          //函数返回响应式数据
          message: () => this.message,
        }
      }
    };
    script>
    
    <template>
      <div>
        <h1>HomeView----{{ message }}h1>
        <p>--------------------p>
        <Content>Content>
        <button @click="message = '勇敢牛牛'">改变message按钮button>
      div>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    content.vue

    <script>
    export default {
        //值传递
        inject: ['message'],
    }
    script>
    
    <template>
        <h1>{{ message() }}h1>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    二、生命周期

    在这里插入图片描述

    1. 实例化一个Vue对象
    2. 初始化事件和生命周期
    3. 执行beforeCreate钩子函数(Vue实例还没创建,获取不到Dom节点,拿不到data> 数据和methods方法)
    4. 初始化响应式、数据代理和数据监测
    5. 执行created钩子函数(Vue实例已经创建完毕,data数据和methods方法这些已经成功绑定到Vue实例中,但是Dom元素还没有生成,还不能调用)
    6. 有没有template选项
      1)Yes,把template编译成渲染函数
      2)No,我们将el挂载的html编译成template
    7. 执行beforeMount钩子函数(页面还没渲染前,el还挂载在虚拟Dom里)
    8. 将编译好的html去替换掉el属性里的Dom对象 ,把虚拟Dom变为真实Dom放入页面
    9. 执行mounted钩子函数(页面已经渲染出来,用来获取数据或者发送网络请求)
    10. 实时监听数据变化,随时更新Dom,当我们的数据发生变化时
      1) 执行beforeUpdate钩子函数(在数据改变之前,虚拟Dom已经更新完了,做一些更新之前需要做的事情)
      2)虚拟Dom重新渲染成真实Dom,对比虚拟Dom和真实Dom节点的不同,找需要更新的节点,从而更新
      3)执行updated钩子函数(数据改变之后)
      4)直到Vue实例对象被销毁
    11. 执行beforeUnmout钩子函数(Vue实例销毁之前,data数据和methods方法之类还没有被销毁,还能调用,可以解绑事件监听或者清除掉定时器之类事件)
    12. 执行unmouted钩子函数(Vue实例销毁之后)

    三、生命周期钩子

    App.vue

    <script>
    //声明式渲染,可以提高开发效率
    import content from './components/Content.vue'
    export default {
      data() {
        return {
          message: "hello vue",
          isShow: true,
        }
      },
      components: {
        content
      }
    };
    script>
    
    <template>
      <div>
        <content v-if="isShow">content>
        <button @click="isShow = !isShow">销毁hello组件button>
      div>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    content.vue

    <script>
    export default {
        data() {
            return {
                counter: 0
            }
        },
        beforeCreate() {
            console.log('beforeCreate');
        },
        created() {
            console.log('created');
        },
        beforeMount() {
            console.log('beforeMount');
        },
        mounted() {
            console.log('mounted');
        },
        //页面渲染之后
        beforeUpdate() {
            console.log('beforeUpdate');
        },
        updated() {
            console.log('updated');
        },
        beforeUnmount() {
            console.log('beforeUnmount');
        },
        unmounted() {
            console.log('unmounted');
        }
    }
    script>
    
    <template>
        <div>
            <h1>helloh1>
            <h1>{{ counter }}h1>
            <button @click="counter++">按钮button>
        div>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    在这里插入图片描述

  • 相关阅读:
    Linux服务器的性能监控与分析
    12KM02E-V0002 3EGM030300R0002 模块化和加固的边缘计算加速
    PyQt界面里如何加载本地视频以及调用摄像头实时检测(小白入门必看)
    【电源专题】电流源
    【C++数据结构】线性表的顺序存储结构
    VAD打断方案
    基于springboot+VUE的电视节目管理系统设计与实现
    罗技鼠标接收器丢失或损坏后用另一个接收器配对的方法
    【C语言】解决 “address of stack memory associated with local variable ‘num‘ returned”
    生产问题 Recv-Q101
  • 原文地址:https://blog.csdn.net/weixin_46618592/article/details/126010966