Vue

1.渐进式

渐进式代表的含义是:没有多做职责之外的事。vue.js只提供了vue-cli生态中最核心的组件系统和双向数据绑定。就好像 vuex、vue-router都属于围绕vue.js开发的库。框架做分层设计,每层都可选,不同层可以灵活接入其他方案。而当你都想用官方的实现时,会发现也早已准备好,各层之间包括配套工具都能比接入其他方案更便捷地协同工作。

2.MV* 模式(MVC/MVP/MVVM)

  • “MVC”: model view controller

用户的对View操作以后,View捕获到这个操作,会把处理的权利交移给Controller,Controller会对来自View数据进行预处理,决定调用哪个Model的接口;然后由Model执行相关的业务逻辑(数据请求);当Model变更了以后,会通过观察者模式(Observer Pattern)通知View;View通过观察者模式收到Model变更的消息以后,会向Model请求最新的数据,然后重新更新界面。 把业务逻辑和展示逻辑分离,模块化程度高。但由于View是强依赖特定的Model的,所以View无法组件化,无法复用。

  • “MVP”: model view presenter

和MVC模式一样,用户对View的操作都会从View交移给Presenter。Presenter会执行相应的应用程序逻辑,并且对Model进行相应的操作;而这时候Model执行完业务逻辑以后,也是通过观察者模式把自己变更的消息传递出去,但是是传给Presenter而不是View。Presenter获取到Model变更的消息以后,通过View提供的接口更新界面。 View不依赖Model,View可以进行组件化。但Model->View的手动同步逻辑麻烦,维护困难 。

  • “MVVM” : model view viewmodel

MVVM的调用关系和MVP一样。但是,在ViewModel当中会有一个叫Binder,或者是Data-binding engine 的东西。你只需要在View的模版语法当中,指令式地声明View上的显示的内容是和Model的哪一块数据绑定的。当ViewModel对进行Model更新的时候,Binder会自动把数据更新到View上去,当用户对View进行操作(例如表单输入),Binder也会自动把数据更新到Model上去。这种方式称为:Two-way data- binding,双向数据绑定。可以简单地理解为一个模版引擎,但是会根据数据变更实时渲染,解决了MVP大量的手动View和Model同步的问题,提供双向绑定机制。提高了代码的可维护性。对于大型的图形应用程序,视图状态较多,ViewModel的构建和维护的成本都会比较高。

3.数据双向绑定原理

理解vue双向数据绑定原理

1.vue 双向数据绑定是通过 数据劫持 结合 发布订阅模式的方式来实现的, 也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变;

2.核心:关于VUE双向数据绑定,其核心是 Object.defineProperty()方法;

注意: Vue 3.0 的变化

Object.defineProperty有以下缺点

1、无法监听es6的Set、Map 变化;

2、无法监听Class类型的数据;

3、属性的新加或者删除也无法监听;

4、数组元素的增加和删除也无法监听

针对Object.defineProperty的缺点,ES6 Proxy都能够完美得解决,它唯一的缺点就是,对IE不友好,所以vue3在检测到如果是使用IE的情况下(没错,IE11都不支持Proxy),会自动降级为Object.defineProperty的数据监听系统

4.模板语法

1. 插值

文本
<span>Message: {{ msg }}</span>

 let vm = new Vue({
        el:"#app",// 受到控制的元素
        data:{
            msg:"👍👍👍👍👍👍"
        }
    })

纯 HTML
<span v-html="rawHtml"></span>
    let vm = new Vue({
        el:"#app",// 受到控制的元素
        data:{
            rawHtml:"<span style='color:red'>红色的内容</span>"
        }
    })

如何防止 XSS(跨站脚本攻击),XSRF(跨站请求伪造)

使用Javascript表达式
{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{ message.split('').reverse().join('') }}

!!!以下语句不会生效

<!-- 这是语句,不是表达式 -->
{{ var a = 1 }}

<!-- 流控制也不会生效,请使用三元表达式 -->
{{ if (ok) { return message } }}

2.指令

指令 (Directives) 是带有 v- 前缀的特殊 attribute。指令 attribute 的值预期是单个 JavaScript 表达式 (v-for 是例外情况)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。

v-bind

动态地绑定一个或多个 attribute,或一个组件 prop 到表达式。

缩写: :

<!-- 绑定一个 attribute -->
<img v-bind:src="imageSrc">

<!-- 动态 attribute 名 (2.6.0+) -->
<button v-bind:[key]="value"></button>

<!-- 缩写 -->
<img :src="imageSrc">

<!-- 动态 attribute 名缩写 (2.6.0+) -->
<button :[key]="value"></button>

<!-- 内联字符串拼接 -->
<img :src="'/path/to/images/' + fileName">
v-if

根据表达式的值的 truthiness 来有条件地渲染元素。在切换时元素及它的数据绑定 / 组件被销毁并重建。

JavaScript 中,truthy(真值)指的是在布尔值上下文中,转换后的值为真的值。所有值都是真值,除非它们被定义为 假值(即除 false0""nullundefinedNaN 以外皆为真值)。

当和 v-if 一起使用时,v-for 的优先级比 v-if 更高。

template v-if ,包装元素template 不会被创建

v-else

不需要表达式,前一兄弟元素必须有 v-ifv-else-if

<div v-if="Math.random()>0.5">
  你可以看到我了
</div>
<div v-else>
  看不到看不到
</div>
v-else-if
<div v-if="type === 'A'">
  A
</div>
<div v-else-if="type === 'B'">
  B
</div>
<div v-else-if="type === 'C'">
  C
</div>
<div v-else>
  Not A/B/C
</div>
v-show

根据表达式之真假值,切换元素的 display CSS property。元素还在,只是看不见而已。

v-on

缩写:@

绑定事件监听器。事件类型由参数指定。表达式可以是一个方法的名字或一个内联语句,如果没有修饰符也可以省略。

用在普通元素上时,只能监听原生 DOM 事件。用在自定义元素组件上时,也可以监听子组件触发的自定义事件

<!-- 方法处理器 -->
<button v-on:click="doThis"></button>

<!-- 动态事件 (2.6.0+) -->
<button v-on:[event]="doThis"></button>

<!-- 内联语句 -->
<button v-on:click="doThat('hello', $event)"></button>

<!-- 缩写 -->
<button @click="doThis"></button>

<!-- 动态事件缩写 (2.6.0+) -->
<button @[event]="doThis"></button>
v-for

基于源数据多次渲染元素或模板块。此指令之值,必须使用特定语法 alias in expression,为当前遍历的元素提供别名:

(1)v-for (特殊 v-for=”n in 10”)

  • in (for in)

  • of (for of)

//没有区别

(2)key:

跟踪每个节点的身份,从而重用和重新排序现有元素

理想的 key 值是每项都有的且唯一的 id。data.id

(3)数组更新检测

a. 使用以下方法操作数组,可以检测变动

​ push() pop() shift() unshift() splice() sort() reverse()

b. filter(), concat() 和 slice() ,map(),新数组替换旧数组

c. 不能检测以下变动的数组

vm.items[indexOfItem] = newValue

解决方法

(1) Vue.set(example1.items, indexOfItem, newValue)

(2) splice

<div v-for="item in items">
  {{ item.text }}
</div>
<div v-for="item in items" :key="item.id">
  {{ item.text }}
</div>
<div v-for="(item, index) in items"></div>
<div v-for="(val, key) in object"></div>
<div v-for="(val, name, index) in object"></div>
v-model

在表单控件或者组件上创建双向绑定

文本

<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>

多行文本

<span>多行文本信息是:</span>
<>{{ message }}</p>
<br>
<textarea v-model="message" placeholder="添加多行文本"></textarea>

复选框

单个复选框

<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>

多个复选框 绑定到同一个数组

<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names: {{ checkedNames }}</span>

单选按钮

<div id="example-4">
  <input type="radio" id="one" value="One" v-model="picked">
  <label for="one">One</label>
  <br>
  <input type="radio" id="two" value="Two" v-model="picked">
  <label for="two">Two</label>
  <br>
  <span>Picked: {{ picked }}</span>
</div>

选择框

单选时

<div id="example-5">
  <select v-model="selected">
    <option disabled value="">请选择</option>
    <option>A</option>
    <option>B</option>
    <option>C</option>
  </select>
  <span>Selected: {{ selected }}</span>
</div>

如果 v-model 表达式的初始值未能匹配任何选项,`` 元素将被渲染为“未选中”状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change 事件。因此,更推荐像上面这样提供一个值为空的禁用选项。

多选时

<div id="example-6">
  <select v-model="selected" multiple style="width: 50px;">
    <option>A</option>
    <option>B</option>
    <option>C</option>
  </select>
  <br>
  <span>Selected: {{ selected }}</span>
</div>
new Vue({
  el: '#example-6',
  data: {
    selected: []
  }
})
v-text

更新元素的 textContent

<span v-text="msg"></span>
<!-- 和下面的一样 -->
<span>{{msg}}</span>
v-html

更新元素的 innerHTML

<div v-html="html"></div>
v-pre

跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。

<span v-pre>{{ this will not be compiled }}</span>
v-cloak

这个指令保持在元素上直到关联实例结束编译。和 CSS 规则如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。

[v-cloak] {
  display: none;
}
<div v-cloak>
  {{ message }}
</div>

不会显示,直到编译结束。

v-once

只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。

<!-- 单个元素 -->
<span v-once>This will never change: {{msg}}</span>
<!-- 有子元素 -->
<div v-once>
  <h1>comment</h1>
  <p>{{msg}}</p>
