尚硅谷Vue2-3, 学习笔记

(一) 基本模块

01基础模板语法

1.1插值语法 -- 解析标签体里的内容

data -> {{}} 里可以写js表达式,或者js语句 : 通过{{}}在标签体里插入data里面的数据

  <div id="root">{{}}</div> //{{}}:插值语法 - vue模板
  <script>
    // 创建Vue实例
    const x = new Vue({
    //配置对象
        // el: element(元素)  -> (挂载)用于指定当前Vue实例为那个容器服务,值一般为css选择器的字符串
      el: '#root',
        //data 中用于存放数据,数据供el所指定的容器去使用,值普通用法可以写成对象
      data: {
        name:'尚硅谷'
      }
    })
  </script>

1.2指令语法 -- 解析标签 数据绑定 条件渲染

1.2.1**v-bind:xxx **

简写 -> : xxx 一般用于解析标签属性,经过解析后 值也要写成js表达式,

    **v-bind:为单向数据绑定 ,数据只能从data流向页面**
  <div :id="root">{{school.age}}</div> // : 指令语法 - v-bind简写
  <script>
    const x = new Vue({
      el: '#root',
      //对象式
      data: {
        name:'尚硅谷',
        school: {  //可以分为多个层级
            age: 12
        }
      //函数式
      data() {
        console.log(this) //this指向Vue实例
        return{}
        }
      }
    })
  </script>

v-bind:动态绑定样式

1.2.2v-model:xxx

**为双向数据绑定,数据可以在页面与data中双向流动,但是一般用于表单类元素上(如:inport, select等) **

  • **在获取表单数据时, v-model会获取value值 **

    **对于一般的文本框, 则输入的内容就是value的值 **

    **对于单选 则要设置要获取的value值 **

    对于多选框 设置要获取的value后还要把v-model绑定的数据写成数组的形式

  • 修饰符

    v-model.number 使输入的数据类型转换成 Number类型

    v-model.lazy 时输入的数据在失去焦点后, 才会被收集

    v-model.trim 去掉前后的空格

1.2.3**v-on:xxx **

简写 -> @xxx 用于绑定事件, 事件的回调要配置在methods中最终会在Vue实例(vm)上,存放在data里的数据才会有数据劫持数据代理 ,@click='showInfo(event)' 使用\占位符,可以传递even参数

  <div id="root">
    <h1>{{name}}</h1>
    <button @click="showInfo"></button>
    <h1>{{address}}</h1>
  </div>
  <script>
    const vm = new Vue({
      el: '#root',
      data: {
        name: '尚硅谷',
        address: '北京' 
      },
      methods: {
        click() {
          console.log('事件被点击');
        }
      }
    })
  </script>

1.2.4事件的修饰符:

Vue中的事件修饰符:

1.prevent:阻止默认事件(常用);

**2.stop:阻止事件冒泡(常用); **

3.once:事件只触发一次(常用)

4.capture:使用事件的捕获模式,在事件捕获阶段进行处理;

5.self:只有event.target是当前操作的元素时才触发事件;

6.passive:事件的默认行为立即执行,无需等待事件回调执行完毕;

在绑定事件的后面添加

<a href="http://www.atguigu.com" @click.prevent="showInfo">点我提示信息</a>

修饰符可以连用

1.2.5键盘事件:

1.Vue中常用的按键别名:

   回车 => enter

   删除 => delete (捕获“删除”和“退格”键)

   退出 => esc

   空格 => space

   换行 => tab (特殊,必须配合keydown去使用)

   上 => up

   下 => down

   左 => left

   右 => right

2.系统修饰键(用法特殊):ctrl、alt、shift、meta

   (1).配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。

   (2).配合keydown使用:正常触发事件。

4.也可以使用 keyCode去指定具体的按键 编码(不推荐,以后可能会移除)

<input type="text" @keydown.13="showInfo">

5.Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名

<input type="text" @keydown.huiche="showInfo">

Vue.config.keyCodes.huiche = 13 //定义了一个别名按键

1.2.6 v-if/v-else-if

写法: v-if=‘表达试’ , 功能与js中的一样, 适用于切换频率比较低的场景, 不展示的DOM元素会直接别移除, 且与v-else-if 结合使用时中间不能添加其他元素, 否则结构会被打断

1.2.7 v-show

写法: v-show=‘表达式’, 适用于切换频率比较低的场景, 不展示的DOM元素会不会被移出,而是使用样式进行隐藏,

vue3中v-if 比 v-show 的优先级要高, 但使用v-if时, 元素可能会获取不到, 而使用v-show时则一定会获取到

1.2.8 v-for

v-for用于展示列表数据

语法: v-for=“(item, index) in xxx “ :key=”yyy”

可以遍历: 数组, 对象, 字符串, 指定遍历次数

1.2.9 其他不常用的内置指令

  • v-text v-html

与差值语法类似,但 v-text不能解析 标签, v-html可以解析 结构标签转成字符串, 但会造成xss(跨域脚本攻击)漏洞

  • v-cloak

为使用该指令的标签 提供一个遮罩, 在Vue实例创建完成,并接管容器后, 会删除v-cloak

使用css的display:none属性, 配合v-cloak解决网速过慢时页面展示出 {{xxx}} 的问题, 防止页面闪烁

  • v-once v-pre

v-once: 所在节点模板只 动态渲染 一次,然后视为静态渲染, 可以优化性能,

v-pre: 跳过所在节点的编译过程, 可用于不使用插值语法,指令语法 的节点, 会加快编译

1.2.10 自定义指令

见1.3.6

1.3Vue实例里的配置项

1.3.1 el [element(元素)]

el:'xxx' 用于挂载当前vue实例指定的容器,值一般为css的选择器字符串,

本质是调用了vue实例里的$mount属性

const vm = new Vue({
  el:'#root'
})
vm.$mount('root')

1.3.2 data

data 用于定义属性(属性名+属性值)

const vm = new Vue({
  el:'#root',
  //对象方法,一般用法
  data: {
      name: '张三'
  },
  //函数方法,用于组件,使函数的this指向Vue实例
  data() {
      name: '张三'
  }
})

1.3.3 methods

methods 方法,主要用于定义 绑定事件的 方法

<div id="root">
    <h1>{{name}}</h1>
    <button @click="showInfo"></button>
    <h1>{{address}}</h1>
  </div>
  <script>
    const vm = new Vue({
      el: '#root',
      data: {
        name: '尚硅谷',
        address: '北京' 
      },
      methods: {  //定义click方法
        click() {
          console.log('事件被点击');
        }
      }
    })
  </script>

methods定义的方法也可以在插值语法{{}},中使用 当data里的数据发生改变则vue会对模板进行重新解析,当解析到定义的函数时,进行调用

