前言
上家公司的项目主要是使用 jQuery 和 Angular1,然后自己学了 React,没想到来到这家公司突然开始做 vue,不过 vue 还是挺容易上手的。下面是 vue 技术栈的一些总结,都是来自官网,主要是自己对 vue 技术栈知识点的一些整理,因此此文很水,建议阅读我的上一篇文章Vuejs 技术栈从 CLI 到打包上线实战全解析
Vue
独立构建和运行时构建
有两种构建方式,独立构建和运行构建。它们的区别在于前者包含模板编译器而后者不包含。
模板编译器的职责是将模板字符串编译为纯 JavaScript 的渲染函数。如果你想要在组件中使用 template 选项,你就需要编译器。
生命周期
具体查看官网的流程图,要注意的是 created 和 mounted 区别,created 是 vm 实例已创建但未挂载,因此一些 DOM 操作应该放在 mounted 中。异步请求放在 created 或者 mounted 暂时没发现什么区别,如您知道有什么区别,请评论指出。
计算(computed)属性
模板内的表达式不应该包含太多的逻辑,对于任何复杂逻辑,都应当使用计算属性
computed 属性和 methods 不同的是计算属性是基于它们的依赖进行缓存的。
computed 属性和 computed 属性,通常更好的想法是使用 computed 属性而不是命令式的 watch 回调。虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的 watcher。当你想要在数据变化响应时,执行异步操作或开销较大的操作,这是很有用的。
数组更新检测
数组的变异方法(mutation method,会改变被这些方法调用的原始数组)会触发视图更新,有以下七个:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
当使用非变异方法时,可以用新数组替换旧数组,或者使用 Vue.set 方法。
对象更新
可以用新对象替换旧对象,或者使用 Vue.set 方法
Vue.set(vm.someObject, 'foo', 'bar');
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 });
事件处理器
Vue.js 为 v-on 提供了事件修饰符和按键修饰符
表单控件绑定
可以用 v-model 指令在表单控件元素上创建双向数据绑定。常见修饰符有.lazy、.number、.trim。
也可以使用自定义事件的表单输入组件。
组件
Vue 组件的 API 来自三部分:props,events 和 slots:
- Props 允许外部环境传递数据给组件
- Events 允许组件触发外部环境的副作用
- Slots 允许外部环境将额外的内容组合在组件中。
1)组件的 data 属性必须是函数
2)父子组件
在 Vue.js 中,父子组件的关系可以总结为 props down, events up 。父组件通过 props 向下传递数据给子组件,子组件通过 events 给父组件发送消息。
prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件无意修改了父组件的状态——这会让应用的数据流难以理解。
另外,每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着你不应该在子组件内部改变 prop。如果你这么做了,Vue 会在控制台给出警告。
为什么我们会有修改 prop 中数据的冲动呢?通常是这两种原因:
1.prop 作为初始值传入后,子组件想把它当作局部数据来用;
2.prop 作为初始值传入,由子组件处理成其它数据输出。
对这两种原因,正确的应对方式是:
1.定义一个局部变量,并用 prop 的值初始化它:
props: ['initialCounter'],
data: function () {
return { counter: this.initialCounter }
}
2.定义一个计算属性,处理 prop 的值并返回。
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
注意在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。
3)非父子组件
有时候两个组件也需要通信(非父子关系)。在简单的场景下,可以使用一个空的 Vue 实例作为中央事件总线。在复杂的情况下,我们应该考虑使用专门的状态管理模式。
4).sync 修饰符
在一些情况下,我们可能会需要对一个 prop 进行『双向绑定』。
2.0 中移除了.sync,Vue2.3.0+又将其添加回来了,但是这次它只是作为一个编译时的语法糖存在,它会被扩展为一个自动更新父组件属性的 v-on 侦听器。如下代码
<comp :foo.sync="bar"></comp>
会被扩展为:
<comp :foo="bar" @update:foo="val => bar = val"></comp>
当子组件需要更新 foo 的值时,它需要显式地触发一个更新事件:
this.$emit('update:foo', newValue);
5)使用 slot 进行内容分发
作用域插槽:接收从子组件中传递的 prop 对象。作用域插槽更具代表性的用例是列表组件,允许组件自定义应该如何渲染列表每一项
6)动态组件、is 特性和 keep-alive 指令
7)子组件索引
尽管有 props 和 events,但是有时仍然需要 JavaScript 中直接访问子组件。为此可以使用 ref 为子组件指定一个索引 ID。
异步更新队列
虽然 Vue.js 通常鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们确实要这么做。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。
过渡效果
Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果。包括以下工具:
- 在 CSS 过渡和动画中自动应用 class
- 可以配合使用第三方 CSS 动画库,如 Animate.css
- 在过渡钩子函数中使用 JavaScript 直接操作 DOM
- 可以配合使用第三方 JavaScript 动画库,如 Velocity.js
1)单元素/组件的过渡
Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加过渡
- 条件渲染(使用 v-if)
- 条件展示(使用 v-show)
- 动态组件
- 组件根节点
2)多个元素的过渡
对于原生标签可以使用 v-if/v-else
3)多个组件的过渡
多个组件的过渡我们可以使用动态组件。
4)列表过渡
Render 函数和 JSX
自定义指令
和 Angular 的指令类似,主要操作 DOM,下面是一个滚动加载的指令,holder 暂时没想到什么更好的处理方法:
let scrollCallback = function (callback) {
let windowH = window.innerHeight;
let getDocumentHeight = function () {
var body = document.body;
var html = document.documentElement;
return Math.max(
body.offsetHeight,
body.scrollHeight,
html.clientHeight,
html.offsetHeight,
html.scrollHeight
);
};
let scrollH = document.documentElement.scrollTop || document.body.scrollTop;
if (windowH + scrollH >= getDocumentHeight() - (this.holder || 20)) {
callback();
}
};
let callBackWarpped;
export default {
bind(el, binding, vnode) {
let holder;
if (
vnode.data &&
vnode.data.attrs &&
vnode.data.attrs['scroll-placeholder']
) {
holder = parseInt(vnode.data.attrs['scroll-placeholder']);
} else {
holder = 20;
}
callBackWarpped = scrollCallback.bind({ el, holder }, binding.value);
window.addEventListener('scroll', callBackWarpped, false);
},
unbind: function () {
window.removeEventListener('scroll', callBackWarpped, false);
},
};
混合
混合是一种灵活的分布式复用 Vue 组件的方式。混合对象可以包含任意组件选项。以组件使用混合对象时,所有混合对象的选项将被混入该组件本身的选项。
插件
1)创建插件
Vue.js 的插件应当有一个公开方法 install。这个方法的第一个参数是 Vue 构造器 , 第二个参数是一个可选的选项对象。
2)使用插件
通过全局方法 Vue.use()使用插件:
// 调用 `MyPlugin.install(Vue)`
Vue.use(MyPlugin);
也可以传入一个选项对象:
Vue.use(MyPlugin, { someOption: true });
vue-router
两种导航方式
1)router-link 声明式导航
<router-link to="/foo">Go to Foo</router-link>
router-link 对应的路由匹配成功,将自动设置 class 属性值.router-link-active。
2)编程式导航
// 字符串
router.push('home');
// 对象
router.push({ path: 'home' });
// 命名的路由
router.push({ name: 'user', params: { userId: 123 } });
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' } });
重命名(redirect)和别名(alias)
两种路由模式
vue-router 默认 hash 模式,也可以设置为路由的 history 模式。
导航钩子
vue-router 提供的导航钩子主要用来拦截导航,让它完成跳转或取消。有多种方式可以在路由导航发生时执行钩子:全局的, 单个路由独享的, 或者组件级的。
路由 meta
一个路由匹配到的所有路由记录会暴露为$route对象(还有在导航钩子中的route对象)的$route.matched 数组。因此,我们需要遍历$route.matched 来检查路由记录中的 meta 字段。
过渡动效
router-view 是基本的动态组件,所以我们可以用 transition 组件给它添加一些过渡效果。
数据获取
有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现:
导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示『加载中』之类的指示。
导航完成之前获取:导航完成前,在路由的 enter 钩子中获取数据,在数据获取成功后执行导航。
滚动行为(scrollBehavior)
注意: 这个功能只在 HTML5 history 模式下可用。
路由懒加载
当打包构建应用时,Javascript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
结合 Vue 的异步组件和 Webpack 的 code splitting 功能,轻松实现路由组件的懒加载。
const Foo = (resolve) => require(['./Foo.vue'], resolve);
const router = new VueRouter({
routes: [{ path: '/foo', component: Foo }],
});
router-link
可以设置 tag、append、active-class、exact 等属性
有时候我们要让 “激活时的 CSS 类名” 应用在外层元素,而不是 a 标签本身,那么可以用 router-link 渲染外层元素,包裹着内层的原生 a 标签:
<router-link tag="li" to="/foo">
<a>/foo</a>
</router-link>
在这种情况下,a 将作为真实的链接(它会获得正确的 href 的),而”激活时的 CSS 类名”则设置到外层的 li。
Router 构造配置
routes、mode、base、linkActiveClass、scrollBehavior
对组件注入
1)注入的属性
通过在 Vue 根实例的 router 配置传入 router 实例,下面两个属性成员会被注入到每个子组件。
$router:router 实例
$route:当前激活的路由信息对象。这个属性是只读的,里面的属性是 immutable(不可变)的,不过你可以 watch(监测变化)它。
2)允许的额外配置:beforeRouteEnter、beforeRouteLeave
vuex
state
1)单一状态树
Vuex 使用单一状态树–是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个『唯一数据源(SSOT)』而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。
2)在 Vue 组件中获得 Vuex 状态
最好在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store 访问到。而不是在每个需要使用 state 的组件中需要频繁地导入。
3)mapState 辅助函数
当一个组件需要获取多个状态时候,可以使用 mapState 辅助函数帮助我们生成计算属性,这样可以简化代码书写。mapState 函数会返回一个对象,然后可以使用对象展开运算符将它与局部计算属性混合使用。
4)不要滥用 vuex
使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。
getters
getters 用来从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数:
computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}
getters 可以认为是 store 的计算属性。和 state 类似,有 mapGetters 辅助函数。
mutations
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutations 非常类似于事件:每个 mutation 都有一个字符串的事件类型(type)和一个回调函数(handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数
1)提交载荷(Payload)
你可以向 store.commit 传入额外的参数,即 mutation 的载荷(payload):
// ...
mutations: {
increment (state, n) {
state.count += n
}
}
store.commit('increment', 10)
2)Mutations 需遵守 Vue 的响应规则
3)使用常量替代 Mutation 事件类型
4)mutation 必须是同步函数
5)在组件中提交 Mutations
你可以在组件中使用 this.$store.commit(‘xxx’)提交 mutation,或者使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store)。
actions
actions 类似于 mutation,不同在于:
- actions 提交的是 mutation,而不是直接变更状态。
- actions 可以包含任意异步操作。
1)在组件中分发 Action
你在组件中使用 this.$store.dispatch(‘xxx’)分发 action,或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store)
2)组合 Actions
Action 通常是异步的,那么如何知道 action 什么时候结束呢?更重要的是,我们如何才能组合多个 action,以处理更加复杂的异步流程?
首先,你需要明白 store.dispatch 可以处理被触发的 action 的回调函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise。
使用 async/await 会更加简单:
// 假设 getData() 和 getOtherData() 返回的是 Promise
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
Modules
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
插件
Vuex 的 store 接受 plugins 选项,这个选项暴露出每次 mutation 的钩子。Vuex 插件就是一个函数,它接收 store 作为唯一参数。
严格模式
开启严格模式,仅需在创建 store 的时候传入 strict:true
在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
不要在发布环境下启用严格模式!严格模式会深度监测状态树来检测不合规的状态变更–请确保在发布环境下关闭严格模式,以避免性能损失。