Vue学习-组件和生命周期

简介: 执行beforeUnmout钩子函数(Vue实例销毁之前,data数据和methods方法之类还没有被销毁,还能调用,可以解绑事件监听或者清除掉定时器之类事件)

前言


本人准备大四,做一些项目学习一些新的技能,如果文章有写的不好的或者不对的地方欢迎评论或者私信指出,谢谢!

欢迎参观本人的个人博客:https://linzyblog.netlify.app/


一、组件基础


通常一个应用会以一棵嵌套的组件树的形式来组织:


ad238cf7cdb54512af6713e41cce957d.png


例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。


1、为什么我们要用组件


了解传统方式到组件化方式的更新。


20e418301432443d9cba314f442297c0.png


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


d70433f82a894ffca857373696f56380.png


2、基本使用


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


1)全局注册


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


const app = Vue.createApp({})
app.component('my-component-name', {
  // ... 选项 ...
})


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


2)局部注册


父组件 App.vue

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


  • header.vue


<script>
export default {
    data() {
        return {
            msg: "这里是头部子组件"
        }
    }
}
</script>
<template>
    <h1>{{ msg }}</h1>
</template>


  • message.vue


<script>
export default {
    data() {
        return {
            msg: "这里是内容子组件"
        }
    }
}
</script>
<template>
    <h1>{{ msg }}</h1>
</template>


  • bottom.vue


<script>
export default {
    data() {
        return {
            msg: "这里是底部子组件"
        }
    }
}
</script>
<template>
    <h1>{{ msg }}</h1>
</template>


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

8a8d9b4d7d354b44bf335de6580e1410.png


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 >


hello.vue


<script>
export default {
    props: ['title'],
}
</script>
<template>
    <div>
        <h1>{{ title }}</h1>
    </div>
</template>

24270f3f334f4d74b5e7f4bd59256352.png


当一个值被传递给一个 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 >


hello.vue


<script>
export default {
    props: ['msg'],
}
</script>
<template>
    <div>
        <h1>hello子组件中</h1>
        <h1>{{ msg }}</h1>
    </div>
</template>

85cd0fe96f5443759e176283b6b63ef0.png


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


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的数据,通过自定义事件 -->
    <!-- 2、在父组件中,通过v-on监听子组件中自定义的事件 -->
    <content @injectMsg="getMsg"></content>
    <h1>{{ message }}</h1>
  </div>
</template>


父级组件可以像处理原生 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 = '你好'">改变msg</button>
        <!-- <Hello></Hello> -->
        <button @click="sendParent">发送数据到父组件</button>
    </div>
</template>


同时子组件可以通过调用内建的 $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>hello</h4>
    <h1>{{ message }}</h1>
    <!-- <h1>{{ aaa }}</h1> -->
    <h1>{{ list }}</h1>
  </div>
</template>


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


5a237cf5a5444d21ab57ef95e0bf0d83.png

ccc48a17cce64a6a9170991f3df9cf26.png


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>


content.vue


<template>
    <div>
        <h1>我是Content组件</h1>
        <div>
            <slot></slot>
        </div>
    </div>
</template>

dff2df815390465184b9e860a9cea594.png


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


2)渲染作用域


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


<todo-button>
  Delete a {{ item.name }}
</todo-button>

84762650c4604b73afb23b6f218d4565.png


3)备用内容


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


<button type="submit">
  <slot>Submit</slot>
</button>


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


<submit-button></submit-button>


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


<button type="submit">
  Submit
</button>


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


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>


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


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


<script>
//声明式渲染,可以提高开发效率
import Content from './components/Content.vue'
export default {
  components: {
    Content
  }
};
</script>
<template>
  <div>
    <Content>
      <!-- v-slot 只能添加在 <template> 上  -->
      <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>

4d3c1c30ce7d4c789ed2807a98e26fc2.png


注意,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>


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>

9fa19cf1b4e74deda2afd7ed521ba90e.png

4d66983db0d34b68bb55c2fc25b082db.png


6、跨级通信Provide / Inject


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


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


80940e5f802a4355b099002b5d4a7023.png


例如,我们有这样的层次结构:


Root
└─ TodoList
   ├─ TodoItem
   └─ TodoListFooter
      ├─ ClearTodosButton
      └─ TodoListStatistics


如果要将 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>


content.vue


<script>
export default {
    //值传递
    inject: ['message'],
    //引用传递
    inject: ['obj']
}
</script>
<template>
    <h2>hello---{{ obj.message }}</h2>
    <h1>{{ message }}</h1>
</template>

123178337db44a578b7ed84ad7360c18.png


注意:值传递就相当于,在子组件开辟了新的属性变量,跟父组件的属性变量没有关系了,引用传递,可以看做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>


content.vue


<script>
export default {
    //值传递
    inject: ['message'],
}
</script>
<template>
    <h1>{{ message() }}</h1>
</template>

3a3ff72e5c5649c69ff8ad59ad2f381f.png


二、生命周期


a21d6ff699a24620914706dfbcf01414.png


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>


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>hello</h1>
        <h1>{{ counter }}</h1>
        <button @click="counter++">按钮</button>
    </div>
</template>

0b8175c92c2949e291daaa6876b08a21.png

目录
相关文章
|
4天前
|
JavaScript
vue组件中的插槽
本文介绍了Vue中组件的插槽使用,包括单个插槽和多个具名插槽的定义及在父组件中的使用方法,展示了如何通过插槽将父组件的内容插入到子组件的指定位置。
|
3天前
|
JavaScript
vue 函数化组件
vue 函数化组件
|
5天前
|
JavaScript API
模块化妙用!用vue3实现一个鼠标追踪器和异步加载组件
该文章展示了如何使用Vue3的Composition API实现鼠标追踪器功能,并介绍了创建异步加载组件的方法,利用TS泛型增强了组件的灵活性与可维护性。
|
5天前
|
JavaScript
vue中组件的局部注册和全局注册
本文介绍了Vue中组件的局部注册和全局注册的方法,并通过示例代码展示了如何在特定组件或整个Vue应用中注册和使用自定义组件。
|
2天前
|
JavaScript
vue消息订阅与发布
vue消息订阅与发布
|
3天前
|
JavaScript 前端开发 IDE
Vue学习笔记5:用Vue的事件监听 实现数据更新的实时视图显示
Vue学习笔记5:用Vue的事件监听 实现数据更新的实时视图显示
|
3天前
|
JavaScript 前端开发 API
Vue学习笔记4:用reactive() 实现数据更新的实时视图显示
Vue学习笔记4:用reactive() 实现数据更新的实时视图显示
|
1天前
|
JavaScript
vue尚品汇商城项目-day07【vue插件-50.(了解)表单校验插件】
vue尚品汇商城项目-day07【vue插件-50.(了解)表单校验插件】
10 4
|
1天前
|
JavaScript
vue尚品汇商城项目-day07【51.路由懒加载】
vue尚品汇商城项目-day07【51.路由懒加载】
11 4
|
2天前
|
JavaScript 前端开发
Vue学习笔记8:解决Vue学习笔记7中用v-for指令渲染列表遇到两个问题
Vue学习笔记8:解决Vue学习笔记7中用v-for指令渲染列表遇到两个问题