vue2讲解

66 阅读9分钟

作者比较玻璃心,骂轻点儿,写着玩儿的

响应式数据

image.png

vue2响应式是通过object.defineProperty里的get和set来实现的;怎么说呢,我理解的是:当你访问obj.value时会执行get,当你修改obj.value的数据时就会执行set;根据这样的机制就操作响应式数据,如果你要问我get和set你怎么知道在什么时候执行,那我就 1_a0a5d6f95271b8b464299604fb5f902b.jpg!!!AI啊 image.png 然后我去实操也正如AI所说,那就是对的了

为什么data是一个函数

这个是我之前面试被问的一个面试题,打个比方说:在开发时多多少少会定义些组件,定义了组件,那不可能只使用一次吧,那么重复使用的情况下,data如果是对象,A页面触发了data里的数据变化后,那B页面也使用该组件,那B页面里的数据也跟着变了,为什么呢,因为它们始终使用的是同一个地址下的数据,可以理解为浅拷贝那种;那为什么data是函数的时候就不会这样了呢,因为在不同地方使用该组件,这个data会返回一个新的数据对象,也就是说使用组件的时候data就会初始化一下,就像深拷贝一样:说明这里的深浅拷贝只是拿来打比如的: OIP-C.jpg

生命周期

图官网链接生命周期钩子 | Vue.js (vuejs.org)

  • 创建前:beforeCreate
  • 创建后:create
  • 挂载前:beforeMount
  • 挂载后:mounted
  • 更新前:beforUpdate
  • 更新后:updated
  • 卸载前:beforeUnmount
  • 卸载后:numounted

这8个生命周期可以划分为4个阶段,创建、挂载、更新、卸载;每个阶段又分为2两种,分别是先执行和后执行 那么这4个阶段是什么时候执行呢:

创建阶段: 进入页面就会执行,不离开页面,后面不再执行,离开页面在进来又会执行

挂载阶段: 只能说同上,那么它们的区别是什么呢,这里先不慌说,后面打个比如就明白了

更新阶段: 当页面发生了变化,比如样式,数据,就会触发更新

卸载阶段: 离开页面或者关闭页面就会触发卸载

比如: 说了这么多,那创建、挂载、更新、卸载到底是什么呢;创建就是从无到有的一个过程,比如说两性深度交流后不是说立马就有了你,只是创建了你;挂载就是说在母亲肚子里就是挂载;更新就算出生后你学说话、穿不同的衣服等等都算更新;卸载就是归西了完蛋了;所以创建、挂载、卸载是只能触发一次的,而更新可以一直触发 16291679702346891.jpg

v-model双向绑定

什么是双向绑定呢,就是 image.png我在input里输入数据,下面也会变化 image.png,所以当页面上绑定多个一样的数据,其中一个变化了,也会改变剩下的变化什么什么什么的,这个就是双向绑定 R-C.jpg 怎么触发双向帮绑定呢,只要一起变化就行了,拿input比如说:

触发input事件获取到它的event的对象,拿event.target.value,也就是input输入框的值,将这个值重新赋值到一个响应式数据上在渲染到页面上,也会呈现和v-model一样的效果 image.png image.png 大致就是这样的,如果说在原先的基础上在多加个input怎么实现呢 image.png 直接动态绑定它的value就ok了

通信

defineProps与$emit

通信经常运用在组件封装方面,进行数据传输,方法传递,等操作;封装组件,在项目里是很有必要的,能大幅度的提高开效率,节约开发成本;但是禁止过度封装,导致组件难懂,不易于维护和接手

1. defineProps

defineProps通信也就是常说的父子组件通信方式,用于父组件向子组件传递数据,并在子组件进行渲染等其它操作,下面是一个简单dom:

微信图片_20241028154028.png

如图,左边为父组件,右边为子组件;在父组件引入并注册,如左侧的第48行为引入,57-59行为注册,引入注册完毕后就绑定到页面上如43行,引入、注册、绑定是使用封装组件固定的操作; 绑定成功后就是传递数据,如左侧43行里:obj_list='obj',这里的:obj_list就是该组件自定义属性,该属性能获取到通过该自定义属性name传递的数据,并在该子组件里通过props获取到传递的数据

2. $emit

$emit通信也就是子父通信方式,用于子组件向父组件传递数据和方法,下面是个简单的dom:

微信图片_20241028163043.png

先在子组件创建事件,接着使用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

微信图片_20241030102805.png

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.png

如何在页面/组件使用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-ifv-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

provideinject用来传递参数的,不管多深都能用来传递,不是响应式的数据,如果是一个可监听的对象,那么对象的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>