Vue自学笔记(一)

144 阅读10分钟

我正在参加「掘金·启航计划」

1. Vue框架部分

1.1 vue的指令
# vue指令及含义
v-cloak: 防止网络不好时出现{{msg}}这种现象
v-text: 数据绑定,插值相当于{{msg}}
v-pre: 显示原始 Mustache 标签 即{{msg}},跳过编译过程
v-slot: 插槽
v-on: 事件监听函数 v-on:click="hadleClick"(@click="handleClick")
v-model: 数据双向绑定 v-model='name'(常用于input输入框)
v-once: 数据加载一次之后就不在改变
v-html: 将html字符串标签解析为html(不建议用于表单提交,会造成xss工具)
v-bind: 属性绑定 v-bind:title="'这是一张图片'"(:title="'这是一张图片'")
v-if: 分支切换(不满足dom则会被删除) 控制元素是否渲染到页面
v-else-if: 分支切换,不满足v-if则会走到v-else-if这里(不满足dom则会被删除)
v-else: 以上都不满足则会走到这里(不满足dom则会被删除)
v-show: 切换显示/隐藏(v-show是将样式display设置为none) 控制元素是否显示(已经渲染到了页面)
v-for: 循环遍历 需要绑定key进一步提高性能
1.2 事件修饰符
# 事件修饰符 v-on:click === @click
<button v-on:[event]="doThis"></button> 动态事件
<form @submit.prevent></form>   阻止默认行为没有表达式
<button v-on="{ mousedown: doThis, mouseup: doThat }"></button>   对象语法绑定事件
@click.stop="handle"   阻止冒泡
@click.prevent="handle"  阻止默认事件
@click.stop.prevent="handle"  阻止冒泡同时也阻止默认事件
@click.self="handle" 只有点击元素自身才会触发
@click.capture  添加事件侦听器时使用capture模式
@click.native 	监听组件根元素的原生事件
@click.once			只触发一次回调 (version > 2.1.4)
@click.passive	滚动事件的默认行为 (即滚动行为) 将会立即触发以{ passive:true }模式添加侦听器(version>2.3.0).passive 修饰符尤其能够提升移动端的性能。

使用修饰符时,顺序很重要,相应的代码会以同样的顺序产生.因此,用

v-on:click.prevent.self 会阻止所有的点击, 而

v-on:click.self.prevent 只会阻止对自身元素的点击

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

用法:

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

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

#触发事件时,传递参数
@click="handle"   // 不传参 handle(val){console.log(val)} // 此时val为$event事件对象
@click="handle(item)"   // 传参
@cick="handle(item,$event)" // 传参,传事件对象
1.3 按键修饰符
@click.{keyCode | keyAlias}   事件是从特定键触发时才触发回调
Vue.config.keyCodes.aaa = 65   // 自定义全局按键修饰符
@click.left			只点击鼠标左键时触发(version>2.2.0)
@click.right		点击鼠标右键时触发(version>2.2.0)
@click.middle   点击鼠标中键时触发(version>2.2.0)
<button @click.ctrl.exact="onCtrlClick">A</button> // 有且只有ctrl被按下的时候才触发
1.4 表单修饰符
表单域修饰符
v-model.number: 转换为数值
v-model.trim: 去掉开始和结尾的空格
v-model.lazy: 将input时间转换为change事件
1.5 样式绑定
1.5.1 class动态绑定
<template>
	<div :class="{active: isActive,bgc:bgc}"></div> // 对象语法绑定类名
	<div :class="[activeClass,bgcClass]"></div>  // 数组语法绑定类名
	<div :class="[activeClass,{active: isActive]"></div> // 对象和数组结合使用
	<div :class="myClass"></div> //数组简化类名
	<div :calss="objClass"></div>  // 对象简化用法
	<div class="base" :calss="objClass"></div> // 静态class和动态class结合
</template>
<script>
	export default {
    data() {
      return {
        isActive: false, // 对象类型
        bgc: false, // 对象类型
        activeClass: 'active', // 数组类型, 去除这个类名时直接赋值为''字符串
        bgcClass: 'bgc', // 数组类型, 去除这个类名时直接赋值为''字符串
        myClass: ['active','bgc'], // 数组简化
        objClass: { // 对象类名简化
          active: true,
          bgc: true
        }
      }
    }
  }
</script>
<style>
  .active{
    color: red
  }
  .bgc{
    background-color: #000;
  }
</style>
1.5.2 style动态绑定
<template>
	<div :style="{border:borderStyle,width: widthStyle}"></div> // 对象语法绑定样式
</template>
<script>
	export default {
    data() {
      return {
        borderStyle: '1px solid red',
        widthStyle: '200px'
      }
    }
  }
</script>
<style>
</style>
1.6 Vue之自定义指令
	// 全局定义指令
