class和style的使用

对象语法

可以:class=”{ active: isActive }”, 也可以是多个对象共存。

  1. <div
  2. class="static"
  3. v-bind:class="{ active: isActive, 'text-danger': hasError }"
  4. ></div>
  1. data: {
  2. isActive: true,
  3. hasError: false
  4. }

数组语法

把一个数字绑定给:classs=”[cls1, cls2]”
在数组语法中可以使用三元表达式

  1. <div v-bind:class="[activeClass, errorClass]"></div>
  1. data: {
  2. activeClass: 'active',
  3. errorClass: 'text-danger'
  4. }

动态组件 & 异步组件

动态组件

在动态组件上可以使用keep-alive来回切换组件的渲染,并且减少对组件的加载。
组件在 <keep-alive> 内被切换,它的 activateddeactivated 这两个生命周期钩子函数将会被对应执行。
activated:组件实例被激活显示
deactivated:组件实例隐藏,失去激活。

  1. <!-- 失活的组件将会被缓存!-->
  2. <keep-alive>
  3. <component v-bind:is="currentTabComponent"></component>
  4. </keep-alive>

异步组件

大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。

  1. // 局部注册组件
  2. components: {
  3. 'my-component': () => import('./my-async-component')
  4. }
  5. // 异步工厂函数导入异步组件
  6. const AsyncComponent = () => ({
  7. // 需要加载的组件 (应该是一个 `Promise` 对象)
  8. component: import('./MyComponent.vue'),
  9. // 异步组件加载时使用的组件
  10. loading: LoadingComponent,
  11. // 加载失败时使用的组件
  12. error: ErrorComponent,
  13. // 展示加载时组件的延时时间。默认值是 200 (毫秒)
  14. delay: 200,
  15. // 如果提供了超时时间且组件加载也超时了,
  16. // 则使用加载失败时使用的组件。默认值是:`Infinity`
  17. timeout: 3000
  18. })

异步更新数据nextTick

vue中数据更新是异步,更改data后不能利可获取修改后的DOM元素。要想获得更新后的DOM使用nextTick。

  1. // 在vue中数据是异步更新,设置数据后,没法里面取到更新的DOM
  2. this.message = "hello world";
  3. const textContent = document.getElementById("text").textContent;
  4. // 直接获取,不是最新的DOM节点
  5. console.log(textContent === "hello world"); // false
  6. // 必须使用nextTick回调才能取到最新值
  7. this.$nextTick(() => {
  8. const textContent = document.getElementById("text").textContent;
  9. console.warn(textContent === "hello world"); // true
  10. });

数据更新是vue的执行过程
1.触发data.set
2.调用Dep.notify
3.Dep会遍历所有相关的watcher 然后执行update方法

  1. class Watcher{
  2. // 4.执行更新操作
  3. update(){
  4. queueWatcher(this)
  5. }
  6. }
  7. const queue = [];
  8. function queueWatcher(watcher: Watcher) {
  9. // 5. 将当前 Watcher 添加到异步队列
  10. queue.push(watcher);
  11. // 6. 执行异步队列,并传入回调
  12. nextTick(flushSchedulerQueue);
  13. }
  14. // 更新视图的具体方法
  15. function flushSchedulerQueue() {
  16. let watcher
  17. // 排序,先渲染父节点,再渲染子节点
  18. // 这样可以避免不必要的子节点渲染,如:父节点中 v-if 为 false 的子节点,就不用渲染了
  19. queue.sort((a, b) => a.id - b.id);
  20. // 遍历所有 Watcher 进行批量更新。
  21. for (let index = 0; index < queue.length; index++) {
  22. watcher = queue[index];
  23. // 更新 DOM
  24. watcher.run();
  25. }
  26. }

从以上第6步可以看出,把具体的更新方法 flushSchedulerQueue 传给 nextTick 进行调用,接下来分析nextTick方法

  1. const callbacks = [];
  2. //第2至35行,主要判断timerFunc对象的不同环境下的兼容性
  3. let timerFunc;
  4. // 判断是否兼容 Promise
  5. if (typeof Promise !== "undefined") {
  6. timerFunc = () => {
  7. Promise.resolve().then(flushCallbacks);
  8. };
  9. // 判断是否兼容 MutationObserver
  10. // https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver
  11. } else if (typeof MutationObserver !== "undefined") {
  12. let counter = 1;
  13. const observer = new MutationObserver(flushCallbacks);
  14. const textNode = document.createTextNode(String(counter));
  15. observer.observe(textNode, {
  16. characterData: true,
  17. });
  18. timerFunc = () => {
  19. counter = (counter + 1) % 2;
  20. textNode.data = String(counter);
  21. };
  22. // 判断是否兼容 setImmediate
  23. // 该方法存在一些 IE 浏览器中
  24. } else if (typeof setImmediate !== "undefined") {
  25. // 这是一个宏任务,但相比 setTimeout 要更好
  26. timerFunc = () => {
  27. setImmediate(flushCallbacks);
  28. };
  29. } else {
  30. // 如果以上方法都不知道,使用 setTimeout 0
  31. timerFunc = () => {
  32. setTimeout(flushCallbacks, 0);
  33. };
  34. }
  35. // nextTick方法的定义
  36. function nextTick(cb?: Function, ctx?: Object) {
  37. let _resolve;
  38. // 1.将传入的 flushSchedulerQueue 方法添加到回调数组
  39. callbacks.push(() => {
  40. cb.call(ctx);
  41. });
  42. // 2.执行异步任务
  43. // 此方法会根据浏览器兼容性,选用不同的异步策略,在timerFunc内部执行flushCallbacks
  44. timerFunc();
  45. }
  46. // 异步执行完后,执行所有的回调方法,也就是执行 flushSchedulerQueue
  47. function flushCallbacks() {
  48. for (let i = 0; i < callbacks.length; i++) {
  49. callbacks[i]();
  50. }
  51. }

