vue2回顾

MVVM

  • M: 模型(Model)–对应data中的数据(就是Model Vue实例中的data)
  • V:视图(View)–模板(就是DOM html写的东西,这里写双括号的时候,里面的变量可以是vm上的任何属性或方法,这也就是为什么可以拿到data)
  • VM:视图模板(ViewModel)–Vue实例对象(整个script中的Vue实例,所以一般都用vm代表Vue实例)

1.模板语法

  • 插值语法 <h>你好,</h>
  • 指令语法 <a v-bind:href="url">点击去学习</a> v-bind可以简写为:

JS表达式可以读取到data中的所有一级属性

2.数据绑定

  • 单向,通过v-bind,数据只能从data流向页面

  • 双向,通过v-model,不仅能从data流向页面,还可以从页面流向data

    v-model只能应用在表单类元素(输入类元素)上,因为v-model默认收集的就是value值

3.数据代理

  • Object.defineProperty

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let person = {
    name: '张三',
    sex: '男'
    }
    // 这种方式绑定的age属性,是不可枚举(遍历):即Object.keys(person)返回的没有age,for循环也拿不到。
    Object.defineProperty(person, 'age', {
    value: 18,
    // enumerable: true, //控制属性是否可枚举,默认是false
    // writable: true, //控制属性是否可被修改,默认是false
    // configurable: true, //控制属性是否可被删除,默认是false
    })
  • 何为数据代理?

    通过一个对象代理对另一个对象中属性的操作(读/写)

    vue:通过vm对象来代理data对象中的属性操作(读/写)

    【原理:通过Object.definedProperty()把data对象中所有属性添加到vm上;为每一个添加到vm上的属性,都指定一个getter/setter;在getter/setter内部去操作(读/写)data中对应的属性】

    比如:用obj2代理obj中的x

    1
    2
    3
    4
    5
    6
    let obj = {x:100}
    let obj2 = {y:200}
    Object.defineProperty(obj2,'x',{
    get() { return obj.x},
    set(value) { obj.x = value}
    })

    只有配置在data中的属性和方法,才可以被数据代理和数据劫持

4.事件处理

  • 使用:v-on:xxx@xxx

  • @click="demo"@click="demo($event)"效果一致,后者可以传参

  • 事件修饰符

    prevent、stop、once、capture、self、passive

5.computed

底层用的其实就是Object.defineProperty方法提供的getter和setter

1
2
3
4
5
6
7
8
9
// 当有人读取fullName时,get就会被调用,且返回值作为fullName的值
// get调用时机--》1.初次读取fullName时(有多次显示只读取一次,与methods实现相比,有缓存效率高) 2.所依赖数据发生变化时
computed:{
fullName:{
get(){
return this.firstName + '-' + this.lastName
}
}
}

6.watch

当被监视的属性变化时,回调函数自动调用,进行相关操作

1
2
3
4
5
6
7
8
9
watch:{
isHot:{
immediate: true, //初始化时让handler调用一次
deep: true, //深度监视对象内部值的改变(多层)
handler(newValue, oldValue){
console.log('isHot被修改了')
}
}
}
  • Vue可以监测对象内部值的改变,但Vue提供的watch默认不可以
  • 使用watch时根据数据的具体结构,决定是否采用深度监视

computed能完成的函数,watch都可以完成。优先computed

watch可以进行异步操作,比如定时器等

7.条件渲染

  • v-if

    切换频率较低的场景

    • 因为很暴力,不展示的DOM元素直接被移除
  • v-show

    切换频繁的场景

    • 不展示的DOM元素未被移除,用样式隐藏

template可以将元素包裹起来,并且不破坏结构,可以和v-if搭配使用。

8.列表渲染

v-for="(item, index) in xxx" :key="yyy"

diff对比过程

虚拟DOM

