vue + vue-router + vuex
工具
Devtools
Vue CLI
核心插件:
vue router
vuex
vue 服务端渲染
Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:
router.push(location, onComplete?, onAbort?)
注意:在 Vue 实例内部,你可以通过$router
访问路由实例。因此你可以调用 this.$router.push
。
想要导航到不同的 URL,则使用 router.push
方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。
当你点击
时,这个方法会在内部调用,所以说,点击
等同于调用 router.push(...)
。
声明式 | 编程式 |
---|---|
|
router.push(...) |
// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
//
router.push({ path: `/user/${userId}` }) // -> /user/123
注意: 如果目的地和当前路由相同,只有参数发生了改变 (比如从一个用户资料到另一个 /users/1
-> /users/2
),你需要使用 beforeRouteUpdate
来响应这个变化 (比如抓取用户信息)。
router.replace(location, onComplete?, onAbort?)
跟 router.push
很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。
声明式 | 编程式 |
---|---|
|
router.replace(...) |
router.go(n)
这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)
。
beforeEach
守卫。beforeRouteUpdate
守卫 (2.2+)。beforeEnter
。beforeRouteEnter
。beforeResolve
守卫 (2.5+)。afterEach
钩子。beforeRouteEnter
守卫中传给 next
的回调函数。状态管理:
数据可以简单地分为两个部分,跨组件的数据和组件内的数据
vuex可以说是专门为vue设计的状态管理工具。和 Redux 中使用不可变数据来表示 state 不同,Vuex 中没有 reducer 来生成全新的 state 来替换旧的 state**,Vuex 中的 state 是可以被修改的**。这么做的原因和 Vue 的运行机制有关系,Vue 基于 ES5 中的 getter/setter 来实现视图和数据的双向绑定,因此 Vuex 中 state 的变更可以通过 setter 通知到视图中对应的指令来实现视图更新。
Vuex 中的 state 是可修改的,而修改 state 的方式不是通过 actions,而是通过 mutations。更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
vuex的数据流简单地说为:
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
state——存储、数据
mutation——修改数据、追踪;同步
action——封装:组合;异步
Getter-- 可以认为是 store 的计算属性,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
Module-模块
vuex辅助方法: | |
---|---|
mapState | state -> computed |
mapActions | actions -> methods |
mapGetters | getters -> computed |
一条重要的原则就是要记住 mutation 必须是同步函数
因为:
mutation 如果是异步函数:因为当 mutation 触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations([
'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
// `mapMutations` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
})
}
}
通过属性访问
this.$store.getters.doneTodosCount
注意,getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的。
#通过方法访问
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
注意,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。
mapGetters 辅助函数
mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])}
}
严格模式
在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到
const store = new Vuex.Store({
// ...
strict: process.env.NODE_ENV !== 'production'
})
不要在发布环境下启用严格模式!严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。
当在严格模式中使用 Vuex 时,在属于 Vuex 的 state 上使用 v-model
会比较棘手:
解决方法1:
给 中绑定 value,然后侦听
input
或者 change
事件,在事件回调中调用 action:
// ...
computed: {
...mapState({
message: state => state.obj.message
})
},
methods: {
updateMessage (e) {
this.$store.commit('updateMessage', e.target.value)
}
}
解决方法2:使用带有 setter 的双向绑定计算属性:
// ...
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块
当使用 DOM 内模板或 JavaScript 内的字符串模板时,模板会在运行时被编译为渲染函数。通常情况下这个过程已经足够快了,但对性能敏感的应用还是最好避免这种用法。
预编译模板最简单的方式就是使用单文件组件——相关的构建设置会自动把预编译处理好,所以构建好的代码已经包含了编译出来的渲染函数而不是原始的模板字符串。
因为箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向 Vue 实例。
箭头函数中的this指向是固定不变的,即是在定义函数时的指向 而普通函数中的this指向时变化的,即是在使用函数时的指向
methods: {
goPage: function (index) {
this.newPage = index
},
inv: function () {
this.invt = setInterval(() => {
this.goPage(this.nextPage)
console.log(this)
//此时的this指向是该vue组件,不管在哪使用,指向都是该vue组件
}, 1000)
}
}
setInterval() 与 setTimeout() 是 window 对象的函数,所以 this 会指向 window
插件的使用方法概括出来就是:
Vue.use(MyPlugin)
使用,本质上是调用MyPlugin.install(Vue)
new Vue()
启动应用之前完成,实例化之前就要配置好。Vue.use
多次注册相同插件,那只会注册成功一次。Vue.use = function (plugin) {
// 忽略已注册插件
if (plugin.installed) {
return
}
// 集合转数组,并去除第一个参数
var args = toArray(arguments, 1);
// 把this(即Vue)添加到数组的第一个参数中
args.unshift(this);
// 调用install方法
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args);
} else if (typeof plugin === 'function') {
plugin.apply(null, args);
}
// 注册成功
plugin.installed = true;
return this;
};
按照插件的开发流程,应该有一个公开方法install
,在install
里面使用全局的mixin
方法添加一些组件选项,mixin
方法包含一个created
钩子函数,在钩子函数中验证this.$options.rules
。
import Vue from 'vue'
// 定义插件
const RulesPlugin = {
// 插件应该有一个公开方法install
// 第一个参数是Vue 构造器
// 第二个参数是一个可选的选项对象
install (Vue) {
// 注入组件
Vue.mixin({
// 钩子函数
created: function () {
// 验证逻辑
const rules = this.$options.rules
if (rules) {
Object.keys(rules).forEach(key => {
// 取得所有规则
const { validate, message } = rules[key]
// 监听,键是变量,值是函数
this.$watch(key, newValue => {
// 验证规则
const valid = validate(newValue)
if (!valid) {
console.log(message)
}
})
})
}
}
})
}
}
// 调用插件,实际上就是调用插件的install方法
// 即RulesPlugin.install(Vue)
Vue.use(RulesPlugin)
1、观察者observe()
obj
作为参数Object.defineProperty
转换对象的所有属性2、observe()
转换对象的属性使之响应式,对于每个转换后的属性,它会被分配一个Dep
实例,该实例跟踪订阅update
函数列表,并在调用setter
时触发它们重新运行
Dep
类,包含两个方法:depend
和notify
autorun
函数,传入一个update
函数作为参数update
函数中调用dep.depend()
,显式依赖于Dep
实例dep.notify()
触发update
函数重新运行3、autorun()
接收update
函数作为参数,并在update
函数订阅的属性发生变化时重新运行
const state = {
count: 0
}
observe(state)
autorun(() => {
console.log(state.count)
})
// 输出 count is: 0
state.count++
// 输出 count is: 1
observe
中修改obj
属性的同时分配Dep
的实例,并在get
中注册订阅者,在set
中通知改变。autorun
函数保存不变。实现如下:
class Dep {
// 初始化
constructor () {
this.subscribers = new Set()
}
// 订阅update函数列表
depend () {
if (activeUpdate) {
this.subscribers.add(activeUpdate)
}
}
// 所有update函数重新运行
notify () {
this.subscribers.forEach(sub => sub())
}
}
function observe (obj) {
// 迭代对象的所有属性
// 并使用Object.defineProperty()转换成getter/setters
Object.keys(obj).forEach(key => {
let internalValue = obj[key]
// 每个属性分配一个Dep实例
const dep = new Dep()
Object.defineProperty(obj, key, {
// getter负责注册订阅者
get () {
dep.depend()
return internalValue
},
// setter负责通知改变
set (newVal) {
const changed = internalValue !== newVal
internalValue = newVal
// 触发后重新计算
if (changed) {
dep.notify()
}
}
})
})
return obj
}
let activeUpdate = null
function autorun (update) {
// 包裹update函数到"wrappedUpdate"函数中,
// "wrappedUpdate"函数执行时注册和注销自身
const wrappedUpdate = () => {
activeUpdate = wrappedUpdate
update()
activeUpdate = null
}
wrappedUpdate()
}
在初始化阶段,本质上发生在auto run
函数中,然后通过render
函数生成Virtual DOM
,view
根据Virtual DOM
生成Actual DOM
。因为render
函数依赖于页面上所有的数据data
,并且这些数据是响应式的,所有的数据作为组件render
函数的依赖。一旦这些数据有所改变,那么render
函数会被重新调用。
在更新阶段,render
函数会重新调用并且返回一个新的Virtual Dom
,新旧Virtual DOM
之间会进行比较,把diff
之后的最小改动应用到Actual DOM
中。
JSX和Template都是用于声明DOM和state之间关系的一种方式,在Vue中,Template是默认推荐的方式,但是也可以使用JSX来做更灵活的事。
JSX更加动态化,对于使用编程语言是很有帮助的,可以做任何事,但是动态化使得编译优化更加复杂和困难。
Template更加静态化并且对于表达式有更多约束,但是可以快速复用已经存在的模板,模板约束意味着可以在编译时做更多的性能优化,相对于JSX在编译时间上有着更多优势
<script type="text/x-template" id="anchored-heading-template">
<h1 v-if="level === 1">
<slot></slot>
</h1>
<h2 v-else-if="level === 2">
<slot></slot>
</h2>
<h3 v-else-if="level === 3">
<slot></slot>
</h3>
<h4 v-else-if="level === 4">
<slot></slot>
</h4>
<h5 v-else-if="level === 5">
<slot></slot>
</h5>
<h6 v-else-if="level === 6">
<slot></slot>
</h6>
script>
渲染函数,它比模板更接近编译器
Vue.component('anchored-heading', {
render: function (createElement) {
return createElement(
'h' + this.level, // 标签名称
this.$slots.default // 子节点数组
)
},
props: {
level: {
type: Number,
required: true
}
}
})
可以使用v-cloak指令用于隐藏未编译完成的插值表达式,一般我们在使用时会与添加一个隐藏该元素的样式同时使用。
{{msg}}
v-text 会将数据以字符串文本的形式更新,而 v-html 则是将数据以 html 标签的形式更新。
v-bind 可以用来在标签上绑定标签的属性(例如:img 的 src、title 属性等等)和样式(可以用style的形式进行内联样式的绑定,也可以通过指定class的形式指定样式)。同时,对于绑定的内容,是做为一个 js 变量,因此,我们可以对该内容进行编写合法的 js 表达式
事件修饰符
...
...
使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用
v-on:click.prevent.self
会阻止所有的点击,而v-on:click.self.prevent
只会阻止对元素自身的点击。
v-if
是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if
也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show
就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if
有更高的切换开销,而 v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show
较好;如果在运行时条件很少改变,则使用 v-if
较好。
注意,v-show 不支持 元素,也不支持 v-else。
不推荐同时使用
v-if
和v-for
当 v-if
与 v-for
一起使用时,v-for
具有比 v-if
更高的优先级