$set更改对象的值

通过下标修改数组的一个元素,必须使用$set来修改,直接修改不起作用

  1. <template>
  2. <div>
  3. <p>{{arr}}</p>
  4. <button @click="update">改变数组</button>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. data(){
  10. return {arr:[1,2,3]}
  11. },
  12. methods:{
  13. update(){
  14. this.$set(this.arr, 0 ,5)
  15. }
  16. }
  17. }
  18. </script>

组件间数据通信

父子组件:props属性传递

  1. // 父组件
  2. <template>
  3. <Son :xing={xing}> </Son>
  4. </template>
  5. <script>
  6. export default {
  7. data(){
  8. return {
  9. xing:"张"
  10. }
  11. }
  12. }
  13. </script>
  14. // 子组件Son中可以接收到props的xing
  15. <script>
  16. export default {
  17. props:['xing'],
  18. mounted(){
  19. console.log("获取父组件传递过来的姓:",xing)
  20. }
  21. }
  22. </script>
  23. //props的对象定义形式
  24. props: {
  25. // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
  26. propA: Number,
  27. // 多个可能的类型
  28. propB: [String, Number],
  29. // 必填的字符串
  30. propC: {
  31. type: String,
  32. required: true
  33. },
  34. // 带有默认值的数字
  35. propD: {
  36. type: Number,
  37. default: 100
  38. },
  39. // 带有默认值的对象
  40. propE: {
  41. type: Object,
  42. // 对象或数组默认值必须从一个工厂函数获取
  43. default: function () {
  44. return { message: 'hello' }
  45. }
  46. },
  47. // 自定义验证函数
  48. propF: {
  49. validator: function (value) {
  50. // 这个值必须匹配下列字符串中的一个
  51. return ['success', 'warning', 'danger'].indexOf(value) !== -1
  52. }
  53. }

slot插槽传值

基本使用

定义一个包含插槽slot的组件navigation-link

  1. <template>
  2. <a v-bind:href="url" class="nav-link">
  3. <slot></slot>
  4. </a>
  5. </template>

当使用组件时slot会被替换为组件标签内定义的内容。插槽内可以包含任何模板代码。

  1. <navigation-link url="/profile">
  2. <!-- 添加一个 Font Awesome 图标 -->
  3. <span class="fa fa-user"></span>
  4. Your Profile
  5. </navigation-link>
  6. // 或者
  7. <navigation-link url="/profile">
  8. <!-- 添加一个图标的组件 -->
  9. <font-awesome-icon name="user"></font-awesome-icon>
  10. Your Profile
  11. </navigation-link>

使用navigation-link组件时,只能访问当前组件中的数据。
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。

具名插槽

定义base-layout模板,主要使用slot的name属性

  1. <div class="container">
  2. <header>
  3. <slot name="header"></slot>
  4. </header>
  5. <main>
  6. <slot></slot>
  7. </main>
  8. <footer>
  9. <slot name="footer"></slot>
  10. </footer>
  11. </div>

使用base-layout组件

  1. <base-layout>
  2. <template v-slot:header>
  3. <h1>Here might be a page title</h1>
  4. </template>
  5. <p>A paragraph for the main content.</p>
  6. <p>And another one.</p>
  7. <template v-slot:footer>
  8. <p>Here's some contact info</p>
  9. </template>
  10. </base-layout>

作用域插槽

使用v-slot绑定数据
定义一个current-user组件模板,并添加slot属性标签

  1. <span>
  2. <slot v-bind:user="user">
  3. {{ user.lastName }}
  4. </slot>
  5. </span>

有时候让插槽的内容能够访问到标签组件里的数据很有用,可能想替换掉子模板中备用内容。绑定在元素上的attribute被称为插槽prop。现在在父级作用域中v-slot来定义提供的插槽prop的数据

  1. <current-user>
  2. <template v-slot:default="slotProps">
  3. {{ slotProps.user.firstName }}
  4. </template>
  5. <template v-slot:other="otherSlotProps">
  6. ...
  7. </template>
  8. </current-user>

如果只用一个插槽出现,可以使用简单的语法

  1. <current-user v-slot="slotProps">
  2. {{ slotProps.user.firstName }}
  3. </current-user>

注意:如果出现多个slot插槽,必须使用上一种完整语法。

子父组件:this.$emit触发事件更新父组件

vue只允许数据的单项数据传递,这时候我们可以通过自定义事件,触发事件来通知父组件来改变数据,从而达到子组件改变父组件数据
子组件:child

  1. <template>
  2. <div @click="up">
  3. {{msg}}
  4. </div>
  5. </template>
  6. <script>
  7. export default{
  8. data(){
  9. return:{}
  10. },
  11. props:["msg"],
  12. methods:{
  13. up(){
  14. // 子组件的$emit方法,更改父组件中的msg数据
  15. this.$emit("upToPar","child event")
  16. }
  17. }
  18. }
  19. </script>

父组件

  1. <template>
  2. <div>
  3. <child @upToPar="change" :msg="msg"></child> //监听子组件触发的upup事件,然后调用change方法
  4. </div>
  5. </tempalte>
  6. <script>
  7. export default{
  8. data(){
  9. return:{
  10. msg:"this is default message;"
  11. }
  12. },
  13. methods:{
  14. change(msg){ //参数msg是在子组件的$emit函数中的第二个参数
  15. this.msg = msg //this.msg是data中的msg
  16. }
  17. }
  18. }
  19. </script>

非父子间组件:EventBus

EventBus通过在main中新建一个公用的Hub对象(Vue的实例)

在main.js中创建

  1. export let Hub = new Vue(); //创建事件中心

组件comA触发

  1. <div @click="changeHub">click Hub event</div>
  2. <script>
  3. import {Hub} from "../main.js"
  4. export default{
  5. methods: {
  6. changeHub() {
  7. Hub.$emit('eventName','hehe'); //Hub触发事件
  8. }
  9. }
  10. }
  11. </script>

在组件comB接收

  1. import {Hub} from "../main.js"
  2. created() {
  3. Hub.$on('eventName', () => { //Hub接收事件
  4. console.log("this is Hub $on message,count follow child Component number")
  5. });
  6. },
  7. beforeDestory(){
  8. Hub.$off('eventName')
  9. }

多层级父子关系provide-inject

App.vue

  1. <Root>
  2. <Father>
  3. <Son>
  4. <Grandson></Grandson>
  5. </Son>
  6. </Father>
  7. </Root>

定义个root根组件

  1. <template>
  2. <div class="root">
  3. 这是root根组件
  4. <p>这是root组件中的rootA{{rootA}}</p>
  5. <slot></slot>
  6. </div>
  7. </template>
  8. <script>
  9. import { setInterval } from 'timers';
  10. export default {
  11. data () {
  12. return {
  13. rootA:"rootA",
  14. };
  15. },
  16. provide(){
  17. return{
  18. rootA:this.rootA
  19. }
  20. },
  21. methods: {
  22. rootFun(){
  23. console.log("rootA",this.rootA);
  24. }
  25. }
  26. }
  27. </script>

父组件Father

  1. <template>
  2. <div class="father">
  3. 这里是father父组件
  4. <p>这是从root组件的provide中获取的数据rootA:{{rootA}}</p>
  5. <b>{{$parent.rootA}}</b>
  6. <slot></slot>
  7. </div>
  8. </template>
  9. <script>
  10. export default {
  11. inject:["rootA"],
  12. }
  13. </script>

子组件Son

  1. <template>
  2. <div class="son">
  3. 这里是son儿组件
  4. <p>这是从root组件的provide中获取的数据rootA:{{rootA}}</p>
  5. <slot></slot>
  6. </div>
  7. </template>
  8. <script>
  9. export default {
  10. inject:["rootA"]
  11. }
  12. </script>

孙组件grandSon

  1. <template>
  2. <div class="grandson">
  3. 这里是grandson孙组件
  4. <p>这是从root组件的provide中获取的数据rootA:{{rootA}}</p>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. inject:["rootA"]
  10. }
  11. </script>