1.3.4 computed

computed 计算属性:

 1.定义:要用的属性不存在,要通过已有属性计算得来。

 2.原理:底层借助了`Objcet.defineproperty`方法提供的getter和setter。

 3.get函数什么时候执行?

    (1).初次读取时会执行一次。

    (2).当依赖的数据发生改变时会被再次调用。

 4.优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。

 5.备注:

   1.计算属性最终会出现在vm上,直接读取使用即可。

   2.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。

   3.==注意: computed里不能写异步任务==

1.3.5 watch

监视属性watch:

 `immediate:true` 在初始化时就进行调用

 `handler(新值,旧值){  }`

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

 2.监视的属性必须存在,才能进行监视!!

 3.监视的两种写法:

   (1).new Vue时传入watch配置

   (2).通过vm.$watch监视

 4.`deep:true` 开启深度监视, 要写成完整形式

1.3.6 directives

  • <span id="jump">用于自定义指令 </span>
    <div id="root"> <h2>放大10倍后的n值是:<span v-xxx="n"></span> </h2> </div>

new Vue({
    el:'#root',
    data:{
        name:'尚硅谷',
        n:1
    },
        //定义全局指令  yyy是对象形式, 如果配置函数形式的全局,则使用回调函数
         Vue.directive('yyy',{  //注意在定义全局时, 是directive 不是 directives
            //指令与元素成功绑定时(一上来)
            bind(element,binding){
                element.value = binding.value
            },
            //指令所在元素被插入页面时
            inserted(element,binding){
                element.focus()
            },
            //指令所在的模板被重新解析时
            update(element,binding){
                element.value = binding.value
            }
        }) 
    directives: {
    //xxx函数合适会被调用? 1,指令与元素绑定成功时  2,指令所在的模板被重新解析时
        //函数形式, 适合简单的操作
        xxx(element, binding) { //element:绑定v-xxx所在的元素(span)       binnding:绑定v-xxx所在的值(n)  console.log(this) // 此处的this是指向window
        },
        //对象形式, 适合一些细节上的操作
        yyy: {
        }
    }
})

(1).局部指令:

new Vue ({ directives:{指令名:配置对象} })

new Vue ({ directives{指令名:回调函数} )}

(2).全局指令:

Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)

1.3.7 mounted

Vue完成模板的解析并把初始的, 真实DOM元素放入页面后(挂载完毕)调用mounted(回调函数)

02MVVM模型

01.png

02.png
  • 模板(view)通过 --> Vue实例对象 --> 监听对应data中的数据(model)

  • data中的数据(model)通过 --> Vue实例对象 --> 绑定模板(view)

  1. data中的所有属性,最后都会出现在vm身上
  2. vm身上所有的数据, 及Vue原型身上所有的属性, 在Vue模板中都可以直接使用

==03数据代理==

    <script type="text/javascript" >
            let number = 18
            let person = {
                name:'张三',
                sex:'男',
            }
            Object.defineProperty(person,'age',{  // 第二个参数,表示要读/写第一个参数的那个属性
                // value:18,
                // enumerable:true, //控制属性是否可以枚举,默认值是false
                // writable:true, //控制属性是否可以被修改,默认值是false
                // configurable:true //控制属性是否可以被删除,默认值是false
                //当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
                get(){
                    console.log('有人读取age属性了')
                    return number
                },
                //当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
                set(value){  //参数value 就是收到的修改的值
                    console.log('有人修改了age属性,且值是',value)
                    number = value
                }
            })
            // console.log(Object.keys(person))
            console.log(person)
        </script>

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

        <script type="text/javascript" >
            let obj = {x:100}
            let obj2 = {y:200}
            Object.defineProperty(obj2,'x',{
                get(){
                    return obj.x
                },
                set(value){
                    obj.x = value
                }
            })
        </script>

使用数据代理可以 通过obj2.x来操作obj.x的值得变化

03.png

通过vm对象来代理data对象中的属性操作(读/写),更方便的data中的数据,通Object.defineProperty()把data对象中所有属性添加到vm上, 并为每个添加上的属性都指定一个 getter/setter 来操作(读/写)data中的对应属性 如果不做数据处理 则 在使用插值语法 插入data中的数据时 要这样{{ _data.name }} {{ _data.adderss }}插入

04监视数据原理

**Vue监视数据的原理: **

1.vue会监视data中所有层次的数据。

2.如何监测对象中的数据?

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

     (1).对象中后追加的属性,Vue默认不做响应式处理

     (2).如需给后添加的属性做响应式,请使用如下API:

         Vue.set(target,propertyName/index,value) 或 

         vm.$set(target,propertyName/index,value)

3.如何监测数组中的数据?

04.png

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

      (1).调用原生对应的方法对数组进行更新。

      (2).重新解析模板,进而更新页面。

4.在Vue修改数组中的某个元素一定要用如下方法

   1.使用这些API:   push()、pop()、shift()、unshift()、splice()、sort()、reverse()

   2.Vue.set() 或 vm.$set()

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

==05Vue的生命周期==

生命周期:

  1.又名:**生命周期回调函数**、生命周期函数、**生命周期钩子**。

  2.是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数。

  3.生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的。

  4.生命周期函数中的this指向是vm 或 组件实例对象。
生命周期.png

常用的生命周期钩子:

  1.mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等**【初始化操作】**。

  2.beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等**【收尾工作】**。

**关于销毁Vue实例**

  1.销毁后借助Vue开发者工具看不到任何信息。

  2.销毁后自定义事件会失效,但原生DOM事件依然有效。

  3.一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。

(二)组件化模块

01 组件基本使用

定义: 实现应用中局部功能 代码 和资源的 集合

Vue中使用组件的三大步骤:

 一、定义组件(创建组件)

 二、注册组件

 三、使用组件(写组件标签)

一、如何定义一个组件?

  使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;

  区别如下:

    1.el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。

    2.data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系(this指向)。

  备注:使用template可以配置组件结构。

二、如何注册组件?

   1.局部注册:靠new Vue的时候传入components选项

   2.全局注册:靠Vue.component('组件名',组件)

三、编写组件标签:

   `<school></school>`