Vue.directive('focus',{
	// el: 指令所绑定的元素,可以用来直接操作dom
	/*	 binding: 一个对象,包含一下property:
		*			name: 指令名,不包括v- 前缀
		*			value: 指令绑定的值, 例如 v-my-directive="1+1", 绑定值为2
		*			oldValue: 指令绑定的前一个值, 仅在update和componentUpdated钩子中可用,无论值是否改变			*		都可用
		*			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: 上一个虚拟节点,仅在update和componentUpdated钩子中可用
	inserted: function(el,binding,vnode,oldVnone){ // 被绑定元素插入父节点时调用(仅保证父节点存在,但不一定被插入文档中)
		// el表示指令所绑定的元素
		el.focus()
	},
	bind: function(el,binding,vnode,oldVnone){
		// 该方法只调用一次,指令第一次绑定到元素时调用,在这里可以进行一次性的初始化设置
	},
	update: function(el,binding,vnode,oldVnone){
		//所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新
	},
	componentUpdated: function(el,binding,vnode,oldVnone){
		// 指令所在组件的 VNode 及其子 VNode 全部更新后调用。
	},
	unbind: function(el,binding,vnode,oldVnone){
		// 只调用一次,指令与元素解绑时调用。
	}
})
// 使用自定义指令
<input v-focus type="text">
// 在组件中定义局部指令,组件中也接受一个directives的选项:
directives: {
  focus: {
    // 指令的定义
    inserted: function (el) {
      el.focus()
    }
  }
}
1.7 计算属性

我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。

Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性而不是命令式的 watch 回调.

使用 watch 选项允许我们执行异步操作 (访问一个 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

<div id="example">
  <p>Original message: "{{ message }}"</p>
  <p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: 'Hello'
  },
  computed: {
    // 计算属性的 getter
    reversedMessage: function () {
      // `this` 指向 vm 实例
      return this.message.split('').reverse().join('')
    }
  }
})
1.8 过滤器

Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示:

<template>
	<!-- 在双花括号中 -->
  {{ message | capitalize }}

  <!-- 在 `v-bind` 中 -->
  <div v-bind:id="rawId | formatId"></div>
	<!-- 过滤器可以串联 -->
	{{ message | filterA | filterB }}
	<!-- 过滤器是 JavaScript 函数,因此可以接收参数: 参数1: message, 参数2: 字符串'arg1',参数3:表达式arg -->
	{{ message | filterA('arg1', arg2) }} 
</template>
<script>
	export default {
    // 局部过滤器
    filters: {
      capitalize: function (value) {
        if (!value) return ''
        value = value.toString()
        return value.charAt(0).toUpperCase() + value.slice(1)
      }
    }
  }
</script>
// 全局过滤器
Vue.filter('capitalize', function (value) {
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

new Vue({
  // ...
})

**注:**当全局过滤器和局部过滤器重名时,会采用局部过滤器

1.9 Vue组件的生命周期
beforeCreate( )// 该钩子函数执行时,组件实例还未创建.
created()//组件初始化完毕,各种数据可以使用,可以使用ajax发送异步请求获取数据
beforeMounted()// 未执行渲染,更新,虚拟DOM完成,真实DOM未创建
mounted()// 初始化阶段结束,真实DOM已经创建,可以发送异步请求获取数据,也可以访问dom元素
beforeUpdate()//更新前,可用于获取更新前各种状态数据
updated()//更新后执行该钩子函数,所有的状态数据是最新的。
beforeDestroy() // 销毁前执行,可以用于一些定时器的清除。
destroyed()//组件已经销毁,事件监听器被移除,所有的子实例也会被销毁。
1.10 数组更新检测
  1. 变更方法(修改原有数组)

Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:

  • 变异数组方法,会修改原有数组
  • push() 向数组的末尾添加一个或多个元素,并返回新的数组
  • pop() 删除数组的最后一个元素并返回删除的元素。
  • shift() 把数组的第一个元素从其中删除,并返回第一个元素的值。
  • unshift() 将新项添加到数组的开头,并返回新的数组。
  • splice() 用于添加或删除数组中的元素,返回被删除的元素数组,没删除返回空数组
  • sort() 方法用于对数组的元素进行排序。参数接受一个函数
  • reverse() 用于颠倒数组中元素的顺序。
  1. 替换数组(生成新的数组)
  变更方法,顾名思义,会变更调用了这些方法的原始数组。相比之下,也有非变更方法,例如 `filter()`、`concat()` 和 `slice()`。它们不会变更原始数组,而**总是返回一个新数组**。当使用非变更方法时,可以用新数组替换旧数组:
example1.items = example1.items.filter(function (item) {
  return item.message.match(/Foo/)
})
1.11 组件化开发
1.11.1 组件注册
  1. 全局组件注册

    记住全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生。直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。

# 短横线模式
Vue.component('my-component-name', { /* ... */ })
# 驼峰命名
Vue.component('MyComponentName', { /* ... */ })
  1. 局部组件注册

    局部组件只能在注册它的父组件中使用