</div>
<!-- `v-for` 指令-->
<ul>
  <li v-for="i in list" v-once>{{i}}</li>
</ul>

3.class、style样式绑定

绑定HTML Class
对象语法
<!-- class样式处理 -->
<!-- 对象语法 -->
<div id="app">
    <div v-bind:class="{active:isActive,active2:isActive2}"></div>
    <button v-on:click="handle">切换</button>
</div>

<script>
    var vm = new Vue({
        el: '#app',
        data: {
            isActive:true,
            isActive2:true
        },
        methods: {
            handle:function(){
                this.isActive = !this.isActive;
                this.isActive2 = !this.isActive2;
            }
        }
    });
</script>
数组语法
<div v-bind:class="[active,active2]"></div>
<div :class="[classA, { classB: isB, classC: isC }]">
绑定内联样式
对象语法
<div :style="{ fontSize: size + 'px' }"></div>
数组语法
<div :style="[styleObjectA, styleObjectB]"></div>

4.事件处理

1. 监听事件,并触发代码

可以用 v-on 指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。

<div id="example-1">
  <button v-on:click="counter += 1">Add 1</button>
  <p>已经点击了 {{ counter }} 次.</p>
</div>

var example1 = new Vue({
  el: '#example-1',
  data: {
    counter: 0
  }
})
2. 事件处理方法

然而许多事件处理逻辑会更为复杂,所以直接把 JavaScript 代码写在 v-on 指令中是不可行的。因此 v-on 还可以接收一个需要调用的方法名称。

<div id="example-2">
  <!-- `greet` 是在下面定义的方法名 -->
  <button v-on:click="greet">Greet</button>
</div>
var example2 = new Vue({
  el: '#example-2',
  data: {
    name: 'Vue.js'
  },
  // 在 `methods` 对象中定义方法
  methods: {
    greet: function (event) {
      // `this` 在方法里指向当前 Vue 实例
      alert('Hello ' + this.name + '!')
      // `event` 是原生 DOM 事件
      if (event) {
        alert(event.target.tagName)
      }
    }
  }
})

// 也可以用 JavaScript 直接调用方法
example2.greet() // => 'Hello Vue.js!'
3. 内联处理器中的方法
<div id="example-3">
  <button v-on:click="say('hi')">Say hi</button>
  <button v-on:click="say('what')">Say what</button>
</div>
new Vue({
  el: '#example-3',
  methods: {
    say: function (message) {
      alert(message)
    }
  }
})

5.修饰符

1.事件修饰符

.stop - 调用 event.stopPropagation()

<body>
    <div id="app">
        <div @click="handleParentClick">
            <button @click.stop="handleClick">点击</button>
        </div>
    </div>
</body>
<script>
    let vm = new Vue({
        el:"#app",
        methods: {
            handleParentClick:function(){
                console.log("parent click")
            },
            handleClick:function(){
                console.log("child click")
            }
        },
    })
</script>

.prevent - 调用 event.preventDefault()

<body>
    <div id="app">
        <a href="http://www.bing.com"  @click.prevent="handleClick">点击</a>
    </div>
</body>
<script>
    let vm = new Vue({
        el:"#app",
        methods: {
            handleClick:function(){
                console.log("child click")
            }
        },
    })
</script>

.capture - 添加事件侦听器时使用 capture 模式

<body>
    <div id="app">
        <div  @click.capture="handleCaptureClick">
            <button @click="handleClick">捕获模式</button>
        </div>
    </div>
</body>
<script>
    let vm = new Vue({
        el:"#app",
        methods: {
            handleCaptureClick:function(){
                console.log("捕获模式先触发");
            },
            handleClick:function(){
                console.log("child click")
            }
        },
    })
</script>

.self - 只当事件是从侦听器绑定的元素本身触发时才触发回调

<body>
        <div id="app">
            <div @click.self="handleParentClick">
                <button @click="handleClick">点击</button>
            </div>
        </div>
    </body>
    <script>
        let vm = new Vue({
            el:"#app",
            methods: {
                handleParentClick:function(){
                    console.log("parent click")
                },
                handleClick:function(){
                    console.log("child click")
                }
            },
        })
    </script>

.{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调

常见的 keyAlias

.enter

.tab

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

.esc

.space

.up

.down

.left

.right

<body>
    <div id="app">
        <!-- 13 代表 enter键 -->
        <input @keyup.enter="handleClick"></input>
        <input @keyup.13="handleClick"></input>
    </div>
</body>
<script>
    let vm = new Vue({
        el:"#app",
        methods: {
            handleClick:function(){
                console.log("键盘按上来了 enter 键")
            }
        },
    })
</script>

.native - 监听组件根元素的原生事件

注意:vue 键盘回车事件

如果是原生的input,使用 @keyup.enter就可以,若是使用了element-ui,则要加上native限制符,因为element-ui把input进行了封装,原事件就不起作用了,代码如下:

<el-input v-model="form.name" placeholder="昵称" @keyup.enter.native="submit"></el-input>

.once - 只触发一次回调

<body>
    <div id="app">
        <button @click.once="handleClick">点击</button>
    </div>
</body>
<script>
    let vm = new Vue({
        el: "#app",
        methods: {
            handleClick: function () {
                console.log("只执行一次")
            }
        },
    })
</script>

.left - (2.2.0) 只当点击鼠标左键时触发。

.right - (2.2.0) 只当点击鼠标右键时触发。

.middle - (2.2.0) 只当点击鼠标中键时触发。

<button @click.left="handleClick">左点击</button>
<button @click.right="handleClick">右点击</button>
<button @click.middle="handleClick">点击鼠标中键</button>

.passive - (2.3.0) 以 { passive: true } 模式添加侦听器

<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成  -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>

这个 .passive 修饰符尤其能够提升移动端的性能

不要把 .passive.prevent 一起使用,因为 .prevent 将会被忽略,同时浏览器可能会向你展示一个警告。请记住,.passive 会告诉浏览器你想阻止事件的默认行为。

2.按键修饰符
<input @keydown.enter="handleKeyEvent" type="text">
<input @keyup.enter="handleKeyEvent" type="text">
3.系统修饰符
可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。
.ctrl
.alt
.shift
.meta

在 Mac 系统键盘上,meta 对应 command 键 (⌘)。在 Windows 系统键盘 meta 对应 Windows 徽标键 (⊞)

<body>
    <div id="app">
         <!-- alt + click -->
        <button @click.alt="handleClick">alt+click</button>
    </div>
</body>
<script>
    let vm = new Vue({
        el: "#app",
        methods: {
            handleClick: function () {
                console.log("key event")
            }
        },
    })
</script>
4.表单修饰符

.lazy

在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步。你可以添加 lazy 修饰符,从而转为在 change 事件_之后_进行同步:

<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg">

.number

如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:

<input v-model.number="age" type="number">

这通常很有用,因为即使在 type="number" 时,HTML 输入元素的值也总会返回字符串。如果这个值无法被 parseFloat() 解析,则会返回原始的值。

.trim

如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:

<input v-model.trim="msg">

6.自定义指令

全局自定义指令
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})
// 使用
<input v-focus>
局部的自定义指令
directives: {
  focus: {
    // 指令的定义
    inserted: function (el) {
      el.focus()
    }
  }
}
钩子函数

bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)

update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。

componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。

unbind:只调用一次,指令与元素解绑时调用。

钩子函数的参数

el:指令所绑定的元素,可以用来直接操作 DOM。

binding:一个对象,包含以下 property:

  • name:指令名,不包括 v- 前缀。
  • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2
  • oldValue:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。
  • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"
  • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"
  • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }

vnode:Vue 编译生成的虚拟节点。

oldVnode:上一个虚拟节点,仅在 updatecomponentUpdated 钩子中可用。

例子
<body>
    <div id="app">
        <input type="text" v-color='msg'>
    </div>

    <script>
        // 全局指令
        Vue.directive('color',{
            bind:function(el,binding){
                // binding.value = msg中的数据
                // console.log(binding);
                el.style.background = binding.value.color;
            }
        })
        var vm = new Vue({
            el: '#app',
            data: {
                msg:{
                    color:'orange'
                }
            },
            methods: {}
        });
    </script>
</body>

7.Vue中nextTick的用法与使用场景

详细参考

例子说明
<template>
    <div class="tick">
        <div ref="msgDiv">{{msg}}</div>
        <div v-if="msg1">Message got outside $nextTick: {{msg1}}</div>
        <div v-if="msg2">Message got inside $nextTick: {{msg2}}</div>
        <div v-if="msg3">Message got outside $nextTick: {{msg3}}</div>
        <button @click="changeMsg">
            Change the Value
        </button>
    </div>
</template>
<script>
    export default{
        data(){
            return {
                msg: '000000',
                msg1: '',
                msg2: '',
                msg3: ''
            }
        },
        methods: {
            changeMsg() {
                this.msg = "111111"
                this.msg1 = this.$refs.msgDiv.innerHTML 
                this.$nextTick(() => {
                    this.msg2 = this.$refs.msgDiv.innerHTML 
                })
                this.msg3 = this.$refs.msgDiv.innerHTML
            }
        }
    }
</script>

在第一次点击按钮时,msg1与mssg3获取的是初次加载时候msg的值,这是因为vue的DOM是异步加载,而使用了nextTick的msg2的值在msg改变之后,就立刻获取到了msg更新之后的值。因此可以得出nextTick的使用场景

应用场景

在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中

created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted()钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题

5.vue常用特性

1.计算属性

为什么需要计算属性?

1.因为表达式的计算可能会比较复杂,使用计算属性可以使模板内容更加简洁