复杂组件间传值:vuex

具体使用及案例参考

事件

事件修饰符

  • stop:阻止事件冒泡传播
  • prevent: v-on:submit.prevent , 提交事件时不重载页面
  • capture:添加事件监听器时使用事件捕获模式
  • self:只当在event.target是当前元素是自身时,触发函数。事件不是从内部元素触发
  • once:点击事件只会触发一次

    按键修饰符

    1. <input v-on:keyup.enter="submit"> // 点击enter进行提交
    2. <input v-on:keyup.page-down="onPageDown"> //点击pagedown按键松开时触发事件

过滤器

注册组件内过滤器

Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 **v-bind** 表达式

  1. <!-- 在双花括号中 -->
  2. {{ message | capitalize }}
  3. <!-- 在 `v-bind` 中 -->
  4. <div v-bind:id="rawId | capitalize"></div>
  5. //对date日期定义过滤器格式化
  6. {{ dateStr | formatDate}}

可以在组件的script中定义

  1. filters: {
  2. capitalize: function (value) {
  3. if (!value) return ''
  4. value = value.toString()
  5. return value.charAt(0).toUpperCase() + value.slice(1)
  6. },
  7. formatDate(value) {
  8. return format(value, "yyyy-MM-DD HH:mm:ss");
  9. }
  10. }