9.Vue监测数据原理

  • 对象

    数据响应式:

    如果改变了data中的name值,Vue会进行如下操作:

    • 改变前,Vue进行数据代理,先加工data,然后把data赋值给vm._data
    • name改变后,Vue会通过vm._data中的set name数据响应式,重新渲染模板
    • 渲染模板后生成虚拟DOM,再进行diff对比,name变化了,name这个节点需要使用新的DOM来进行渲染,其他节点不变继续用旧的DOM

    模拟数据监测过程(只监测一层):

    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
    /**
    * suyufan
    * 2023/12/29 16:47
    * 只能模拟监测一层,不能深层次监测
    */
    let data = {
    name: 'su',
    age: '18'
    }
    // 创建一个监视的实例对象,用于监视data中属性的变化
    const obs = new Observer(data)

    function Observer(obj){
    // 汇总对象中所有属性 形成一个数组
    const keys = Object.keys(obj)
    keys.forEach((i)=>{
    // 这里的this是function函数,不能往data中添,因为data中本身就有get/set会形成死循环
    Object.defineProperty(this,i,{
    get(){
    return obj[k]
    },
    set(val){
    // 除了进行赋值,还有进行模板渲染 diff比对。原生通过Vue.set(target,key,val)或者vm.$set(target,key,val)实现
    obj[k] = val
    }
    })
    })
    }

    // 准备一个vm实例对象
    let vm = {}
    vm._data = obs
  • 数组

    对象是通过set方法响应式监测,数组属性没有set方法,那数组的监测是如何实现的呢???

    Vue对数组的监测,有两步

    1.调用数组原生的变更方法(push()、pop()、shift()、unshift()、splice()、sort()、reserve()

    对于不变更原数组的方法,比如filter()、concat()、slice(),用新数组替换旧数组的方式监听

    2.模板渲染

    即Vue将被监听的数组的变更方法进行了包裹,所以它们也会触发视图更新

Vue监测数据原理:

  • 如何监测对象中的数据?

    通过setter实现监视,且要在new Vue时就传入要监测的数据

    • 对象中后追加的属性,Vue默认不做响应式处理
    • 如需给后添加的属性做响应式,可以使用Vue.set(target,key,val)或者vm.$set(target,key,val)
  • 如何让监测数组中的数据?

    通过包裹数组更新元素的方法实现,本质就是做了两件事

    • 调用原生对应的方法对数组进行更新
    • 重新解析模板,进而更新页面

注意:Vue.set()和vm.$set()不能给vm或vm的根数据对象 添加属性

10.生命周期

还有3个生命周期钩子

1个是nextTick

2个是路由组件特有的,在21节路由中介绍(activateddeactivated)

11.ref属性

  1. 被用来给元素或子组件注册引用信息(id的替代者)

  2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)

  3. 使用方式:

    打标识:<h1 ref="xxx">...</h1>

    获取:this.$refs.xxx

12.props

让组件接收外部传过来的数据。父给子传 很方便使用。

  • 传递数据:<Demo age="xxx">

  • 接收数据:

    • 只接收:props:['age']

    • 限制类型

      1
      2
      3
      props: {
      age: Number
      }
    • 限制类型、限制必要性、指定默认值

      1
      2
      3
      4
      5
      6
      7
      props: {
      age: {
      type: Number,
      required: true, //必要性
      default: "老王" //默认值
      }
      }

props是只读的,Vue底层会监测开发者对props的修改,如果进行修改,会警告。

如果需要需要修改,复制props的内容到data中,修改data

13.事件总线

任意组件间通信

  • 安装

    1
    2
    3
    4
    5
    6
    new Vue({
    ...
    beforeCreate(){
    Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
    }
    })
  • 使用

    • 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身

      1
      2
      3
      4
      5
      6
      method(){
      demo(data){}
      }
      mounted() {
      this.$bus.$on('xxx', this.demo)
      }
    • 提供数据:this.$bus.$emit('xxx',数据)

  • 最好在beforeDestroy钩子中,用$off解绑当前组件所用到的事件

14.消息订阅与发布

需要数据的去订阅消息,发送数据的去发布消息。任意组件间通信

  • 订阅消息:pubsub.subscribe('消息名',function('消息名', 接收到的数据))
  • 发布消息:pubsub.publish('消息名',数据)
  • 最好在beforeDestroy钩子中,用pubsub.unsubscribe(pid)去取消订阅

15.Vuex

也是一种组件通信方式,由store管理state、actions、mutations

  • 共享

  • actions:用于响应组件中的动作
  • mutations:用于操作加工数据
  • state:用于存储数据

vc可以dispatch经action到mutations,还可以直接commit到mutations

  • mapState:生成计算属性,从state中读取数据
    • 对象写法:computed:{...mapState({he:'sum',xuexiao:'school'})}
    • 数组写法:computed:{...mapState(['sum','school'])}
  • mapGetters:生成计算属性,从getters中读取数据
  • mapMutations:生成对应方法,方法中调用commit去联系mutations
    • 对象写法:methods:{...mapMutations({he:'sum',xuexiao:'school'})}
    • 数组写法:methods:{...mapMutations(['sum','school'])}
  • mapActions:生成对应方法,方法中调用dispatch去联系actions

16.插槽

也是一种组件间通信的方式,适用于父组件==》子组件,让父组件可以向子组件指定位置插入html结构

  • 默认插槽
  • 具名插槽
  • 作用域插槽:anger:

17.webStorage

浏览器通过Window.sessionStorageWindow.localStorage属性实现本地存储

  • 相关API

    • xxxStorage.setItem('key','value')

      该方法接收一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值

    • xxxStorage.getItem('key')

    • xxxStorage.clear()

18.nextTick