var ComponentA = { /* ... */ }
new Vue({
  el: '#app',
  components: {
    'component-a': ComponentA,
  }
})

组件注册注意事项:

  • data必须是一个函数
  • 组件模板根内容必须是单个元素
  • 组件模板内容可以是模板字符串
1.11.2 父子组件传值

props属性名规则:

  • 在props中使用驼峰形式,在模板中使用短横线形式
  • 字符串形式的模板中没有这个限制

props值类型

  • Number 数值
  • String 字符串
  • Boolean 布尔值
  • Array 数组
  • Object 对象
<template>
  <div>
    <h1>父组件</h1>
    <son v-bind:fData="data1" :fMessage="data2"></son>
  </div>
</template>
 
<script>
export default {
  data () {
    return {
      data1: '父组件数据data1',
      data2: '父组件数据data2',
    };
  },
  components: {
    Son
  }
}
</script>
# 子组件
<template>
  <div>
    <h1>子组件</h1>
    <p>下面是父组件传过来的数据</p>
    <p>第一个数据:{{fData}}</p>
    <p>第二个数据:{{fMessage}}</p>
  </div>
</template>
  
<script>
export default {
  name: 'son'
  props: ['fData', 'fMessage'],
  data () {
    return {
  
    };
  }
}
</script>
1.11.3 子组件向父组件传值
  • 父组件在组件上定义了一个自定义事件childFn,事件名为parentFn用于接受子组件传过来的message值。
<!-- 父组件 -->
<template>
    <div class="test">
      <test-com @childFn="parentFn"></test-com>
      <br/> 
      子组件传来的值 : {{message}}
    </div>
</template>

<script>
export default {
    // ...
    data() {
        return {
             message: ''
        }
    },
    methods: {
       parentFn(payload) {
        this.message = payload;
      }
    }
}
</script>
  • 子组件是一个buttton按钮,并为其添加了一个click事件,当点击的时候使用$emit()触发事件,把message传给父组件。
<!-- 子组件 -->
<template> 
<div class="testCom">
    <input type="text" v-model="message" />
    <button @click="click">Send</button>
</div>
</template>
<script>
export default {
    // ...
    data() {
        return {
          // 默认
          message: '我是来自子组件的消息'
        }
    },
    methods: {
      click() {
            this.$emit('childFn', this.message);
        }
    }    
}
</script>
1.11.4 兄弟组件通信
// 定义事件中心
const eventBus = new Vue()
<!-- ParentCard -->
<template>
    <div class="card">
        <div class="card-header">
            <h5 v-text="theCardTitle"></h5>
        </div>
        <div class="card-body">
            <brother-card></brother-card>
            <sister-card></sister-card>
        </div>
    </div>
</template>
 
<script>
    import BrotherCard from "./BrotherCard";
    import SisterCard from "./SisterCard";
 
    export default {
        name: "ParentCard",
        data: () => ({
            theCardTitle: "Parent Card"
        }),
        components: {
            BrotherCard,
            SisterCard
        }
    };
</script>
<!-- SisterCard.vue -->
<template>
    <div class="message">
        <div class="message-header">
            <h5 v-text="theCardTitle"></h5>
        </div>
        <div class="message-body">
            <p class="message-text">我是Sister组件</p>
            <button @click="messageBrother" class="btn">给哥哥发消息</button>
 
            <div v-if="fromBrother" class="alert" v-html="fromBrother"></div>
        </div>
    </div>
</template>
 
<script>
    import { eventBus } from "../main";
 
    export default {
        name: "SisterCard",
        data: () => ({
            theCardTitle: "Sister Card",
            fromBrother: ""
        }),
        methods: {
            messageBrother() {
                eventBus.$emit("sisterSaid", "妈妈说,该做作业了!(^_^)!!!");
            }
        },
        created() {
            eventBus.$on("brotherSaid", message => {
                this.fromBrother = message;
            });
        }
    };
</script>
 
<!-- BrotherCard.vue -->
<template>
    <div class="message">
        <div class="message-header">
            <h5 v-text="theCardTitle"></h5>
        </div>
        <div class="message-body">
            <p class="message-text">我是Brother组件</p>
            <button @click="messageSister" class="btn">给妹妹发消息</button>
 
            <div v-if="fromSister" class="alert" v-html="fromSister"></div>
        </div>
    </div>
</template>
 