注册全局过滤器

有些过滤器使用的很频繁,比如上面提到的日期过滤器,在很多地方都要使用,这时候如果在每一个要用到的组件里面都去定义一遍,就显得有些多余了,这时候就可以考虑Vue.filter注册全局过滤器
对于全局过滤器,一般建议在项目里面添加filters目录,然后在filters目录里面添加

  1. // filters\index.js
  2. import Vue from 'vue'
  3. import { format } from '@/utils/date'
  4. Vue.filter('formatDate', value => {
  5. return format(value, 'yyyy-MM-DD HH:mm:ss')
  6. })

使用的时候将该filters/index.js引入到main.js中

vuex的使用

详细的使用过程参考

vue-router的使用

基本使用

vue单页面开发,vue-router在前端进行页面逻辑的跳转。

路由的两种模式:hash和history

  1. hash —— 地址栏 URL 中有 # 符号(此 hash 不是密码学里的散列运算)。
    比如URL:http://www.abc.com/#/hello,hash 的值为 #/hello。它的特点在于:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。使用 window.location.hash 读取 # 值。这个属性可读可写。读取时,可以用来判断网页状态是否改变;每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用”后退”按钮,可以回到上个位置。
  2. history —— 利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。(需要特定浏览器支持)
    这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。

hash 和 history的使用场景
一般场景下,hash 和 history 都可以,除非你更在意颜值, # 符号夹杂在 URL 里看起来确实有些丑陋。
history 的优点
另外,根据 Mozilla Develop Network 的介绍,调用 history.pushState() 相比于直接修改 hash,存在以下优势:

  • pushState() 设置的新 URL 可以是与当前 URL 同源的任意 URL;而 hash 只可修改 # 后面的部分,因此只能设置与当前 URL 同文档的 URL;
  • pushState() 设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中;而 hash 设置的新值必须与原来不一样才会触发动作将记录添加到栈中;
  • pushState() 通过 stateObject 参数可以添加任意类型的数据到记录中;而 hash 只可添加短字符串;
  • pushState() 可额外设置 title 属性供后续使用。

history 的缺陷
SPA 虽然在浏览器里游刃有余,但真要通过 URL 向后端发起 HTTP 请求时,两者的差异就来了。尤其在用户手动输入 URL 后回车,或者刷新(重启)浏览器的时候。

  1. hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.abc.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。
  2. history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.abc.com/book/id。如果后端缺少对 /book/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回一个 index.html 页面,这个页面就是你 app 依赖的页面。”

在页面中是router-link进行导航,使用router-view进行相应组件的展示

  1. <router-link to="/foo">Go to Foo</router-link>
  2. <router-link to="/bar">Go to Bar</router-link>
  3. <!-- 路由匹配到的组件将渲染在这里 -->
  4. <router-view></router-view>