<!-- 准备好一个容器-->
        <div id="root">
            <hello></hello> //全局组件标签
            <!-- 第三步:编写组件标签 -->
            <school></school>
            <student></student>
        </div>
        <div id="root2">
            <hello></hello>
        </div>
    </body>
    <script type="text/javascript">
        Vue.config.productionTip = false
        //第一步:创建school组件
        const school = Vue.extend({
            // el:'#root', 组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。
            template:`  //template标签 会替换模板标签
                <div class="demo">
                    <h2>学校名称:{{schoolName}}</h2>
                    <h2>学校地址:{{address}}</h2>
                    <button @click="showName">点我提示学校名</button>  
                </div>
            `,
            data(){
                return {    schoolName:'尚硅谷',address:'北京昌平' }
            },
        })
        //第一步:创建student组件
        const student = Vue.extend({
            template:`
                <div>
                    <h2>学生姓名:{{studentName}}</h2>
                    <h2>学生年龄:{{age}}</h2>
                </div>
            `,
            data(){
                return {    studentName:'张三',age:18 }
            }
        })  
        
        //第一步:创建hello全局组件
        const hello = Vue.extend({ //Vue.extend 可省略
            template:`
                <div>   
                    <h2>你好啊!{{name}}</h2>
                </div>
            `,
            data(){
                return {    name:'Tom'  }
            }
        })  
        //第二步:全局注册组件
        Vue.component('hello',hello)
        //创建vm
        new Vue({
            el:'#root',
            data:{  msg:'你好啊!'  },
            //第二步:注册组件(局部注册)
            components:{school,student}
        })
        new Vue({
            el:'#root2',
        })
    </script>

==02关于VueComponent:==

  1.school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。

  **2.我们只需要写`<school/>`或`<school></school>`,Vue解析时会帮我们创建(new 了一个)school组件的实例对象,即Vue帮我们执行的:new VueComponent(options), 当然你也可以自己new一个。**

  **3.特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!**

  4.关于this指向:

    (1).组件配置中:

       data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。

    (2).new Vue(options)配置中:

       data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。

==03对象原型==

对象原型.png

Vue构造函数的属性中存在一个prototype属性, 值为一个对象, this指向他的 原型对象. 这个指向也叫作做 ==显式原型链==. 当new一个Vue的实例对象时,他的属性中存在一个__proto__属性, 值为一个对象, this也指向Vue原型对象, 这个指向也叫作,==隐式原型链==. 而Vue的原型对象属性里也会在一个__proto__属性, this指向Object的原型对象, 他的属性中也存在一个__proto__属性, this指向为null

VueComponent构造函数的属性中存在一个prototype属性, 值为一个对象, this指向他的 原型对象. 这个指向也叫作做 ==显式原型链==. 当编写一个组件标签时(或者new一个VueComponent的实例对象)时,他的属性中存在一个__proto__属性, 值为一个对象, this也指向VueComponent原型对象, 这个指向也叫作,==隐式原型链==. 而VueComponent的原型对象属性里也会在一个__proto__属性, (原本的this指向应该指向Object的原型对象, ), 但是在Vue中, Vue把这条==隐式原型链==, 指向了Vue的原型对象, 即:

==VueComponent.prototype.__proto__ === Vue.prototype== , 从而使组件实例对象可以访问到Vue原型上的属性和 方法

04render()函数

关于不同版本的Vue:

1.vue.js与vue.runtime.xxx.js的区别:

(1).vue.js是完整版的Vue,包含:核心功能+模板解析器。

(2).vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。

2.因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用 render函数接收到的 ceateElement函数去指定具体内容。

new Vue({
    el:'#app',
    //render函数完成了这个功能:将App组件放入容器中
  render: h => h(App),
    // render:q=> q('h1','你好啊')
    // template:`<h1>你好啊</h1>`,
    // components:{App},
})

(二)脚手架

01 文件目录结构

├── node_modules 
├── public
│   ├── favicon.ico: 页签图标
│   └── index.html: 主页面
├── src
│   ├── assets: 存放静态资源
│   │   └── logo.png
│   │── component: 存放组件
│   │   └── HelloWorld.vue
│   │── App.vue: 汇总所有组件
│   │── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件 
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件

02 vue.config.js配置文件

  1. 使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
  2. 使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh

03 ref属性

  1. 被用来给元素或子组件注册引用信息(id的替代者)
  2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
  3. 使用方式:
    1. 打标识:<h1 ref="xxx">.....</h1><School ref="xxx"></School>
    2. 获取:this.$refs.xxx

04 props配置项

  1. 功能:让组件接收外部传过来的数据

  2. 传递数据:<Demo name="xxx"/> <strong style="color:red">父组件 ===> 子组件</strong>

  3. 接收数据:

    1. 第一种方式(只接收):props:['name']

    2. 第二种方式(限制类型):props:{name:String}

    3. 第三种方式(限制类型、限制必要性、指定默认值):

      props:{
        name:{
        type:String, //类型
        required:true, //必要性
        default:'老王' //默认值
        }
      }
      

    备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。

05 mixin(混入)

  1. 功能:可以把多个组件共用的配置提取成一个混入对象

  2. 使用方式:

    第一步定义混合:

    {
        data(){....},
        methods:{....}
        ....
    }
    

    第二步使用混入:

    全局混入:Vue.mixin(xxx)
    局部混入:mixins:['xxx']

06 插件

  1. 功能:用于增强Vue

  2. 本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。

  3. 定义插件:

    对象.install = function (Vue, options) {
        // 1. 添加全局过滤器
        Vue.filter(....)
        // 2. 添加全局指令
        Vue.directive(....)
        // 3. 配置全局混入(合)
        Vue.mixin(....)
        // 4. 添加实例方法
        Vue.prototype.$myMethod = function () {...}
        Vue.prototype.$myProperty = xxxx
    }
    
  4. 使用插件:Vue.use()

05 scoped样式

  1. 作用:让样式在局部生效,防止冲突。
  2. 写法:<style scoped>

06 总结TodoList案例

  1. 组件化编码流程:

    (1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。

    (2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:

         1).一个组件在用:放在组件自身即可。
    
         2). 一些组件在用:放在他们共同的父组件上(<span style="color:red">状态提升</span>)。
    

    (3).实现交互:从绑定事件开始。

  2. props适用于:

    (1).父组件 ==> 子组件 通信

    (2).子组件 ==> 父组件 通信(要求父先给子一个函数)

  3. 使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!

  4. props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。

07 webStorage

  1. 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)

  2. 浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。

  3. 相关API:

    1. xxxxxStorage.setItem('key', 'value');
      该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。

    2. xxxxxStorage.getItem('person');

        该方法接受一个键名作为参数,返回键名对应的值。
      
    3. xxxxxStorage.removeItem('key');

        该方法接受一个键名作为参数,并把该键名从存储中删除。
      
    4. xxxxxStorage.clear()

        该方法会清空存储中的所有数据。
      
  4. 备注:

    1. SessionStorage存储的内容会随着浏览器窗口关闭而消失。
    2. LocalStorage存储的内容,需要手动清除才会消失。
    3. xxxxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem的返回值是null。
    4. JSON.parse(null)的结果依然是null。