<script>
    import { eventBus } from "../main.js";
    export default {
        name: "BrotherCard",
        data: () => ({
            theCardTitle: "Brother Card",
            fromSister: ""
        }),
        methods: {
            messageSister() {
                eventBus.$emit("brotherSaid", "妈妈说,该做作业了!(^_^)!!!");
            }
        },
        created() {
            eventBus.$on("sisterSaid", message => {
                this.fromSister = message;
            });
        }
    };
</script>
this.$off('xxx')  // 销毁事件
1.11.5 插槽
<navigation-link url="/profile">  // 父组件传递内容
  Your Profile
</navigation-link>
然后你在 <navigation-link> 的模板中可能会写为:
<a
   v-bind:href="url"
   class="nav-link"
>
  <slot></slot>  // 子组件在这里接收父组件传递的内容
</a>
  当组件渲染的时候,<slot></slot> 将会被替换为“Your Profile”。插槽内可以包含任何模板代码,包括 HTML:
  <navigation-link url="/profile">
    <!-- 添加一个 Font Awesome 图标 -->
    <span class="fa fa-user"></span>
    Your Profile
  </navigation-link>
<navigation-link url="/profile">
  Clicking here will send you to: {{ url }}
  <!--
  这里的 `url` 会是 undefined,因为其 (指该插槽的) 内容是
  _传递给_ <navigation-link> 的而不是
  在 <navigation-link> 组件*内部*定义的。
  -->
</navigation-link>

父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。

有时我们需要多个插槽。例如对于一个带有如下模板的 <base-layout> 组件:

<div class="container">
  <header>
    <!-- 我们希望把页头放这里 -->
  </header>
  <main>
    <!-- 我们希望把主要内容放这里 -->
  </main>
  <footer>
    <!-- 我们希望把页脚放这里 -->
  </footer>
</div>

对于这样的情况,<slot> 元素有一个特殊的 attribute:name。这个 attribute 可以用来定义额外的插槽:

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

一个不带 name<slot> 出口会带有隐含的名字“default”。

在向具名插槽提供内容的时候,我们可以在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:

<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

现在 <template> 元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有 v-slot<template> 中的内容都会被视为默认插槽的内容。

作用域插槽:

有时让插槽内容能够访问子组件中才有的数据是很有用的。例如,设想一个带有如下模板的 <current-user> 组件:

<span>
  <slot>{{ user.lastName }}</slot>
</span>

我们可能想换掉备用内容,用名而非姓来显示。如下:

<current-user>
  {{ user.firstName }}
</current-user>

然而上述代码不会正常工作,因为只有 <current-user> 组件可以访问到 user,而我们提供的内容是在父级渲染的。

为了让 user 在父级的插槽内容中可用,我们可以将 user 作为 <slot> 元素的一个 attribute 绑定上去:

<span>
  <slot v-bind:user="user">
    {{ user.lastName }}
  </slot>
</span>

绑定在 <slot> 元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字:

<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>
</current-user>

在这个例子中,我们选择将包含所有插槽 prop 的对象命名为 slotProps,但你也可以使用任意你喜欢的名字。

1.12 Vue路由
1.12.1 基本使用步骤
# 安装 vue-router
npm install vue-router
1.12.2 动态路由props传参
routes = [
  {
    path: '/my/:id'
    name: 'My',
    component: My,
    props: true, // 开启路由props传参
  }
  // props 可以为函数类型 props: route =>({uname: '123', id: route.params.id })
  // props 可以返回一个静态数据 props: {uname: 'lisi', age: 20}
]
// 组件内访问路由的id
export default {
	props: ['id']
}
1.12.3 命名路由
// name: 表示命名路由,此时可以通过 router.push({name: 'My',params: {id:123 }})跳转
routes = [
  {
    path: '/my/:id'
    name: 'My',
    component: My,
    props: true, // 开启路由props传参
  }
  // props 可以为函数类型 props: route =>({uname: '123', id: route.params.id })
  // props 可以返回一个静态数据 props: {uname: 'lisi', age: 20}
]
1.12.4 编程式导航
this.$router.push('地址')
this.$router.go(n) // n 为数值,正数代表前进,负数表示后退
1.13 this.$nextTick()的作用

$nextTick方法的作用,就是当页面上元素被重新渲染之后,才会执行回调函数中的代码

1.14 完整的导航解析流程
/*
*     1: 导航被触发;
*     2: 在失活的组件里调用离开守卫;
*     3: 调用全局的beforeEach守卫;
*     4: 在重用的组件里调用beforeRouteUpdate守卫;
*     5: 在路由配置里面调用beforeEnter;
*     6: 解析异步路由组件;
*     7: 在被激活的组件里调用beforeRouteEnter;
*     8: 调用全局的beforeResolve守卫;
*     9: 导航被确认;
*     10: 调用全局的afterEnter钩子;
*     11: 触发Dom更新;
*     12: 用创建好的实例调用beforeRouterEnter守卫中传给next的回调函数
*
* */