router和route的区别

  1. router:表示整个项目下的路由器对象,有push方法,进行路由跳转
  2. route:表示当前页面的组件路由信息,可以获取当前页面对象的name、path、query、params等。

    子路由嵌套

    比如新闻标签下存在热门新闻、最新新闻
    1. <ul>
    2. <li><router-link to="/">home</router-link></li>
    3. <li><router-link to="/news">news</router-link></li>
    4. <ol>
    5. <li><router-link to="/news/hot">hotNews</router-link></li>
    6. <li><router-link to="/news/latest">latestNews</router-link></li>
    7. </ol>
    8. <li><router-link to="/info">info</router-link></li>
    9. </ul>
    10. <router-view><router-view>
    创建router对象
    1. export default new VueRouter({
    2. mode: 'history',
    3. base : __dirname,
    4. routes:[
    5. {path:'/', component:Home},
    6. {path:'/news', component:Child,
    7. //在子路由的children中不能设置/,否则会被当做跟路由渲染
    8. children: [
    9. {path: ' ', component:News},
    10. {path: 'hot', component:Hot},
    11. {path: 'latest', component:Latest}
    12. ]},
    13. {path:'/info', component:Info}
    14. ]
    15. })

    路由传参

    name和params配合传参

    在template模板中,通过:to绑定对象传参
    1. <ol> <!--通过设置:to,可以在这里面进行参数设置并传递到子页面-->
    2. <li><router-link :to="{name:'HotNews',params:{ num :3}}">hotNews</li>
    3. <li><router-link :to="{name:'LatestNews',params:{ num :5}}">latestNews</li>
    4. </ol>
    必须在router对象中设置对应的name属性
    1. export default new VueRouter({
    2. mode: 'history',
    3. base : __dirname,
    4. routes:[
    5. {path:'/',name:'Home', component:Home},
    6. {path:'/news', component:Child,
    7. children: [ <!--这些name参数值,就可以显示在App.vue文件中-->
    8. {path: '/', name:'News', component:News},
    9. {path: 'hot', name:'HotNews',component:Hot},
    10. {path: 'latest', name:'LatestNews', component:Latest}
    11. ]},
    12. {path:'/info', name:'Info', component:Info}
    13. ]
    14. })
    然后就可以在页面中读取到数据
    1. <template>
    2. <div>
    3. <h2>子路由+通过绑定:to传递给子页面的参数{{$route.params.num}}</h2> <!--{{ $route.params.num就可接收到在App.vueto中设置的参数}}-->
    4. <router-view></router-view>
    5. </div>
    6. </template>

    path和query配合传参

    取得路径url中query的值,可以配合path使用
    页面模板中定义path和query的值
    1. <li><router-link :to="{path:'/users/小明',query:{aaa:'bbb'}}">xiaoming</router-link></li>
    新建router对象
    1. {
    2. path:"/users/:username",
    3. name:"users",
    4. component: ()=> import(/* webpackChunkName: "user" */ '../views/User.vue')
    5. }
    在子页面文件可以读取到参数数据
    1. <p>{{$route.params.username}}+{{$route.query.aaa}}</p> //显示 小明+bbb
    path后的参数使用params查询,query的参数用query获取

    重命名和alias别名

    在定义的router对象中
    1. {path: 'third', redirect:'News'} //直接重定向到News组件
    alias别名可以给一个组件页面定义多个名称
    1. // router.js
    2. {
    3. path: '/info',
    4. name: 'Info',
    5. components: {
    6. default: Info,
    7. left: Hot,
    8. right: Latest
    9. },
    10. alias: ['/xiaoxi', '/xinxi']
    11. }
    1. // App.vue
    2. <li><router-link to="xiaoxi">xiaoxi</router-link></li>
    3. <li><router-link to="xinxi">xinxi</router-link></li>

    路由守卫

    全局守卫

    router.beforeEach是全局的路由守卫,所有路由访问必经此方法
    1. const router = new VueRouter({ ... })
    2. router.beforeEach((to, from, next) => {
    3. // to: Route: 即将要进入的目标 路由对象
    4. // from: Route: 当前导航正要离开的路由
    5. // next: Function: 一定要调用该方法来 resolve 这个钩子。
    6. })

    路由router配置信息中设置独享守卫

    1. const router = new VueRouter({
    2. routes: [
    3. {
    4. path: '/foo',
    5. component: Foo,
    6. beforeEnter: (to, from, next) => {
    7. // to: Route: 即将要进入的目标 路由对象
    8. // from: Route: 当前导航正要离开的路由
    9. // next: Function: 一定要调用该方法来 resolve 这个钩子。
    10. }
    11. }
    12. ]
    13. })

    组件内守卫

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

    在路由守卫时做权限校验和设置title

    权限校验,配置接口请求的返回值和路由守卫
    1. router.beforeEach((to, from, next) => {
    2. if(to.path === 'b页面路径'){
    3. axios({
    4. method:'post',
    5. url:'http://xxx.request.com.cn/login',
    6. data:{}
    7. })
    8. .then(response => {
    9. if(response.data.success){
    10. if(response.data.data.elecAccStatus != 2){
    11. next(false);
    12. }else{
    13. /* 从其他页面到b页面没问题走这里能进入 */
    14. next();
    15. }
    16. }else{
    17. /*b页面二次刷新接口不通走这里 */
    18. next(false);
    19. }
    20. })
    21. .catch(error => {
    22. next(false);
    23. })
    24. }
    25. })

在router.js的路由配置中设置meta:{title:”index”},然后在router对象的beforeEach守卫做设置

  1. let router = new Router({
  2. routes: [
  3. {
  4. path: "/index",
  5. name: "index",
  6. component: () =>
  7. import(/* webpackChunkName: "index" */ "@/views/common/index.vue"),
  8. meta:{
  9. title:'index'
  10. }
  11. },
  12. ]
  13. });
  14. router.beforeEach((to, from, next) => {
  15. if (to.meta.title) {
  16. document.title = to.meta.title
  17. } else {
  18. document.title = "other"
  19. }
  20. next();
  21. });

自定义指令