2.方法没有缓存 计算属性有缓存 是基于它们的依赖msg进行缓存的 methods调用多少次,执行多少次

3.计算属性特点:提高效率,减少性能消耗,避免重复运行相同的结果,减少开销

<div>{{reverseString}}</div>
var vm = new Vue({
    el: '#app',
    data: {
        msg:'hello'
    },
    // 方法没有缓存
    methods: {},

    computed: {
        reverseString:function(){
            return this.msg.split('').reverse().join('');
        }
    },
});

2.侦听器

侦听器的应用场景

数据变化时执行异步或开销较大的操作

 <div id="app">
     <input type="text" v-model.lazy='uname'>
     <span>{{tip}}</span>
</div>

<script>
    var vm = new Vue({
        el: '#app',
        data: {
            uname: '',
            tip: ''
        },
        methods: {
            checkName: function (uname) {
                // 使用定时任务的方式模拟接口调用
                // 缓存this
                var that = this;
                setTimeout(function () {
                    // 模拟接口调用
                    if (uname == 'admin') {
                        // 注意this指向问题
                        that.tip = '用户名已经存在,请更换一个';
                    }else{
                        that.tip = '用户名可用';
                    }
                },2000);
            }
        },
        watch: {
            uname: function (val) {
                // 调用后台接口验证用户名的合法性
                this.checkName(val);
                // 修改提示信息
                this.tip = '正在验证...';
            }
        }
    });
</script>

3.过滤器

<div id="app">
    <input type="text" v-model='msg'>
    <div>{{msg | upper}}</div>
    <!-- 属性绑定 -->
    <div :class='msg | upper'></div>
</div>

<script>
    // 定义全局过滤器
    // Vue.filter('upper',function(val){
    //     // 将第一个字母大写
    //     return val.charAt(0).toUpperCase() + val.slice(1);
    // })
    var vm = new Vue({
        el: '#app',
        data: {
            msg:'',
        },
        methods: {},
        // 定义局部过滤器
        filters:{
            upper:function(val){
                return val.charAt(0).toUpperCase() + val.slice(1);
            }
        }
    });
</script>
带参数的过滤器
<div id="app">
    <div>{{date | format('yyyy-MM-dd')}}</div>
</div>

<script>
    // Vue.filter('format',function(value,arg){
    //     if(arg == 'yyyy-MM-dd'){
    //         var ret = value.getFullYear() + '-' + (value.getMonth() + 1) + '-' + value.getDate();
    //         return ret;
    //     }
    // })
    Vue.filter('format', function (value, arg) {
        function dateFormat(date, format) {
            if (typeof date === "string") {
                var mts = date.match(/(\/Date\((\d+)\)\/)/);
                console.log(mts,'----------');
                if (mts && mts.length >= 3) {
                    date = parseInt(mts[2]);
                }
            }
            date = new Date(date);
            if (!date || date.toUTCString() == "Invalid Date") {
                return "";
            }
            var map = {
                "M": date.getMonth() + 1,//月份
                "d": date.getDate(),//日
                "h": date.getHours(),//小时
                "m": date.getMinutes(),//分
                "s": date.getSeconds(),//秒
                "q": Math.floor((date.getMonth() + 3) / 3),//季度
                "S": date.getMilliseconds(),//毫秒
            };
            // format 格式 yyyy-MM-dd
            format = format.replace(/([yMdhmsqS])+/g , function (all, t) {
                // console.log(all,t);//MM M dd d ...
                // 匹配到的字母出现的次数all.length  t是分组
                var v = map[t];//5 17
                if (v !== undefined) {
                    if (all.length > 1) {
                        v = '0' + v;
                        v = v.substr(v.length - 2);
                    }
                    console.log(v);//05 17
                    return v;
                } else if (t === 'y') {
                    console.log((date.getFullYear() + '').substr(4 - all.length));//2020
                    return (date.getFullYear() + '').substr(4 - all.length);
                }
                console.log(all);
                return all;
            })
            console.log(format);//2020-05-17
            return format;
        }
        return dateFormat(value,arg);
    })
    var vm = new Vue({
        el: '#app',
        data: {
            date: new Date()
        },
        methods: {}
    });
</script>

4.生命周期

\1. new Vue();

init Event&Lifecycle

\2. 加载vue的事件,以及加载vue的生命周期函数(不是调用函数)

\3. 执行beforeCreate()生命周期函数

Init Injections&reactivity 把配置项当中的所有值都给加载出来

\4. 数据执行了观测,已经加载了事件等其他一些跟dom无关初始化操作(watch,computed,。。。。)

\5. 执行created()生命周期函数,只要跟dom没有关系的操作,在这个生命周期函数中都可以执行。(可以操作this)

Has “el” option?

\6. 判断是否配置了 e l , 如 果 没 有 就 不 管 理 d o m , 直 至 动 态 调 用 了 el,如果没有就不管理dom,直至动态调用了 eldommount(el)才会管理dom

Has “template” option?

\7. 判断是否有template

\8. 有,直接编译模版,没有,把el.outerHTML当成模版,进行编译

\9. 执行beforeMount()生命周期函数

\10. 将渲染好的dom替换到页面中

\11. 执行mounted()生命周期函数,操作dom

\12. 当数据变化后,dom使用了该数据 如果数据更新了但是dom没有使用该数据 那么不会执行更新钩子函数

\13. 执行beforeUpdate()生命周期函数

\14. 比对虚拟dom,更新dom

\15. 执行updated()生命周期函数

\16. 当实例调用$destroy()时

\17. 执行beforeDestroy()生命周期函数,移除闭包,移除事件监听。。

\18. 移除子组件移除事件监听等等会造成内存泄漏的

\19. 执行destroyed()生命周期函数

// template会被编译一个虚拟dom 直接会被当成render函数的内容
// 经过webpack打包后就没有template这个标签 只有一个render函数了
<template>
<div id="app">

</div>
</template>

<script>
export default {

}
</script>

<style>

</style>

6.组件化开发

1.定义全局组件

<div id="app">
    <mycomponent></mycomponent>
</div>

<script>
    // 定义全局自定义组件
    Vue.component('mycomponent' , {
        data:function(){
            return {
                count:0,
            }
        },
        template:'<button @click="handle">提交了{{count}}</button>',
        methods:{
            handle:function(){
                this.count++;
            }
        }
    });
    var vm = new Vue({
        el: '#app',
        data: {},
        methods: {}
    });
</script>

2.定义局部组件

局部组件

components: {

​ 组件名字: 组件配置项,

​ 组件名字: 组件配置项,

}

<div id="app">
    <mycomponentone></mycomponentone>
    <mycomponenttwo></mycomponenttwo>
    <!-- <component></component> -->
</div>

<script>
    // 定义局部组件
    var mycomponentone = {
        data:function(){
            return {
                msg:'hello'
            }
        },
        template:'<div>{{msg}}</div>'
    }
    var mycomponenttwo = {
        data:function(){
            return {
                msg:'hi'
            }
        },
        template:'<div>{{msg}}</div>',
    }
    var vm = new Vue({
        el: '#app',
        data: {},
        methods: {},
        components:{
            mycomponentone,
            mycomponenttwo,
        }
    });
</script>

4.父传子

props验证

props验证

props: {
  value: String,
  age:Number,
  ....
 }

子组件通过props属性接收

<div id="app">
    <menu-item title="我是父组件传的值"></menu-item>
    <!-- 动态显示 -->
    <menu-item :title="ptitle"></menu-item>
    <input type="text" v-model="ptitle">
</div>

<script>
    Vue.component('menu-item' , {
        props:['title'],
        data:function(){
            return {
                msg:'我是子组件',
            }
        },
        template:'<div>{{msg + "----" + title}}</div>'
    })
    var vm = new Vue({
        el: '#app',
        data: {
            ptitle:'父组件',
        },
        methods: {}
    });
</script>

5.子传父

(自定义事件_$emit)子组件传值给父组件

父组件内容
<template>
  <div class="father">
    <h1>父组件</h1>

    <Son :change="changeAction"/>
	// 在子组件上绑定自定义事件 子组件通过$emit触发 案后执行父组件中的change2Action方法并接收传过来的参数
    <Son @my-event="change2Action"/>

  </div>
</template>

<script>
import Son from './Son'
export default {
  components: {
    Son
  },
  methods: {
    changeAction(...rest){
      console.log('changeAction执行了.....');
      console.log(rest);
    },
    change2Action(...rest){
      console.log('change2Action执行了.....');
      console.log(rest);
    }
  }
}
</script>

<style scoped>
.father{
  padding: 20px;
  border: 5px solid salmon;
}
.father .son{
  border: 5px dashed slateblue;
}
</style>
子组件内容
<template>
  <div class="son">
    <h1>子组件</h1>
    <p>选择了:{{selected}}</p>
    <nav>
      <button v-for="item in btns" :key="item"
        @click="btnAction(item)">
        {{item}}
      </button>
    </nav>
  </div>
</template>

<script>
export default {
  props: {
    change: Function
  },
  data(){
    return {
      btns: ['A', 'B', 'C', 'D'],
      selected: ''
    }
  },
  methods: {
    btnAction(item){
      this.selected = item;
      // 调用父组件的方法,传值给父组件
      // this.change(this.selected);

      // 调用组件标签上的任意的自定义事件
      // 参数1:自定义事件的名字
      // 参数2:触发自定义事件时的参数
      this.$emit('my-event', 1, 2, 3, this.selected);
    }

  },
  created(){
    console.log(this.change);
  }

}
</script>