08 组件的自定义事件

  1. 一种组件间通信的方式,适用于:<strong style="color:red">子组件 ===> 父组件</strong>

  2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(<span style="color:red">事件的回调在A中</span>)。

  3. 绑定自定义事件:

    1. 第一种方式,在父组件中:<Demo @atguigu="test"/><Demo v-on:atguigu="test"/>

    2. 第二种方式,在父组件中:

      <Demo ref="demo"/>
      ......
      mounted(){
         this.$refs.xxx.$on('atguigu',this.test)
      }
      
    3. 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。

  4. 触发自定义事件:this.$emit('atguigu',数据)

  5. 解绑自定义事件this.$off('atguigu')

  6. 组件上也可以绑定原生DOM事件,需要使用native修饰符。

  7. 注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调<span style="color:red">要么配置在methods中</span>,<span style="color:red">要么用箭头函数</span>,否则this指向会出问题!

09 全局事件总线(GlobalEventBus)

  1. 一种组件间通信的方式,适用于<span style="color:red">任意组件间通信</span>。

  2. 安装全局事件总线:

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

    1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的<span style="color:red">回调留在A组件自身。</span>

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        //this.$bus.$on('xxxx',this.demo)
          // 使用箭头函数, 不用再上面使用 methods 配置项
          this.$bus.$on('xxx',(data) => {
             console.log(data)
          })
      }
      
    2. 发送数据:this.$bus.$emit('xxxx',数据)

  4. 最好在beforeDestroy钩子中,用$off去解绑<span style="color:red">当前组件所用到的</span>事件。

10 消息订阅与发布(pubsub)

  1. 一种组件间通信的方式,适用于<span style="color:red">任意组件间通信</span>。

  2. 使用步骤:

    1. 安装pubsub:npm i pubsub-js

    2. 引入: import pubsub from 'pubsub-js'

    3. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的<span style="color:red">回调留在A组件自身。</span>

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息
      }
      
    4. 提供数据:pubsub.publish('xxx',数据)

    5. 最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)去<span style="color:red">取消订阅。</span>

11 nextTick

  1. 语法:this.$nextTick(回调函数)
  2. 作用:在下一次 DOM 更新结束后执行其指定的回调。
  3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。

12 Vue封装的过度与动画

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

  2. 写法:

    1. 准备好样式:

      • 元素进入的样式:
        1. v-enter:进入的起点
        2. v-enter-active:进入过程中
        3. v-enter-to:进入的终点
      • 元素离开的样式:
        1. v-leave:离开的起点
        2. v-leave-active:离开过程中
        3. v-leave-to:离开的终点
    2. 使用<transition>包裹要过度的元素,并配置name属性:

      <transition name="hello">
        <h1 v-show="isShow">你好啊!</h1>
      </transition>
      
    3. 备注:若有多个元素需要过度,则需要使用:<transition-group>,且每个元素都要指定key值。

(三)vue脚手架配置代理

01 方法一

在vue.config.js中添加如下配置:
devServer:{
  proxy:"http://localhost:5000"
}

说明:

  1. 优点:配置简单,请求资源时直接发给前端(8080)即可。
  2. 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
  3. 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)

02 方法二

编写vue.config.js配置具体代理规则:
module.exports = {
    devServer: {
      proxy: { //请求一
      '/api1': {// 匹配所有以 '/api1'开头的请求路径
        target: 'http://localhost:5000',// 代理目标的基础路径
        changeOrigin: true,
        pathRewrite: {'^/api1': ''} //重写路径头.把所有符合正则匹配的转为空字符串
      },
      //请求二
      '/api2': {// 匹配所有以 '/api2'开头的请求路径
        target: 'http://localhost:5001',// 代理目标的基础路径
        changeOrigin: true,
        pathRewrite: {'^/api2': ''}
      }
    }
  }
}
/*
   changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
   changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
   changeOrigin默认值为true
*/

说明:

  1. 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
  2. 缺点:配置略微繁琐,请求资源时必须加前缀。

03 插槽

  1. 作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 <strong style="color:red">父组件 ===> 子组件</strong> 。

  2. 分类:默认插槽、具名插槽、作用域插槽

  3. 使用方式:

    1. 默认插槽:

      父组件中:
              <Category>
                 <div>html结构1</div>
              </Category>
      子组件中:
              <template>
                  <div>
                     <!-- 定义插槽 -->
                     <slot>插槽默认内容...</slot>
                  </div>
              </template>
      
    2. 具名插槽:

      父组件中:
              <Category>
                  <template slot="center">
                    <div>html结构1</div>
                  </template>
                  <template v-slot:footer>
                     <div>html结构2</div>
                  </template>
              </Category>
      子组件中:
              <template>
                  <div>
                     <!-- 定义插槽 -->
                     <slot name="center">插槽默认内容...</slot>
                     <slot name="footer">插槽默认内容...</slot>
                  </div>
              </template>
      
    3. 作用域插槽:

      1. 理解:<span style="color:red">数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。</span>(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)

      2. 具体编码:

        父组件中:
               <Category>
                   <template scope="scopeData">
                       <!-- 生成的是ul列表 -->
                       <ul>
                           <li v-for="g in scopeData.games" :key="g">{{g}}</li>
                       </ul>
                   </template>
               </Category>
        
               <Category>
                   <template slot-scope="scopeData">
                       <!-- 生成的是h4标题 -->
                       <h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
                   </template>
               </Category>
        子组件中:
                <template>
                    <div>
                        <slot :games="games"></slot>
                    </div>
                </template>    
                <script>
                    export default {
                        name:'Category',
                        props:['title'],
                        //数据在子组件自身
                        data() {
                            return {
                                games:['红色警戒','穿越火线','劲舞团','超级玛丽']
                            }
                        },
                    }
                </script>
        

(四)Vuex

1.概念

    在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
vuex.png

在Vuex中存在三个对象 -->: State, Actions, Mutations 首先在State中进行声明初始状态, 然后在组件Vue Components中定义事件, 调用dispatch(‘事件类型’, 事件操作)

进入Actions对象,且Actions对象中存在一对(key, value) 与dispatch的参数对应(value为一个函数会有,dispatch中的事件操作), 调用commit()进行事件提交,

进入Mutations对象,且存在一对(key, value) (value为一个函数会有,dispatch中的事件操作和, state中定义的初始状态), 通过Vuex底层的Mutate进行加工修改, 使state中的初始状态发生改变,

进入State, 重新编译解析Vue Components组件,把经过Mutate进行加工修改的值, 通过Vuex底层的Render进行渲染到页面