除了默认设置的核心指令( v-model 和 v-show ), Vue 也允许注册自定义指令。下面我们注册一个全局指令 v-focus, 该指令的功能是在页面加载时,元素获得焦点:

  1. <template>
  2. <div>
  3. <input value="自动获取焦点" v-focus />
  4. </div>
  5. </template>
  6. <script>
  7. // 注册一个全局自定义指令 v-focus
  8. Vue.directive('focus', {
  9. // bind钩子函数,只调用一次,指令第一次绑定到元素时调用
  10. bind(){ console.log("bind 钩子") },
  11. // inserted: 被绑定元素插入父节点时调用
  12. inserted(el){
  13. el.focus()
  14. }
  15. })
  16. // 注册一个数的平方指令
  17. Vue.directive("n",{
  18. bind(el,binding){
  19. el.textContent = Math.pow(binding.value, 2)
  20. },
  21. update(el,binding){
  22. el.textContent = Math.pow(binding.value, 2)
  23. }
  24. })
  25. </script>

指令定义函数提供了几个钩子函数(可选):

  • bind: 只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。
  • inserted: 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。
  • update: 被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新(详细的钩子函数参数见下)。
  • componentUpdated: 被绑定元素所在模板完成一次更新周期时调用。
  • unbind: 只调用一次, 指令与元素解绑时调用。

钩子函数的参数有:

  • el: 指令所绑定的元素,可以用来直接操作 DOM 。
  • binding: 一个对象,包含以下属性:
    • name: 指令名,不包括 v- 前缀。
    • value: 指令的绑定值, 例如: v-my-directive="1 + 1", value 的值是 2
    • oldValue: 指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。
    • expression: 绑定值的表达式或变量名。 例如 v-my-directive="1 + 1" , expression 的值是 "1 + 1"
    • arg: 传给指令的参数。例如 v-my-directive:foo, arg 的值是 "foo"
    • modifiers: 一个包含修饰符的对象。 例如: v-my-directive.foo.bar, 修饰符对象 modifiers 的值是 { foo: true, bar: true }
  • vnode: Vue 编译生成的虚拟节点。
  • oldVnode: 上一个虚拟节点,仅在 updatecomponentUpdated 钩子中可用。

typescript的支持

引入并定义的对ts支持的组件类需要的对象

  1. import { Component, Vue, Prop, Watch } from "vue-property-decorator";
  2. import { Route } from "vue-router";
  3. class tsCom extends Vue{ ... }
  4. export default tsCom;

@Component属性

引入外部组件、定义过滤器方法、父组件属性的传值都写在该类中

  1. @Component({
  2. // 组件注册
  3. components: {
  4. 'another-vue': AnotherVue
  5. },
  6. // 过滤器
  7. filters: {
  8. filterNumberToString(value: Number) {
  9. // 对数字类型进行千分位格式化
  10. return Number(value).toLocaleString();
  11. }
  12. },
  13. // 属性传递
  14. props: {
  15. hideHeader: {
  16. type: Boolean,
  17. required: false,
  18. default: false // 默认属性的默认值
  19. }
  20. }
  21. })

在组件内定义数据,类似于data的数据

  1. @Prop({
  2. type: Boolean,
  3. required: false,
  4. default: false // 默认属性的默认值
  5. })
  6. //只有组件内部使用
  7. private hideHeader!: boolean | undefined;
  8. // 继承该组件的子组件可以使用
  9. protected userList?: string[] = ["a", "b", "c"]; // 其他没有默认值的传值
  10. public oneKeyObj: Object = { name: "key", age: 1 };
  11. selfKey: string = "自己的一个变量";

生命周期钩子

  1. created() {console.log("created 创建阶段")}
  2. mounted() {
  3. console.log("mounted 挂载阶段");
  4. }
  5. updated(){}
  6. beforeDestory(){}

计算属性computed

  1. get computedKey() {
  2. return this.userList.length;
  3. }

监听器watch

  1. // 监听器,监听计算数据的变化
  2. @Watch("computedKey")
  3. getcomputedKey(newVal: any) {
  4. console.log("computedKey.length newVal", newVal);
  5. }
  6. // 监听路由变化, immediate表示立即执行一次,deep表示对对象深度监听。
  7. @Watch("$route", { immediate: true, deep:true })
  8. private changeRouter(route: Route) {
  9. console.log("监听路由route对象变化", route);
  10. }
  11. // 导航守卫函数
  12. beforeRouteEnter(to: Route, from: Route, next: () => void): void {
  13. console.log("beforeRouteEnter", to, from);
  14. next();
  15. }
  16. beforeRouteLeave(to: Route, from: Route, next: () => void): void {
  17. console.log("beforeRouteLeave", to, from);
  18. next();
  19. }

方法的定义

  1. addText() {
  2. this.selfKey += ",追加文字!";
  3. }
  4. nowDate(){
  5. console.log(Date.now())
  6. }
  7. // 子组件向父组件发送事件
  8. @Emit()
  9. private sendMsg():string{
  10. console.log("sendmsg");
  11. msg = "father"
  12. // 事件的返回值为传递的参数
  13. return "this is send to father message"
  14. }

Emit()转换的js是将事件名称用-连接的事件方法

  1. send-msg(){
  2. this.$emit("send-msg");
  3. console.log("sendmsg");
  4. msg = "father"
  5. // 事件的返回值为传递的参数
  6. return "this is send to father message"
  7. }

