Github 源码 :https://github.com/OYCodeSite/VueCode.git
vue 环境搭建
一、初始化项目
- 生成 package.json
1 | yarn init -y |
- 创建入口 js: src/index.js
1 | console.log('Hello Webpack!') |
- 创建页面文件: index.html
1 | <div id="root"></div> |
二、webpack 基本使用
- 下载依赖包
1 | yarn add -D webpack webpack-cli |
- 创建 webpack 配置: webpack.config.js
1 | const path = require('path') |
- 生成环境打包并运行
1 | 配置打包命令: "build": "webpack --mode production" |
三、开发环境运行
- 现在的问题
1 | 每次修改项目代码后, 必须重新打包, 重新运行 |
- 下载依赖包
1 | yarn add -D webpack-dev-server |
- 配置开发服务器
1 | devServer: { open: true, ? // 自动打开浏览器 |
- 配置开启 source-map 调试
1 | devtool: "cheap-module-eval-source-map" |
- 开发环境运行
1 | 配置命令: "dev": "webpack-dev-server --mode development" |
四、打包处理 ES6/CSS/图片
- 处理 ES6
1 | a. 下载依赖包 |
- 处理 CSS
1 | a. 下载依赖包 |
- 处理图片
1 | a. 下载依赖包 |
- 测试
1 | a. 添加图片: src/assets/imgs/logo.png |
五、搭建 vue 的环境
1 | 1). 文档: |
六、解决开发环境 ajax 请求跨域问题
- 利用 webpack-dev-server 进行请求代理转发
1 | webpack-dev-server内部利用http-proxy-middle包对特定请求进行转发操作 |
- 配置:
1 | devServer: { |
1 | 1). 利用webpack-dev-server进行请求代理转发 |
七、配置 async/await 的编译环境
- 下载包
1 | yarn add @babel/runtime-corejs2 |
- 配置
1 | presets: [ |
八、解决 mint-ui 按需引入配置异常的问题
- 文档上的配置
1 | "plugins": [ |
- 异常信息
1 | Error: .plugins[0][1] must be an object, false, or undefined |
- 原因
1 | 文档编写时, 是根据老的babel版本编写的, 新版本的babel配置有变化 |
- 修正
1 | "plugins": [ |
九、解决 history 模式路由请求 404 的问题
1 | devServer: historyApiFallback: true, // 任意的 404 响应都被替代为 index.html |
vue 组件化
一、vue 单文件组件
1 | <template> |
二、组件化编码的基本流程
拆分界面, 抽取组件
编写静态组件
编写动态组件
- 初始化数据, 动态显示初始化界面
- 实现与用户交互功能
设计 data
类型: [{id: 1, title: ‘xxx’, completed: false}]
名称: todos
位置: 如果只是哪个组件用, 交给它, 如果是哪些组件用, 交给共同的父组件关于状态数据的更新
data 数据定义在哪个组件, 更新数据的行为就定义在哪个组件
如果子组件要更新父组件的数据, 调用父组件的更新函数来更新父组件的数据
一个组件接收属性数据不要直接修改, 只是用来读取显示的
三、组件间通信
- 组件通信的 5 种方式
props
vue 的自定义事件
全局事件总线
slot
vuex
- props
1 | 父子组件间通信的基本方式 |
- vue 自定义事件
- 给子组件标签绑定事件监听
- 子组件向父组件的通信方式
- 功能类似于 function props
- 不适合隔层组件和兄弟组件间的通信
- 全局事件总线
- 利用 vm 对象的$on()/$emit()/$off()
- 利用 vm 对象是组件对象的原型对象
- 创建 vm 对象作为全局事件总线对象保存到 Vue 的原型对象上, 所有的组件对象都可以直接可见:
- Vue.prototype.$bus = new Vue()
- 任意组件 A 可以通过 this.$bus.$on()绑定监听接收数据
- 任意组件 B 可以通过 this.$bus.$emit()分发事件, 传递数据
- slot
- 父组件向子组件通信
- 通信是带数据的标签
注意
: ==标签是在父组件中解析==
- vuex
- 多组件共享状态(数据的管理)
- 组件间的关系也没有限制
- 功能比事件总线强大, 更适用于 vue 项目
vue 单文件组件
1 | <template> |
vue-ajax
一、vue 项目中常用的 2 个 ajax 库
vue-resource
vue 插件,非官方库, vue1.x 使用广泛
在线文档:https://github.com/pagekit/vue-resource/blob/develop/docs/http.md
axios
通用的 ajax 请求库,官方推荐, vue2.x 使用广泛
在线文档:https://github.com/pagekit/vue-resource/blob/develop/docs/http.md
安装插件:
1 | // yarn 安装 |
二、vue-resource、axios 的使用
案例效果
vue-resource:
编码:
index.js
1 | import Vue from "vue"; |
APP.vue
1 | <template> |
axios
index.js
1 | import Vue from "vue"; |
APP.vue
1 | <template> |
三、使用 express 快速搭建后台接口
编码:
server.js
1 | /* |
测试:
启动 express: node server.js
vuex
一、vuex 的核心概念
1、state
1 | vuex管理的状态对象 它应该是唯一的 const state = { xxx: initValue } |
2、mutations
1 | 包含多个直接更新state的方法(回调函数)的对象 谁来触发: |
3、actions
1 | 包含多个事件回调函数的对象 通过执行: commit()来触发mutation的调用, 间接更新state |
4、getters
1 | 包含多个计算属性(getter)的对象 谁来读取: 组件中: $store.getters.xxx const |
5、modules
包含多个module的对象
一个module是一个包含state/mutations/actions/getters的对象
是将一复杂应用的vuex代码进行多模块拆分的第2种方式
6、store
vuex的核心管理对象, 是组件与vuex通信的中间人
读取数据的属性
state: 包含最新状态数据的对象
getters: 包含getter计算属性的对象
更新数据的方法
dispatch(): 分发调用action
commit(): 提交调用mutation
二、使用 vuex
1. 创建并向外暴露 store 对象
export default new Vuex.Store({
state,
mutations,
actions,
getters,
modules: {
a,
b
}
})
2. 注册 store: main.js
import store from './store'
new Vue({
store
})
3. 组件中通过 store 与 vuex 通信
import {mapState, mapGetters} from 'vuex'
export default {
computed: (
...mapState(['xxx']),
...mapGetters(['yyy'])
)
methods: {
test () {
this.$store.dispatch('zzz', data)
this.$store.commit('zzz', data)
}
}
}
三、 Vuex 结构图
Vue-router
一、vue-router 的基本使用
1). 创建路由器: router/index.js
new VueRouter({
mode: 'hash/history'
routes: [
{ // 一般路由
path: '/about',
component: About
},
{ // 自动跳转路由
path: '/',
redirect: '/about'
}
]
})
2). 注册路由器: main.js
import router from './router'
new Vue({
router
})
3). 使用路由组件标签:
<router-link to="/xxx">Go to XXX</router-link> // 可以不使用
<router-view></router-view> // 必须使用
4). 2个对象
$router: 代表路由器对象, 包含一些实现路由跳转/导航的方法: push()/replace()/back()
$route: 代表当前路由对象, 包含一些路由相关的属性: path/params/query/meta
二、编写路由的 3 步
定义路由组件
映射路由
使用
显示当前路由组件
三、 嵌套路由
children: [
{
path: '/home/news/:id/:title',
component: news
},
{
path: 'message',
component: message
}
]
四、 向路由组件传递数据
params/query: <router-link to="/home/news/123/abc?zzz=1234">
将请求参数映射成props: props=true | props: route => ({id: route.params.id})
变相props: <router-view msg='abc'>
五、动态路由与路由别名
注册路由:
{
name: 'news'
path: '/home/news/:id/:title',
component: News
}
跳转:
<router-link to="{name: 'news', params: {id: 1, title: 'abc'}}">
router.push({name: 'news', params: {id: 1, title: 'abc'}})
六、缓存路由组件
路由组件对象默认的生命周期: 被切换时就会死亡, 切换回来时重新创建
<keep-alive exlude="A,B">
<router-view></router-view>
</keep-alive>
七、 路由的编程式导航
this.$router.push(path): 相当于点击路由链接(可以返回到当前路由界面)
this.$router.replace(path): 用新路由替换当前路由(不可以返回到当前路由界面)
this.$router.back(): 请求(返回)上一个记录路由
八、 路由的 2 种模式比较, 解决 history 模式 404 问题
hash模式:
路径中带#: http://localhost:8080/#/home/news
发请求的路径: http://localhost:8080 项目根路径
响应: 返回的总是index页面 ==> path部分(/home/news)被解析为前台路由路径
history模式:
路径中不带#: http://localhost:8080/home/news
发请求的路径: http://localhost:8080/home/news
响应: 404错误
希望: 如果没有对应的资源, 返回index页面, path部分(/home/news)被解析为前台路由路径
解决: 添加配置
devServer: historyApiFallback: true, // 任意的 404 响应都被替代为 index.html
output: publicPath: '/', // 引入打包的文件时路径以/开头
Vue 源码分析
一、debug 调试
调试的目的
查找 bug: 不断缩小可疑代码的范围
查看程序的运行流程(用于熟悉新接手项目的代码)
如何开启调试模式
添加 debugger 语句: 程序运行前 此方式用打包后才运行的项目
添加(打)断点: 程序运行前或者过程中 此方式用运行源码 js
如何进行调试操作
resume: 恢复程序执行(可能执行完或者进入下一个断点处)
step over: 单步跳转, 尝试执行完当前语句, 进入下一条(如果内部有断点, 自动进入内部断点处)
step into: 跳入, 进入当前调用函数内部
step out: 跳出, 一次性执行完当前函数后面所有语句,并出去
deactivate breakpoints: 使所有断点暂时失效call stack: 显示是程序函数调用的过程
scope: 当前执行环境对应的作用域中包含的变量数据
breakpoints: 断点列表
二、准备
- [].slice.call(lis): 将伪数组转换为真数组
- node.nodeType: 得到节点类型
- Object.defineProperty(obj, propertyName, {}): 给对象添加/修改属性(指定描述符)
- configurable: true/false 是否可以重新 define
- enumerable: true/false 是否可以枚举(for..in / keys())
- value: 指定初始值
- writable: true/false value 是否可以修改存取(访问)描述符
- get: 函数, 用来得到当前属性值
- set: 函数, 用来监视当前属性值的变化
- Object.keys(obj): 得到对象自身可枚举的属性名的数组
- DocumentFragment: 文档碎片(高效批量更新多个节点)
- obj.hasOwnProperty(prop): 判断 prop 是否是 obj 自身的属性
三、数据代理(MVVM.js)
通过一个对象代理对另一个对象中属性的操作(读/写)
通过 vm 对象来代理 data 对象中所有属性的操作
好处: 更方便的操作 data 中的数据
基本实现流程
- 通过 Object.defineProperty()给 vm 添加与 data 对象的属性对应的属性描述符
- 所有添加的属性都包含 getter/setter
- 在 getter/setter 内部去操作 data 中对应的属性数据
四、模板解析(compile.js)
1.模板解析的关键对象: compile 对象 2.模板解析的基本流程:
将 el 的所有子节点取出, 添加到一个新建的文档 fragment 对象中
对 fragment 中的所有层次子节点递归进行编译解析处理
- 对插值文本节点进行解析
- 对元素节点的指令属性进行解析
- 事件指令解析
- 一般指令解析
将解析后的 fragment 添加到 el 中显示
3.解析插值语法节点: textNode.textContent = value
根据正则对象得到匹配出的表达式字符串: 子匹配/RegExp.$1
从 data 中取出表达式对应的属性值
将属性值设置为 文本节点的 textConten
4.事件指令解析: elementNode.addEventListener(‘eventName’, callback.bind(vm))
从指令名中取出事件名
根据指令属性值(表达式)从 methods 中得到对应的事件处理函数对象
给当前元素节点绑定指定事件名和回调函数的 dom 事件监听
指令解析完后, 移除此指令属性
5.一般指令解析: elementNode.xxx = value
得到指令名和指令值(表达式)
从 data 中根据表达式得到对应的值
根据指令名确定需要操作元素节点的什么属性
- v-text—textContent 属性
- v-html—innerHTML 属性
- v-class–className 属性
五、数据劫持–>数据绑定
数据绑定(model==>View)
一旦更新了 data 中的某个属性数据, 所有界面上直接使用或间接使用了此属性的节点都会更新(更新)
数据劫持
- 数据劫持是 vue 中用来实现数据绑定的一种技术
- 基本思想: 通过 defineProperty()来监视 data 中所有属性(任意层次)数据的变化, 一旦变化就去更新界面
四个重要对象
Observer
用来对 data 所有属性数据进行劫持的构造函数
- 给 data 中所有属性重新定义属性描述(get/set)
- 为 data 中的每个属性创建对应的 dep 对象
- 给 data 中所有属性重新定义属性描述(get/set)
Dep(Depend)
data 中的每个属性(所有层次)都对应一个 dep 对象
创建的时机:
在初始化 define data 中各个属性时创建对应的 dep 对象
1
2
3
4
5
6在data中的某个属性值被设置为新的对象时
对象的结构
{
id, // 每个dep都有一个唯一的id
subs //包含n个对应watcher的数组(subscribes的简写)
}
subs 属性说明
当一个 watcher 被创建时, 内部会将当前 watcher 对象添加到对应的 dep 对象的 subs 中
当此 data 属性的值发生改变时, 所有 subs 中的 watcher 都会收到更新的通知, 从而最终更新对应的界面
Compile
- 用来解析模板页面的对象的构造函数(一个实例)
- 利用 compile 对象解析模板页面
- 每解析一个表达式(非事件指令)都会创建一个对应的 watcher 对象, 并建立 watcher 与 dep 的关系
- complie 与 watcher 关系: 一对多的关系
Watcher
- 模板中每个非事件指令或表达式都对应一个 watcher 对象
- 监视当前表达式数据的变化
- 创建的时机: 在初始化编译模板时
- 对象的组成
1 | { vm, //vm对象 exp, //对应指令的表达式 cb, |
总结: dep 与 watcher 的关系: 多对多
- 一个 data 中的属性对应对应一个 dep, 一个 dep 中可能包含多个 watcher(模板中有几个表达式使用到了属性)
- 模板中一个非事件表达式对应一个 watcher, 一个 watcher 中可能包含多个 dep(表达式中包含了几个 data 属性)
- 数据绑定使用到 2 个核心技术
- defineProperty()
- 订阅者-发布者
- defineProperty()
双向数据绑定
- 双向数据绑定是建立在单向数据绑定(model==>View)的基础之上的
- 双向数据绑定的实现流程:
- 在解析 v-model 指令时, 给当前元素添加 input 监听
- 当 input 的 value 发生改变时, 将最新的值赋值给当前表达式所对应的 data 属性
Vue CLI
一、安装
1 | npm install -g @vue/cli |
二、vue 的脚手架
- 脚手架 V2: 相对老的项目
- 脚手架 v3: 新的项目
- 脚手架 v4: 最新的(当前用得还少)
三、使用 vue 的脚手架
脚手架 v3
1 | npm install -g @vue/cli |
脚手架 v2
Vue CLI >= 3 和旧版使用了相同的 vue
命令,所以 Vue CLI 2 (vue-cli
) 被覆盖了。如果你仍然需要使用旧版本的 vue init
功能,你可以全局安装一个桥接工具:
1 | npm install -g @vue/cli-init |
四、比较 V2 与 V3 创建的项目
- v2 的配置是直接可见, v3 是包装隐藏起来了
- 修改配置: v2 是直接在配置文件中修改, v3 提供了一个专门的配置: vue.config.js, 我们可以根据文档在此文件中添加配置
- vue 使用的是不带编译器的版本, 打包文件更小 不写 template 配置, 直接 render 配置