<style>
.son{
  padding: 20px;
  border: 5px solid seagreen;
}
</style>

6.vue事件监听机制

执行事件监听: $on $once

触发事件: $emit

移除事件监听 $off

<script>
export default {
  created(){
    // vue组件/vue实例 都有关于事件监听的方法

    // 执行事件监听: $on  $once
    // 触发事件: $emit
    // 移除事件监听 $off

    // 监听
    const cb1 = ()=>{
      console.log('监听到了1...');
    };
    const cb2 = ()=>{
      console.log('监听到了2...');
    };
    this.$on('test-event', cb1);
    this.$on('test-event', cb2);
    
    this.$on('my-event', ()=>{
      console.log('my-event监听到了....');
    });

    // this.$off();
    // this.$off('test-event');
    // this.$off('test-event', cb1);

    this.$emit('test-event'); 
  }
}
</script>

7.非父子组件的传值(event-bus)

main主入口文件添加事件中心

import Vue from 'vue'
import App from './App.vue'//根组件

// 在Vue原型添加Vue实例
Vue.prototype.$eventBus = new Vue();

new Vue({
  el: '#app',
  render: h => h(App)
});

One.vue

<template>
  <div class="two">
    <ul>
      <li v-for="(item, index) in list" :key="index">{{item}}</li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      list: []
    };
  },
  created() {
    // 监听事件
    this.$eventBus.$on("add", value => {
      console.log("接收到了:", value);
      this.list.push(value);
    });
  }
};
</script>
<style>
</style>

Two.vue

<template>
  <div class="one">
    <input type="text" ref="in" />
    <button @click="btnAction">添加</button>
  </div>
</template>

<script>
export default {
  methods: {
    btnAction() {
      const value = this.$refs.in.value;
      //触发事件
      this.$eventBus.$emit("add", value);

      this.$refs.in.value = '';
    }
  }
};
</script>

<style>
</style>

8.状态提升实现兄弟组件之间传值

App.vue文件

<template>
<div id="app">
  <h1>app</h1>
  <div class="wrap">

    <One @add="handleAddAction"/>

    <hr/>
    <Two :data="list"/>

  </div>

</div>
</template>

<script>
import One from './components/One'
import Two from './components/Two'
export default {
  components: {
    One,
    Two
  },
  data(){
    return {
      list: []
    }
  },
  methods: {
    handleAddAction(value){
      this.list.push(value);
    }
  }
  
}
</script>

<style>

</style>

One.vue文件

<template>
  <div class="one">
    <input type="text" ref="in" />
    <button @click="btnAction">添加</button>
  </div>
</template>

<script>
export default {
  methods: {
    btnAction() {
      const value = this.$refs.in.value;
      
      // 将输入框的值告诉父组件
      this.$emit('add', value);

      this.$refs.in.value = '';
    }
  }
};
</script>

<style>
</style>

Two.vue文件

<template>
  <div class="two">
    <ul>
      <li v-for="(item, index) in data" :key="index">{{item}}</li>
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    data: Array
  }
};
</script>

</style>

9.v-model在组件上使用,实现受控组件

v-model = @input + :value

父组件
<!-- <Tabs @input="changeAction" :value="selected"/> -->
<!-- 受控组件 -->
<Tabs v-model="selected"/>
子组件
<script>
export default {
  props: {
    value: Number
  },
  data() {
    return {
      navs: ["one", "two", "three"]
    };
  },
  methods: {
    selectAction(index) {
      this.$emit("input", index);
    }
  }
};
</script>

10.动态组件

<!-- 动态组件 -->
<!-- is的值是组件的标签名字 -->
<component :is="comName"/>
<template>
  <div id="app">

    <!-- <One v-if="selected===0"/>
    <Two v-else-if="selected===1"/>
    <Three v-else-if="selected===2"/> -->

    <!-- 动态组件 -->
    <!-- is的值是组件的标签名字 -->
    <component :is="comName"/>

    <!-- <component></component> -->


    <Tabs v-model="selected"/>

  </div>
</template>

<script>
import One from "./pages/One";
import Two from "./pages/Two";
import Three from "./pages/Three";
import Tabs from "./components/Tabs";
export default {
  components: {
    one: One,
    two: Two,
    three: Three,
    Tabs
  },
  data() {
    return {
      selected: 1
    };
  },
  computed: {
    comName(){
      switch (this.selected) {
        case 0:
          return 'one';
        case 1:
          return 'two';
        case 2:
          return 'three';
      }
    }
  }
};
</script>

<style>
* {
  margin: 0;
  padding: 0;
}
.page{
  width: 100%;
  position: absolute;
  top: 0;
  left: 0;
  bottom: 50px;
}
</style>

11.keep-active

<template>
  <div id="app">

    <!-- 每一次切换不会销毁组件,组件只是隐藏了。但是初始化开销大 -->
    <!-- <One v-show="selected===0"/>
    <Two v-show="selected===1"/>
    <Three v-show="selected===2"/> -->

    <!-- 切换开销大 -->
    <!-- <One v-if="selected===0"/>
    <Two v-else-if="selected===1"/>
    <Three v-else-if="selected===2"/> -->

    <!-- 切换开销大 -->
    <!-- <component :is="comName"/> -->

    <!-- 
      keep-alive内部的组件按照原来的方式创建
      但是呢,组件销毁时,会拦截不让组件销毁,对组件进行缓存。
      下一次再调用该组件,就不再创建,直接去上一次缓存的组件
     -->
    <keep-alive>

      <component :is="comName"/>

    </keep-alive>


    <Tabs v-model="selected"/>

  </div>
</template>

为什么使用keep-active?

为了减少开销

v-show:每一次切换不会销毁组件,组件只是隐藏了。但是初始化开销大 初始化组件都被创建

v-if:切换组件 组件频繁创建销毁 开销大

使用keep-active:内部的组件按照原来的方式创建 组件销毁时,会拦截不让组件销毁,对组件进行缓存 下一次再调用该组件,就不再创建,直接去上一次缓存的组件

<script>
export default {
  created(){
    console.log('one created....');
  },
  // keep-active
  activated(){
    console.log('one activated....');
  },
  deactivated(){
    console.log('one deactivated....');
  },
  destroyed(){
    console.log('one destroyed....');
  }
}
</script>

12.组件懒加载

原先加载方式
<script>
import One from "./pages/One";
import Two from "./pages/Two";
import Three from "./pages/Three";
import Tabs from "./components/Tabs";
export default {
  components: {
    one: One,
    two: Two,
    three: Three,
    One,
    Two,
    Three,
    Tabs
  }
}
</script>
懒加载
<script>
import Tabs from "./components/Tabs";
export default {
  components: {
    // 组件懒加载
    one: ()=>import('./pages/One'),
    two: ()=>import('./pages/Two'),
    three: ()=>import('./pages/Three'),
    Tabs
  }
 }
</script>

13.slot

Wrap.vue

<template>
<div class="wrap">
  <h1>wrap</h1>
  <slot name="title2"/>
  <slot name="title1"/>
  <slot/>
</div>
</template>

<script>
export default {
  name: 'app-wrap'
}
</script>

<style>

</style>

App.vue

<template>
  <div id="app">

    <app-wrap>

      <h1 slot="title1">test</h1>
      <h3 slot="title2">test</h3>
      <h6>test</h6>
      <div>test</div>
      <p>test</p>
      test

    </app-wrap>

  </div>
</template>

<script>
import Wrap from './components/wrap'
export default {
  components: {
    [Wrap.name]: Wrap,
  }
}
</script>

<style>

</style>
slot通信

content.vue

<template>
  <div class="content" v-if="show">
    <slot/>
  </div>
</template>

<script>
export default {
  name: 'app-content',
  props: {
    title: String,
    id: String
  },
  data(){
    return {
      show: false
    }
  },
  mounted(){
    console.log(this.$parent);
  }
}
</script>

<style>

</style>

wrap.vue

<template>
  <div class="wrap">
    <nav>
      <li
        v-for="item in navList"
        :key="item.id"
        :class="{active: item.id === value}"
        @click="changeAction(item.id)"
      >{{item.title}}</li>
    </nav>

    <slot />
  </div>
</template>

<script>
export default {
  name: "app-wrap",
  props: {
    value: String,
  },
  data() {
    return {
      navList: [],
    };
  },
  watch: {
    value() {
      this.findShowContent();
    },
  },
  methods: {
    findShowContent() {
      // 查找需要显示的content
      this.$children.forEach((item) => {
        if (item.id === this.value) {
          //显示content的内容
          item.show = true;
        } else {
          //隐藏content的内容
          item.show = false;
        }
      });
    },
    changeAction(id) {
      this.$emit("input", id);
    },
  },
  mounted() {
    // 访问slot
    console.log(this.$slots);
    // 访问子节点
    console.log(this.$children);

    // 取出所有的title,进行展示
    this.navList = this.$children.map((item) => ({
      title: item.title,
      id: item.id,
    }));

    this.findShowContent();
  },
};
</script>

<style>
.active {
  color: darkcyan;
}
</style>

App.vue

<template>
  <div id="app">

    <app-wrap v-model="selected">

      <app-content id="home" title="首页">
        <h1>首页的内容</h1>
        <h1>首页的内容</h1>
        <h1>首页的内容</h1>
      </app-content>
      <app-content id="hot" title="热门">
        <h1>热门的内容</h1>
      </app-content>
      <app-content id="finance" title="财经">
        <h1>财经的内容</h1>
      </app-content>

    </app-wrap>

  </div>
</template>