vue2数据绑定源码分析

vue2使用Object.defineProperty方法把data对象的全部属性转化成getter/setter,当属性被访问或修改时通知变化。

将数据变成可以响应式

  1. const students = {
  2. name:"北鸟南游",
  3. age:16
  4. }
  5. // 创建响应式函数
  6. function defineReactive(obj, key, val){
  7. Object.defineProperty(obj, key, {
  8. get(){
  9. console.log(`读取了${key}的值为${val}`);
  10. //源码中是下面一句:
  11. // if (Dep.target) { dep.depend(); } 作用进行依赖收集
  12. return val
  13. },
  14. set(newVal){
  15. console.log(`设置了${key}的值为${newVal}`);
  16. val = newVal
  17. // 源码中是下面一句,对dep进行派发更新
  18. // dep.notify();
  19. }
  20. })
  21. }
  22. // 将对象的每一个属性都转为可观察的属性
  23. function observable (obj) {
  24. const keys = Object.keys(obj);
  25. keys.forEach((key) => {
  26. defineReactive(obj, key, obj[key])
  27. })
  28. // 一定必须要把这个对象返回出去
  29. return obj
  30. }
  31. students = observable(students)

此时在控制台访问student的name会打印get读取的信息,设置age信息,会打印set的设置信息。

设置监听器watcher

Vue源码中Watcher类的简化版

  1. class Watcher {
  2. constructor(vm: Component, expOrFn: string | Function) {
  3. // 将 vm._render 方法赋值给 getter。
  4. // 这里的 expOrFn 其实就是 vm._render,后文会讲到。
  5. this.getter = expOrFn;
  6. this.value = this.get();
  7. }
  8. get() {
  9. // 给 Dep.target 赋值为当前 Watcher 对象
  10. Dep.target = this;
  11. // this.getter 其实就是 vm._render,this.getter的执行就是Watcher的更新
  12. // vm._render 用来生成虚拟 dom、执行 dom-diff、更新真实 dom。
  13. const value = this.getter.call(this.vm, this.vm);
  14. return value;
  15. }
  16. addDep(dep: Dep) {
  17. // 将当前的 Watcher 添加到 Dep 收集池中
  18. dep.addSub(this);
  19. }
  20. update() {
  21. // 开启异步队列,批量更新 Watcher
  22. queueWatcher(this);
  23. }
  24. run() {
  25. // 和初始化一样,会调用 get 方法,更新视图
  26. const value = this.get();
  27. }
  28. }

Watcher存在的意义:响应式数据value发生了变化,希望Watcher能够触发视图更新,将响应式数据的key与Watcher建立相对应关系。下面实验一个简化版的Watcher

  1. // obj和key相当于vm实例,cb相当于expOrFn,数据变化后进行的更新变化
  2. function watcher(obj, key, cb) {
  3. const onComputedUpdate = (val) => {
  4. console.log(`我是${val}学生`)
  5. }
  6. Object.defineProperty(obj, key, {
  7. get() {
  8. let val = cb()
  9. onComputedUpdate(val)
  10. return val
  11. },
  12. set() {
  13. console.error("观察者属性不能被赋值")
  14. }
  15. })
  16. }
  17. let student = {
  18. name: '北鸟南游',
  19. age: 16
  20. }
  21. watcher(student, 'level', () => {
  22. return student.age > 16 ? "大" : '小'
  23. })
  24. console.log(student.level);
  25. student.age = 18
  26. console.log(student.level);

运行结果:伴随着age数据的变化,level会有不同结果值渲染。
我是小学生

我是大学生

添加依赖收集

现在已存在了响应式数据observable和观察者watcher的函数,接下来是怎么把两者建立联系。假如说能够在响应式对象的getter/setter里能够执行监听器watcher的
onComponentUpdate()方法,就可以实现让对象主动出发渲染更新的功能。
由于watcher内的onComponentUpdate()需要接收回调函数的返回值作为参数,但是响应式对象内没有这个回调函数,需要借助一个第三方对象这个回调函数传递给响应式对象里面,即把watcher对象传递给响应式对象。第三方全局对象Dep就应运而生。
Dep对象依赖收集器对象,Dep 对象用于依赖收集(收集监听器watcher内 回调函数的值以及onComponentUpdate()方法),它实现了一个发布订阅模式,完成了响应式数据 Data 和观察者 Watcher 的订阅。
定义一个Dep对象

  1. const Dep = {
  2. target: null //Vue源码中Dep.target是Watcher的一个实例对象
  3. }