一般情况下可以跳过dispatch(), 直接使用commit()进入到Mutations对象, 但是因为Actions对象与后端接口绑定(Backend Api), 所以在执行异步请求数据时, 则不能跳过

也可以使用context上下文参数, 来直接对state进行操作,跳过Mutations对象, 但是因为Mutations对象与Devtools(开发者工具)进行绑定, 跳过则 Vue的开发者工具会监听不到操作

2.何时使用?

    多个组件需要共享数据时

3.搭建vuex环境

  1. 创建文件:src/store/index.js

    //引入Vue核心库
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //应用Vuex插件
    Vue.use(Vuex)
    //准备actions对象——响应组件中用户的动作
    const actions = {}
    //准备mutations对象——修改state中的数据
    const mutations = {}
    //准备state对象——保存具体的数据
    const state = {}
    //创建并暴露store
    export default new Vuex.Store({
     actions,
     mutations,
     state
    })
    
  2. main.js中创建vm时传入store配置项

    ......
    //引入store
    import store from './store'
    ......
    //创建vm
    new Vue({
     el:'#app',
     render: h => h(App),
     store
    })
    

4.基本使用

  1. 初始化数据、配置actions、配置mutations,操作文件store.js

    //引入Vue核心库
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //引用Vuex
    Vue.use(Vuex)
    const actions = {
        //响应组件中加的动作
     jia(context,value){
         // console.log('actions中的jia被调用了',miniStore,value)
         context.commit('JIA',value)
     },
    }
    const mutations = {
        //执行加
     JIA(state,value){
         // console.log('mutations中的JIA被调用了',state,value)
         state.sum += value
     }
    }
    //初始化数据
    const state = {
       sum:0
    }
    //创建并暴露store
    export default new Vuex.Store({
     actions,
     mutations,
     state,
    })
    
  2. 组件中读取vuex中的数据:$store.state.sum

  3. 组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据)$store.commit('mutations中的方法名',数据)

    备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit

5. getters的使用

  1. 概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。

  2. store.js中追加getters配置

    ......
    
    const getters = {
     bigSum(state){
         return state.sum * 10
     }
    }
    
    //创建并暴露store
    export default new Vuex.Store({
     ......
     getters
    })
    
  3. 组件中读取数据:$store.getters.bigSum

6.四个map方法的使用

  1. <strong>mapState方法:</strong>用于帮助我们映射state中的数据为计算属性

    computed: {
        //借助mapState生成计算属性:sum、school、subject(对象写法)
         ...mapState({sum:'sum',school:'school',subject:'subject'}),
             
        //借助mapState生成计算属性:sum、school、subject(数组写法)
        ...mapState(['sum','school','subject']),
    },
    
  2. <strong>mapGetters方法:</strong>用于帮助我们映射getters中的数据为计算属性

    computed: {
        //借助mapGetters生成计算属性:bigSum(对象写法)
        ...mapGetters({bigSum:'bigSum'}),
    
        //借助mapGetters生成计算属性:bigSum(数组写法)
        ...mapGetters(['bigSum'])
    },
    
  3. <strong>mapActions方法:</strong>用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数

    methods:{
        //靠mapActions生成:incrementOdd、incrementWait(对象形式)
        ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
        //靠mapActions生成:incrementOdd、incrementWait(数组形式)
        ...mapActions(['jiaOdd','jiaWait'])
    }
    
  4. <strong>mapMutations方法:</strong>用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数

    methods:{
        //靠mapActions生成:increment、decrement(对象形式)
        ...mapMutations({increment:'JIA',decrement:'JIAN'}),
        
        //靠mapMutations生成:JIA、JIAN(对象形式)
        ...mapMutations(['JIA','JIAN']),
    }
    

备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。 在使用数组方式时, 要与在mapActions,mapMutations里定义的名字一样

7.模块化+命名空间

  1. 目的:让代码更好维护,让多种数据分类更加明确。

  2. 修改store.js

    const countAbout = {
      namespaced:true,//开启命名空间
      state:{x:1},
      mutations: { ... },
      actions: { ... },
      getters: {
        bigSum(state){
           return state.sum * 10
        }
      }
    }
    const personAbout = {
      namespaced:true,//开启命名空间
      state:{ ... },
      mutations: { ... },
      actions: { ... }
    }
    const store = new Vuex.Store({
      modules: {
        countAbout,
        personAbout
      }
    })
    
  3. 开启命名空间后,组件中读取state数据:

    //方式一:自己直接读取
    this.$store.state.personAbout.list
    //方式二:借助mapState读取:
    ...mapState('countAbout',['sum','school','subject']),
    
  4. 开启命名空间后,组件中读取getters数据:

    //方式一:自己直接读取
    this.$store.getters['personAbout/firstPersonName']
    //方式二:借助mapGetters读取:
    ...mapGetters('countAbout',['bigSum'])
    
  5. 开启命名空间后,组件中调用dispatch

    //方式一:自己直接dispatch
    this.$store.dispatch('personAbout/addPersonWang',person)
    //方式二:借助mapActions:
    ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
  6. 开启命名空间后,组件中调用commit

    //方式一:自己直接commit
    this.$store.commit('personAbout/ADD_PERSON',person)
    //方式二:借助mapMutations:
    ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
    

(五)路由

  1. 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
  2. 前端路由:key是路径,value是组件。

1.基本使用

  1. 安装vue-router,命令:npm i vue-router

  2. 应用插件:Vue.use(VueRouter)

  3. 编写router配置项:

    //引入VueRouter
    import VueRouter from 'vue-router'
    //引入Luyou 组件
    import About from '../components/About'
    import Home from '../components/Home'
    
    //创建router实例对象,去管理一组一组的路由规则
    const router = new VueRouter({
     routes:[
         {
             path:'/about',
             component:About
         },
         {
             path:'/home',
             component:Home
         }
     ]
    })
    
    //暴露router
    export default router
    
  4. 实现切换(active-class可配置高亮样式)

    <router-link active-class="active" to="/about">About</router-link>
    
  5. 指定展示位置

    <router-view></router-view>
    

2.几个注意点

  1. 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。
  2. 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
  3. 每个组件都有自己的$route属性,里面存储着自己的路由信息。
  4. 整个应用只有一个router,可以通过组件的$router属性获取到。

3.多级路由(多级路由)

  1. 配置路由规则,使用children配置项:

    routes:[
     {
         path:'/about',
         component:About,
     },
     {
         path:'/home',
         component:Home,
         children:[ //通过children配置子级路由
             {
                 path:'news', //此处一定不要写:/news
                 component:News
             },
             {
                 path:'message',//此处一定不要写:/message
                 component:Message
             }
         ]
     }
    ]
    
  2. 跳转(要写完整路径):

    <router-link to="/home/news">News</router-link>
    

