```html
1. ### vue 渐进式框架(易用灵活高效)
在使用的时候可以 不用安装 vue-cli 不需要 babel 转义
比起 react 要轻很多 只需要直接 script 导入就可以完成 helloword
####易用
封装了比如 v-model v-if v-show v-for v-else v-html 等等 指令
作用于 dom 元素 替代原生的操作 比如 v-if 的操作
用于 document.createElement document.getElementById().remove()
来创建元素 移除 元素 其他指令后续介绍
####灵活
提供了全局 api 与组件实例 api 方便我们操作 dom 与数据流 比如 this.$refs 用来获取节点
Vue.use 拓展插件 带 Vue.xxx 的全局 api 用作于 所有的组件 关于为何用于所有的组件后续介绍
####高效
采用了单文件组件的模板 让 html js css 分开 利于维护 观察 数据流的变化
提供了组件的复用 mixin mixins provide inject props 等等可供数据传递的方法
解决组件的使用场景 比如 父子组件 子孙组件 兄弟组件 陌生组件 关系直接的数据流动
2. ###基于起步 vue 导入
####下载 js 文件导入
挂载使用 new Vue({el:'选择器'}) el 表示 document 选择器 class 类选择器 id 选择器 等等都可以
data 是对象 亦可以是函数 区别在于 函数作用域独立 不会影响各自组件
对象存在引用关系 如图 a 变量所示 所有东西都是响应式的
也可以使用
new Vue({
render: (h) => h(App), // app 组件
}).$mount("#app"); // $mount 挂载的节点
new Vue({
components: { App },
template: `<App/>`,
}).$mount("#app")
####单文件组件模板 使用 render template el(只作用于 root 组件)
如果使用多个 会根据 render > template > el 来渲染结果
因为 不管 el 还是 template 都会经过转化 变成 render 函数 来进行 虚拟 dom 的对比
虚拟 dom 不是 vue 框架特有的 可以说是借鉴了其他框架
关于虚拟 dom 也就类似于把模板里面的同名的 html 标签 比如 div p 这些当做字符串来进行正则匹配
最终处理成树结构对象字面量的形式 因为结合树的关系
提供了 类型与 this.$children this.$parent 函数来获取 父节点 与 子节点
特别注意的是 里面有很多自定义的标签 比如 hello el-input van-button u-button v-button 等等
诸如此类的组件库里面使用的组件 关于这些自定义的组件 是如何识别的
提供了一个全局 api 名字 vue.extend
let textDocument = Vue.extend({
props: {
},
methods: {
},
template: `<button @click="but"><span>as</span><child></child></button>`
})
####用于解析单文件组件
一个组件里面的模板内容 包含 同名的 html 标签字符串与自定义的组件名称
同名的标签就直接解析 自定义的组件 就通过 Vue.extend 解析
然后挂载在组件实例的 this.$children上面 然后形成了 组件有父子级 甚至可以通过不断的$parent $children
来找自己的子辈 组辈 从而追溯到树的根 this.$root 在编译的时候通过关系的追溯 从而实现从上到下的编译 从下到上的挂载
比如 如上的 button 会形成 vnode 虚拟节点 里面有 children 数组 包含了 span 标签 child 自定义组件
组件与标签会通过编译返回 对象字面量的虚拟节点 tag children text 两者很多属性都一样
不一样的是组件会有 VueComponent 自己特有的属性 比如 data 里面有特有的方法 这些方法就会去启动生命周期函数 与响应式数据拦截
3. ### 如何实现响应式
data 函数返回 对象 被 Object.defineProperty 做数据拦截
触发了 VueComponent 的观察者 它通知组件 做渲染
不管是 v-model 还是 input 等等改变数据的方法 如果没有在 data 里面写入 观察者就不会去做拦截
组件也就不会刷新 有一些特定的场景下面也存在不刷新页面的情况
####讨论响应式的原理
Vue.observable( object )
让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象。
返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新
这个 api 会把 data 数据做拦截
使用 Object.defineProperty 把这些 property 全部转为 getter/setter。
Object.defineProperty 是 ES5 中一个无法 支持 的特性,
这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因
在转 getter 的时候 会把 data 里的属性与 value 存在一个 Dep 依赖数组里面 根据存进去加的唯一值 key
let data = function () { // 数组的 data
return {
message: 'Hello Vue!',
text: 123
}
}
let data1 = data()
let c = {} // c 用于数据拦截的对象 也是替换模板 监听 渲染 变量 的 实质对象
Object.keys(data1).map(res => {
Object.defineProperty(c, res, {
enumerable: true,
configurable: true,
get() { return data1[res]; }, // 获取初始化指 会存入 Dep 数组
set(newValue) { data1[res] = newValue; console.log('改变', newValue); },// 设置属性 dep.notify();触发通知
})
})
// 设置属性 dep.notify();触发通知 告诉 watcher 对象 去执行 run 函数 触发 update 函数
// 触发 update 函数 => 再走一遍初始化的流程 把变量 替换 成最新的
if (!config.async) {
flushSchedulerQueue();// resetSchedulerState();callActivatedHooks(activatedQueue);
callUpdatedHooks(updatedQueue);
return;
}
nextTick(flushSchedulerQueue);
// Vue.prototype.$forceUpdate = function () {
var vm = this; //组件实例
if (vm.\_watcher) {
vm.\_watcher.update();
}
};
4. ### 响应式的失效
如上我们知道 之所以有响应式 是 因为 数据拦截 触发 update 函数 页面同步渲染
失效 也就是 有时候 我们的操作 不会 进行数据拦截 由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。
尽管如此我们还是有一些办法来回避这些限制并保证它们的响应性。
对于对象 新增 属性 this.$set(this.someObject,'b',2)
对于数组 this.$set(arr, index, newValue) 修改 数组 指定位置
删除对象 与 属性 this.$delete(obj,key)
其实对于数组 我们通常 使用 新数组 替换 旧数组 来 保证 响应式
####使用了 object.freeze()
function observe(value, asRootData) {
if (!isObject(value) || value instanceof VNode) {
return;
}
var ob;
if (hasOwn(value, "**ob**") && value.**ob** instanceof Observer) {
ob = value.**ob**;
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value.\_isVue
) { // 如果不具有 Object.isExtensible 也就是不能修改的对象
ob = new Observer(value);
}
if (asRootData && ob) {
ob.vmCount++;
}
return ob; // 直接返回结果
}
5. ### 生命周期函数
####单组件
beforeCreate 把 vue 实例初始化,数据方法还没有加载
created 已经加载数据方法
beforeMount 模板数据已经编译
mounted 渲染视图
前面四个是组件初始化加载经过的生命周期函数
beforeUpdate 没有修改数据
updated 修改之后
beforeDestroy 组件销毁之前
destroyed 销毁组件 这个经常用于切换各个组件销毁定时器
deactivated 缓存组件激活
activated 缓存组件失活 外加一个错误捕获 errorCaptured 当捕获一个来自子孙组件的错误时被调用
####多组件
解析:Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:
加载渲染过程 : 父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created ->子 beforeMount -> 子 mounted -> 父 mounted
子组件更新过程 : 父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
父组件更新过程 : 父 beforeUpdate -> 父 updated
销毁过程 : 父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
6. ### 模板语法 指令 事件 按键修饰符(不会被最新浏览器支持)
7. ### 计算属性 来源于 响应式 data 数据的处理结果 根据不同的需求处理不一样的结果
比如 一个数组 let arr = [{sex:0,name:'小美'},{sex:1,name:'小明'}]
computed: {
boy: function(){
return this.arr.filter(res=>{return res.sex === 1})
},
girl:function(){
return this.arr.filter(res=>{return res.sex === 0})
},
}
8. ###侦听器
监听的范围:
props,
inject
当前组件的 data
计算属性
路由信息$route
vuex 的初始化 getter
包含所以的数据类型
数组监听下标
对象可以表示为 key
‘a.b’
#### 计算属性与侦听器对比
计算属性是通过 data 里面的变量综合处理返回后的结果 避免了模板里面逻辑过重
侦听器是监听 data 里面某一个变量来触发后续变化
使用场景 计算属性 用于处理数据返回后的结果
侦听器通常建议是用于两种三种状态的切换 比如 1 2 3 三种结果可预期的
true 与 false 这样的值
但有时候不是状态的切换 但一定是可预期的结果 比如你知道变化后的数据类型等等
9. ### 绑定 html class style
表达形式 数组 对象 字符串
<div :class="class1" :style="style">asd1</div>
<tep :class="class1" a="1" b="1" id="1" />
class1: ['a', 'b', { c: false }],
style等于内置style样式 数组语法
只不过 font-size 变化
fontSize
如此类似
css 变量
style: {
border: '1px solid red'
}
10. ### 条件渲染 v-if v-show v-for
不推荐一起用 因为 每走一次 for 循环都会去执行 if 所以当你需要过滤数据展示
你完全可以使用之前提过的计算属性 或者 你需要判断 数据 存在 后执行循环
建议把 if 提出来 注意 v-for 与 key 必须一起用 有时候可以不用 但是
存在数据修改 一定用 所以别忘记 因为 vue 对比新旧节点是通过 key 来的
不建议使用 index 作为 key 因为数组变化 index 就已经不是之前的 index 会导致页面错误变化
if 建议是控制元素在页面展示只有一种状态时使用比如存在 与不存在
show 是切换页面使用控制隐藏显示
11. ### 数组更新检测
可以改变原数组的方法 会被 vue 监听响应式
push pop unshift shift sort reserve splice
除去以上方法 其他都不改变原数组 除了存在引用关系例外
filter()、concat() 和 slice() 等等之类的 返回新组件 替换数组
然后这些 concat slice 就不会响应式 所以把新数组替换 data 的原数组
12. ### 监听事件 v-on 对应 onClick 原生监听操作
13. ### 表单输入绑定 v-model 的使用
input 是 文本字符串
radio 是 布尔值
复选框 是数组
表单 form 里面 不建议使用 button 因为默认会提交 通常都是 div 自行画的按钮
由于登录之后 浏览器存在 默认行为 会自动 填充 输入框 所以需要注意
修饰符.lazy .trim .number 这是处理输入文本
#### 组件的使用
<input v-model=“”/> 如果是在原生标签上面使用默认是 value 与@input 事件也就是原生
js 的 oninput 与 e.target.value 和 value 实现一样的功能 但是有时候作用于复选框下拉框
我们原生实现需要不同的写法 但 v-model 在编译的时候通过判断标签属性来动态处理
基础组件指不与原生标签同名的标签 比如 my-input 之类的 自定义 在自定义上面使用
v-model 就如上图所示 出现了 props 与 model 结合的案例
就可以把自定义的 input 的组件
当做 input 来实现
model 有两个属性 prop 与 props 对应
event 这个默认是 change 可以更改 然后子组件触发 this.$emit('change')
14. ### .sync 修饰符
避免修改 props 的错误 提供了这个方法来改变父组件 @update:xxx xxx="a" this.$emit('update:xxx','x')
15. ### 插槽 默认插槽 具名插槽 插槽作用域
插槽 是父组件 分发给子组件的内容 切记 切记
<template>
<div id="app">
<ss>
<div>默认插槽</div>
<div slot="app">hello</div>
<div slot="data" slot-scope="user">//插槽作用域起了一个别名
//类似于export default 封装方法与属性
<span v-for="a in user.data" :key="a.id">{{a.name}}</span>
</div>
</ss>
</div>
</template>
子组件
默认插槽当父组件是除了具名插槽什么都没有才显示
<template>
<div>
<slot>123q</slot>//默认插槽没有name
<slot name="app">hello</slot>//具名插槽对应父组件slot="app"这个属性
<slot name="data" :data="data"></slot>//具名插槽作用域
//子组件使用slot绑定data数据,使用别名
//在父组件调用子组件里面使用slot-scope插槽作用域来获取子组件数据
</div>
</template>
<script>
export default {
data(){
return {
msg:"",
data:[{id:1,name:"user"},{id:2,name:"pass"}]
}
}
}
</script>
16. ### 进入/离开 & 列表过渡 动画 结合 animate.css 库 一起用 可以尝试 官网推荐
17. ### 混入 分组件混入 全局混入
数据以组件优先 钩子函数
会合并 命名 data 与 methods
不要冲突不然以组件的优先
覆盖
18. ### 自定义指令 分全局与 组件内部
19. ### 单文件组件
20. ### 路由配置
import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";
Vue.use(VueRouter);
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/404",
name: "404",
component: 404,
},
{
path: "/about",
name: "About",
props: (route) => {
return { query: route.query, params: route.params };
}, // this.$attrs {params: {},query: {a: "1"}}
component: () => import("../views/About.vue"),
component: function(resolve) {
return require(["../views/index.vue"], resolve); // 异步组件
},
},
{
path: '\*',
redirect: '/404'
}
];
const router = new VueRouter({
mode: "hash",
base: process.env.BASE_URL,
routes:[],
});
export default router;
21. ### 路由跳转 守卫 传参 获取
beforeEach 还没进入页面 to from next 是一个函数 next执行进去页面
beforeRouteEnter(to, from, next) {
next({ path: "/about" });
},
路由跳转 push replace go back forward resolve // location.replace
全局守卫 beforeEach afterEach // 权限判断
路由独享 beforeEnter
组件守卫 beforeRouteEnter beforeRouteLeave beforeRouteUpdate
addRoutes 新增路径 参数是一个数组 用于 把权限页面路由 与基础路由做合并
push({path:'',query:{}})
push({name: '',params:{}})
// 组件守卫 beforeRouteEnter beforeRouteLeave beforeRouteUpdate
// 通过在VueRouter进行合并策略 可以使全部组件里面与守卫函数一样的同名函数具有一样的效果
Vue.config.optionMergeStrategies._my_option = function (parent, child, vm) {
console.log(child)
return child + 1
}
const Profile = Vue.extend({
_my_option: 1
})
console.log(Profile.options._my_option)
// Profile.options._my_option = 2
Vue.mixin({
beforeCreate: function beforeCreate () {
if (isDef(this.$options.router)) { // this.$options.router 根组件
this._routerRoot = this;
this._router = this.$options.router;
this._router.init(this);
Vue.util.defineReactive(this, '_route', this._router.history.current);
} else { // 根组件 下面的 子节点组件 都是通过获取父级信息来新增路由控制
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this;
}
registerInstance(this, this);
},
destroyed: function destroyed () {
registerInstance(this);
}
});
Object.defineProperty(Vue.prototype, '$router', {
get: function get () { return this._routerRoot._router }
}); // 由于组件在实例化的过程 是通过 Vue.extend 来实现的 所以this是指向一直都是Vue 位于Vue 原型上面的东西 也可以通过原型链向上查找 获取到对象
Object.defineProperty(Vue.prototype, '$route', {
get: function get () { return this._routerRoot._route }
});
Vue.component('RouterView', View);
// 这个View 是函数组件functional = true 然后组件实例化的过程 已经有路由对象
// 所以这是封装后的内置组件 通过之前初始化路由生成的 nameMap listMap pathMap
// 也就是我们配置的routes 路由列表 然后根据 component参数与path 渲染出对应的/// render函数 形成虚拟的节点 最终转化成 dom 形成页面
Vue.component('RouterLink', Link);
// 这个是普通的render函数渲染的组件 通过已经存在的 路由对象
// 提供参数方便我们调转 与函数手动调转 一致
var strats = Vue.config.optionMergeStrategies;
// 合并策略 在路由初始化激活的时候 就 给到了 内置 函数
// 合并组件同名函数 提供权限判断
strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created;
this.matcher = createMatcher(options.routes || [], this);// 路由配置列表
options.routes => routes:[]// 路由配置列表
var ref = createRouteMap(routes);
// 解析 路由配置列表 递归子路由 执行addRouteRecord
// 然后生成nameMap listMap pathMap
routes.forEach(function (route) {
addRouteRecord(pathList, pathMap, nameMap, route, parentRoute);
});
route.children
addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs);
function addRouteRecord (
pathList,
pathMap,
nameMap,
route,
parent,
matchAs
) {
var path = route.path;
var name = route.name;
var record = {
path: normalizedPath,
regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
components: route.components || { default: route.component },
alias: route.alias
? typeof route.alias === 'string'
? [route.alias]
: route.alias
: [],
instances: {},
enteredCbs: {},
name: name,
parent: parent,
matchAs: matchAs,
redirect: route.redirect,
beforeEnter: route.beforeEnter,
meta: route.meta || {},
props:
route.props == null
? {}
: route.components
? route.props
: { default: route.props }
};
if (route.children) {
{
if (
route.name &&
!route.redirect &&
route.children.some(function (child) { return /^\/?$/.test(child.path); })
) {
// warn
}
}
route.children.forEach(function (child) {
var childMatchAs = matchAs
? cleanPath((matchAs + "/" + (child.path)))
: undefined;
addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs);
});
}
if (!pathMap[record.path]) {
pathList.push(record.path);
pathMap[record.path] = record;
}
if (route.alias !== undefined) {
var aliases = Array.isArray(route.alias) ? route.alias : [route.alias];
for (var i = 0; i < aliases.length; ++i) {
var alias = aliases[i];
if ( alias === path) {
// warn
continue
}
var aliasRoute = {
path: alias,
children: route.children
};
addRouteRecord(
pathList,
pathMap,
nameMap,
aliasRoute,
parent,
record.path || '/' // matchAs
);
}
}
if (name) {
if (!nameMap[name]) {
nameMap[name] = record;
} else if ( !matchAs) {
// warn
}
}
}
22. ### vuex
import Vue from "vue";
import vuex from "vuex";
Vue.use(vuex);
const state = {
// 仓库
msg: "欢迎你 我的朋友",
dev: process.env.NODE_ENV,
};
const mutations = {
// 同步提交
mutHello: function(stata, data) {
console.log(stata, data);
stata.msg = "123";
},
};
const actions = {
// 分发 dispatch
actHello: function({ commit }, data) {
commit("mutHello", data);
},
};
const getters = {
// 获取属性
hello(sta) {
return sta.msg;
},
};
const ma = {
// 模块
state: { num: 1 },
mutations: {
changeNum: function(sta, date) {
console.log(date);
sta.num++;
},
},
actions: {
actChangeNum({ commit }, date) {
commit("changeNum", date);
},
},
getters: {
amin: function(sta) {
return sta.num + "动画";
},
},
namespaced: true,
};
const modules = {
// 模块
ma,
};
let store = new vuex.Store({
state,
mutations,
actions,
getters,
modules,
});
store.subscribeAction((fn, state) => {
// 监听异步
console.log(fn, state);
});
store.subscribe((mutation, state) => {
// 监听同步
console.log(mutation); //
console.log(state);
});
store.watch(
(state, getters) => state.msg,
(res) => {
console.log("loaded", res); // 监听仓库
}
);
export default store;
mapState: mapState,
mapMutations: mapMutations,
mapGetters: mapGetters,
mapActions: mapActions,
createNamespacedHelpers: createNamespacedHelpers,
// 同步 操作 必须这样改 配合 vue-devtools 调试工具 使用
// 虽说可以直接 赋值 但 不会改变状态 不妥
$store.commit('ma/changeNum'); => createNamespacedHelpers('ma') mapMutations(['changeNum'])
$store.dispatch('ma/actChangeNum'); => createNamespacedHelpers('ma') mapActions(['actChangeNum'])
$store.commit('mutHello') => mapMutations(['mutHello'])
$store.commit('actHello') => mapMutations(['actHello'])
Vue.mixin({ beforeCreate: vuexInit });
function vuexInit () { // 和路由一样的 初始化 子组件获取根节点组件 下一级获取父级
var options = this.$options;
// store injection
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store;
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store;
}
}
23. ### sync
<template>
<div>12{{ title }}</div>
</template>
<script>
export default {
destroyed() {
console.log("as");
},
props: {
title: {
type: String,
},
}, // 可以不写这个 使用 $attrs.title
created() {
this.$emit("update:title", "asa a");
},
// delimiters 改变模板{{}}
};
</script>
<style></style>