Dep的target用来存放监听器Watcher及监听器里的onComponentUpdate()方法。
重写上面简化版的watcher函数

  1. const onComputedUpdate=(val)=> {
  2. console.log(`我是${val}学生`);
  3. }
  4. function watcher(obj, key, cb) {
  5. // 1定义一个函数,稍后将全局Dep.target对象指向该函数,将cb的回调返回值可以传递给响应式对象
  6. const onDepUpdate = () => {
  7. let val = cb()
  8. onComputedUpdate(val)
  9. }
  10. Object.defineProperty(obj, key, {
  11. get() {
  12. Dep.target = onDepUpdate
  13. // 2 执行cb()的过程中会用到Dep.target,
  14. // 3 当cb()执行完重置Dep.target为null
  15. let val = cb()
  16. Dep.target = null
  17. return val
  18. }
  19. })
  20. }

在监听器内部定义了一个新的onDepUpdate()方法,这个方法很简单,就是把监听器回调函数的值以及onComputedUpdate()给打包到一块,然后赋值给Dep.target。这一步非常关键,通过这样的操作,依赖收集器就获得了监听器的回调值以及onComputedUpdate()方法。作为全局变量,Dep.target理所当然的能够被可观测对象的getter/setter所使用。
改写响应式方法函数observable,将Dep.target添加到函数内

  1. const Dep = {
  2. target: null
  3. }
  4. function observable(obj) {
  5. let keys = Object.keys(obj)
  6. let deps = []
  7. keys.forEach(key => {
  8. let val = obj[key]
  9. Object.defineProperty(obj, key, {
  10. get() {
  11. if (Dep.target && deps.indexOf(Dep.target) < 0) {
  12. deps.push(Dep.target)
  13. }
  14. return val
  15. },
  16. set(newVal) {
  17. val = newVal
  18. deps.forEach(dep => {
  19. dep()
  20. })
  21. }
  22. })
  23. })
  24. return obj
  25. }

在observable函数内定义一个空数组deps。当obj的key的getter被触发时,就给deps添加Dep.target。此时Dep.target已经被赋值为onDepUpdate,让响应式对象可以访问到watcher。当响应式对象obj中key的setter被触发时,就调用deps中所保存的Dep.target方法,也就自动触发监听器内部的onComponentUpdate()函数。
deps被定义为数组而不是一个基本变量,是因为同一个属性key可以被多个watcher所依赖,可能存在多个Dep.target。定义deps数组,如果当前属性setter被触发,可以批量调用多个watcher的更新方法deps.forEach的操作。

可以添加实验测试:

  1. const student = observable({
  2. name: "kk",
  3. age: 16
  4. })
  5. watcher(student, "level", () => {
  6. return student.age > 16 ? "大" : "小"
  7. })
  8. console.log(student.level);
  9. student.age =18

运行结果:

我是大学生
给响应式对象student的age属性重新赋值,会主动触发onComputedUpdate()方法的指向。打印出“我是大学生”

改写成类class的形式

把依赖收集器对象的功能进行聚合,把Dep执行的方法放到Dep类中。

  1. class Dep {
  2. constructor() {
  3. this.deps = []
  4. }
  5. depend() {
  6. if (Dep.target && this.deps.indexOf(Dep.target) < 0)
  7. this.deps.push(Dep.target)
  8. }
  9. notify() {
  10. this.deps.forEach(dep => {
  11. dep()
  12. })
  13. }
  14. }
  15. Dep.target = null
  16. class Observable{
  17. constructor(obj){
  18. this.obj = obj
  19. return this.main()
  20. }
  21. main(){
  22. let _self = this
  23. let keys = Object.keys(this.obj)
  24. let dep = new Dep()
  25. keys.forEach(key=>{
  26. let val = this.obj[key]
  27. Object.defineProperty(this.obj,key,{
  28. get(){
  29. dep.depend()
  30. return val
  31. },
  32. set(newVal){
  33. val = newVal
  34. dep.notify()
  35. }
  36. })
  37. })
  38. return this.obj
  39. }
  40. }
  41. const onComputedUpdate=(val)=>{
  42. console.log(`我是${val}学生`);
  43. }
  44. class Watcher{
  45. constructor(obj,key,cb){
  46. this.obj = obj
  47. this.key = key
  48. this.cb = cb
  49. this.watcherFun()
  50. }
  51. watcherFun(){
  52. let _self = this
  53. const onDepUpdate=()=>{
  54. let val = this.cb()
  55. onComputedUpdate(val)
  56. }
  57. //简化版watcher的get方法
  58. Object.defineProperty(this.obj,this.key,{
  59. get(){
  60. Dep.target = onDepUpdate
  61. let val = _self.cb()
  62. Dep.target = null
  63. return val
  64. }
  65. })
  66. }
  67. }
  68. let student = new Observable({
  69. name:'北鸟南游',
  70. age:16
  71. })
  72. new Watcher(student,'level',()=>{
  73. return student.age >16 ?"大":"小"
  74. })
  75. console.log(student.level);
  76. student.age = 18

简化vue数据绑定更新的流程,理清了响应对象data、watcher及Dep的职责和关系。

更详细的vue响应式原理分析参考https://lmjben.github.io/blog/library-vue-flow.html