<script>
import Wrap from './components/wrap'
import Content from './components/content'
export default {
  components: {
    [Wrap.name]: Wrap,
    [Content.name]: Content,
  },
  data(){
    return {
      selected: 'hot'
    }
  }
}
</script>

<style>

</style>

14.动画

动画

<template>
  <div id="app">
    <input type="checkbox" v-model="value"/>

    <!-- 
      dom进入时,添加v-enter v-enter-to v-enter-active
     -->
    <transition>
      <div class="box" v-show="value">Hello</div>
    </transition>


    <!-- 
      dom进入时,添加slide-enter slide-enter-to slide-enter-active
     -->
    <transition name="slide">
      <div class="box" v-show="value">Hello</div>
    </transition>
    

    <!-- 进入的class名字可以自定义 -->
    <transition-group
      enter-class=""
      enter-to-class=""
      enter-active-class="animate__tada"
      leave-class="leave-start"
      leave-to-class="leave-end"
      leave-active-class="leave-active">

      <div class="box" key="1" v-show="value">Hello1</div>
      <div class="box" key="2" v-show="value">Hello2</div>

    </transition-group>

    <!-- 离开 -->
    <!-- v-leave  v-leave-to  v-leave-active -->
    <!-- [name]-leave  [name]-leave-to  [name]-leave-active -->
    <!-- 自定义名字:leave-class leave-to-class leave-active-calss -->



  </div>
</template>

<script>
import 'animate.css'
export default {
  data(){
    return {
      value: true
    }
  }
}
</script>

<style>
.box{
  width: 300px;
  height: 100px;
  text-align: center;
  color: #fff;
  line-height: 100px;
  background: darkgoldenrod;
  margin: 10px;
}
.box2{
  background: darkolivegreen;
}

/* 过渡 */
/* .v-enter{
   transform: scale(0);
   opacity: 0;
}
.v-enter-to{
  transform: scale(1);
  opacity: 1;
}
.v-enter-active{
  transition: 5s;
} */


/* 动画 */
@keyframes enter {
  0%{
    transform: scale(0);
    opacity: 0;
  }
  100%{
    transform: scale(1);
    opacity: 1;
  }
}
.v-enter-active{
  animation: enter 5s;
}

@keyframes slide {
  0%{
    transform: translateX(-100%);
  }
  100%{
    transform: translateX(0);
  }
}
.slide-enter-active{
  animation: slide 5s;
}


.animate__tada{
  animation-duration: 3s;
}

.leave-end{
  transform: rotate(360deg) scale(0);
}
.leave-active{
  transition: 3s;
}

</style>

7.路由

1.Vue Router的基本使用

<body>
    <div id="app">
        <!-- 1,添加vue vue-router依赖 -->
        <!-- 2,添加路由连接 -->
        <!-- 使用 router-link 组件来导航. -->
        <!-- 通过传入 `to` 属性指定链接. -->
        <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
        <router-link to="/user">user</router-link>
        <router-link to="/register">register</router-link>

        <!-- 3,添加路由填充位(占位) -->
        <!-- 路由出口 -->
      	<!-- 路由匹配到的组件将渲染在这里 -->
        <router-view></router-view>
    </div>

    <script>
        // 4,定义路由组件
        // 可以从其他文件 import 进来
        const User = {
            template:`<div>user 组件</div>`
        }
        const Register = {
            template:`<div>Register 组件</div>`
        }

        // 5,配置路由规则并创建路由实例
        // 每个路由应该映射一个组件。 其中"component" 可以是通过 Vue.extend() 创建的组件构造器,
        // 或者,只是一个组件配置对象
        var router = new VueRouter({
            routes:[
                {path:'/user',component:User},
                {path:'/register',component:Register}
            ]
        });
        var vm = new Vue({
            el: '#app',
            data: {},
            // 6,把路由挂载到Vue根实例中
            // 通过 router 配置参数注入路由,
            // 从而让整个应用都有路由功能
            router,
            methods: {}
        });
    </script>
</body>

2.路由重定向(redirect)

<body>
    <div id="app">
        <!-- 什么是路由重定向? -->
        <!-- 用户在访问地址a的时候,强制用户跳转到地址c,从而展示特定的组件页面 -->
        <!-- 在路由规则中新增属性redirect -->
        <router-link to="/user">user</router-link>
        <router-link to="/register">register</router-link>

        <router-view></router-view>
    </div>

    <script>
        const User = {
            template:`<div>user 组件<div>`
        }
        const Register = {
            template:`<div>register 组件<div>`
        }

        var router = new VueRouter({
            routes:[
                {path:'/',redirect:'/user'},
                {path:'/user',component:User},
                {path:'/register',component:Register}
            ]
        })
        var vm = new Vue({
            el: '#app',
            data: {},
            router,
            methods: {}
        });
    </script>
</body>

3.嵌套路由

在脚手架中书写

import Vue from 'vue'
import Router from 'vue-router'
import Home from '../pages/Home'
import Category from '../pages/Category'
import Location from '../pages/Location'
import Detail from '../pages/Detail'

// 插件
Vue.use(Router);

const routes = [
  {
    path: '/one',
    name: 'home',
    component: Home,
    children: [
      {
        path: 'part_a',
        name: 'location',
        component: Location
      },
      {
        path: 'part_detail/:id',
        name: 'detail',
        component: Detail
      }
    ]
  },
  {
    path: '/two',
    name: 'category',
    component: Category
  },
  {
    path: '/three',
    name: 'mine',
    component: ()=>import('../pages/Mine')
  }
];


const router = new Router({
  mode: 'history',//abstract,hash,history
  // base: '/project_a',
  base: '/',
  routes
});

export default router;

在html中书写

<body>
    <div id="app">
        <router-link to="/user">user</router-link>
        <router-link to="/register">register</router-link>
        <router-view></router-view>
    </div>

    <script>
        const User = {
            template:`<div>user 组件</div>`
        }
        const Register = {
            template:`<div>
                <h1>Register 组件</h1>
                <hr/>
                <router-link to="/register/tab1">Tab1</router-link>
                <router-link to="/register/tab2">Tab2</router-link>
                <router-view></router-view>
            </div>`
        }
        const Tab1 = {
            template:`<div>Tab1 组件</div>`
        }
        const Tab2 = {
            template:`<div>Tab2 组件</div>`
        }
        var router = new VueRouter({
            routes:[
                {path:'/user',component:User},
                {path:'/register',component:Register,children:[
                    {path:'/register/tab1',component:Tab1},
                    {path:'/register/tab2',component:Tab2}
                ]}
            ]
        })
        var vm = new Vue({
            el: '#app',
            data: {},
            router,
            methods: {}
        });
    </script>
</body>

4.动态路由匹配

访问路由参数 this.$route.params.路由路径后面携带的动态参数名(例如::id)

共用同一个路由 通过$route.params.id(id为动态参数,可以变更)

<body>
    <div id="app">
        <!--  共用同一个路由 通过$route.params.id(id为动态参数,可以变更)-->
        <router-link to="/user/1">user1</router-link>
        <router-link to="/user/2">user2</router-link>
        <router-link to="/user/3">user3</router-link>
        <router-link to="/register">register</router-link>

        <router-view></router-view>
    </div>

    <script>
        const User = {
            template:`<div>user 组件{{$route.params.id}}<div>`
        }
        const Register = {
            template:`<div>register 组件<div>`
        }

        var router = new VueRouter({
            routes:[
                {path:'/user/:id',component:User},
                {path:'/register',component:Register}
            ]
        })
        var vm = new Vue({
            el: '#app',
            data: {},
            router,
            methods: {}
        });
    </script>
</body>

拓展:编程式导航的两种方式

// this.$router.push(`/one/part_detail/${动态参数}`);
// 也可
this.$router.push({
name: 'detail',// 定义name属性 找对应路由中的name进行跳转 
params: {id: 动态参数},
query: {title: 'test'}//携带url参数
});

5.路由组件传递参数

共用同一个路由 通过$route.params.id(id为动态参数,可以变更)

$route与对应路由形成高度耦合,不够灵活,所以可以使用props将组件和路由解耦

{
    path: '/gd/:id',
    // 将参数设置为动态参数传给组件
    props: true,
    props: {
      //将对象上的值传给组件作为props
      value: 'hello'
    },

    props(){
      //将函数返回的对象上的值传给组件作为props
      return {
        message: 'hello world'
      }
    },

    component: ()=>import('../project/gd/content')
  }
第一种情况 props设置为布尔类型
<body>
    <div id="app">
        <!--  共用同一个路由 通过$route.params.id(id为动态参数,可以变更)-->
        <router-link to="/user/1">user1</router-link>
        <router-link to="/user/2">user2</router-link>
        <router-link to="/user/3">user3</router-link>
        <router-link to="/register">register</router-link>

        <router-view></router-view>
    </div>
    <!-- $route与对应路由形成高度耦合,不够灵活,所以可以使用props将组件和路由解耦 -->
    <!-- 第一种情况 props设置为布尔类型 -->
    <script>
        const User = {
            props:['id'],
            template: `<div>user 组件{{id}}<div>`
        }
        const Register = {
            template: `<div>register 组件<div>`
        }

        var router = new VueRouter({
            routes: [
                { path: '/user/:id', component: User ,props:true},
                { path: '/register', component: Register }
            ]
        })
        var vm = new Vue({
            el: '#app',
            data: {},
            router,
            methods: {}
        });
    </script>
