作者比较玻璃心,骂轻点儿,写着玩儿的
响应式数据
vue2响应式是通过object.defineProperty里的get和set来实现的;怎么说呢,我理解的是:当你访问obj.value时会执行get,当你修改obj.value的数据时就会执行set;根据这样的机制就操作响应式数据,如果你要问我get和set你怎么知道在什么时候执行,那我就
!!!AI啊
然后我去实操也正如AI所说,那就是对的了
为什么data是一个函数
这个是我之前面试被问的一个面试题,打个比方说:在开发时多多少少会定义些组件,定义了组件,那不可能只使用一次吧,那么重复使用的情况下,data如果是对象,A页面触发了data里的数据变化后,那B页面也使用该组件,那B页面里的数据也跟着变了,为什么呢,因为它们始终使用的是同一个地址下的数据,可以理解为浅拷贝那种;那为什么data是函数的时候就不会这样了呢,因为在不同地方使用该组件,这个data会返回一个新的数据对象,也就是说使用组件的时候data就会初始化一下,就像深拷贝一样:说明这里的深浅拷贝只是拿来打比如的:
生命周期
图官网链接生命周期钩子 | Vue.js (vuejs.org)
- 创建前:beforeCreate
- 创建后:create
- 挂载前:beforeMount
- 挂载后:mounted
- 更新前:beforUpdate
- 更新后:updated
- 卸载前:beforeUnmount
- 卸载后:numounted
这8个生命周期可以划分为4个阶段,创建、挂载、更新、卸载;每个阶段又分为2两种,分别是先执行和后执行 那么这4个阶段是什么时候执行呢:
创建阶段: 进入页面就会执行,不离开页面,后面不再执行,离开页面在进来又会执行
挂载阶段: 只能说同上,那么它们的区别是什么呢,这里先不慌说,后面打个比如就明白了
更新阶段: 当页面发生了变化,比如样式,数据,就会触发更新
卸载阶段: 离开页面或者关闭页面就会触发卸载
比如: 说了这么多,那创建、挂载、更新、卸载到底是什么呢;创建就是从无到有的一个过程,比如说两性深度交流后不是说立马就有了你,只是创建了你;挂载就是说在母亲肚子里就是挂载;更新就算出生后你学说话、穿不同的衣服等等都算更新;卸载就是归西了完蛋了;所以创建、挂载、卸载是只能触发一次的,而更新可以一直触发
v-model双向绑定
什么是双向绑定呢,就是
我在input里输入数据,下面也会变化
,所以当页面上绑定多个一样的数据,其中一个变化了,也会改变剩下的变化什么什么什么的,这个就是双向绑定
怎么触发双向帮绑定呢,只要一起变化就行了,拿input比如说:
触发input事件获取到它的event的对象,拿event.target.value,也就是input输入框的值,将这个值重新赋值到一个响应式数据上在渲染到页面上,也会呈现和v-model一样的效果
大致就是这样的,如果说在原先的基础上在多加个input怎么实现呢
直接动态绑定它的value就ok了
通信
defineProps与$emit
通信经常运用在组件封装方面,进行数据传输,方法传递,等操作;封装组件,在项目里是很有必要的,能大幅度的提高开效率,节约开发成本;但是禁止过度封装,导致组件难懂,不易于维护和接手
1. defineProps
defineProps通信也就是常说的父子组件通信方式,用于父组件向子组件传递数据,并在子组件进行渲染等其它操作,下面是一个简单dom:
如图,左边为父组件,右边为子组件;在父组件引入并注册,如左侧的第48行为引入,57-59行为注册,引入注册完毕后就绑定到页面上如43行,引入、注册、绑定是使用封装组件固定的操作; 绑定成功后就是传递数据,如左侧43行里:obj_list='obj',这里的:obj_list就是该组件自定义属性,该属性能获取到通过该自定义属性name传递的数据,并在该子组件里通过props获取到传递的数据
2. $emit
$emit通信也就是子父通信方式,用于子组件向父组件传递数据和方法,下面是个简单的dom:
先在子组件创建事件,接着使用this.$emit('eventName',参数)进行创建属性,在父组件页面绑定的标签上使用@eventName=''创建事件,该事件默认会将参数带画出来,如61行事件一样,这里只要将数据赋值回正确的地方即可完成,为什么不能直接在子组件修改传递的数据能,因为这个操作违反了vue的数据单向流原则
vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
安装命令:npm i vuex
语法:在src文件夹下创建js文件,并在文件里初始化,并在vue里进行注册如下:
import { createStore } from 'vuex'
const store = createStore({
state() {
return {
}
},
mutations: {
},
getters: {
},
actions: {
}
})
export default store
vuex的api:
// state ==> 创建数据
// mutations ==> 唯一一个改变state数据
// getters ==> 类似于计算属性,return一个结果
// actions ==> 处理异步操作的,数据需要通过mutations才能更新到state里去
import { createStore } from 'vuex'
const store = createStore({
state() {
return {
}
},
mutations: {
},
getters: {
},
actions: {
}
})
export default store
vuex流程图:
如何在页面/组件使用vuex:
// vuex代码
import { createStore } from 'vuex'
const store = createStore({
state() {
return {
// 创建一个数据
num:1,
arr_list:[]
}
},
mutations: {
// 创建一个修改state里的num的方法
// 在mutations里创建的方法,统一接收两个参数,一个是state指向创建的数据里;一个是e传递的参数
setStoreNmu(state,e){
state.num += e
},
// 这里接收到actions请求下发的数据并传递到arr_list里
axios_num(state,e){
state.arr_list = e
}
},
getters: {
// 状态机的里的计算属性
new_num(state){
return state.num/2 == 0
}
},
actions: {
// 创建一个请求方法
axiosNum(state,e){
axios.get(e).then(res=>{
// 请求到的数据通过commit传递到mutations里
state.commit('axios_num',res.data)
})
},
}
})
export default store
// vue页面/组件
<template>
<div>
<div>
获取到vuex里的state:{{num}}
</diiv>
<button @click='setStoreAdd' >修改store:{{num}}</button>
<div>vuex里的num/2==0的返回值{{newNum}}<div>
<div @click='setAxiosNum'>点击在vuex里发送请求</div>
</div>
</template>
<script>
export default {
data(){
return{
n:1
}
},
// 获取store里的数据,只能在computed里获取
computed:{
// 获取vuex里的store里的数据
name(){
return this.$store.state.num
},
// 获取vuex里的getters里的数据
newNum(){
return this.$store.getters.new_num
},
// 获取到vuex请求存储后的数据
arrList(){
return this.$store.getters.arr_list
},
},
methods:{
setStoreAdd(){
// 获取vuex里的mutations里的一个名为setStoreNmu的方法里,并传递参数
this.$store.commit('setStoreNmu',this.n)
},
// 触发actions里的axiosNum方法
setAxiosNum(){
this.$store.dispatch('axiosNum','url')
}
}
}
</script>
<style lang="scss" scoped>
</style>
所有操作全都写在一个vuex里代码不仅多,后续维护查找也是很难的,所以vuex有第二种写法
拆分vuex:
// vuex 这里为整个vuex的,这里的只处理将拆分后的vuex模块引入到vuex里来
// modules 集中管理多个vuex模块
import { createStore } from 'vuex'
import vuex_som from '@/vuex_somurl.js'
const store = createStore({
modules:{
vuex_som,
}
})
export default store
// vuex_som,将不同功能拆分为多个vuex,在逐个引入到vuex里
// 在里面的操作和之前操作vuex的操作一模一样
export default {
namespaced:true,//开启局部命名空间
state(){
return{}
},
mutations:{},
getters:{},
actions:{}
}
vuejs页面/组件
// 如何在组件使用拆分后的vuex
<template>
<div>
<div>
获取到vuex里的state:{{num}}
</diiv>
<button @click='setStoreAdd' >修改store:{{num}}</button>
<div>vuex里的num/2==0的返回值{{newNum}}<div>
<div @click='setAxiosNum'>点击在vuex里发送请求</div>
</div>
</template>
<script>
export default {
data(){
return{
n:1
}
},
// 获取store里的数据,只能在computed里获取
computed:{
// 获取vuex里的store里的数据
name(){
return this.$store.state.vuex_som
},
// 获取vuex里的getters里的数据
newNum(){
return this.$store.getters.vuex_som
},
// 获取到vuex请求存储后的数据
arrList(){
return this.$store.getters.vuex_som
},
},
methods:{
setStoreAdd(){
// 获取vuex里的mutations里的一个名为setStoreNmu的方法里,并传递参数
this.$store.commit('vuex_som/setStoreNmu',this.n)
},
// 触发actions里的axiosNum方法
setAxiosNum(){
this.$store.dispatch('vuex_som/axiosNum','url')
}
}
}
</script>
<style lang="scss" scoped>
</style>
Router
router路由,页面管理跳转
安装命令:npm i vue-router@4
创建基本路由
1.初始化:
// router里的参数
// path ==> 路由路径
// component ==> vue页面文件
// children ==> 配置子页面
// alias ==> 路由别名
// name ==> 命名路由
// redirect ==> 重定向
// meta => 路由元信息
// ...
// 创建一个js文件,并初始化router
import {createWebHashHistory,createRouter} from 'vue-router'
// 引入vue页面文件
const indexA = () => import ('页面文件路径')
const router =createRouter({
history:createWebHashHistory(),
routes:[
{
path:'/indexA',//跳转时的name
component:indexA,//需要和引入进来的文件的变量名一致
alias:'/',//路由别名,访问该页面时url路径为/或/indexA都是指向这个页面
name:'newIndexA',//命名路由,给路由重新取名
meta:{name:'路由信息',....},//配置路由还可以自定义添加其他信息
children:[
// 子页面路由路径,写法于routes一致
]
},
{
path:'/:pathMatch(.*)*', // 当访问没有权限或者没有的url时会重定向到/404页面
redirect:'/404',//重定向/404url路径
}
]
})
export default router
2.绑定router
// 在main.js进行绑定
import { createApp } from 'vue'
import App from './App.vue'
import router from '路由js文件路径'
let app = createApp(App)
app.use(router) // 绑定路由
app.mount('#app')
3.呈现路由
// router-view标签用来呈现路由
<script setup>
</script>
<template>
<div>
<router-view />
</div>
</template>
<style scoped>
</style>
// router-link路由跳转标签
// router-link标签上的属性:
// to ==> 路由路径,用来切换跳转 to="/路径?键名=键值&..."
<script setup>
</script>
<template>
<div>
<router-link to="">点击跳转</router-link>
</div>
</template>
<style scoped>
</style>
路由拦截
全局守卫
// 所有页面跳转都会触发的
router.beforeEach((to,from,next)=>{
// to ==> 想要访问的路由信息
// from ==> 当前路由信息
// next ==> 是否同意跳转
// next() 同意放行 next('路由路径')
})
路由独享守卫
// 只有在path指定的路由里才会触发
{
path:'/xxx',
beforeEnter(to,from,next){
// 和全局守卫一样的操作
},
component:xxx
}
组件内守卫
<template>
<div>
</div>
</template>
<script>
export default{
beforeRouteEnter(){
// 进入路由
}
beforeRouteUpdate(){
// 更新路由
}
beforeRouteLeave(){
// 离开路由
}
}
</script>
<style scoped>
</style>
编程式路由导航
路由跳转/传参
// 不一定需要用router-link进行路由跳转,可以使用函数方式进行路由跳转
<template>
<div>
<div @click="setRouter">点击跳转</div>
</div>
</template>
<script>
export default {
data(){
return{}
},
methods:{
// 通过事件触发路由跳转
setRouter(){
// 方法一:单独跳转
this.$router.push(router地址)
// 方法二:跳转并传递参数
this.$router.push({
path:router地址,
params:{
// 参数
}
});
},
}
}
</script>
<style scoped>
</style>
接收路由跳转过来传递的数据
<template>
<div>
</div>
</template>
<script>
export default {
data(){
return{}
},
created(){
console.log('获取到组件传递过来的参数',this.$route.query)
}
}
</script>
<style scoped>
</style>
其它
操作DOM
// 在元素上绑定ref属性,赋一个name,并通过this.$refs.name进行访问该元素的真实DOM
<template>
<div>
<div ref="myDom"></div>
<button @click="setMydom"></button>
</div>
</template>
<script>
export default {
data(){
return{}
},
methods:{
setMydom(){
// 通过事件操作DOM
console.log(this.$refs.myDom)
}
}
}
</script>
<style scoped>
</style>
修饰符
<template>
<div>
// 事件修饰符
<button @click.stop="setMydom"></button> //事件冒泡
<button @submit.prevent="setMydom"></button> // 不在加载页面
<button @click.once="setMydom"></button> //只触发一次
// 按键修饰符 enter、tab、esc、space...
<input @keyup.enter="submit" />
// 系统按键修饰符 ctrl、alt、shift、meta
<input @keyup.alt.enter="clear" /> //Alt + Enter
<div @click.ctrl="doSomething">Do something</div> //ctrl+点击
</div>
</template>
<script>
export default {
data(){
return{}
},
methods:{
}
}
</script>
<style scoped>
</style>
提交渲染
v-if
v-else-if
v-else
v-show
v-if和v-show的区别
v-if
:当条件为true时会进行渲染显示,当为false会不进行渲染;true与false进行切换时会反复的销毁和重建
v-show
:只是css里的display属性在进行切换
v-if和v-for
v-if
与v-for
不推荐一起使用,因为v-for
优先级高于v-if
,所以会先进行遍历完一次进行判,这样非常影响性能所以不建议
类与样式绑定
class
<template>
<div>
// class动态绑定
<div :class="['text ',new_class?'active':'']"></div> 等价 <div class="text active"></div>
// 通过计算属性返回
<div :class="['text ',classObject]"></div> 等价 <div class="text active"></div>
</div>
</template>
<script>
export default {
data(){
return{
new_class:true
}
},
computed:{
classObject(){
return this.new_class?'active':''
}
}
}
</script>
<style scoped>
.text{
color:red;
}
.active{
background-color:#000;
}
</style>
style:
<template>
<div>
// style动态绑定
<div :style="{'font-size':new_class+'px'}"></div> 等价 <div style="font-size:20px;"></div>
// 通过计算属性返回
<div :style="{'font-size':classObject+'px'}"></div> 等价 <div style="font-size:10px;"></div>
</div>
</template>
<script>
export default {
data(){
return{
new_class:20,
new_class_a:true
}
},
computed:{
classObject(){
return this.new_class_a?10:20
}
}
}
</script>
<style scoped>
</style>
watch监听
// 不能使用箭头函数,因为箭头函数绑定了父级作用域的上下文,所以this不会按照期望指向vue实例
<template>
<div>
</div>
</template>
<script>
export default {
data(){
return{
num:20,
}
},
watch:{
// 监听num数据
num:function(val,oldVal){
console.log('变化后的值',val)
console.log('变化前的值',oldVal)
},
// 当num变化了就会执行该方法
num:this.someMethod(),
// 开启深度监听
num:function{
handler(val,oldVal){
console.log('变化后的值',val)
console.log('变化前的值',oldVal)
},
deep:true
},
// 在监听开始后会立即调用
num:{
handler(val,oldVal){
this.someMethod()
},
immediate:true
}
},
methods:{
someMethod(newVal, oldVal){
console.log('变化后的值',val)
console.log('变化前的值',oldVal)
}
}
}
</script>
<style scoped>
</style>
provide与inject
provide
与inject
用来传递参数的,不管多深都能用来传递,不是响应式的数据,如果是一个可监听的对象,那么对象的property
还是响应式的,示例:
// 传递参数
<template>
<div>
</div>
</template>
<script>
export default {
data(){
return{}
},
// 传递数据
provide(){
return{
theme:'dark'
}
}
}
</script>
<style scoped>
</style>
// 接收参数
<template>
<div>
</div>
</template>
<script>
export default {
// 获取数据
inject:['theme']//通过this.thme访问
data(){
return{}
},
methods:{
}
}
</script>
<style scoped>
</style>
插槽slot
插槽在封装组件方面使用的非常多,因为插槽可以
// 父组件
<template>
<div>
<匿名插槽组件>
<template></template>
</匿名插槽组件>
<具名插槽组件>
<template name="nameSlot"></template>
</具名插槽组件>
<作用域插槽>
<template #default="useProps">
{{useProps.user.name}} ==> 渲染出来是张三
</template>
</作用域插槽>
</div>
</template>
<script>
export default {
data(){
return{}
},
methods:{
}
}
</script>
<style scoped>
</style>
// 匿名插槽
<template>
<div>
<slot>
<p>这里是匿名插槽</p>
</slot>
</div>
</template>
<script>
export default {
data(){
return{}
},
methods:{
}
}
</script>
<style scoped>
</style>
// 具名插槽
<template>
<div>
<slot #nameSlot>
<p>具名插槽</p>
</slot>
</div>
</template>
<script>
export default {
data(){
return{}
},
methods:{
}
}
</script>
<style scoped>
</style>
// 作用域插槽
<template>
<div>
<slot v-bind:user="user">
作用域插槽
</slot>
</div>
</template>
<script>
export default {
data(){
return{
user:{
name:'张三'
}
}
},
methods:{
}
}
</script>
<style scoped>
</style>