4.路由的query参数

  1. 传递参数

    <!-- 跳转并携带query参数,to的字符串写法 -->
    <li v-for="m in xxx" :key="m.id">
    <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">跳转</router-link> // 在 `` 模板字符串里, 使用js表达式 --> ${表达式}
        
    <!-- 跳转并携带query参数,to的对象写法 -->
    <router-link 
     :to="{
         path:'/home/message/detail',
         query:{
            id:m.id,
               title:m.title
         }
     }"
    >跳转到{{m.title}}</router-link>
    </li>
    
  2. 接收参数:

    <li>编号: {{$route.query.id}}</li>
    <li>标题: {{$route.query.title}}</li>
    

5.命名路由

  1. 作用:可以简化路由的跳转。

  2. 如何使用

    1. 给路由命名:

      {
        path:'/demo',
        component:Demo,
        children:[
            {
                path:'test',
                component:Test,
                children:[
                    {
                            name:'hello' //给路由命名
                        path:'welcome',
                        component:Hello,
                    }
                ]
            }
        ]
      }
      
    2. 简化跳转:

      <!--简化前,需要写完整的路径 -->
      <router-link to="/demo/test/welcome">跳转</router-link>
      
      <!--简化后,直接通过名字跳转 -->
      <router-link :to="{name:'hello'}">跳转</router-link>
      
      <!--简化写法配合传递参数 -->
      <router-link 
        :to="{
            name:'hello',
            query:{
               id:666,
                  title:'你好'
            }
        }"
      >跳转</router-link>
      

6.路由的params参数

  1. 配置路由,声明接收params参数

    {
     path:'/home',
     component:Home,
     children:[
         {
             path:'news',
             component:News
         },
         {
             component:Message,
             children:[
                 {
                     name:'xiangqing',
                     path:'detail/:id/:title', //使用占位符声明接收params参数
                     component:Detail
                 }
             ]
         }
     ]
    }
    
  2. 传递参数

    <!-- 跳转并携带params参数,to的字符串写法 -->
    <router-link :to="/home/message/detail/666/你好">跳转</router-link>
                 
    <!-- 跳转并携带params参数,to的对象写法 -->
    <router-link 
     :to="{
         name:'xiangqing',
         params:{
            id:666,
                title:'你好'
         }
     }"
    >跳转</router-link>
    

    特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!

  3. 接收参数:

    $route.params.id
    $route.params.title
    

7.路由的props配置

作用:让路由组件更方便的收到参数
{
    name:'xiangqing',
    path:'detail/:id',
    component:Detail,

    //第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
    // props:{a:900}

    //第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
    // props:true
    
    //第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
    props($route){
        return {
            id:$route.query.id,
            title:$route.query.title
        }
    }
}

8.<router-link>的replace属性

  1. 作用:控制路由跳转时操作浏览器历史记录的模式
  2. 浏览器的历史记录有两种写入方式:分别为pushreplacepush是追加历史记录,replace是替换当前记录。路由跳转时候默认为push
  3. 如何开启replace模式:<router-link replace .......>News</router-link>

9.编程式路由导航

  1. 作用:不借助<router-link>实现路由跳转,让路由跳转更加灵活

  2. 具体编码:

    //$router的两个API
    this.$router.push({  //一般栈结构, 
     name:'xiangqing',
         params:{
             id:xxx,
             title:xxx
         }
    })
    this.$router.replace({ //栈结构, 但新的数据,会替换旧的
     name:'xiangqing',
         params:{
             id:xxx,
             title:xxx
         }
    })
    this.$router.forward() //前进
    this.$router.back() //后退
    this.$router.go() //可前进也可后退, 通过传递参数,来控制前进与后退的跳转页数
    

10.缓存路由组件

  1. 作用:让不展示的路由组件保持挂载,不被销毁。

  2. 具体编码:

    <keep-alive include="News"> 
        // include: 添加保持挂载的范围(News),值为'组件名',写成数组形式,可添加多个范围
        <router-view></router-view>
    </keep-alive>
    

11.两个新的生命周期钩子

  1. 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
  2. 具体名字:
    1. activated路由组件被激活时触发。
    2. deactivated路由组件失活时触发。

12.==路由守卫==

  1. 作用:对路由进行权限控制

  2. 分类:全局守卫、独享守卫、组件内守卫

  3. 全局守卫:

    //全局前置守卫:初始化时执行、每次路由切换前执行
    router.beforeEach((to,from,next)=>{
     console.log('beforeEach',to,from)
     if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
         if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
             next() //放行
         }else{
             alert('暂无权限查看')
             // next({name:'guanyu'})
         }
     }else{
         next() //放行
     }
    })
    
    //全局后置守卫:初始化时执行、每次路由切换后执行
    router.afterEach((to,from)=>{
     console.log('afterEach',to,from)
     if(to.meta.title){ 
         document.title = to.meta.title //修改网页的title
     }else{
         document.title = 'vue_test'
     }
    })
    
  4. 独享守卫:

    beforeEnter(to,from,next){
     console.log('beforeEnter',to,from)
     if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
         if(localStorage.getItem('school') === 'atguigu'){
             next()
         }else{
             alert('暂无权限查看')
             // next({name:'guanyu'})
         }
     }else{
         next()
     }
    }
    
  5. 组件内守卫:

    //进入守卫:通过路由规则,进入该组件时被调用
    beforeRouteEnter (to, from, next) {
    },
    //离开守卫:通过路由规则,离开该组件时被调用
    beforeRouteLeave (to, from, next) {
    }
    

13.路由器的两种工作模式

  1. 对于一个url来说,什么是hash值?—— # 及其后面的内容就是hash值。

  2. hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。

  3. hash模式:

    1. 地址中永远带着#号,不美观 。
    2. 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
    3. 兼容性较好。
  4. history模式:

    1. 地址干净,美观 。
    2. 兼容性和hash模式相比略差。
    3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。

(六) Vue3 新特性

一、创建Vue3.0工程

1.使用 vue-cli 创建

官方文档:https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create

## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或者升级你的@vue/cli
npm install -g @vue/cli
## 创建
vue create vue_test
## 启动
cd vue_test
npm run serve

2.使用 vite 创建

官方文档:https://v3.cn.vuejs.org/guide/installation.html#vite

vite官网:https://vitejs.cn

  • 什么是vite?—— 新一代前端构建工具。
  • 优势如下:
    • 开发环境中,无需打包操作,可快速的冷启动。
    • 轻量快速的热重载(HMR)。
    • 真正的按需编译,不再等待整个应用编译完成。
  • 传统构建 与 vite构建对比图
web.png
vite.png
## 创建工程
npm init vite-app <project-name>
## 进入工程目录
cd <project-name>
## 安装依赖
npm install
## 运行
npm run dev