</body>
第二种情况 props设置为对象 静态参数
<body>
    <div id="app">
        <!--  共用同一个路由 通过$route.params.id(id为动态参数,可以变更)-->
        <router-link to="/user/1">user1</router-link>
        <router-link to="/user/2">user2</router-link>
        <router-link to="/user/3">user3</router-link>
        <router-link to="/register">register</router-link>

        <router-view></router-view>
    </div>
    <!-- $route与对应路由形成高度耦合,不够灵活,所以可以使用props将组件和路由解耦 -->
    <!-- 第二种情况 props设置为对象 静态参数 -->
    <script>

        const User = {
            props:['name','age'],
            template: `<div>user 组件{{name + '-----' + age}}<div>`
        }
        const Register = {
            template: `<div>register 组件<div>`
        }

        var router = new VueRouter({
            routes: [
                { path: '/user/:id', component: User ,props:{name:'lisi',age:12}},
                { path: '/register', component: Register }
            ]
        })
        var vm = new Vue({
            el: '#app',
            data: {},
            router,
            methods: {}
        });
    </script>
</body>
第三种情况 props设置为函数类型 静态参数+动态参数
<body>
    <div id="app">
        <!--  共用同一个路由 通过$route.params.id(id为动态参数,可以变更)-->
        <router-link to="/user/1">user1</router-link>
        <router-link to="/user/2">user2</router-link>
        <router-link to="/user/3">user3</router-link>
        <router-link to="/register">register</router-link>

        <router-view></router-view>
    </div>
    <!-- $route与对应路由形成高度耦合,不够灵活,所以可以使用props将组件和路由解耦 -->
    <!-- 第三种情况 props设置为函数类型 静态参数+动态参数 -->
    <script>

        const User = {
            props:['name','age','id'],
            template: `<div>user 组件{{id + name + '-----' + age}}<div>`
        }
        const Register = {
            template: `<div>register 组件<div>`
        }

        var router = new VueRouter({
            routes: [
                { path: '/user/:id', component: User ,props:function(route){return ({name:'lisi',age:12,id:route.params.id})}},
                { path: '/register', component: Register }
            ]
        })
        var vm = new Vue({
            el: '#app',
            data: {},
            router,
            methods: {}
        });
    </script>
</body>

6.命名路由

<router-link :to="{name: 'home'}">首页</router-link>

找到对应 的路由

 {
    path: '/one',
    name: 'home',
    component: Home,
    children: [
      {
        path: 'part_a',
        name: 'location',
        component: Location
      },
      {
        path: 'part_detail/:id',
        name: 'detail',
        component: Detail
      }
    ]
  }

另一种:对象中携带参数params

<router-link :to="{name:'user',params:{id:3}}">user3</router-link>
routes: [{ name:'user' , path: '/user/:id', component: User}]
<body>
    <div id="app">
        <!--  共用同一个路由 通过$route.params.id(id为动态参数,可以变更)-->
        <router-link to="/user/1">user1</router-link>
        <router-link to="/user/2">user2</router-link>
        <router-link :to="{name:'user',params:{id:3}}">user3</router-link>
        <router-link to="/register">register</router-link>

        <router-view></router-view>
    </div>
    <script>

        const User = {
            props:['name','age','id'],
            template: `<div>user 组件{{id + name + '-----' + age}}<div>`
        }
        const Register = {
            template: `<div>register 组件<div>`
        }

        var router = new VueRouter({
            routes: [
                { name:'user' , path: '/user/:id', component: User ,props:function(route){return ({name:'lisi',age:12,id:route.params.id})}},
                { path: '/register', component: Register }
            ]
        })
        var vm = new Vue({
            el: '#app',
            data: {},
            router,
            methods: {}
        });
    </script>
</body>

7.编程式导航

页面导航的两种方式

1,声明式导航:通过 点击链接 实现的方式 例如a标签

2,编程式导航:通过调用javascript形式的API实现导航的方式,叫做编程式导航

例如:location.href

<body>
    <div id="app">
        <!-- 
            页面导航的两种方式
            1,声明式导航:通过 点击链接 实现的方式 例如a标签
            2,编程式导航:通过调用javascript形式的API实现导航的方式,叫做编程式导航
            例如:location.href
         -->
        <router-link to="/user/1">user1</router-link>
        <router-link to="/user/2">user2</router-link>
        <router-link :to="{name:'user',params:{id:3}}">user3</router-link>
        <router-link to="/register">register</router-link>

        <router-view></router-view>
    </div>
    <script>

        const Index = {
            template:`<div>
                    <h1>Index 组件</h1>
                    <button @click="goRegister">跳转register页面</button>
                </div>
            `,
            methods: {
                goRegister(){
                    // this.$router.push({name:'user' ,params:{id:333}});
                    
                    // /register?uname=lisi
                    this.$router.push({path:'/register',query:{uname:'lisi'}})
                    this.$router.push({ name: 'user', params: { id: '123' }})
                }
            },
        }

        const User = {
            props:['name','age','id'],
            template: `<div>user 组件{{id + '------' + name + '-----' + age}}<div>`
        }
        const Register = {
            template: `<div>
                    <h1>register 组件</h1>
                    <button @click="goback">回去</button>
                </div>`,
            methods: {
                goback(){
                    this.$router.go(-1);
                }
            },
        }


        var router = new VueRouter({
            routes: [
                {path:'/',component:Index},
                { name:'user' , path: '/user/:id', component: User ,props:function(route){return ({name:'lisi',age:12,id:route.params.id})}},
                { path: '/register', component: Register }
            ]
        })
        var vm = new Vue({
            el: '#app',
            data: {},
            router,
            methods: {}
        });
    </script>
</body>

8.路由的命名视图

App.vue

router-view不能并列只能嵌套 但是设置了name属性的router-view只要name属性值不相同可以并列设置多个

<div class="app-wrap">
    <router-view name="menu" class="app-menu"></router-view>
    <router-view name="content" class="app-conent"></router-view>
</div>

router/index.js

const routes = [
  {
    path: '/oa',
    components: {
      menu: OA_Menu,//属性对应上面name属性值
      content: OA_Content
    }
  }
]

如果没有命名可以设置为默认 default 那么它就会找对应的没有命名的视图容器router-view

{
    path: '/gd',
        components: {
            default: () => import('../project/gd/content')
        }
}
<router-view class="app-panel"></router-view>

代码

App.vue

<template>
<div id="app">
  <header class="header">
    <h1 class="title">
      <img :src="logopath" alt="">
    </h1>
    <nav class="nav">
      <router-link class="nav-item" v-for="item in navs" :key="item.id"
        :to="'/'+item.id">
        {{item.name}}
      </router-link>
    </nav>
  </header>

  <router-view class="app-panel"></router-view>

  <div class="app-wrap">
    <router-view name="menu" class="app-menu"></router-view>
    <router-view name="content" class="app-conent"></router-view>
  </div>

</div>
</template>

<script>
export default {
  data(){
    return {
      logopath: 'http://oa.1000phone.net/Public/assets/css/images/logo.png?1595403488',
      navs: [
        {id: 'oa', name: 'oa办公系统'},
        {id: 'jx', name: '教学系统'},
        {id: 'gd', name: '工单系统'},
        {id: 'ot', name: '运营系统'}
      ]
    }
  }
}
</script>
<style>
</style>

router/index.js

import Vue from 'vue'
import Router from 'vue-router'
import OA_Menu from '../project/oa/menu'
import OA_Content from '../project/oa/content'

Vue.use(Router);

const routes = [
  {
    path: '/oa',
    components: {
      menu: OA_Menu,
      content: OA_Content
    }
  },
  {
    path: '/jx',
    components: {
      menu: ()=>import('../project/jx/menu'),
      content: ()=>import('../project/jx/content'),
    }
  },
  {
    path: '/gd',
    components: {
      default: () => import('../project/gd/content')
    }
  },
  {
    path: '/ot',
    components: {
      menu: ()=>import('../project/ot/menu'),
      content: ()=>import('../project/ot/content')
    }
  }
];

export default new Router({
  mode: 'history',
  routes
})

9.路由守卫

导航守卫

完整的导航解析流程
  1. 导航被触发。
  2. 在失活的组件里调用 beforeRouteLeave 守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。
组件one.vue中test
<template>
  <div class="one">
    <h1>one页面</h1>
  </div>
</template>

<script>
export default {
  beforeCreate() {test
    console.log("one beforeCreate...");
  },
  created() {
    console.log("one created...");
  },
  mounted() {
    console.log("one mounted...");
    console.log(this);
  },
  beforeDestroy() {
    console.log("one beforeDestroy...");
  },
  destroyed() {
    console.log("one destroyed...");
  },

  // 组件内部守卫, 守卫在组件内部,
  // beforeRouteEnter不可以使用this
  // beforeRouteUpdate , beforeRouteLeave可以使用this
  beforeRouteEnter(to, from, next){
    console.log('组件内部守卫 one beforeRouteEnter...');
    next((com)=>{
      console.log('next的回调函数执行了.....');
      console.log(com);//com就是当前的组件,如果需要操作组件中的内容,那么在next回调函数中,接收参数,该参数为当前组件对象
    });
  },
  beforeRouteUpdate(to, from, next){
    console.log('组件内部守卫 one beforeRouteUpdate...');
    next();
  },
  beforeRouteLeave(to, from, next){
    console.log('组件内部守卫 one beforeRouteLeave...');
    next();
  }
  
};
</script>

<style>
</style>
路由index.js中
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router);

const router =  new Router({
  mode: 'history',
  routes: [
    {
      path: '/one/:id',
      component: () => import('../pages/one'),
      beforeEnter(to, from, next){
        console.log('路由独享的守卫__触发了......');
        next();
      }
    },
    {
      path: '/two',
      component: () => import('../pages/two')
    }
  ]
});