this.$nextTick(回调函数)

  • 在下一次DOM更新结束后执行其指定的回调
  • 当改变数据后,基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行

19.Vue动画

在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名

20.跨域

数据交互的方式有:xhr、jQuery、axios、fetch、vue-resource

vue-resource就是一个插件,现在不怎么用了。

vue-resource使用方式:

  • 安装
  • main.js中注册引用vue.use(vueResource)
  • 引用之后在vue中this上就能看到$http,直接this.$http.get('服务端地址').then()

跨域解决方式:

  1. cors:通过后端人员配置响应头Access-Control-Allow-Origin

  2. jsonp:通过script的src不受同源策略限制,只能解决get请求的跨域,且需要后端配合

  3. 代理,代理服务器的端口号跟前端一致

    1. Nginx反向代理

    2. 通过vue-cli脚手架,也就是devServer代理

      1
      2
      3
      4
      5
      6
      7
      8
      9
      // 在vue.config.js中配置代理
      module.exports = {
      devServer: {
      proxy: 'http://localhost:5000' //服务端地址
      }
      }

      // vue中调接口发请求时
      axios.get('http://localhost:8080/student').then()
      • 缺点1:只能配置一个端口号的服务器,如果有多个端口不同的服务器,无法配置

      • 缺点2:如果前端的public中有一个名为student的文件,则接口不会发送到后端,会从前端读取文件数据

      • 解决方式:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        // 在vue.config.js中配置代理
        module.exports = {
        devServer: {
        proxy: {
        '/api': {
        target: 'http://localhost:5000', //服务端地址
        pathRewrite: {'^/api':''}, //由于服务端接口就是/student,如果直接访问/api/student会报错404
        ws: true, //用于支持websocket,默认就是true(React中默认是false)
        changeOrigin: true //用于控制请求头中的host值,true的时候是真的请求头8080,false的时候是假的5000.默认值为true(React中默认是false)
        }
        }
        }
        }

        // vue中调接口发请求时
        axios.get('http://localhost:8080/api/student').then()

21.路由

  • 路由route:一组key-value的对应关系
  • 路由器router

SPA应用,局部刷新的原理实现过程:

工具就是vue-router

菜单栏中选中一个菜单项,比如班级管理,浏览器地址栏会在localhost:8080后匹配上一个/class,Vue中的路由器router实时监控着地址栏的变化,现在地址栏变成了/classrouter会根据开发者写的route规则去匹配,有一条route规则是:/class ==> 班级组件,能匹配上,内容区展示班级组件。

  • 两个生命周期钩子:(用于捕获路由组件的激活状态)

    • activated:路由组件被激活时触发
    • deactivated:路由组件失活时触发
  • 全局路由守卫

    • 前置守卫:初始化时执行、每次路由切换前执行router.beforeEach((to,from,next)=>{})
    • 后置守卫:初始化时执行、每次路由切换后执行router.afterEach((to,from)=>{})
  • router两种模式

    • hash

      地址有/#/

      如果将地址通过第三方手机app分享,若app校验严格,地址会被标记为不合法

      与服务端兼容性好

      SEO差

    • history

      打包到服务端,可能会有404问题

      也有解决404的方案:

      • node端自己慢慢写正则匹配,哪个是前端的,哪个是服务端的
      • 推荐:node端下载connect-history-api-fallbeck,专门用来解决history模式404问题

    一般来说,后台管理类的项目喜欢用hash模式,求稳;前端类项目,比如淘宝、B站用的是history模式,美观

一些关于路由的细节点:

路由传参(query【/?id=1&name=’s’】、params【/1/s】、props);

​ 传递params参数时,如使用to的对象写法,必须使用name配置项,不能用path

router-link的两种模式(push、replace);

编程式路由导航(不借助<router-link>实现路由跳转,更灵活);

缓存路由<keep-alive>:让不展示的路由组件保持挂载,不被销毁;

除了全局路由守卫,还有独享守卫和组件内守卫

  • 独享守卫:beforeEnter
    • 组件内守卫:
      • beforeRouteEnter进入守卫:通过路由规则,进入该组件时被调用
      • beforeRouteLeave离开守卫:通过路由规则,离开该组件时被调用

22.打包部署步骤

npm run build 打成dist包

node+express作后端:npm init创建项目,之后npm i express,之后在创建出来的项目文件中右击新建一个server.js

测试服务端是否成功,终端node server

将打包的dist内,所有内容放到node项目的static下

node端的server.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const express = require('express')
const history = require('connect-history-api-fallback') //用来解决history模式下的404问题

const app = express()
app.use(history())
app.use(express.static(__dirname+'/static'))

app.get('/person',(req,res)=>{
res.send({
name: 'tom',
age: 18
})
})

app.listen(5005,(err)=>{
if(!err) console.log("服务器启动了")
})