二、常用 Composition API

官方文档: https://v3.cn.vuejs.org/guide/composition-api-introduction.html

1.拉开序幕的setup

  1. 理解:Vue3.0中一个新的配置项,值为一个函数。
  2. setup是所有<strong style="color:#DD5145">Composition API(组合API)</strong><i style="color:gray;font-weight:bold">“ 表演的舞台 ”</i>。
  3. 组件中所用到的:数据、方法等等,均要配置在setup中。
  4. setup函数的两种返回值:
    1. 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
    2. <span style="color:#aad">若返回一个渲染函数:则可以自定义渲染内容。(了解)</span>
  5. 注意点:
    1. 尽量不要与Vue2.x配置混用
      • Vue2.x配置(data、methos、computed...)中<strong style="color:#DD5145">可以访问到</strong>setup中的属性、方法。
      • 但在setup中<strong style="color:#DD5145">不能访问到</strong>Vue2.x配置(data、methos、computed...)。
      • 如果有重名, setup优先。
    2. setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)

2.ref函数

  • 作用: 定义一个响应式的数据
  • 语法: const xxx = ref(initValue)
    • 创建一个包含响应式数据的<strong style="color:#DD5145">引用对象(reference对象,简称ref对象)</strong>。
    • JS中操作数据: xxx.value
    • 模板中读取数据: 不需要.value,直接:<div>{{xxx}}</div>
  • 备注:
    • 接收的数据可以是:基本类型、也可以是对象类型。
    • 基本类型的数据:响应式依然是靠Object.defineProperty()getset完成的。
    • 对象类型的数据:内部 <i style="color:gray;font-weight:bold">“ 求助 ”</i> 了Vue3.0中的一个新函数—— reactive函数。

3.reactive函数

  • 作用: 定义一个<strong style="color:#DD5145">对象类型</strong>的响应式数据(基本类型不要用它,要用ref函数)
  • 语法:const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个<strong style="color:#DD5145">代理对象(Proxy的实例对象,简称proxy对象)</strong>
  • reactive定义的响应式数据是“深层次的”。
  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。

==4.Vue3.0中的响应式原理==

1. vue2.x的响应式

  • 实现原理:

    • 对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。

    • 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。

      Object.defineProperty(data, 'count', {
          get () {}, 
          set () {}
      })
      
  • 存在问题:

    • 新增属性、删除属性, 界面不会更新。
    • 直接通过下标修改数组, 界面不会自动更新。

2. Vue3.0的响应式

5.reactive对比ref

  • 从定义数据角度对比:
    • ref用来定义:<strong style="color:#DD5145">基本类型数据</strong>。
    • reactive用来定义:<strong style="color:#DD5145">对象(或数组)类型数据</strong>。
    • 备注:ref也可以用来定义<strong style="color:#DD5145">对象(或数组)类型数据</strong>, 它内部会自动通过reactive转为<strong style="color:#DD5145">代理对象</strong>。
  • 从原理角度对比:
    • ref通过Object.defineProperty()getset来实现响应式(数据劫持)。
    • reactive通过使用<strong style="color:#DD5145">Proxy</strong>来实现响应式(数据劫持), 并通过<strong style="color:#DD5145">Reflect</strong>操作<strong style="color:orange">源对象</strong>内部的数据。
  • 从使用角度对比:
    • ref定义的数据:操作数据<strong style="color:#DD5145">需要</strong>.value,读取数据时模板中直接读取<strong style="color:#DD5145">不需要</strong>.value
    • reactive定义的数据:操作数据与读取数据:<strong style="color:#DD5145">均不需要</strong>.value

6.==setup的两个注意点==

  • setup执行的时机

    • beforeCreate之前执行一次,this是undefined。,因此在setup中不能使用, this. 来找数据
  • setup的参数

    • props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
    • context:上下文对象
      • attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs
      • slots: 收到的插槽内容, 相当于 this.$slots。在使用具名插槽时, 要使用 v-slots: xxx 的方式命名
      • emit: 分发自定义事件的函数, 相当于 this.$emit

==7.计算属性与监视==

1.computed函数

  • 与Vue2.x中computed配置功能一致

  • 写法

    import {computed} from 'vue'
    
    setup(){
        ...
      //计算属性——简写, (不考虑, 计算属性被修改的情况)
        let fullName = computed(()=>{
            return person.firstName + '-' + person.lastName
        })
        //计算属性——完整
        let fullName = computed({
            get(){
                return person.firstName + '-' + person.lastName
            },
            set(value){
                const nameArr = value.split('-')
                person.firstName = nameArr[0]
                person.lastName = nameArr[1]
            }
        })
    }
    

2.watch函数

  • 与Vue2.x中watch配置功能一致

  • 两个小“坑”:

    • 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
    • 监视reactive定义的响应式数据中某个属性时:deep配置有效。
    //情况一:监视ref定义的响应式数据
    watch(sum,(newValue,oldValue)=>{
      console.log('sum变化了',newValue,oldValue)
    },{immediate:true})
    
    //情况二:监视多个ref定义的响应式数据
    watch([sum,msg],(newValue,oldValue)=>{
      console.log('sum或msg变化了',newValue,oldValue)
    }) 
    
    /* 情况三:监视reactive定义的响应式数据中的全部属性
              若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
              若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 
    */
    watch(person,(newValue,oldValue)=>{
      console.log('person变化了',newValue,oldValue)
    },{immediate:true,deep:false}) //此处的deep配置不再奏效
    
    //情况四:监视reactive定义的响应式数据中的某个属性
    watch(()=>person.job,(newValue,oldValue)=>{
      console.log('person的job变化了',newValue,oldValue)
    },{immediate:true,deep:true}) 
    
    //情况五:监视reactive定义的响应式数据中的某些属性
    watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
      console.log('person的job变化了',newValue,oldValue)
    },{immediate:true,deep:true})
    
    //特殊情况
    watch(()=>person.job,(newValue,oldValue)=>{
        console.log('person的job变化了',newValue,oldValue)
    },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
    

3.watchEffect函数

  • watch的套路是:既要指明监视的属性,也要指明监视的回调。

  • watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。

  • watchEffect有点像computed:

    • 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
    • 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
    //watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
    watchEffect(()=>{
        const x1 = sum.value
        const x2 = person.age
        console.log('watchEffect配置的回调执行了')
    })
    

8.生命周期

vue2生命周期.png

vue3生命周期.jpg
  • Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
    • beforeDestroy改名为 beforeUnmount
    • destroyed改名为 unmounted
  • Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
    • beforeCreate===>setup()
    • created======>setup()
    • beforeMount ===>onBeforeMount
    • mounted=======>onMounted
    • beforeUpdate===>onBeforeUpdate
    • updated =======>onUpdated
    • beforeUnmount ==>onBeforeUnmount
    • unmounted =====>onUnmounted

9.自定义hook函数

  • 什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。
  • 类似于vue2.x中的mixin。
  • 自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。

10.toRef

  • 作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。直接使用ref, 与使用toRef , 可以理解为前者只是==复制==原对象中的某个属性来进行响应式, 而 toRef则是==引用==原对象中的某个属性来进行响应式
  • 语法:const name = toRef(person,'name')
  • 应用: 要将响应式对象中的某个属性单独提供给外部使用时。
  • 扩展:toRefstoRef功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)