// 全局前置守卫
router.beforeEach((to, from, next)=>{
  console.log('全局前置守卫1__触发了......');
  next();//放行
  // next(false);//拦截

  // 重定向
  // if(to.path === '/two'){
  //   next();
  // }
  // else{
  //   next('/two');
  // }


});

// 提供很多守卫
// 控制大批页面用全局比较合适
// 如果只控制一个分派任务的页面 管理员才能进 在页面内部做守卫就可以
// 虽然提供很多守卫 但是在不同功能守卫功能放到不同的位置


// 全局解析守卫(也就是所有前置守卫执行完了解析守卫才执行)
router.beforeResolve((to, from, next)=>{
  console.log('全局解析守卫__触发了......');
  next();
});

// 后置钩子,不能拦截 守卫可以拦截
// 解析守卫放行才能执行  
router.afterEach((to, from) => {
  // ...
  console.log('全局后置钩子__触发了......');
});




export default router;
全局前置守卫

你可以使用 router.beforeEach 注册一个全局前置守卫:

const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
  // ...
})

当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中

每个守卫方法接收三个参数:

  • to: Route: 即将要进入的目标 路由对象
  • from: Route: 当前导航正要离开的路由
  • next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
    • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
    • next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
    • next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: truename: 'home' 之类的选项以及任何用在 router-linkto proprouter.push 中的选项。
    • next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

**确保 next 函数在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。**这里有一个在用户未能验证身份时重定向到 /login 的示例:

// BAD
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  // 如果用户未能验证身份,则 `next` 会被调用两次
  next()
})
// GOOD
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  else next()
})
全局解析守卫

2.5.0 新增

在 2.5.0+ 你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。

全局后置钩子

你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:

router.afterEach((to, from) => {
  // ...
})
路由独享的守卫

你可以在路由配置上直接定义 beforeEnter 守卫:

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})

这些守卫与全局前置守卫的方法参数是一样的。

组件内的守卫

最后,你可以在路由组件内直接定义以下路由导航守卫:

  • beforeRouteEnter
  • beforeRouteUpdate (2.2 新增)
  • beforeRouteLeave
const Foo = {
  template: `...`,
  beforeRouteEnter (to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
  },
  beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }
}

beforeRouteEnter 守卫 不能 访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。

不过,你可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。

beforeRouteEnter (to, from, next) {
  next(vm => {
    // 通过 `vm` 访问组件实例
  })
}

注意 beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdatebeforeRouteLeave 来说,this 已经可用了,所以不支持传递回调,因为没有必要了。

beforeRouteUpdate (to, from, next) {
  // just use `this`
  this.name = to.params.name
  next()
}

这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消。

beforeRouteLeave (to, from, next) {
  const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
  if (answer) {
    next()
  } else {
    next(false)
  }
}

8.vuex

看文档看文档看文档

vuex

什么是vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式**。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。**

vuex的使用

1.将仓库引入主入口文件main.js中

import Vue from "vue";
import App from "./App";
import router from "./router";
import store from "./store";

new Vue({
  el: "#app",
  render: (h) => h(App),
  router,
  store,
});

2.在仓库中配置 store/index.js

import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);

// state是全局状态
const state = {
  name: "zhangsan",
  age: 10,
  birthYear: 1999,
  list: [1, 2, 3, 4, 5, 6, 7]
};

// 全局的计算属性
const getters = {
  total(state, getters){
    let tmp = 0;
    state.list.forEach(item=>(tmp += item));
    return tmp;
  },
  type(state, getters){
    if(getters.total % 2 === 1){
      return 'odd';
    }else{
      return 'even';
    }
  }
};  

// mutations是全局唯一修改state的途径
const mutations = {
  modify(state, value) {
    console.log("modify执行了....");
    state.age = value;
  },
  modifyName(state, value){
    state.name = value;
  },
  modifyBirthYear(state, value){
    state.birthYear = value;
  }
};

// Action 提交的是 mutation,而不是直接变更状态。
// Action 可以包含任意异步操作。
const actions = {
  timeOut(context, value){
    console.log('timeOut执行了...');
    setTimeout(() => {
      context.commit('modifyBirthYear', value);
    }, 1000);
  }
};

const store = new Vuex.Store({
  state,

  getters,

  mutations,

  actions
});

console.log(store);

export default store;

3.就可以在组件中对数据进行操作了

State

store/index.js

import Vuex from "vuex";
Vue.use(Vuex);
const store = new Vuex.Store({
  state:{
    name: "zhangsan",
  }
});
export default store;

组件中调用

<script>
	this.$store.state.name
</script>
<template>
	// 在组件template中可以省略this
	{{store.state.name}}
</template>
mapState辅助函数

mapState([‘name’])属性名称name固定不可变
可以使用mapState[{homeName:‘name’}] 更改名字

// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'

