titel:Vue 核心
Vue 核心
一、Vue 的基本认识
渐进式 JavaScript 框架,用来动态构建用户界面
特点
- 遵循 MVVM 模式
- 编码简洁,体积小,运行效率高,适合 移动/pc 端开发
- 它本身只关注 UI,可以轻松引入 vue 插件或其它第三方库开发项目
与其他前端 JS 框架的关联
- 借鉴 angular 的模板 和 数据绑定技术
- 借鉴 react 的组件化 和 虚拟 DOM 技术(提高效率)
Vue 扩展插件、
】、
- vue-cli:vue 脚手架
- vue-resource(axios):ajax 请求
- vue-router:路由
- vuex:状态管理(它是 vue 的插件但是没有用 vue-xxx 的命名规则)
- vue-lazyload:图片懒加载
- vue-scroller:页面滑动相关
- mint-ui:基于 vue 的 UI 组件库(移动端)
- element-ui:基于 vue 的 UI 组件库(PC 端)二、Vue 的基本使用
编码
-
引入Vue.js
-
创建 Vue 对象
el:指定根 element (选择器)
data:初始化数据(页面可以访问)
-
双向数据绑定:v-model
-
显示数据:{{xxx}}
-
理解 vue 的 mvvm 实现
<!--template模板-->
<div id="test">
<input type="text" v-model="msg"><br><!--指令-->
<input type="text" v-model="msg"><!--指令-->
<p>hello {{msg}}</p><!--大括号表达式-->
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
const vm = new Vue({ // 配置对象 options
// 配置选项(option)
el: '#test', // element: 指定用vue来管理页面中的哪个标签区域 也可以通过¥mount()来处理
data: {//data可以时数据和函数类型 指定初始化状态属性数据的对象 vm自动拥有data中所有的属性
msg: 'atguigu'
}
})
</script>
理解 Vue 的 MVVM
MVVM --> model-view-viewModel
model:模型,数据对象(data)
view:视图,模板页面
viewModel:视图模型(vue 的实例)
MVVM 本质上是 MVC (Model-View- Controller)的改进版。即模型-视图-视图模型。
模型
指的是后端传递的数据,视图
指的是所看到的页面。
视图模型
是 mvvm 模式的核心,它是连接 view 和 model 的桥梁。它有两个方向:
- 将
模型
转化成视图
,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。 - 将
视图
转化成模型
,即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。
这两个方向都实现的,我们称之为数据的双向绑定。
三、模板语法
模板的理解
- 动态的 html 页面
- 包含了一些 JS 语法代码:
- 双大括号表达式
- 指令(以 v- 开头的自定义标签属性)
双大括号表达式
- 语法:{{exp}}
- 功能:向页面输出数据
- 可以调用对象的方法
指令一:强制数据绑定
功能:指定变化的属性值
完整写法:v-bind:xxx=‘yyy’ //yyy会作为表达式解析执行
简洁写法::xxx=‘yyy’
第一种
:title="`字符串${xx}`"
第二种
:title="'字符串' + xx"
指令二:绑定事件监听
功能:绑定指定事件名的回调函数
完整写法:v-on:keyup=‘xxx’,v-on:keyup=‘xxx(参数)’,v-on:keyup.enter=‘xxx’
简洁写法:@keyup=‘xxx’,@keyup.enter=‘xxx’
四、计算属性和监视
原理为Object.defineProperty()
计算属性
- 在 computed 属性对象中定义计算属性的方法
- 在页面中使用 {{方法名}} 来显示计算的结果
监视属性a
-
通过 vm 对象的
$watch()
或watch 配置
来监视指定的属性 -
当属性变化时,回调函数自动调用,在函数内部进行计算
vm监视data中数据的特点
所有层次属性会监视
对象内部也会监视 对象属性添加set方法
数组内部元素监视 重写数组原型方法 1、调用原有方法处理数据 2、更新界面
计算属性高级
- 通过 getter/setter 实现对属性数据的计算读取 和 变化监视
- 计算属性存在缓存,多次读取只执行一次 getter 计算(对象解构缓存数据
五、class 与 style 绑定
- 在应用界面中, 某个(些)元素的样式是变化的
- class/style 绑定就是专门用来实现动态样式效果的技术
class 绑定::class='xxx'
- 表达式是字符串: ‘classA’
- 表达式是对象: {classA:isA, classB: isB}
- 表达式是数组: [‘classA’, ‘classB’]
style 绑定::style="{ color: activeColor, fontSize: fontSize + 'px' }"
其中 activeColor/fontSize 是 data 属性
六、条件渲染条件渲染指令
- v-if + v-else
- v-show
如果需要频繁切换 v-show 较好。当条件不成立时, v-if 的所有子节点不会解析。
七、列表渲染
列表显示指令:
- 数组:v-for/index
- 对象:v-for/key
列表的更新显示:
1.删除item:变更方法,顾名思义,会变更调用了这些方法的原始数组。
// 两种更新方式
this.persons[index] = newP
// 这样只更新persons中的某一个数据,vue根本就不知道,视图不会更新
this.persons.splice(index, 1, newP)
// splice方法被 Vue 将进行了包裹,所以也将会触发视图更新。
这些被包裹过的方法包括:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
2.替换item:相比之下,也有非变更方法,例如 filter()
、concat()
和 slice()
。它们不会变更原始数组,而总是返回一个新数组。当使用非变更方法时,可以用新数组替换旧数组。
let fpersons = persons.filter(
p => p.name.includes(searchName)
)
列表的高级处理:
列表过滤
filterPerson(){
//得到依赖数据
const{searchName ,persons}=this
//进行计算处理,产生结果数据返回
过滤
consr arr=persons.filter(p=>p.name.indexOf(searchName)>=0)
return arr
}
列表排序
fpersons.sort(function (p1, p2) {
if (orderType === 1) { // 降序
return p2.age - p1.age
} else { // 升序
return p1.age - p2.age
}
})
八、事件处理
绑定监听
- v-on:xxx=“fun”
- @xxx=“fun”
- @xxx=“fun(参数)”
- 默认事件形参: event, 隐含属性对象:
$event
就是当前触发事件的元素,即使不传 $event
,在回调函数中也可以使用 event 这个参数。
事件修饰符
事件修饰符用来控制事件的冒泡和默认行为。
- .prevent : 阻止事件的默认行为event.preventDefault()
- .stop : 停止事件冒泡event.stopPropagation()
<!-- 阻止事件冒泡 -->
<div id="big" @click="test">
<div id="small" @click.stop="test2"></div>
</div>
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>
按键修饰符
- .keycode : 操作的是某个 keycode 值的键
- .keyName : 操作的某个按键名的键(少部分)
<!-- 任何按键按下都会触发回调函数 -->
<textarea @keyup="testKeyup"></textarea>
<!-- 下面的两种写法效果是一致的 -->
<!-- 使用按键码,回车键的keyCode是13 -->
<textarea @keyup.13="testKeyup"></textarea>
<!-- 使用按键修饰符,因为回车键比较常用,所以vue为他设置了名称,可以直接使用enter来代替 -->
<textarea @keyup.enter="testKeyup"></textarea>
九、表单输入绑定
使用 v-model 对表单数据自动收集
- text/textarea
- checkbox
- radio
- select
十、vue 实例生命周期
生命周期流程图
vue 生命周期分析
- 初始化显示
- beforeCreate()//
- created()//实现了数据代理
- beforeMount()
- mounted()
- 更新显示:this.xxx = value
- beforeUpdate()
- updated()
- 销毁 vue 实例:vm.$destroy()
- beforeDestory()
- destoryed()
常用的生命周期方法
- created()/mounted():发送 ajax 请求,启动定时器等异步任务
- beforeDestroy():做收尾工作,如:清除定时器
十一、过渡&动画
vue 动画的理解
-
操作 css 的 transition 或 animation
-
vue 会给目标元素添加/移除特定的 class
-
过渡的相关类名
xxx-enter-active:指定显示的 transition
xxx-leave-active:指定隐藏的 transition
xxx-enter/xxx-leave-to:指定隐藏时的样式
基本过渡动画的编码
-
在目标元素外包裹
<transition name="xxx">
-
定义 class 样式
指定过渡样式:transition
指定隐藏时的样式:opacity/其它
十二、过滤器
理解过滤器
-
功能: 对要显示的数据进行特定格式化后再显示(比如日期格式)
-
注意: 并没有改变原本的数据, 可是产生新的对应的数据
定义和使用过滤器
定义过滤器:
Vue.filter(filterName, function(value[,arg1,arg2,...]){
// 进行一定的数据处理
return newValue
})
使用过滤器 :
其中,myData 会作为 value 传入 filter 中。
十三、内置指令与自定义指令
常用内置指令
-
v-text : 更新元素的 textContent
-
v-html : 更新元素的 innerHTML
v-text和{{}}表达式渲染数据,不解析标签。
v-html不仅可以渲染数据,而且可以解析标签。
-
v-if : 如果为 true, 当前标签才会输出到页面
-
v-else: 如果为 false, 当前标签才会输出到页面
-
v-show : 通过控制 display 样式来控制显示/隐藏
-
v-for : 遍历数组/对象
-
v-on : 绑定事件监听, 一般简写为@
-
v-bind : 强制绑定解析表达式, 可以省略 v-bind:
-
v-model : 双向数据绑定
-
ref : 指定唯一标识, vue 对象通过$els 属性访问这个元素对象
ref 被用来给DOM元素或子组件注册引用信息。引用信息会根据父组件的 $refs 对象进行注册。如果在普通的DOM元素上使用,引用信息就是元素; 如果用在子组件上,引用信息就是组件实例注意:只要想要在Vue中直接操作DOM元素,就必须用ref属性进行注册
- v-cloak : 防止闪现, 与 css 配合: [v-cloak] { display: none }
自定义指令
el:指令所在的标签对象
binding:包含指令相关数据的容器对象
- 注册全局指令 :
Vue.directive('my-directive', function(el, binding){
el.innerHTML = binding.value.toupperCase()
})
- 注册局部指令 :
directives: {
'my-directive'(el, binding) {
el.innerHTML = binding.value.toupperCase()
}
}
- 使用指令 :v-my-directive=‘xxx’
(binding.value 就是 xxx 的值)
十四、自定义插件
说明
-
Vue 插件是一个包含 install 方法的对象
-
通过 install 方法给 Vue 或 Vue 实例添加方法,定义全局
(function(window){
const MyPlugin={}//定义插件对象
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或 property
Vue.myGlobalMethod = function () {
// 逻辑...
}
// 2. 添加全局资源
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// 逻辑...
}
...
})
// 3. 注入组件选项
Vue.mixin({
created: function () {
// 逻辑...
}
...
})
// 4. 添加实例方法
Vue.prototype.$myMethod = function (methodOptions) {
// 逻辑...
}
}
暴漏插件
window.MyPlugin=MyPlugin
})(window)
其他 API
vm.$nextTick([callback])
用法:将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick
一样,不同的是回调的 this
自动绑定到调用它的实例上。
用途:需要在视图更新之后,基于新的视图进行操作。
//改变数据
vm.message = 'changed'
//想要立即使用更新后的DOM。这样不行,因为设置message后DOM还没有更新
console.log(vm.$el.textContent) // 并不会得到'changed'
//这样可以,nextTick里面的代码会在DOM更新后执行
Vue.nextTick(function(){
console.log(vm.$el.textContent) //可以得到'changed'
})
vue 组件化编码
vue组件定义
VUE组件中的data必须是返回对象的函数
保证该组件的多个实例都有自己独立的data对象
动态组件
动态组件
有的时候,在不同组件之间进行动态切换是非常有用的,比如在一个多标签的界面里切换组件
可以通过 Vue 的 <component>
元素加一个特殊的 is
attribute 来实现:
<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>
在上述示例中,currentTabComponent
可以包括
- 已注册组件的名字,或
- 一个组件的选项对象
缓存组件
<!-- 失活的组件将会被缓存!-->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
异步组件
在需要组件的时候 ,才异步请求加载组件的代码(后台)
Vue能够将组件定义为一个工厂函数,此函数可以异步解析组件
import()语法比较适合的是路由的异步懒加载
组件间通信
组件间通信基本原则
- 不要在子组件中直接修改父组件的状态数据
- 数据在哪,更新数据的行为(函数)就应该定义在哪
vue 组件间通信方式
- props
- vue 的自定义事件
- 全局事件总线 global event bus 实际是一个全局的用于绑定事件监听和分发事件的对象 Vue.protoType.$globaleventbus=new Vue()
- 消息订阅与发布(如: pubsub.js 库)
- slot
- vuex
props
使用组件标签时:
<my-component name='tom' :age='3' :set-name='setName'></my-component>
定义 MyComponent 时:
1.在组件内声明所有的 props:
// 方式一:只指定名称
props: ['name', 'age', 'setName']
// 方式二:指定名称和类型
props: {
name: String,
age: Number,
setNmae: Function
}
// 方式三:指定名称/类型/必要性/默认值
props: {
name: {type: String, required: true, default:xxx},
}
注意
- 此方式用于父组件向子组件传递数据
- 所有标签属性都会成为组件对象的属性,模板页面可以直接引用
- 问题:
a. 如果需要向非子后代传递数据必须多层逐层传递
b. 兄弟组件间也不能直接 props 通信,必须借助父组件才可以
vue 自定义事件
绑定事件监听(绑定在父组件中)
// 方式一: 通过v-on 绑定
@delete_todo="deleteTodo"
// 方式二: 通过$on()
<TodoHeader ref="header"/>
mounted () {
this.$refs.header.$on('delete_todo', this.deleteTodo)
}
触发事件(写在子组件中)
// 触发事件(只能在父组件中接收)
this.$emit('delete_todo', data)
注意
- 此方式只用于子组件向父组件发送消息(数据)
- 问题:隔代组件或兄弟组件间通信此种方式不合适
消息订阅与发布(PubSubJS 库)
订阅消息
PubSub.subscribe('msg', function(msg, data){//对象
...
})
发布消息
PubSub.publish('msg', data)//异步发布
PubSub.publishSync('MY TOPIC', 'hello world!');//同步发布
注意
- 优点:此方式可实现任意关系组件间通信(数据)
事件的2个重要操作(总结)
- 绑定事件监听(订阅消息)
目标:标签元素<button>
事件名(类型):click/focus
回调函数:function(event){} - 触发事件(发布消息)
DOM 事件:用户在浏览器上对应的界面上做对应的操作
自定义:编码手动触发
slot
此方式用于父组件向子组件传递标签数据
子组件: Child.vue
<template>
<div>
<slot name="xxx">不确定的标签结构1</slot>
<div>组件确定的标签结构</div>
<slot name="yyy">不确定的标签结构2</slot>
</div>
</template>
父组件: Parent.vue
<child>
<div slot="xxx">xxx 对应的标签结构</div>
<div slot="yyy">yyyy 对应的标签结构</div>
</child>
axios
为脚手架添加 axios 模块
1.本地安装 axios 模块:
npm i -save axios
2.在脚手架项目源代码的 src/main.js 中,new Vue() 前引入 axios 模块
import axios from "axios"
// node_modules中安装的模块,引入时都不用加路径
3.设置 axios 对象的基础路径属性:
axios.defaults.baseURL="http://服务器端域名"
4.将 axios 对象放入 Vue 的原型对象中
Vue.prototype.axios = axios;
5.结果:因为所有组件对象都是 Vue 类型的子对象,所以在所有组件内,任何位置都可用 this.$axios.get()
和 this.$axios.post()
访问 Vue.prototype 中的 axios 对象里的函数。
// 发送 ajax 请求
this.$axios.get('/index')
.then(response => {
console.log(response.data) // 得到返回结果数据
})
.catch(error => {
console.log(error.message)
})
let data = {
pagenum: 1
};
this.$axios
.post("/users/signin", data)
.then(res=>{
console.log(res.data);
})
.catch(err => {
console.log(err.message);
});
render 配置
在 main.js 文件中
// 原始写法
new Vue({
el: '#app',
components: { // 将App.vue映射成标签
APP
},
template: '<App/>', // 将App标签转化成模板
})
// 更简洁的写法
new Vue({
el: '#app',
render: h => h(App),
})
render 是一个渲染函数,h => h(App) 是一个箭头函数,其参数 h 是一个函数,这个函数执行接收一个参数 App 组件。
h(App) 执行后返回的结果就是 render 的值。
也就是:
new Vue({
el: '#app',
render: function (createElement) { // h就是createElement,用来创建元素标签
return createElement(App) // <App/>
},
})
// 其中App标签被插入到el中
vue-router
官方提供的用来实现 SPA 的 vue 插件。
相关 API 说明
1.注册路由器(在 main.js)
import Vue from 'vue'
import router from './router'
// 创建vue 配置路由器
new Vue({
el: '#app',
router,
render: h => h(app)
})
2.路由器配置:(在 router 目录下 index.js)
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
3.路由配置:
const routes = [
{
path: '/home',
component: home,
// 嵌套路由
children: [
{
path: 'news',
component: News
},
{
path: 'message',
component: Message
}
]
},
{
// 一般路由
path: 'about',
component: About
},
{
// 自动跳转路由
path: '/',
redirect: '/about'
}
]
3.VueRouter():用于创建路由器的构建函数
const router = new VueRouter({
mode: "history", // 模式
base: process.env.BASE_URL,
routes
});
export default router;
4.使用路由组件标签
-
<router-link>
:用来生成路由链接<router-link to="/home/news">News</router-link>
它默认会被渲染成一个带有链接的a标签,通过to属性指定链接地址。
注意:被选中的router-link将自动添加一个class属性值.router-link-active。 -
<router-view>
:用于渲染匹配到的组件。<router-view></router-view>
向路由组件传递数据
方式1: 路由路径携带参数(param/query)
- 配置路由
children: [
{
path: 'mdetail/:id',
component: MessageDetail
}
]
- 路由路径
<router-link :to="'/home/message/mdetail/'+m.id">{{m.title}}</router-link>
- 路由组件中读取请求参数
this.$route.params.id
方式2: <router-view>
属性携带数据
<router-view :msg="msg"></router-view>
缓存路由组件对象
- 默认情况下,被切换的路由组件对象会死亡释放,再次回来时是重新创建的(原来的数据就没有了)
- 如果可以缓存路由组件对象,可以提高用户体验
<keep-alive>
<router-view></router-view>
</keep-alive>
两种模式的区别
前后端分离 ===> 利用Ajax,可以在不刷新浏览器的情况下异步数据请求交互。
单页应用(只有一个html文件,整个网站的所有内容都在这一个html里,通过js来处理)不仅仅是在页面交互是无刷新的,连页面跳转都是无刷新的。为了实现单页应用 ==> 前后端分离 + 前端路由。(更新视图但不重新请求页面)
前端路由实现起来其实也很简单,就是匹配不同的 url 路径,进行解析,加载不同的组件,然后动态的渲染出区域 html 内容。
vue-router 默认 hash 模式,还有一种是 history 模式。
hash 模式
只能改变 # 后面的 url 片段即 hash 值。hash 值的变化,并不会导致浏览器向服务器发出请求,浏览器不发出请求,也就不会刷新页面。每次 hash 值的变化,会触发 hashchange
这个事件,通过这个事件我们就可以知道 hash 值发生了哪些变化。然后我们便可以监听 hashchange
来实现更新页面部分内容的操作:
hash 模式的工作原理是 hashchange 事件,可以在 window 监听 hash 的变化。
<div id="test" style="height: 500px;width: 500px;margin: 0 auto"></div>
<script>
window.onhashchange = function (event) {
console.log(event) // HashChangeEvent {..., newURL: "...test.html#red", oldURL: "...test.html", ...}
console.log(location) // location {..., hash: "#red", ...}
let hash = location.hash.slice(1); // red
document.body.style.color = hash;
document.getElementById('test').style.backgroundColor = hash
}
</script>
在 url 后面随便添加一个 #xx 会触发 onhashchange 事件。打印 event,里边有两个属 性 newURL 和 oldURL。可以通过模拟改变 hash 的值,动态改变页面数据。
相关API
HashHistory 的方法
this.$router.push(path)
:相当于点击路由链接(可以返回到当前路由界面)
this.$router.replace(path)
:用新路由替换当前路由(不可以返回到当前路由界面)
3)this.$router.back()
:请求(返回)上一个记录路由
4) this.$router.go(-1)
:请求(返回)上一个记录路由
5) this.$router.go(1)
:请求下一个记录路由
因为 hash 发生变化的 url 都会被浏览器记录(历史访问栈)下来,从而你会发现浏览器的前进后退都可以用了。尽管浏览器没有请求服务器,但是页面状态和 url 已经关联起来了,这就是所谓的前端路由,单页应用的标配。
history 模式
前面的 hashchange,只能改变 # 后面的 url 片段,而 history api 则给了前端完全的自由。
通过history api,我们丢掉了丑陋的 #,但是它也有个毛病:
不怕前进,不怕后退,就怕f5刷新,刷新是实实在在地去请求服务器的。在 hash 模式下,前端路由修改的是 # 中的信息,而浏览器请求时是跟它无关的,所以没有问题。
但是在 history 下,你可以自由的修改 path,当刷新时,如果服务器中没有相应的响应或者资源,会刷出404来。
解决刷新404问题
路径不带# http://localhost:5000/home/views
发送的请求路径 http://localhost:5000/home/views
响应404错误
希望:如果没有对应的资源返回index页面 path部分被解析成前台路由路径 http://localhost:5000
解决 添加配置
当使用 HTML5 History API 时,任意的 404
响应都可能需要被替代为 index.html
。通过传入以下启用:
historyApiFallback: true 注意资源引用路径问题
output:publicPath:'/' 引入的打包路径以/开头
相关API
多了两个 API,pushState()
和 replaceState()。
通过这两个 API:
1)可以改变 url 地址且不会发送请求
2)不仅可以读取历史记录栈,还可以对浏览器历史记录栈进行修改。
window.history.pushState(stateObject, title, URL)
window.history.replaceState(stateObject, title, URL)
history.go(-2);//后退两次
history.go(2);//前进两次
history.back(); //后退
hsitory.forward(); //前进
区别
- 前面的hashchange,你只能改变 # 后面的 url 片段。而 pushState 设置的新 URL 可以是与当前 URL 同源的任意 URL。
- history 模式则会将 URL 修改得就和正常请求后端的 URL 一样,如后端没有配置对应 /user/id 的路由处理,则会返回404错误
$router
与$route
的区别
$route
是一个跳转的路由对象,每一个路由都会有一个 route 对象,是一个局部的对象。可以获取对应的 name、path、query、params 等(<router-link>
传的参数由this.$route.query
或者this.$route.params
接收)$router
为通过 Vue.use(VueRouter) 和 VueRouter 构造函数得到的一个 router 的实例对象,这个对象是一个全局的对象。想要导航到不同 URL,则使用$router.push
方法;返回上一个 history 也是使用$router.go
方法
总结: 编写使用路由的3步
- 定义路由组件
- 注册路由
- 使用路由
<router-link>
<router-view>
vuex
vuex 是什么:对 vue 应用中多个组件的共享状态进行集中式的管理(读/写)
状态自管理应用:
- state:驱动应用的数据源
- view:以声明方式将 state 映射到视图
- actions:响应在 view 上的用户输入导致的状态变化(包含 n 个更新状态的方法)
多组件共享状态的问题:
- 多个视图依赖于同一状态
- 来自不同视图的行为需要变更同一状态
以前的解决办法:
- 将数据以及操作数据的行为都定义在父组件
- 将数据以及操作数据的行为传递给需要的各个子组件(有可能需要多级传递)
vuex 就是用来解决这个问题的。
vuex 核心概念和 API
state
vuex 管理的状态对象。它应该是唯一的:
const state = {
xxx: initValue
}
getters
包含多个计算属性(get)的对象
由谁读取:组件中 $store.getters.xxx
const getters = {
nnn(state) {
return ...
}
mmm(state, getters) {
return getters.nnn...
//注意:引入getters时,必须放在第二位,因为第一位默认是state
}
}
actions
包含多个事件回调函数的对象。通过执行 commit()
来触发 mutation 的调用,间接更新 state。
由谁触发:组件中 $store.dispatch('action 名称', data1)
// ‘zzz’
可以包含异步代码(定时器,ajax)
const actions = {
zzz ({commit, state}, data1) {
commit('yyy', {data1}) // 传递数据必须用大括号包裹住
}
}
mutations
包含多个直接更新 state 的方法(回调函数)的对象。
由谁触发:action 中的 commit('mutation 名称')
来触发。
只能包含同步的代码,不能写异步代码。
const mutations = {
yyy (state, {data1}) { // 传递数据必须用大括号包裹住
// 更新 state 的某个属性
}
}
modules
包含多个 module。一个 module 是一个 store 的配置对象,与一个组件(包含有共享数据)对应
核心模块 store 对象
index.js 固定写法:
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
Vue.use(Vuex)
export default new Vuex.store({
state,
mutations,
actions,
getters
})
组件中
{{xxx}} {{mmm}} @click="zzz(data)"
import {mapState. mapGetters, mapActions} from 'vuex'
export default{
computed: {
...mapState(['xxx']), //相当于 this.$store.state.xxx
...mapGetters(['mmm']), //相当于 this.$store.getters['mmm']
},
methods: {
...mapActions(['zzz']) //相当于 this.$store.dispatch('zzz')
}
}
映射 store
在 main.js 中
import store from './store'
new Vue({
store
})
store 对象
1.所有用 vuex 管理的组件中都多了一个属性 $store,它就是一个 store 对象
2.属性:
state:注册的 state 对象
getters:注册的 getters 对象
3.方法:
dispatch(actionName, data):分发调用 action
Vuex 结构分析
vue 源码分析
分析 vue 作为一个MVVM 框架的基本实现原理
1.[].slice.call(this) 将伪数组转换为真数组
2、node.nodeType 得到节点类型
3、Object.defineProperty(obj,propertyName,{}) 给对象添加属性
3、Object.keys(obj) 得到对象自身可以枚举属性组成的数组
5、obj.hasOwnProperty(prop) 判断prop是否是obj的属性
6、DocumentFragment 文档碎片 高效批量更新多个节点
数据代理
数据代理:通过一个对象代理对另一个对象(在前一个对象内部)中属性的操作(读/写)
vue 数据代理:data 对象的所有属性的操作(读/写)由 vm 对象来代理操作
好处:通过 vm 对象就可以方便的操作 data 中的数据
基本实现流程:
- 通过 Object.defineProperty() 给 vm 添加与 data 对象的属性对应的属性描述符
- 所有添加的属性都包含 getter/setter
- getter/setter 内部去操作 data 中对应的属性数据
模板解析
模板解析的基本流程
-
将 el 的所有子节点取出,添加到一个新建的文档 fragment 对象中
-
对 fragment 中的所有层次子节点递归进行编译解析处理
- 对大括号表达式文本节点进行解析
- 对元素节点的指令属性进行解析
- 事件指令解析
- 一般指令解析
- 将解析后的 fragment 添加到 el 中显示
大括号表达式解析
- 根据正则对象得到匹配出的表达式字符串:子匹配/RegExp.$1 name
- 从 data 中取出表达式对应的属性值
- 将属性值设置为文本节点的 textContent
事件指令解析 elementNode.addEventListener(‘eventname’, callback.bind(vm), false)
- 从指令名中取出事件名
- 根据指令的值(表达式)从 methods 中得到对应的事件处理函数对象
- 给当前元素节点绑定指定事件名和回调函数的 dom 事件监听
- 指令解析完后,移除此指令属性
一般指令解析
-
得到指令名和指令值(表达式) text/html/class msg/myClass
-
从 data 中根据表达式得到对应的值
-
根据指令名确定需要操作元素节点的什么属性
- v-text—textContent 属性
- v-html—innerHTML 属性
- v-class–className 属性
-
将得到的表达式的值设置到对应的属性上
-
移除元素的指令属性
数据绑定
数据绑定
一旦更新了 data 中的某个属性数据,所有界面上直接使用或间接使用了此属性的节点都会更新。
数据劫持
- 数据劫持是 vue 中用来实现数据绑定的一种技术
- 基本思想:通过 defineProperty() 来监视 data 中所有属性(任意层次)数据的变化, 一旦变化就去更新界面
四个重要对象
实现数据的绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器 Observer,用来监听所有属性。
如果属性发生变化了,就需要告诉订阅者 Watcher 看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器 Dep 来专门收集这些订阅者,然后在监听器 Observer 和订阅 Watcher 之间进行统一管理。
接着,我们还需要有一个指令解析器 Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者 Watcher,并替换模板数据或者绑定相应的函数。此时当订阅者 Watcher 接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。
Observer(监听器)
1.用来对 data 所有属性数据进行劫持的构造函数
2.给 data 中所有属性重新定义属性描述(get/set)
3.为 data 中的每个属性创建对应的 dep 对象
Dep(Depend)
1.data 中的每个属性(所有层次)都对应一个 dep 对象
2.创建的时机:
- 在初始化 define data 中各个属性时创建对应的 dep 对象
- 在 data 中的某个属性值被设置为新的对象时
3.对象的结构
function Dep() {
// 标识属性
this.id = uid++; // 每个dep都有一个唯一的id
// 相关的所有watcher的数组
this.subs = []; //包含n个对应watcher的数组(subscribes的简写)
}
{
this.id = uid++,
this.subs = []
}
4.subs 属性说明
- 当 watcher 被创建时,内部将当前 watcher 对象添加到对应的 dep 对象的subs 中
- 当此 data 属性的值发生改变时,subs 中所有的 watcher 都会收到更新的通知,从而最终更新对应的界面
Compiler(指令解析器)
- 用来解析模板页面的对象的构造函数(一个实例)
- 利用 compile 对象解析模板页面
- 每解析一个表达式(非事件指令,如{{}}或v-text,v-html)都会创建一个对应的 watcher 对象,并建立 watcher 与 dep 的关系
- complie 与 watcher 关系:一对多的关系
Watcher(订阅者)
1.模板中每个非事件指令或表达式都对应一个 watcher 对象
2.监视当前表达式数据的变化
3.创建的时机:在初始化编译模板时(compiler中)
4.对象的组成
function Watcher(vm, exp, cb){
this.vm = vm; // vm 对象
this.exp = exp; // 对应指令的表达式
this.cb = cb; // 当表达式所对应的数据发生改变的回调函数
this.value = this.get(); // 表达式当前的值
this.depIds = {};
// 表达式中各级属性所对应的dep对象的集合对象
// 属性名为dep的id, 属性值为dep
}
总结:dep 与 watcher 的关系 --> 多对多
- data 中的一个属性对应一个 dep,一个 dep 中可能包含多个 watcher(模板中有几个表达式使用到了同一个属性)
- 模板中一个非事件表达式对应一个 watcher,一个 watcher 中可能包含多个dep(表达式是多层:a.b.c)
- 数据绑定使用到2个核心技术
- defineProperty()
- 消息订阅与发布
MVVM 原理图分析
MVVM 中会创建 Observer(用来劫持/监听所有属性)和 Compile(解析指令/大括号表达式),
Observer:要劫持就需要对应的set()方法,所以在observer中为每一个属性创建了一个 dep 对象(与 data 中的属性一一对应)
Compile:(做了两件事)
1.目的是初始化视图(显示界面),调用 updater(有很多更新节点的方法)
2.为表达式创建对应的 Watcher ,同时指定了更新节点的函数
Watcher 和 Dep 建立关系:
1.watcher 放到 dep 中(添加订阅者)
dep 中有一个 subs,是用来保存 n 个 watcher 的数组容器
2.dep 放到 watcher 中
watcher 中的 depIds 是用来保存 n 个 dep 的对象容器。为了判断 dep 与 watcher 的关系是否已经建立(防止重复的建立关系)
以上都是初始化阶段会经历的过程
更新阶段:
vm.name = ‘Tom’ 导致 data 中的数
据变化,会触发监视 data 属性的 observer 中的 set() 方法,然会它又会通知 dep,dep 会去通知它保存的所有相关的 watcher,watcher 收到信息后,其回调函数会去调用 updater 更新界面
如下图所示:(黑线是初始化阶段,红线是更新阶段)
数据绑定实现
1数据监视/劫持 defineProperty()
2订阅者 发布者
observer 发布者
dep 订阅器
**watcher 订阅者 通知更新节点**
双向数据绑定
- 双向数据绑定是建立在单向数据绑定(model==>View)的基础之上的
- 双向数据绑定的实现流程:
a. 在解析 v-model 指令时,给当前元素添加 input 监听
b. 当 input 的 value 发生改变时,将最新的值赋值给当前表达式所对应的 data 属性
路由导航守卫
导航守卫提供下面两个方面的功能
监视路由跳转 控制路由跳转
应用
在跳转页面前,进行用户权限检查限制(验证是否登录)
在界面离开前 做收尾工作
导航守卫分类
全局守卫
全局前置守卫
全局后置守卫
全局前置守卫:在准备跳转到某个路由组件之前
router.beforeEach((to, from, next)=>{
}
说明: to 将要访问的路径
from 从那个路径跳转过来
next 是一个函数 表示放行
next()执行下一个守回调 如果没有跳转到目标路由
next(false) 不执行 跳转流程在当前出中短 不会跳转到目标路由组件
全局后置守卫:在跳转到某个路由之后
router.afterEach((to, from) => {
// ...
})
组件守卫
进入前
当前路由改变
离开后
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
next(component=>{}) 回调函数访问组件对象
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
导航解析流程
导航被触发
在失活的组件里调用组件离开守卫 :beforeRouteLeave
调用全局的前置守卫:router.beforeEach
在被激活的组件里调用组件进入守卫:beforeRouteEnter 函数内部执行 next(component=>{}) 导航被确认
导航被确认
创建组件对象
调用组件通过next(component=>{}) 指定的回调函数,并将创建好的组件对象传入
调用全局的后置组件对象
使用
// 挂载路由守卫
router.beforeEach((to, from, next) => {
// to 将要访问的路径
// from 从那个路径跳转过来
// next 是一个函数 表示放行
// 如果用户访问的登录页,直接放行
if (to.path === '/login') return next()
// 从 sessionStorage 中获取到 保存的 token 值
const tokenStr = window.sessionStorage.getItem('token')
// 没有token,强制跳转到登录页
if (!tokenStr) return next('/login')
next()
})
可以一路径为参考也可以使用路由验证配置
{
path:'/userinfo',
component:userinfo,
meta:{
istoken:true
}
}
通过to.meta.istoken获取路由属性值判断
Vue事件
原生事件 vue自定义 事件 全局加载总线
什么条件下绑定的是原生DOM事件监听
1、给html原生标签绑定dom监听 <div @click=‘handleClicl’>
2、给组件标签绑定dom事件监听使用.native <My commponent @click.native=“handleClick”>
什么条件下绑定的vue自定义事件监听
1、自定义事件名 <My commponent @xxx=“handleClick2”>
2、与dom事件同名 <My commponent @click=“handleClick2”>
利用vm实现全局eventBus
1、前置知识
vue原型对象上有3个事件处理的方法 $on $emit $off $once
组件对象的原型对象是一个VM对象 组件对象可以直接访问Vue原型的方法
2、实现
创建vm作为全局事件总线 Vue.prototype.$bus=new Vue()
分发事件/传递数据的组件 this. b u s . bus. bus.emit(‘eventName’,data)
vue之自定义组件的 v-model
当用在组件上时,v-model
则会这样:
<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>
为了让它正常工作,这个组件内的 <input>
必须:
- 将其
value
attribute 绑定到一个名叫value
的 prop 上 - 在其
input
事件被触发时,将新的值通过自定义的input
事件抛出
写成代码之后是这样的:
Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})
现在 v-model
就应该可以在这个组件上完美地工作起来了:
<custom-input v-model="searchText"></custom-input>
Vue的响应式原理
基本原理
在初始化时 利用Object.defineProperty()给data属性添加setter监听数据变化
在初始化时候 每个组件的实例都有相应的watcher对象 每个属性都关联上了所有的watcher对象
在更新数据后 对应的setter调用,通知相关的watcher watcher内异步更新节点或者子组件
细节
只有data中属性是响应式对饿,只在组件对象上的属性不是响应式的
data中所i有层次的属性都是响应式的
直接在data中响应式属性对象中添加一个新的属性,默认不响应式的,需要通过Vue提供的语法添加 Vue.set(obj,propName,value)
服务器代理 CROS
正向代理
客户端代理,代理客户端,服务器端不知道实际发起请求的客户端
例子 Dev-server 使用了非常强大的 http-proxy-middleware 包
devServer:{
host: 'localhost',//target host
port: 8080,
//proxy:{'/api':{}},代理器中设置/api,项目中请求路径为/api的替换为target
proxy:{
'/api':{
target: 'http://192.168.1.30:8085',//代理地址,这里设置的地址会代替axios中设置的baseURL
changeOrigin: true,// 如果接口跨域,需要进行这个参数配置
//ws: true, // proxy websockets
//pathRewrite方法重写url
pathRewrite: {
'^/api': '/'
//pathRewrite: {'^/api': '/'} 重写之后url为 http://192.168.1.16:8085/xxxx
//pathRewrite: {'^/api': '/api'} 重写之后url为 http://192.168.1.16:8085/api/xxxx
}
}}
},
//...
}
反向代理
服务器代理 ,代理服务端 服务端不知道发起请求的客户端
例子 Nginx
CROS
服务器端添加Access-Control-Allow-Origin:"*"
debug调试
1、调试的目的
查找bug 不断缩小可以代码的范围
查看程序的运行流程
2、如何开启调试模式
添加debugger语句:程序运行前 次方式用于打包后运行的项目
添加断点 : 程序运行前或者过程中 此方式运行源码js
3、如何进行调试工作
resume 恢复程序执行(可能执行完或者进入下一个断点)
step ove单步跳转 尝试执行完当前语句 进入下一条(如果内部有断点 自动进入内部断点处)
step into 跳入 进入当前调用函数内部
step out 跳出 一次性执行完当前函数后面的语句并出去
deactivate breakpoints 使所有断点暂时失效
call stack 显示是程序函数的调用过程
scop 当前执行环境对应的作用域包含的变量数据
breakpoints 断点列表