三、其它 Composition API

1.shallowReactive 与 shallowRef

  • shallowReactive:只处理对象最外层属性的响应式(浅响应式)。

  • shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。

  • 什么时候使用?

    • 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
    • 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。

2.readonly 与 shallowReadonly

  • readonly: 让一个响应式数据变为只读的(深只读)。
  • shallowReadonly:让一个响应式数据变为只读的(浅只读)。
  • 应用场景: 不希望数据被修改时。

3.toRaw 与 markRaw

  • toRaw:
    • 作用:将一个由==reactive==生成的<strong style="color:orange">响应式对象</strong>转为<strong style="color:orange">普通对象</strong>。
    • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
  • markRaw:
    • 作用:标记一个对象,使其永远不会再成为响应式对象。
    • 应用场景:
      1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。
      2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。

4.customRef

  • 作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。

  • 实现防抖效果:

    <template>
      <input type="text" v-model="keyword">
      <h3>{{keyword}}</h3>
    </template>
    
    <script>
      import {ref,customRef} from 'vue'
      export default {
          name:'Demo',
          setup(){
              // let keyword = ref('hello') //使用Vue准备好的内置ref
              //自定义一个myRef
              function myRef(value,delay){
                  let timer
                  //通过customRef去实现自定义
                  return customRef((track,trigger)=>{
                      return{
                          get(){
                              track() //告诉Vue这个value值是需要被“追踪”的
                              return value
                          },
                          set(newValue){
                              clearTimeout(timer)
                              timer = setTimeout(()=>{
                                  value = newValue
                                  trigger() //告诉Vue去更新界面
                              },delay)
                          }
                      }
                  })
              }
              let keyword = myRef('hello',500) //使用程序员自定义的ref,并传入delay延迟500ms
              return {
                  keyword
              }
          }
      }
    </script>
    

5.provide 与 inject

[图片上传失败...(image-bb16a6-1659157988580)]

  • 作用:实现<strong style="color:#DD5145">祖与后代组件间</strong>通信

  • 套路:父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据

  • 具体写法:

    1. 祖组件中:

      setup(){
         ......
          let car = reactive({name:'奔驰',price:'40万'})
          provide('car',car)
          ......
      }
      
    2. 后代组件中:

      setup(props,context){
         ......
          const car = inject('car')
          return {car}
         ......
      }
      

6.响应式数据的判断

  • isRef: 检查一个值是否为一个 ref 对象
  • isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
  • isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
  • isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

四、Composition API 的优势

1.Options API 存在的问题

使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。

OptionsAPI模式.gif

OptionsAPI模式.gif

2.Composition API 的优势

我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。

Composition API模式.gif

Composition API模式.gif

五、新的组件

1.Fragment

  • 在Vue2中: 组件必须有一个根标签
  • 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
  • 好处: 减少标签层级, 减小内存占用

2.Teleport

  • 什么是Teleport?—— Teleport 是一种能够将我们的<strong style="color:#DD5145">组件html结构</strong>移动到指定位置的技术。

    <teleport to="移动位置">
      <div v-if="isShow" class="mask">
          <div class="dialog">
              <h3>我是一个弹窗</h3>
              <button @click="isShow = false">关闭弹窗</button>
          </div>
      </div>
    </teleport>
    

3.Suspense

  • 等待异步组件时渲染一些额外内容,让应用有更好的用户体验

  • 使用步骤:

    • 异步引入组件

      import {defineAsyncComponent} from 'vue'
      const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
      
    • 使用Suspense包裹组件,并配置好defaultfallback

      <template>
          <div class="app">
              <h3>我是App组件</h3>
              <Suspense>
                  <template v-slot:default>
                      <Child/>
                  </template>
                  <template v-slot:fallback>
                      <h3>加载中.....</h3>
                  </template>
              </Suspense>
          </div>
      </template>
      

六、其他

1.全局API的转移

  • Vue 2.x 有许多全局 API 和配置。

    • 例如:注册全局组件、注册全局指令等。

      //注册全局组件
      Vue.component('MyButton', {
        data: () => ({
          count: 0
        }),
        template: '<button @click="count++">Clicked {{ count }} times.</button>'
      })
      
      //注册全局指令
      Vue.directive('focus', {
        inserted: el => el.focus()
      }
      
  • Vue3.0中对这些API做出了调整:

    • 将全局的API,即:Vue.xxx调整到应用实例(app)上

      2.x 全局 API(Vue 3.x 实例 API (app)
      Vue.config.xxxx app.config.xxxx
      Vue.config.productionTip <strong style="color:#DD5145">移除</strong>
      Vue.component app.component
      Vue.directive app.directive
      Vue.mixin app.mixin
      Vue.use app.use
      Vue.prototype app.config.globalProperties

2.其他改变

  • data选项应始终被声明为一个函数。

  • 过度类名的更改:

    • Vue2.x写法

      .v-enter,
      .v-leave-to {
        opacity: 0;
      }
      .v-leave,
      .v-enter-to {
        opacity: 1;
      }
      
    • Vue3.x写法

      .v-enter-from,
      .v-leave-to {
        opacity: 0;
      }
      
      .v-leave-from,
      .v-enter-to {
        opacity: 1;
      }
      
  • <strong style="color:#DD5145">移除</strong>keyCode按键编码作为 v-on 的修饰符,同时也不再支持config.keyCodes自定义按键事件

  • <strong style="color:#DD5145">移除</strong>v-on.native修饰符

    • 父组件中绑定事件

      <my-component
        v-on:close="handleComponentEvent"
        v-on:click="handleNativeClickEvent"
      />
      
    • 子组件中声明自定义事件

      <script>
        export default {
          emits: ['close'] //通过emit来指定@close为自定义事件
        }
      </script>
      
  • <strong style="color:#DD5145">移除</strong>过滤器(filter)

    过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。

  • 其他: https://v3.cn.vuejs.org/guide/migration/introduction.html#%E6%A6%82%E8%A7%88!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容