export default {
  // ...
  computed: mapState({
    // 箭头函数可使代码更简练
    count: state => state.count,

    // 传字符串参数 'count' 等同于 `state => state.count`
    countAlias: 'count',

    // 为了能够使用 `this` 获取局部状态,必须使用常规函数
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}

展开运算符

computed: {
	...mapState([
      // 映射 this.count 为 store.state.count
      'count'
	])
}

不要直接在组件中修改state数据 应该在mutations对象中对state中的数据进行修改

mutations对象中的方法中的形参必须有 该形参就是state对象 通过形参来操作state中的数据

mutations中不能有异步请求操作 即使视图页面正常变化 如果在控制台使用状态管理工具就能发现数据不同步

mutations

mutations是全局唯一修改state的途径

store/index.js

mutations:{
    modify(state, value) {
      state.age = value;
    }
  }

组件中触发

this.$store.commit('modify', 12);
mapMutations辅助函数
<button @click="modifyAge(13)">按钮2</button>
<button @click="modifyName('李四')">修改名字</button> 
methods: {
    // 将触发全局mutations中的modify方法,转为组件内部的modify方法
    ...mapMutations(['modify', 'modifyName']),
    ...mapMutations({
      modifyAge: 'modify',
      modifyName: 'modifyName',
    })
  }
}

mutations中不能有异步请求操作 在actions中进行异步操作

actions

Action 提交的是 mutation,而不是直接变更状态

Action 可以包含任意异步操作

actions:{
  timeOut(context, value){
    console.log('timeOut执行了...');
    setTimeout(() => {
      context.commit('modifyBirthYear', value);
    }, 1000);
  }
}

组件中触发

this.$store.dispatch('timeOut', 2001);
mapActions辅助函数
<template>
<div id="setting">
  <h1>设置</h1>
  <button @click="btnAction">按钮</button>
  <button @click="timeOut(2005)">按钮</button>
</div>
</template>

<script>
import {mapActions} from 'vuex'
export default {
  methods: {
    // 将触发全局actions中的timeOut方法,转为组件内部的timeOut方法
    ...mapActions(['timeOut']),
    ...mapActions({
      timeOut: 'timeOut'
    }),
  }

}
</script>

<style>

</style>
getters

类似计算属性 对状态数据进行包装加工

getters:{
  total(state, getters){
    let tmp = 0;
    state.list.forEach(item=>(tmp += item));
    return tmp;
  },
  type(state, getters){
    if(getters.total % 2 === 1){
      return 'odd';
    }else{
      return 'even';
    }
  }
};
<template>
<div id="mine">
  <h1>我的</h1>
</div>
</template>

<script>
import {mapGetters} from 'vuex'
export default {
  computed: {
    ...mapGetters(['total', 'type']),
    ...mapGetters({
      count: 'total',
      flag: 'type'
    })
  },
  mounted(){
    console.log(this.$store.getters.total);
    console.log(this.$store.getters.type);
    console.log(this.total);
    console.log(this.type);
  }
}
</script>

<style>

</style>
Module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

store/module/user.js

命名空间

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。

如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名

export default {
  // 命名空间
  namespaced: true,
  state: {
    name: 'zhangsan'
  },
  getters: {
    num(state, getters, rootState, rootGetters){
      return 3;
    }
  },
  mutations: {
    modify(state, value){
      console.log('user的modify执行了....');
    }
  },
  actions: {
    timeout(context, value){
      console.log('user的timeout执行了....');

      setTimeout(() => {
        //....
      }, 1000);
    }
  },
}

store/index.js

import Vue from "vue";
import Vuex from "vuex";
import cart from './modules/cart'
import user from './modules/user'
Vue.use(Vuex);



const store = new Vuex.Store({
  strict: true,
  state: {},
  getters: {
    num(state, getters){
      return 1;
    }
  },
  mutations: {
    modify(){
      console.log('root的modify执行了....');
    }
  },
  actions: {},
  
  modules: {
    user,
    cart
  }
});

console.log(store);

export default store;

组件中触发对应的状态

<template>
  <div id="home">
    <h1>首页</h1>

  </div>
</template>

<script>
import {mapState, mapGetters, mapMutations, mapActions} from 'vuex'
export default {
  computed: {
    ...mapState({
      username: state=>state.user.name
    }),
    ...mapGetters({
      num: 'user/num'
    }),
  },
  methods: {
    ...mapMutations({
      modify: 'user/modify'
    }),
    ...mapActions({
      timeout: 'user/timeout'
    })
  },
  mounted(){
    // state
    console.log(this.$store.state.user.name);
    console.log(this.username);

    // getters
    console.log(this.$store.getters['user/num']);
    console.log(this.num);

    // mutations
    this.$store.commit('user/modify', 'test');
    this.modify();

    // actions
    this.$store.dispatch('user/timeout', 'value');

    this.timeout();

  }
};
</script>

<style>
</style>
严格模式

状态不是通过mutation 函数修改的就会报错 例如直接通过this.$store.state.xx=xx

开启严格模式,仅需在创建 store 的时候传入 strict: true

const store = new Vuex.Store({
  // ...
  strict: true
})

在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。

开发环境与发布环境

不要在发布环境下启用严格模式!严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。

类似于插件,我们可以让构建工具来处理这种情况:

const store = new Vuex.Store({
  // ...
  strict: process.env.NODE_ENV !== 'production'
})
表单处理

input表单处理v-model问题 把v-model拆开:value和@input input触发的函数通过this.$store.commit()去触发mutations中的方法
这样就解决了input双向绑定直接修改state数据的问题

<input v-model="message">
<input :value="message" @input="updateMessage">
computed: {
  ...mapState({
    message: state => state.message
  })
},
methods: {
  updateMessage (e) {
    this.$store.commit('updateMessage', e.target.value)
  }
}

下面是 mutation 函数:

// ...
mutations: {
  updateMessage (state, message) {
    state.message = message
  }
}
双向绑定的计算属性

必须承认,这样做比简单地使用“v-model + 局部状态”要啰嗦得多,并且也损失了一些 v-model 中很有用的特性。另一个方法是使用带有 setter 的双向绑定计算属性:

<input v-model="message">
// ...
computed: {
  message: {
    get () {
      return this.$store.state.obj.message
    },
    set (value) {
      this.$store.commit('updateMessage', value)
    }
  }
}

混入

mixins

mixins/test.js

export default {
  data(){
    return {
      value: 100
    }
  },
  methods: {
    btnAction(){
      alert(1);
    }
  },
  mounted(){
    console.log('mixin 的 mounted执行了....');
  }
}

components/xx.vue

<template>
<div class="box" id="one">
  <h1>one</h1>
  <p>{{value}}</p>
  <input type="number" v-model="value"/>
  <button @click="btnAction">按钮</button>
</div>
</template>

<script>
import testMixin from '../mixins/test'
import helloMixin from '../mixins/hello'
export default {
  mixins: [
    testMixin,
    helloMixin
  ],
  data(){
    return {
      value: 200
    }
  },
  methods: {
    btnAction(){
      alert(2);
    }
  },
  mounted(){
    console.log('one 的 mounted执行了....');
    console.log(this.$store);

    console.log(this.$user);
  }
}
</script>

<style>

</style>
export default {
  mixins: [
    testMixin,
    helloMixin
  ]
}

组件中的data 和混入mixins中的data相同时 会合并为一个 并且使用的是mixins中的data 如果是钩子函数 按照传入顺序依次调用,并在调用组件自身的钩子之前被调用

var mixin = {
  created: function () { console.log(1) }
}
var vm = new Vue({
  created: function () { console.log(2) },
  mixins: [mixin]
})
// => 1
// => 2

指令

自定义全局指令
import Vue from "vue";
import App from "./App";

// 自定义全局指令
Vue.directive("my-text", (el, {value}) => {
  console.log("my-text指令触发了...");
  el.innerText = value;
});

Vue.directive('my-show', (el, {value})=>{
  el.style.display = value ? '' : 'none';
});

new Vue({
  el: "#app",
  render: (h) => h(App),
});

在组件中使用

<h1 v-my-show="false">app</h1>
<p v-my-text="1+2+3+4"></p>
<p v-my-text="message"></p>
自定义局部指令

实现拖拽效果 设置修饰符指定方向可以移动

<template>
<div id="app">
  <h1 v-my-show="false">app</h1>
  <p v-my-text="1+2+3+4"></p>
  <p v-my-text="message"></p>

  <div class="box" v-drag>1</div>
  <div class="box" v-drag.x>2</div>
  <div class="box" v-drag.y>3</div>
  <div class="box" v-drag.x.y>3</div>
</div>
</template>

<script>
export default {
  // 局部指令
  directives: {
    drag(el, info){
      let {x, y} = info.modifiers;
      if(!x && !y){
        x = y = true;
      }
      console.log('x:', x, 'y:', y);

      let tmpX = 0;
      let tmpY = 0;
      let offsetX = 0;
      let offsetY = 0;
      el.onmousedown = (ev)=>{
        console.log("开始拖拽...");
        const startX = ev.clientX - tmpX;
        const startY = ev.clientY - tmpY;
        el.style.position = 'relative';
        document.onmousemove = (ev) => {
          console.log("正在拖拽...");
          offsetX = ev.clientX - startX;
          offsetY = ev.clientY - startY;
          x && (el.style.left = offsetX + 'px');
          y && (el.style.top = offsetY + 'px');

        }
        document.onmouseup = ()=>{
          console.log("结束拖拽...");
          document.onmousemove = null;
          tmpX = offsetX;
          tmpY = offsetY;
        }
      }
    },
  },
  data(){
    return {
      message: 'hello world'
    }
  }
}
</script>

<style>
.box{
  width: 100px;
  height: 100px;
  background: rebeccapurple;
  margin: 10px;
}
</style>

过滤器

只有说花括号{{}}和v-bind才可以使用过滤器 其他都不行

自定义全局过滤器
import Vue from "vue";
import App from "./App";

// 自定义全局过滤器
Vue.filter('currency', (value, flag = '$', num = 2)=>{
  console.log('currency 过滤器执行了.....');

  if(typeof value === 'number'){
    value = value.toFixed(num);
    return `${flag}${value}`;
  }else{
    throw new Error('currency过滤器只能处理数字类型的数据');
  }
  
});

new Vue({
  el: "#app",
  render: (h) => h(App),
});

自定义局部过滤器
<template>
<div id="app">
  <h1>app</h1>
  <p>
    单价:{{ value | currency('¥', 1) }}
  </p>
  <p>
    <input type="number" v-model="count"/>
  </p>
  <p>
    总价: {{count * value | currency }}
  </p>

  <p>{{message | uppercase}}</p>
  <p>{{message | lowercase}}</p>
</div>
</template>

<script>
export default {
  // 局部过滤器
  filters: {
    uppercase(value){
      if(typeof value === 'string'){
        return value.toUpperCase();
      }else{
        throw new Error('uppercase过滤器只能处理字符串类型的数据');
      }
    },
    lowercase(value){
      if(typeof value === 'string'){
        return value.toLowerCase();
      }else{
        throw new Error('lowercase过滤器只能处理字符串类型的数据');
      }
    }
  },

  data(){
    return {
      value: 129,
      count: 1,
      message: 'Hello World'
    }
  },

  mounted(){
    console.log(this.value);
  }

}
</script>

<style>

</style>

数据请求

server.js

简单的起服务

const express = require('express');

const app = express();

app.use(express.json());
app.use(express.urlencoded({}));


app.get('/api/test', (req, res)=>{
  console.log('get请求触发了');
  console.log(req.query);
  res.json({
    code: 0,
    message: 'get ok'
  })
});

app.post('/api/test', (req, res)=>{
  console.log('post请求触发了');
  console.log(req.body);
  res.json({
    code: 0,
    message: 'post ok'
  })
});

app.listen('9000', (error)=>{
  console.log(error);
})

在vue-cli脚手架项目中发送请求

main.js

get/post
import Vue from "vue";
import App from "./App";
import axios from "axios";

// get请求
axios
  .get(
    //请求的url
    "/api/test",
    //请求配置项
    {
      //get请求参数
      params: { a: 1, b: 2 },
      headers: new Headers({}),
    }
  )
  .then((res) => {
    console.log(res);
    console.log(res.data);
  })
  .catch((error) => {
    console.log(error);
  });

// post请求
axios
  .post(
    //请求的url
    "/api/test",
    // post请求参数
    { a: 3, b: 4 },
    //请求配置项
    {
      headers: new Headers({}),
    }
  )
  .then((res) => {
    console.log(res);
    console.log(res.data);
  })
  .catch((error) => {
    console.log(error);
  });

new Vue({
  el: "#app",
  render: (h) => h(App),
});

在vue.config.js配置代理实现跨域请求

module.exports = {
  // 配置代理
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:9000',
        changeOrigin: true
      }
    }
  }
}

9.高级内容

1.为什么不使用vue文件编写而去使用render函数?
第一:做服务端渲染会用到render函数 不写vue文件用render函数渲染的不是一个dom节点是一个字符串
第二:不确定用户的环境是不是runtime环境
(jsx语法只是为了让render函数更好的实现)

2.函数式组件用来做性能优化,但一般不会用 因为不会把性能优化分这么细

3.写开源项目 开源的框架 比如说ui组件库这样的 在不确定用户环境是什么的情况下 而写的这份代码需要用到用户的任何场景下 那就需要用到vue高级部分内容

写项目需求一般不会用到

render渲染函数&jsx&函数式组件

provide / inject

provide / inject

父组件

export default {
  components:{
    Son
  }
  provide(){
    return {
      message: 'hello world',
      data: this.list
    }
  }
}

子组件

<template>
<div class="two">
  <h1>two</h1>
  <p>{{message}}</p>
  <ul>
    <li v-for="(item, index) in data" :key="index">
      {{item}}
    </li>
  </ul>
  <p>{{value}}</p>
  <p>{{value2}}</p>
</div>
</template>

<script>
export default {
  // 数组形式
  inject: ['message', 'data'],
  // 对象形式
  inject: {
    message: {},
    data: {},
    value: {
      default: 'hi~'
    },
    value2: {
      from: 'message'
    }
  }
}
</script>

递归组件

vue中递归组件的使用

vue组件中那么的作用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值