Little H title

this is subtitle


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 公益404

axios使用

发表于 2018-02-05 | 分类于 前端

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。

5种
用于进行 AJAX 调用的顶级 JavaScript 库

Fetch API
Fetch API 是 XMLHttpRequest 的现代替代方法,用于从服务器检索资源

Axios
Axios 是一个基于 XMLHttpRequest 构建的现代 JavaScript 库,用于进行 AJAX 调用

jQuery
jQuery 曾经是 JavaScript 的一线类库,涵盖从 AJAX 调用到操作 DOM 的内容。

SuperAgent
SuperAgent 是一个轻量级和渐进式 AJAX 库,更侧重于可读性和灵活性。

Request - 一个简单的 HTTP 客户端
这个 Request 库是进行 HTTP 调用最简单的方法之一。

我个人喜欢的是 Axios ,因为就我个人而言感觉它更具可读性和友好(easy on the eyes)。你也可以使用 Fetch ,因为它有良好的文档记录和标准化的解决方案。

promise

https

DNS

vuex使用总结

发表于 2018-02-04 | 分类于 前端

vuex总结(这个也是插件哦)

vuex官网

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。

什么是“状态管理模式”?(这个例子别和后面的搞混了)

让我们从一个简单的 Vue 计数应用开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
new Vue({
// state
data () {
return {
count: 0
}
},
// view
template: `
<div>{{ count }}</div>
`,
// actions
methods: {
increment () {
this.count++
}
}
})

注意看上面的注释
这个状态自管理应用包含以下3个部分:

  • state,驱动应用的数据源;
  • view,以声明方式将 state 映射到视图;
  • actions,响应在 view 上的用户输入导致的状态变化。

以下是一个表示“单向数据流”理念的极简示意:

单向数据流

但是,当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:

  • 多个视图依赖于同一状态。
  • 来自不同视图的行为需要变更同一状态。

对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。
对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。

因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!

另外,通过定义和隔离状态管理中的各种概念并强制遵守一定的规则,我们的代码将会变得更结构化且易维护。

这就是 Vuex 背后的基本思想,借鉴了 Flux、Redux、和 The Elm Architecture。与其他模式不同的是,Vuex 是专门为 Vue.js 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新。

vuex

什么情况下我应该使用 Vuex?

虽然 Vuex 可以帮助我们管理共享状态,但也附带了更多的概念和框架。这需要对短期和长期效益进行权衡。

如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 global event bus 就足够您所需了。但是,如果您需要构建是一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。引用 Redux 的作者 Dan Abramov 的话说就是:

Flux 架构就像眼镜:您自会知道什么时候需要它。

开始(这里开始才是重头戏)

每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:

  1. Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  2. 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

最简单的 Store

安装 Vuex 之后,让我们来创建一个 store。创建过程直截了当——仅需要提供一个初始 state 对象和一些 mutation(这个当成类actions好了):

1
2
3
4
5
6
7
8
9
10
11
// 如果在模块化构建系统中,请确保在开头调用了 Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})

现在,你可以通过 store.state 来获取状态对象(后面用computed来返回),以及通过 store.commit 方法触发状态变更:

1
2
3
4
//commit一个increment到mutations触发,然后这个mutations会mutate一个state
store.commit('increment')

console.log(store.state.count) // -> 1

再次强调,我们通过提交 mutation 的方式,而非直接改变 store.state.count,是因为我们想要更明确地追踪到状态的变化。这个简单的约定能够让你的意图更加明显,这样你在阅读代码的时候能更容易地解读应用内部的状态改变。此外,这样也让我们有机会去实现一些能记录每次状态改变,保存状态快照的调试工具。有了它,我们甚至可以实现如时间穿梭般的调试体验。

由于 store 中的状态是响应式的,在组件中调用 store 中的状态简单到仅需要在计算属性中返回即可。触发变化也仅仅是在组件的 methods 中提交 mutation。

这是一个最基本的 Vuex 记数应用示例。

接下来,我们将会更深入地探讨一些核心概念。让我们先从 State 概念开始

核心概念

在这一章,我们将会学到 Vue 的这些核心概念。他们是:

  • State
  • Getter
  • Mutation
  • Action
  • Module

State

单一状态树
Vuex 使用单一状态树——是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT)”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。

单状态树和模块化并不冲突——在后面的章节里我们会讨论如何将状态和状态变更事件分布到各个子模块中。

那么我们如何在 Vue 组件中展示状态呢?由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:

1
2
3
4
5
6
7
8
9
// 创建一个 Counter 组件
const Counter = {
template: `<div>{{ count }}</div>`,
computed: { //稳得一比,前面console.log只是打印
count () {
return store.state.count //下面的例子会改下这里,从store变为this.$store
}
}
}

每当 store.state.count 变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM。

然而,这种模式导致组件依赖全局状态单例。在模块化的构建系统中,在每个需要使用 state 的组件中需要频繁地导入,并且在测试组件时需要模拟状态。

Vuex 通过 store 选项,提供了一种机制将状态从根组件“注入”到每一个子组件中(需调用 Vue.use(Vuex)):

1
2
3
4
5
6
7
8
9
10
11
const app = new Vue({
el: '#app',
// 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件(多个了store,和router一样用法)
store,
components: { Counter },
template: `
<div class="app">
<counter></counter>
</div>
`
})

通过在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store 访问到。让我们更新下 Counter 的实现:

1
2
3
4
5
6
7
8
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count //这里从store变为this.$store(从vue实例)
}
}
}

mapState 辅助函数

当一个组件需要获取多个状态时候,将这些状态都声明为计算属性(这个函数还是在computed中用哦)会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'

export default {
// ...
computed: mapState({ //还是computed中用
// 箭头函数可使代码更简练
count: state => state.count,

// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count',

// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}

当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。

1
2
3
4
computed: mapState([
// 映射 this.count 为 store.state.count
'count'
])

对象展开运算符(…)

mapState 函数返回的是一个对象。我们如何将它与局部计算属性混合使用呢?通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed 属性。但是自从有了对象展开运算符(现处于 ECMASCript 提案 stage-3 阶段),我们可以极大地简化写法:

1
2
3
4
5
6
7
computed: {
localComputed () { /* ... */ },
// 使用对象展开运算符将此对象混入到外部对象中,上面算完后,这里获取
...mapState({
// ...
})
}

组件仍然保有局部状态

使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。

Getter(在state基础上,可以认为是 store 的计算属性)

有时候我们需要从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数:

1
2
3
4
5
6
computed: {
doneTodosCount () {
//在state中的todos进行过滤
return this.$store.state.todos.filter(todo => todo.done).length
}
}

如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。

Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

Getter 接受 state 作为其第一个参数(废话,在state的基础上啊,然后也有mapGetters的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
//这就直接上了,这里就一个参数
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})

Getter 会暴露为 store.getters 对象(废话了,和state调用一样):

1
store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]

Getter 也可以接受其他 getter 作为第二个参数:

1
2
3
4
5
6
7
getters: {
// 话说第二个参数就是自己么
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
store.getters.doneTodosCount // -> 1

我们可以很容易地在任何组件中使用它:

1
2
3
4
5
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
}

你也可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。

1
2
3
4
5
6
7
getters: {
// 这还两个箭头了
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }

mapGetters 辅助函数

mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { mapGetters } from 'vuex'

export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中,也是多个getters
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
如果你想将一个 getter 属性另取一个名字,使用对象形式:

mapGetters({
// 映射 `this.doneCount` 为 `store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})

Mutation(mutation 都是同步事务)

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数(毕竟要改state):

1
2
3
4
5
6
7
8
9
10
11
12
//还是那个计数的例子
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
})

你不能直接调用一个 mutation handler。这个选项更像是事件注册:“当触发一个类型为 increment 的 mutation 时,调用此函数。”要唤醒一个 mutation handler,你需要以相应的 type 调用 store.commit 方法:

1
store.commit('increment')

提交载荷(Payload)

你可以向 store.commit 传入额外的参数,即 mutation 的 载荷(payload):

1
2
3
4
5
6
7
8
// 这个n就是payload
mutations: {
increment (state, n) {
state.count += n
}
}
//当然相应的
store.commit('increment', 10)

在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:

1
2
3
4
5
6
7
8
9
// ...
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit('increment', {
amount: 10
})

对象风格的提交方式

提交 mutation 的另一种方式是直接使用包含 type 属性的对象:

1
2
3
4
store.commit({
type: 'increment',
amount: 10
})

当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数,因此 handler 保持不变:

1
2
3
4
5
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}

Mutation 需遵守 Vue 的响应规则

既然 Vuex 的 store 中的状态是响应式的,那么当我们变更状态时,监视状态的 Vue 组件也会自动更新。这也意味着 Vuex 中的 mutation 也需要与使用 Vue 一样遵守一些注意事项:

  1. 最好提前在你的 store 中初始化好所有所需属性(响应式都这样)。

  2. 当需要在对象上添加新属性(这不就和v-for那个一样么,用set)时,你应该

    • 使用 Vue.set(obj, ‘newProp’, 123), 或者

    • 以新对象替换老对象。例如,利用 stage-3 的对象展开运算符我们可以这样写:

      state.obj = { ...state.obj, newProp: 123 }

使用常量替代 Mutation 事件类型

使用常量替代 mutation 事件类型在各种 Flux 实现中是很常见的模式。这样可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// mutation-types.js    这些大写的就是啦
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'

const store = new Vuex.Store({
state: { ... },
mutations: {
// 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名,还有这操作
[SOME_MUTATION] (state) {
// mutate state
}
}
})

用不用常量取决于你——在需要多人协作的大型项目中,这会很有帮助。但如果你不喜欢,你完全可以不这样做

Mutation 必须是同步函数

一条重要的原则就是要记住 mutation 必须是同步函数。为什么?请参考下面的例子:

1
2
3
4
5
6
7
mutations: {
someMutation (state) {
api.callAsyncMethod(() => {
state.count++
})
}
}

现在想象,我们正在 debug 一个 app 并且观察 devtool 中的 mutation 日志。每一条 mutation 被记录,devtools 都需要捕捉到前一状态和后一状态的快照。然而,在上面的例子中 mutation 中的异步函数中的回调让这不可能完成:因为当 mutation 触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。

在组件中提交 Mutation

你可以在组件中使用 this.$store.commit(‘xxx’) 提交 mutation,或者使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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')`
})
}
}

下一步:Action

在 mutation 中混合异步调用会导致你的程序很难调试。例如,当你能调用了两个包含异步回调的 mutation 来改变状态,你怎么知道什么时候回调和哪个先回调呢?这就是为什么我们要区分这两个概念。在 Vuex 中,mutation 都是同步事务:

store.commit('increment')
// 任何由 “increment” 导致的状态变更都应该在此刻完成。
为了处理异步操作,让我们来看一看 Action。

Action(为了处理异步操作)

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。

让我们来注册一个简单的 action:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
//就是这个了,看图是mutation之前的一个
actions: {
increment (context) {
context.commit('increment')
}
}
})

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。当我们在之后介绍到 Modules 时,你就知道 context 对象为什么不是 store 实例本身了。

实践中,我们会经常用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用 commit 很多次的时候):

1
2
3
4
5
actions: {
increment ({ commit }) {
commit('increment')
}
}

分发 Action

Action 通过 store.dispatch 方法触发:

store.dispatch('increment')
乍一眼看上去感觉多此一举,我们直接分发 mutation 岂不更方便?实际上并非如此,还记得 mutation 必须同步执行这个限制么?Action 就不受约束!我们可以在 action 内部执行异步操作:

1
2
3
4
5
6
7
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}

Actions 支持同样的载荷方式和对象方式进行分发:

1
2
3
4
5
6
7
8
9
10
// 以载荷形式分发
store.dispatch('incrementAsync', {
amount: 10
})

// 以对象形式分发
store.dispatch({
type: 'incrementAsync',
amount: 10
})

来看一个更加实际的购物车示例,涉及到调用异步 API 和分发多重 mutation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
actions: {
checkout ({ commit, state }, products) {
// 把当前购物车的物品备份起来
const savedCartItems = [...state.cart.added]
// 发出结账请求,然后乐观地清空购物车
commit(types.CHECKOUT_REQUEST)
// 购物 API 接受一个成功回调和一个失败回调
shop.buyProducts(
products,
// 成功操作
() => commit(types.CHECKOUT_SUCCESS),
// 失败操作
() => commit(types.CHECKOUT_FAILURE, savedCartItems)
)
}
}

注意我们正在进行一系列的异步操作,并且通过提交 mutation 来记录 action 产生的副作用(即状态变更)。

在组件中分发 Action

你在组件中使用 this.$store.dispatch(‘xxx’) 分发 action,或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { mapActions } from 'vuex'

export default {
// ...
methods: {
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`

// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
}

组合 Action

Action 通常是异步的,那么如何知道 action 什么时候结束呢?更重要的是,我们如何才能组合多个 action,以处理更加复杂的异步流程?

首先,你需要明白 store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise:

1
2
3
4
5
6
7
8
9
10
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}

现在你可以:

1
2
3
store.dispatch('actionA').then(() => {
// ...
})

在另外一个 action 中也可以:

1
2
3
4
5
6
7
8
actions: {
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}

最后,如果我们利用 async / await,我们可以如下组合 action:

1
2
3
4
5
6
7
8
9
10
// 假设 getData() 和 getOtherData() 返回的是 Promise
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}

一个 store.dispatch 在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。

Module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 的状态

模块的局部状态

对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const moduleA = {
state: { count: 0 },
mutations: {
increment (state) {
// 这里的 `state` 对象是模块的局部状态
state.count++
}
},

getters: {
doubleCount (state) {
return state.count * 2
}
}
}

同样,对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState:

1
2
3
4
5
6
7
8
9
10
11
const moduleA = {
// ...
actions: {
//这有3个参数了,第3个参数是根节点的状态
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}

对于模块内部的 getter,根节点状态会作为第三个参数暴露出来:

1
2
3
4
5
6
7
8
const moduleA = {
// ...
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}

命名空间(暂不懂)

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。

如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为命名空间模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如:

1
2


在命名空间模块内访问全局内容(Global Assets)

带命名空间的绑定函数

给插件开发者的注意事项

模块动态注册

模块重用

项目结构

Vuex 并不限制你的代码结构。但是,它规定了一些需要遵守的规则:

  1. 应用层级的状态应该集中到单个 store 对象中。

  2. 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。

  3. 异步逻辑都应该封装到 action 里面。

只要你遵守以上规则,如何组织代码随你便。如果你的 store 文件太大,只需将 action、mutation 和 getter 分割到单独的文件。

对于大型应用,我们会希望把 Vuex 相关代码分割到模块中。下面是项目结构示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块

严格模式

开启严格模式,仅需在创建 store 的时候传入 strict: true:

1
2
3
4
const store = new Vuex.Store({
// ...
strict: true
})

在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。

开发环境与发布环境
不要在发布环境下启用严格模式!严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。

类似于插件,我们可以让构建工具来处理这种情况:

1
2
3
4
const store = new Vuex.Store({
// ...
strict: process.env.NODE_ENV !== 'production'
})

vue-router使用总结

发表于 2018-02-03 | 分类于 前端

vue-route(这货是个插件)

主要想整理的是这个配合v-for一起用

vue-router官网的介绍

使用vue-cli来构建路由

先说下3个基本概念:route, routes, router
详解vue-router基本使用

1, route,它是一条路由,是单数。
Home按钮 => home内容, 这是一条route,
about按钮 => about 内容, 这是另一条路由。

2, routes 是一组路由,把上面的每一条路由组合起来,形成一个数组。
[
{home 按钮 =>home内容 },
{ about按钮 => about 内容}
]

3, router 是一个机制,相当于一个管理者,它来管理路由。
因为routes 只是定义了一组路由放在那里,是静止的,当有请求时,怎么找到对应的那条route呢? 比如当用户点击home 按钮的时候,怎么知道跳转到home页面?这时router 就起作用了,它到routes 中去查找,去找到对应的route(home页面),所以页面中就显示了home 内容。

4,客户端中的路由,实际上就是dom 元素的显示和隐藏。当页面中显示home 内容的时候,about 中的内容全部隐藏,反之也是一样。
客户端路由有两种实现方式:基于hash 和基于html5 history api.

基本的路由(静态路由)

下面开始
官网一个基本的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
HTML
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

<div id="app">
<h1>Hello App!</h1>
<p>
<!-- 使用 router-link 组件来导航. -->
<!-- 通过传入 `to` 属性指定链接. -->
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
<!-- 当 <router-link> 对应的路由匹配成功,将自动设置 class 属性值 .router-link-active -->
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>

JavaScript
// 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)

// 1. 定义(路由)组件。
// 可以从其他文件 import 进来
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }

// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
// 我们晚点再讨论嵌套路由(就是放在children中)。
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]

// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
routes // (缩写)相当于 routes: routes
})

// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
router
}).$mount('#app')

// 现在,应用已经启动了!

可以看到html中只用了2个标签
<router-link> <router-view>

<router-view>反正是显示路由内容的,
功能都在<router-link>,它定义了to那个路由 。

javascript页面
可以简单的理解为就是定义了一组routers,然后通过vue的vuerouter来管理

动态路由(也就是把routers中的path改了: 多对一,传参而已。另外注意watch和beforeRouteUpdate)

除了这个传参,还有url 这个query传参
上面的例子中router里面都是静态的,一一对应

1
2
3
4
5
6
7
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>

const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]

我们来看下动态路由,然后比较他们的区别

我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件(组件复用)。例如,我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。那么,我们可以在 vue-router 的路由路径中使用『动态路径参数』(dynamic segment)来达到这个效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//这还是原来的User组件
const User = {
template: '<div>User</div>'
}
//动态路由下复用这个User组件,改为
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}

const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/user/:id', component: User }
]
})

在组件中使用 $route 会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性,使用 props 将组件和路由解耦,详见路由组件传参

现在呢,像 /user/foo 和 /user/bar 都将映射到相同的路由。

一个『路径参数』使用冒号 : 标记。当匹配到一个路由时,参数值会被设置到 this.$route.params,可以在每个组件内使用。于是,我们可以更新 User 的模板,输出当前用户的 ID:

这里如果你看<router-link>这个to还是不动的,该foo该bar。只是routes和组件内变化(毕竟动态)

把这个当做函数的传参记,比如<router-link to="/ser/foo">这个to中传过来一个foo,(这里的话还是和静态一样,只是routes变了,组件复用当然也给改,就是拿出参数问题)
然后再routers中的{ path: ‘/user/:id’, component: User } 匹配到了这个id,成了这么一个对象{id:foo}
最后去User组件中用$route.params这个对象可以拿出来用,毕竟就是传过来用的
还有就是beforeRouteUpdate的用处

模式 匹配路径 $route.params
/user/:username /user/evan { username: ‘evan’ }
/user/:username/post/:post_id /user/evan/post/123 { username: ‘evan’, post_id: 123 }

响应路由参数的变化
提醒一下,当使用路由参数时,例如从 /user/foo 导航到 /user/bar,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。

复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch(监测变化) $route 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const User = {
template: '...',
watch: {
'$route' (to, from) {
// 对路由变化作出响应...
}
}
}

或者使用 2.2 中引入的 beforeRouteUpdate 守卫:

const User = {
template: '...',
beforeRouteUpdate (to, from, next) {
// react to route changes...
// don't forget to call next()
}
}

匹配优先级
有时候,同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最高。

嵌套路由 (就是一个套一个,在<router-view>中套,在routes中套children)(同级的呢,命名看命名视图)

前面的例子并没有嵌套,现在搞下嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//这个是默认app挂在的地方,看到app了么
<div id="app">
<router-view></router-view>
</div>

//这是User组件
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}
//配置哦
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User }
]
})

在app中的 <router-view>是最顶层的出口,渲染最高级路由匹配到的组件。同样地,一个被渲染组件同样可以包含自己的嵌套 。例如,在 User 组件的模板添加一个 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//这是User组件,现在就有两层了
const User = {
template: `
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<router-view></router-view>
</div>
`
}

//要在嵌套的出口中渲染组件,需要在 VueRouter 的参数中使用 children 配置(上面嵌套了2层,**想一下再往下呢,就是再放一个children咯**):
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User,
children: [ //要再嵌套可以在放一个children
{
// 当 /user/:id/profile 匹配成功,
// UserProfile 会被渲染在 User 的 <router-view> 中
path: 'profile', //可以注意这里是相对路径哦
component: UserProfile
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 会被渲染在 User 的 <router-view> 中
path: 'posts',
component: UserPosts
},
// 这是一个当做默认路由的,空路由
// 当 /user/:id 匹配成功,
// UserHome 会被渲染在 User 的 <router-view> 中
{ path: '', component: UserHome },

// ...其他子路由
]
}
]
})

问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//嵌套不是这样嵌套,
<div id="app">
<router-view>
<router-view></router-view>
</router-view>
</div>
//也不是这样哦。 嵌套是一对一啊,这个user套在app去,同理 你要多层的嵌套就再导入一个组件呗,在那个组件里写router-view
const User = {
template: `
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<router-view>
<router-view></router-view> <!-- 不报错,不过反正是丢弃 -->
</router-view>
</div>
`
}

//还有既然这样不行,那么直接在router-view写东西怎么样,和slot一回事么
<div id="app">
<router-view>
在这写东西怎么处理的?<!-- 这个直接丢弃了 -->
</router-view>
</div>

命名路由

有时候,通过一个名称来标识(name)一个路由显得更方便一些,特别是在链接一个路由,或者是执行一些跳转的时候。你可以在创建 Router 实例的时候,在 routes 配置中给某个路由设置名称。(只要先记住name好了,下面的编程式路由接着往下看)

1
2
3
4
5
6
7
8
9
const router = new VueRouter({
routes: [
{
path: '/user/:userId',
name: 'user', //就多了这个name
component: User
}
]
})

要链接到一个命名路由,可以给 <router-link>的 to 属性传一个对象:(这块往下看)

1
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>

这跟代码调用 router.push() 是一回事(就是编程式路由一回事):

1
router.push({ name: 'user', params: { userId: 123 }})

这两种方式都会把路由导航到 /user/123 路径。

编程式的导航(借助 router 的实例方法,通过编写代码来实现,重要的是学to的参数用法)

1
router.push(location, onComplete?, onAbort?)

在 2.2.0+,可选的在 router.push 或 router.replace 中提供 onComplete 和 onAbort 回调作为第二个和第三个参数。这些回调将会在导航成功完成 (在所有的异步钩子被解析之后) 或终止 (导航到相同的路由、或在当前导航完成之前导航到另一个不同的路由) 的时候进行相应的调用。

注意:如果目的地和当前路由相同,只有参数发生了改变 (比如从一个用户资料到另一个 /users/1 -> /users/2),你需要使用 beforeRouteUpdate 来响应这个变化 (比如抓取用户信息) (就是前面的动态路由 组件复用)。

注意:在 Vue 实例内部,你可以通过 $router 访问路由实例。因此你可以调用 this.$router.push。

想要导航到不同的 URL,则使用 router.push 方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。

当你点击 <router-link> 时,这个方法会在内部调用,所以说,点击 <router-link :to="...">(看到这个用了v-bind哦动态) 等同于调用 router.push(…)。

声明式 编程式
<router-link :to="..."> router.push(…)

上面没啥,下面重要来了,to的参数
同样的规则也适用于 router-link 组件的 to 属性。

该方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:

(3种传参,[path]/[name params]/[path/name+query])

1
2
3
4
5
6
7
8
9
10
11
// 字符串
router.push('home')

// 对象
router.push({ path: 'home' })

// 命名的路由
router.push({ name: 'user', params: { userId: 123 }})

// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})

Get请求传参, 看最后一个带查询参数
这个明明实在不好形容啊。不过真的是和Get请求一样。你完全可以在链接后加上?进行传参。
样例:http://localhost:8080/linkParamsQuestion?age=18
项目里获取:
let age = this.$route.query.age; //问号后面参数会被封装进 this.$route.query;

注意:如果提供了 path,params 会被忽略(毕竟路径就代表参数),上述例子中的 query 并不属于这种情况。取而代之的是下面例子的做法,你需要提供路由的 name 或手写完整的带有参数的 path:

1
2
3
4
5
const userId = 123
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123 es6写法
// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user

router.replace(location, onComplete?, onAbort?)

跟 router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。

声明式 编程式
<router-link :to="..." replace> router.replace(…)

router.go(n)
这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)。

例子

1
2
3
4
5
6
7
8
9
10
11
12
// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)

// 后退一步记录,等同于 history.back()
router.go(-1)

// 前进 3 步记录
router.go(3)

// 如果 history 记录不够用,那就默默地失败呗
router.go(-100)
router.go(100)

操作 History(我怎么觉得这东西都不用了)
你也许注意到 router.push、 router.replace 和 router.go 跟 window.history.pushState、 window.history.replaceState 和 window.history.go好像, 实际上它们确实是效仿 window.history API 的。

因此,如果你已经熟悉 Browser History APIs,那么在 vue-router 中操作 history 就是超级简单的。

还有值得提及的,vue-router 的导航方法 (push、 replace、 go) 在各类路由模式(history、 hash 和 abstract)下表现一致。

命名视图(同级的视图view,前面讲过嵌套的)

有时候想同时(同级)展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar(侧导航) 和 main(主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default。

1
2
3
<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>

一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components 配置(带上 s):

1
2
3
4
5
6
7
8
9
10
11
12
const router = new VueRouter({
routes: [
{
path: '/',
components: {
default: Foo,
a: Bar,
b: Baz
}
}
]
})

嵌套命名视图(厉害了,嵌套,同级的合在一起)

重点记住这个就好了
我们也有可能使用命名视图创建嵌套视图的复杂布局。这时你也需要命名用到的嵌套 router-view 组件。我们以一个设置面板为例:

1
2
3
4
5
6
7
8
9
/settings/emails                                       /settings/profile
+-----------------------------------+ +------------------------------+
| UserSettings | | UserSettings |
| +-----+-------------------------+ | | +-----+--------------------+ |
| | Nav | UserEmailsSubscriptions | | +------------> | | Nav | UserProfile | |
| | +-------------------------+ | | | +--------------------+ |
| | | | | | | | UserProfilePreview | |
| +-----+-------------------------+ | | +-----+--------------------+ |
+-----------------------------------+ +------------------------------+
  • UserSettings 是一个视图组件。
  • Nav 只是一个常规组件。
  • UserEmailsSubscriptions、UserProfile、UserProfilePreview 是嵌套的视图组件。

就是UserSettings下有左右两块,右边那块又有嵌套。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- UserSettings.vue -->
<div>
<h1>User Settings</h1>
<NavBar/>
<router-view/>
<router-view name="helper"/>
</div>

{
path: '/settings',
// 你也可以在顶级路由就配置命名视图
component: UserSettings,
children: [{
path: 'emails',
component: UserEmailsSubscriptions
}, {
path: 'profile',
components: {
default: UserProfile,
helper: UserProfilePreview
}
}]
}

重定向 和 别名

重定向(一对一,有3种: ‘’, name, =>)

重定向也是通过 routes 配置来完成,下面例子是从 /a 重定向到 /b:

1
2
3
4
5
6
7
8
const router = new VueRouter({
routes: [
{
path: '/a',
redirect: '/b' //就加了这么一条redirect
}
]
})

重定向的目标也可以是一个命名的路由(和编程式to的 name写法一样):

1
2
3
4
5
6
7
8
const router = new VueRouter({
routes: [
{
path: '/a',
redirect: { name: 'foo' }
}
]
})

甚至是一个方法(es6的箭头函数),动态返回重定向目标:

1
2
3
4
5
6
7
8
9
10
const router = new VueRouter({
routes: [
{
path: '/a',
redirect: to => {
// 方法接收 目标路由 作为参数
// return 重定向的 字符串路径/路径对象
}}
]
})

注意导航守卫并没有应用在跳转路由上,而仅仅应用在其目标上。在下面这个例子中,为 /a 路由添加一个 beforeEach 或 beforeLeave 守卫并不会有任何效果。

别名(一对多)
『重定向』的意思是,当用户访问 /a时,URL 将会被替换成 /b,然后匹配路由为 /b,那么『别名』又是什么呢?

/a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样。

上面对应的路由配置为:

1
2
3
4
5
6
7
8
9
const router = new VueRouter({
routes: [
{
path: '/a',
component: A,
alias: '/b'
}
]
})

『别名』的功能让你可以自由地将 UI 结构映射到任意的 URL,而不是受限于配置的嵌套路由结构。

路由组件传参(props 也有3种模式: 布尔,对象,函数)

在组件中使用 $route 会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。

使用 props 将组件和路由解耦,取代与 $route 的耦合

1
2
3
4
5
6
7
8
9
//这是动态路由那块的东西
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User }
]
})

通过 props 解耦

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const User = {
props: ['id'],
template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User, props: true },

// 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:
{
path: '/user/:id',
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false }
}
]
})

布尔模式
如果 props 被设置为 true,route.params 将会被设置为组件属性。

对象模式
如果 props 是一个对象,它会被按原样设置为组件属性。当 props 是静态的时候有用。

1
2
3
4
5
6
7
8
9
const router = new VueRouter({
routes: [
{
path: '/promotion/from-newsletter',
component: Promotion,
props: { newsletterPopup: false } <!-- 这个newsletterPopup是name么?和布尔模式啥区别,就是同级的用命名视图解耦用么? -->
}
]
})

函数模式
你可以创建一个函数返回 props。这样你便可以将参数转换成另一种类型,将静态值与基于路由的值结合等等。

1
2
3
4
5
6
7
8
9
const router = new VueRouter({
routes: [
{
path: '/search',
component: SearchUser,
props: (route) => ({ query: route.query.q })
}
]
})

URL /search?q=vue 会将 {query: ‘vue’} 作为属性传递给 SearchUser 组件。

请尽可能保持 props 函数为无状态的,因为它只会在路由发生变化时起作用。如果你需要状态来定义 props,请使用包装组件,这样 Vue 才可以对状态变化做出反应。

HTML5 History 模式(暂时不知道)


进阶

导航守卫(『导航』表示路由正在发生改变。暂时用全局的)

(做登录拦截 或 loading)

正如其名,vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。
记住参数或查询的改变并不会触发进入/离开的导航守卫。你可以通过观察(watch) $route 对象来应对这些变化,或使用 beforeRouteUpdate 的组件内守卫。

全局守卫

你可以使用 router.beforeEach 注册一个全局前置守卫:

1
2
3
4
5
const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
// ...
})

当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。

每个守卫方法接收三个参数:

to: Route: 即将要进入的目标 路由对象

from: Route: 当前导航正要离开的路由

next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。

  • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。

  • next(false): 中断当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。

  • next(‘/‘) 或者 next({ path: ‘/‘ }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: ‘home’ 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。

  • next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

确保要调用 next 方法,否则钩子就不会被 resolved。

全局解析守卫(router.beforeResolve)

2.5.0 新增

在 2.5.0+ 你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。

全局后置钩子

你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:

1
2
3
router.afterEach((to, from) => {
// ...
})

组件内的守卫(3个)

最后,你可以在路由组件内直接定义以下路由导航守卫:

beforeRouteEnter
beforeRouteUpdate (2.2 新增)
beforeRouteLeave

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}

beforeRouteEnter 守卫 不能访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。

不过,你可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。

1
2
3
4
5
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}

注意 beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdate 和 beforeRouteLeave 来说,this 已经可用了,所以不支持传递回调,因为没有必要了。

1
2
3
4
5
beforeRouteUpdate (to, from, next) {
// just use `this`
this.name = to.params.name
next()
}

这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消。

1
2
3
4
5
6
7
8
beforeRouteLeave (to, from , next) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (answer) {
next()
} else {
next(false)
}
}

完整的导航解析流程

  1. 导航被触发。
  2. 在失活的组件里调用离开守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter。
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter。
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

路由元信息(可以用来搞允许登录页面)

定义路由的时候可以配置 meta 字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
children: [
{
path: 'bar',
component: Bar,
// a meta field 和props挺像的啊
//设置一个允不允许登录的标志
meta: { requiresAuth: true }
}
]
}
]
})

那么如何访问这个 meta 字段呢?

用处

首先,我们称呼 routes 配置中的每个路由对象为 路由记录。路由记录可以是嵌套的,因此,当一个路由匹配成功后,他可能匹配多个路由记录

例如,根据上面的路由配置,/foo/bar 这个 URL 将会匹配父路由记录以及子路由记录。

一个路由匹配到的所有路由记录会暴露为 $route 对象(还有在导航守卫中的路由对象)的 $route.matched 数组。因此,我们需要遍历 $route.matched 来检查路由记录中的 meta 字段。

下面例子展示在全局导航守卫中检查元字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//全局导航守卫
router.beforeEach((to, from, next) => {
//这个就难懂了
if (to.matched.some(record => record.meta.requiresAuth)) {
// this route requires auth, check if logged in
// if not, redirect to login page 登录页.
//这个auth.loggedIn 方法是外部引入的,你可以先写好一个校验是否登录的方法,再import进 router.js中去判断
if (!auth.loggedIn()) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next() // 确保一定要调用 next()
}
})

过渡效果(暂时不管)

数据获取(这里有渲染导航,还有获取数据两块的)

有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现:

  • 导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示『加载中』之类的指示。

  • 导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。

从技术角度讲,两种方式都不错(我喜欢之后的) —— 就看你想要的用户体验是哪种。

导航完成后获取数据

当你使用这种方式时,我们会马上导航和渲染组件,然后在组件的created 钩子中获取数据。这让我们有机会在数据获取期间展示一个 loading 状态,还可以在不同视图间展示不同的 loading 状态。

假设我们有一个 Post 组件,需要基于 $route.params.id 获取文章数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<template>
<div class="post">
<div class="loading" v-if="loading">
Loading...
</div>

<div v-if="error" class="error">
{{ error }}
</div>

<div v-if="post" class="content">
<h2>{{ post.title }}</h2>
<p>{{ post.body }}</p>
</div>
</div>
</template>

export default {
data () {
return {
loading: false,
post: null,
error: null
}
},
created () {
// 组件创建完后获取数据,
// 此时 data 已经被 observed 了
this.fetchData()
},
watch: {
// 如果路由有变化,会再次执行该方法
'$route': 'fetchData'
},
methods: {
fetchData () {
this.error = this.post = null
this.loading = true
// replace getPost with your data fetching util / API wrapper
getPost(this.$route.params.id, (err, post) => {
this.loading = false
if (err) {
this.error = err.toString()
} else {
this.post = post
}
})
}
}
}

在导航完成前获取数据

通过这种方式,我们在导航转入新的路由前获取数据。我们可以在接下来的组件的 beforeRouteEnter 守卫中获取数据,当数据获取成功后只调用 next 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
export default {
data () {
return {
post: null,
error: null
}
},
beforeRouteEnter (to, from, next) {
getPost(to.params.id, (err, post) => {
next(vm => vm.setData(err, post))
})
},
// 路由改变前,组件就已经渲染完了
// 逻辑稍稍不同
beforeRouteUpdate (to, from, next) {
this.post = null
getPost(to.params.id, (err, post) => {
this.setData(err, post)
next()
})
},
methods: {
setData (err, post) {
if (err) {
this.error = err.toString()
} else {
this.post = post
}
}
}
}

在为后面的视图获取数据时,用户会停留在当前的界面,因此建议在数据获取期间,显示一些进度条或者别的指示。如果数据获取失败,同样有必要展示一些全局的错误提醒。

滚动行为(就是跳转后滚动条位置确定, 用scrollBehavior方法)

使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。

注意: 这个功能只在支持 history.pushState 的浏览器中可用。

当创建一个 Router 实例,你可以提供一个 scrollBehavior 方法:

1
2
3
4
5
6
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
// return 期望滚动到哪个的位置
}
})

scrollBehavior 方法接收 to 和 from 路由对象。第三个参数 savedPosition 当且仅当 popstate 导航 (通过浏览器的 前进/后退 按钮触发) 时才可用。

这个方法返回滚动位置的对象信息,长这样:

1
2
{ x: number, y: number }
{ selector: string, offset? : { x: number, y: number }} (offset 只在 2.6.0+ 支持)

如果返回一个 falsy (译者注:falsy 不是 false,参考这里)的值,或者是一个空对象,那么不会发生滚动。

举例:

1
2
3
scrollBehavior (to, from, savedPosition) {
return { x: 0, y: 0 }
}

对于所有路由导航,简单地让页面滚动到顶部
返回 savedPosition,在按下 后退/前进 按钮时,就会像浏览器的原生表现那样:

1
2
3
4
5
6
7
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
}

如果你要模拟『滚动到锚点』的行为:

1
2
3
4
5
6
7
scrollBehavior (to, from, savedPosition) {
if (to.hash) {
return {
selector: to.hash
}
}
}

路由懒加载(路由不用变,变的是webpack配置,和我想的有点不一样)

Router 构造配置

routes(一看感觉都见过了,确实)
类型: Array

RouteConfig 的类型定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
declare type RouteConfig = {
path: string;
component?: Component;
name?: string; // 命名路由
components?: { [name: string]: Component }; // 命名视图组件
redirect?: string | Location | Function;
props?: boolean | string | Function;
alias?: string | Array<string>;
children?: Array<RouteConfig>; // 嵌套路由
beforeEnter?: (to: Route, from: Route, next: Function) => void;
meta?: any;

// 2.6.0+
caseSensitive?: boolean; // 匹配规则是否大小写敏感?(默认值:false)
pathToRegexpOptions?: Object; // 编译正则的选项
}

mode(一种hash, 一种history)
类型: string

默认值: “hash” (浏览器环境) | “abstract” (Node.js 环境)

可选值: “hash” | “history” | “abstract”

配置路由模式:

hash: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器。

history: 依赖 HTML5 History API 和服务器配置。查看 HTML5 History 模式。

abstract: 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式。

base
类型: string

默认值: “/“

应用的基路径。例如,如果整个单页应用服务在 /app/ 下,然后 base 就应该设为 “/app/“。
一般写成 __dirname,在webpack中有配置。

vue2getstart

发表于 2018-01-31 | 分类于 前端

vue2的入门

开始梳理一下vue2的入门,主要还是一些模块的使用,反正vue2就是路由和模块

vue2 getstart vue实例

通过下面语句可以动态查看data

1
<pre>{{$data | json}} </pre>

你也可以自定义一个json的filters ,方法同methods
有时候删除不一定要定义methods 也可以用filters

老外的那个例子来讲些vue基础

主要讲解用到了

1
2
3
4
5
v-bind

v-text {{}} v-if v-show v-for v-on ref v-model .prevant

el data methdos computed filters mounted

过滤器|只用在mustache和v-bind表达式中,在v-for中用的话写一个computed

做一个,todolist的例子,实现增删改功能。
上面部分是tasks,下面部分是完成打钩的tasks。

getstart


创建一个 Vue 实例以及数据与方法

v-bind和v-model都是双向绑定,但v-model特别的用在表单中就好了,v-bind也可以用在表单,用来动态绑定value

当一个 Vue 实例被创建时,它向 Vue 的响应式系统中加入了其 data 对象中能找到的所有的属性。
只有当实例被创建时 data 中存在的属性才是响应式的,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div>{{meg}}</div>
<div>{{name}}</div>

var vm = new Vue({
data: {
msg: '' //这个msg是响应的,name不是响应式的,也可以说在vue实例中没name这个东西,不存在给name响应式。
//name: '' 所以给暂时没用到的可以设置为空值就好了
}
})

//一般实例就
new Vue({
// 选项
})

关于使用Object.freeze(obj)来阻止响应式,如果这个data是放在vue的data中该怎么freeze?
比如上面的msg,我总不能也在vue实例外面var一个吧,然后freeze。

官网的例子是可以freeze的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var obj = {
foo: 'bar'
}

Object.freeze(obj)

new Vue({
el: '#app',
data () { //看到这里和上面的写法不一样,是因为这个是按组件的写法,data写成一个函数,防止复用组件时作用域混了
return {
obj
}
}
})

除了数据属性,Vue 实例还暴露了一些有用的实例属性与方法,它们都有前缀 $,以便与用户定义的属性区分开来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var data = { a: 1 }
var vm = new Vue({
el: '#example',
data: data
})

//实例属性
vm.$el === document.getElementById('example') // => true
vm.$data === data // => true

// $watch 是一个实例方法
vm.$watch('a', function (newValue, oldValue) {
// 这个回调将在 `vm.a` 改变后调用
})
//嵌套对象的增添
vm.$set(this.userProfile, 'age', 27)

对于
new Vue({
el: '#example',
data: data
})

//$el,$data
$el === document.getElementById('example') // => true
$data === data // => true

// $watch 是一个实例方法
$watch

比如用来看数据
<pre> {{$data | json}} </pre>

vue lifecycle

可以通过例子vuelifecycyle.html来观察

beforecreate : 可以在这加个loading事件
created :在这结束loading,还做一些初始化,实现函数自执行
mounted : 在这发起axios请求,拿回数据,配合路由钩子做一些事情
beforeDestory: destoryed :当前组件已被删除,清空相关内容

Vue2.0 探索之路——生命周期和钩子函数的一些理解


模板语法 去HTML中了

插值 Mustache 和 v-bind(:) (一个用在文本,一个用在属性)

常见的数据绑定就是使用“Mustache”语法 (双大括号) 的文本插值

1
2
3
4
5
6
<span>Message: {{ msg }}</span>
//v-text效果一样,但不好用
<span v-text="msg"></span>
//加了v-once表示执行一次性地插值,只执行一次哦
<span v-once>这个将不会改变: {{ msg }}</span>
//v-html输出真正的 HTML,暂时不用,防止被xss攻击

上面Mustache 语法用在文本上,不能作用在 HTML 特性(属性)上,遇到这种情况应该使用 v-bind 指令:

1
2
<div v-bind:id="dynamicId"></div>
<button v-bind:disabled="isButtonDisabled">Button</button>

上面两者都只能使用单个js表达式,事实上所有v-指令除了v-for都是。
指令么暂时 v-if v-show v-for v-on(@)
参数
再说下布尔值 truthy和falsy。falsy有5种:0,false,undefined, null, ‘’
还有别的参数,就当变量好了:串,函数

修饰符 缩写


又回到vue实例中:计算属性和观察者

1
2
3
4
5
6
new Vue({
el: '#app'
data: {

}
})

从刚开始的 el data
后面还有 filters
现在加上 computed methods watch 三者区别看史上最详细 VUE2.0 全套 demo 讲解 基础3(计算属性)

一.computed前面说了是适用于对多数据变动进行监听,然后来维护一个状态,就是返回一个状态
二.watch是对一个数据监听,在数据变化时,会返回两个值 ,一个是value(当前值),二个是oldvalue是变化前的值,我们可以通过这些变化也可以去维护一个状态,但是不符合场景,主要用于什么地方呢?主要用于监听一个数据来进行复杂的逻辑操作,如图片加载完ajax,开销大的

当然别忘了lifecycle的 created mounted updated destroyed 以及他们对应的before

在模板中(mustache),对于任何复杂逻辑,你都应当使用计算属性

官网例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div id="example">
{{ message.split('').reverse().join('') }} //太多了
</div>

//改写为
<div id="example">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p> //这就清楚多了
</div>

var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
}
})

计算属性的 setter
计算属性默认只有 getter ,不过在需要时你也可以提供一个 setter :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 注意这里的写法和默认只有get的写法稍有不同
computed: {
fullName: { //here
// getter
get: function () { //这里看到了吗
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}

计算属性缓存 vs 方法

计算属性(computed)是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变时才会重新求值。
方法(mtehods):每当触发重新渲染时,调用方法将总会再次执行函数

计算属性 vs 侦听属性
侦听属性:当你有一些数据需要随着其它数据变动而变动时.通常更好的做法是使用计算属性而不是命令式的 watch 回调.当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的

官网例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question">
</p>
<p>{{ answer }}</p>
</div>
<!-- 因为 AJAX 库和通用工具的生态已经相当丰富,Vue 核心代码没有重复 -->
<!-- 提供这些功能以保持精简。这也可以让你自由选择自己更熟悉的工具。 -->
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script>
<script>
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: 'I cannot give you an answer until you ask a question!'
},
watch: {
// 如果 `question` 发生改变,这个函数就会运行
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.getAnswer()
}
},
methods: {
// `_.debounce` 是一个通过 Lodash 限制操作频率的函数。
// 在这个例子中,我们希望限制访问 yesno.wtf/api 的频率
// AJAX 请求直到用户输入完毕才会发出。想要了解更多关于
// `_.debounce` 函数 (及其近亲 `_.throttle`) 的知识,
// 请参考:https://lodash.com/docs#debounce
getAnswer: _.debounce(
function () {
if (this.question.indexOf('?') === -1) {
this.answer = 'Questions usually contain a question mark. ;-)'
return
}
this.answer = 'Thinking...'
var vm = this
axios.get('https://yesno.wtf/api')
.then(function (response) {
vm.answer = _.capitalize(response.data.answer)
})
.catch(function (error) {
vm.answer = 'Error! Could not reach the API. ' + error
})
},
// 这是我们为判定用户停止输入等待的毫秒数
500
)
}
})
</script>


Class 与 Style 绑定 又回到模板中

都是属性啊,所以用v-bind,而且表达式结果的类型除了字符串之外,还可以是对象或数组
注意对象是key:value 数组时value 反正就是看对value就行

class

对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//这就是对象了 ={} key:value  本来是字符串=""
<div v-bind:class="{ active: isActive }"></div>

//多个
<div class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }">
</div>

data: {
isActive: true,
hasError: false
}

//当然也可以直接就写个对象名
<div v-bind:class="classObject"></div>

data: {
classObject: {
active: true,
'text-danger': false
}
}

//常用的还有写成computed的
data: {
isActive: true,
error: null
},
computed: {
classObject: function () {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal'
}
}
}

数组

1
2
3
4
5
6
7
8
9
//数组 =[]    稍微和对象不一样哦这里只有value
<div v-bind:class="[activeClass, errorClass]"></div>
data: {
activeClass: 'active',
errorClass: 'text-danger'
}

//数组中也有对象哦,可以当做有个默认值
<div v-bind:class="[{ active: isActive }, errorClass]"></div>

style (会自动添加浏览器前缀)

对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//CSS 属性名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用单引号括起来) 来命名,不推荐
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

data: {
activeColor: 'red',
fontSize: 30
}

//直接绑定到一个样式对象通常更好,这会让模板更清晰:
<div v-bind:style="styleObject"></div>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
//同样的,对象语法常常结合返回对象的计算属性使用

数组

1
2
3
//v-bind:style 的数组语法可以将多个样式对象应用到同一个元素上:

<div v-bind:style="[baseStyles, overridingStyles]"></div>

当然他们还有在组件中应用,暂时不说


接着说指令了,开始v-if v-show v-for -v-on

条件渲染 先说v-if v-show

看代码就明白了 v-if

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//针对一个元素 v-if v-else-if v-else 一个表示就一个根节点
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>

//多个元素就要加上<template>,这个表示同级的(没有根节点,比如在表单中就不会用到嵌套,用的是同级的),当然你直接在父元素上加也行啊(这就是嵌套)
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>

再说下key,上面的没加key是会复用的,所以会发生不会清除用户已经输入内容的情况
key表示”这两个元素是完全独立的,不要复用它们”

1
2
3
4
5
6
7
8
9
10
<template v-if="loginType === 'username'">
<label>Username</label>
//key加在了这里哦
<input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
<label>Email</label>
//还有这里
<input placeholder="Enter your email address" key="email-input">
</template>

v-show

1
2
//只有一种形式,不多对于多个的只需要在父节点处用v-show就行了,反正不用同级的
<h1 v-show="ok">Hello!</h1>

v-if vs v-show
v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。

v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换display:none。

一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

v-if 与 v-for 一起使用
当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级


列表渲染 也就是v-for 优先级比v-if高

当然这个使用在对数组[] 以及对象的 {}

用 v-for 把一个数组对应为一组元素

在 v-for 块中,我们拥有对父作用域属性的完全访问权限
2种方式。一个是v-for=”item in items” items 是源数据数组并且 item 是数组元素迭代的别名
一个是v-for=”(item, index) in items”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//第一种只有value
<ul id="example-1">
<li v-for="item in items">
{{ item.message }}
</li>
</ul>
var example1 = new Vue({
el: '#example-1',
data: {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})

//第二种有value和index
<ul id="example-2">
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
</ul>
var example2 = new Vue({
el: '#example-2',
data: {
parentMessage: 'Parent',
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})

一个对象对应为一组元素

这就有3种了 毕竟对象有key: value 不过这里按 value key index 理由当然也是先拿value咯,不过还是按key的顺序哦
在遍历对象时,是按 Object.keys() 的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下是一致的。
第1种是v-for=”value in object”
第2种是v-for=”(value, key) in object”
第3种是v-for=”(value, key, index) in object”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//
<ul id="v-for-object" class="demo">
<li v-for="value in object">
{{ value }}
</li>
</ul>
new Vue({
el: '#v-for-object',
data: {
object: {
firstName: 'John',
lastName: 'Doe',
age: 30
}
}
})

//提供第二个的参数为键名:
<div v-for="(value, key) in object">
{{ key }}: {{ value }}
</div>

//第三个参数为索引:
<div v-for="(value, key, index) in object">
{{ index }}. {{ key }}: {{ value }}
</div>

key,当然表示唯一咯,同v-if啦

建议尽可能在使用 v-for 时提供 key
理想的 key 值是每项都有的且唯一的 id,它的工作方式类似于一个属性,所以你需要用 v-bind 来绑定动态值。
一般在数组或对象中提供了

1
2
3
<div v-for="item in items" :key="item.id">
<!-- 内容 -->
</div>

它是 Vue 识别节点的一个通用机制,key 并不与 v-for 特别关联,key 还具有其他用途,我们将在后面的指南中看到其他用途

数组更新检测(其实不用管这个,v-for还是会触发视图更新)

变异方法 就是改变原数组咯

  • push()
  • pop()
  • unshift()
  • shift()
  • reverse()
  • sort()
  • splice()
  • clear()

替换数组 就是返回一个新数组咯

filter(), concat() 和 slice() map()

有2种情况下数组检测不到更新(因为js的限制,要换方法使用)

  1. 当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue
  2. 当你修改数组的长度时,例如:vm.items.length = newLength

看下面 我觉得统一下 用splice好了

1
2
3
4
5
6
7
8
为了解决第一类问题,以下两种方式都可以实现和 vm.items[indexOfItem] = newValue 相同的效果,同时也将触发状态更新:
// Vue.set
Vue.set(example1.items, indexOfItem, newValue)
// Array.prototype.splice
example1.items.splice(indexOfItem, 1, newValue)

为了解决第二类问题,你可以使用 splice:
example1.items.splice(newLength)

上面是数组的增删,可以检测到,但对象属性的增删(又由于JS限制,检测不到。增加一个属性或多个属性)

就是不对根级别添加,而是对根里面嵌套的对象的添加

1
2
3
4
5
6
7
8
9
10
11
还是由于 JavaScript 的限制,Vue 不能检测对象属性的添加或删除:

var vm = new Vue({
data: {
a: 1
}
})
// `vm.a` 现在是响应式的

vm.b = 2
// `vm.b` 不是响应式的

对于已经创建的实例,Vue 不能动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, key, value) 方法向嵌套对象添加响应式属性。例如,对于

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

var vm = new Vue({
data: {
userProfile: { //看这里,嵌套哦
name: 'Anika'
}
}
})
你可以添加一个新的 age 属性到嵌套的 userProfile 对象:

Vue.set(vm.userProfile, 'age', 27)

你还可以使用 vm.$set 实例方法,它只是上面全局 Vue.set 的别名:

vm.$set(this.userProfile, 'age', 27)

有时你可能需要为已有对象赋予多个新属性,比如使用 Object.assign() 或 _.extend()。在这种情况下,你应该用两个对象的属性创建一个新的对象。所以,如果你想添加新的响应式属性,不要像这样:

1
2
3
4
5
6
7
8
9
10
Object.assign(this.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
你应该这样做:

this.userProfile = Object.assign({}, this.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})

v-for 显示过滤/排序结果(这个用不来filters,用computed,methods)

数组
有时,我们想要显示一个数组的过滤或排序副本,而不实际改变或重置原始数据。在这种情况下,可以创建返回过滤或排序数组的计算属性。

例如:

1
2
3
4
5
6
7
8
9
10
11
<li v-for="n in evenNumbers">{{ n }}</li>
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
evenNumbers: function () {
return this.numbers.filter(function (number) {
return number % 2 === 0
})
}
}

在计算属性不适用的情况下 (例如,在嵌套 v-for 循环中) 你可以使用一个 method 方法:

1
2
3
4
5
6
7
8
9
10
11
<li v-for="n in even(numbers)">{{ n }}</li>     //可以看到这里不同
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
methods: {
even: function (numbers) {
return numbers.filter(function (number) {
return number % 2 === 0
})
}
}

一段取值范围的 v-for 整数
v-for 也可以取整数。在这种情况下,它将重复多次模板。

1
2
3
<div>
<span v-for="n in 10">{{ n }} </span>
</div>

v-for on a <template> 多个同级,上面是一个根元素
类似于 v-if,你也可以利用带有 v-for 的

1
2
3
4
5
6
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider"></li>
</template>
</ul>

v-for with v-if 前面说的优先级问题,在同一个标签上v-for高
当它们处于同一节点,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。当你想为仅有的一些项渲染节点时,这种优先级的机制会十分有用,如下:

1
2
3
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo }}
</li>

上面的代码只传递了未完成的 todos。

而如果你的目的是有条件地跳过循环的执行,那么可以将 v-if 置于外层元素 (或

这里没啥优先级而言,都不在一个元素标签上

1
2
3
4
5
6
<ul v-if="todos.length">
<li v-for="todo in todos">
{{ todo }}
</li>
</ul>
<p v-else>No todos left!</p>

组件的v-for (看完组件那块再回来体会)

在自定义组件里,你可以像任何普通元素一样用 v-for 。用法不变,key一定要加 (但是这种做法错误的哦)。

1
<my-component v-for="item in items" :key="item.id"></my-component>

重点

因为任何数据都不会被自动传递到组件里,因为组件有自己独立的作用域。为了把迭代数据传递到组件里,我们要用 props :

1
2
3
4
5
6
7
//上面的要改为这样写
<my-component
v-for="(item, index) in items"
v-bind:item="item"
v-bind:index="index"
v-bind:key="item.id"
></my-component>

不自动将 item 注入到组件里的原因是,这会使得组件与 v-for 的运作紧密耦合。明确组件数据的来源能够使组件在其他场合重复使用

下面是一个简单的 todo list 的完整例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div id="todo-list-example">
<input
v-model="newTodoText"
v-on:keyup.enter="addNewTodo"
placeholder="Add a todo"
>
<ul>
<li
is="todo-item" //这里不是<todo-item> </todo-item>
v-for="(todo, index) in todos"
v-bind:key="todo.id"
v-bind:title="todo.title" //这里title 在组件中的props中绑定,todo.title才是变量名
v-on:remove="todos.splice(index, 1)"
></li>
</ul>
</div>

注意这里的 is=”todo-item” 属性。这种做法在使用 DOM 模板时是十分必要的,因为在 <ul> 元素内只有 <li> 元素会被看作有效内容。这样做实现的效果与 相同,但是可以避开一些潜在的浏览器解析错误

类似的还有在<ul>、<ol>、<table>、<select>

当然有办法不这么麻烦 最简单的就是使用vue-cli咯

如果使用来自以下来源之一的字符串模板,则没有这些限制:

  • <script type="text/x-template">
  • JavaScript 内联模板字符串
  • .vue 组件

因此,请尽可能使用字符串模板。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Vue.component('todo-item', {
template: '\
<li>\
{{ title }}\
<button v-on:click="$emit(\'remove\')">X</button>\
</li>\
',
props: ['title'] //这是属性哦,title是属性名,不是变量名
})

new Vue({
el: '#todo-list-example',
data: {
newTodoText: '',
todos: [
{
id: 1,
title: 'Do the dishes',
},
{
id: 2,
title: 'Take out the trash',
},
{
id: 3,
title: 'Mow the lawn'
}
],
nextTodoId: 4
},
methods: {
addNewTodo: function () {
this.todos.push({
id: this.nextTodoId++,
title: this.newTodoText
})
this.newTodoText = ''
}
}
})

事件处理 (到v-on了 @ 这个是监听事件,不是触发)

Vue2.0进阶组件篇1 教你秒撸(短信倒计时组件)

可以用 v-on 指令监听(on) DOM 事件,并在触发时(emit)运行一些 JavaScript 代码。

示例:

1
2
3
4
5
6
7
8
9
10
<div id="example-1">
<button v-on:click="counter += 1">Add 1</button> //这里把方法直接写在""内了
<p>The button above has been clicked {{ counter }} times.</p>
</div>
var example1 = new Vue({
el: '#example-1',
data: {
counter: 0
}
})

更常见的,直接写一个方法名称

然而许多事件处理逻辑会更为复杂,所以直接把 JavaScript 代码写在 v-on 指令中是不可行的。因此 v-on 还可以接收一个需要调用的方法名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<div id="example-2">
<!-- `greet` 是在下面定义的方法名 -->
<button v-on:click="greet">Greet</button>
</div>

var example2 = new Vue({
el: '#example-2',
data: {
name: 'Vue.js'
},
// 在 `methods` 对象中定义方法
methods: {
greet: function (event) {
// `this` 在方法里指向当前 Vue 实例
alert('Hello ' + this.name + '!')
// `event` 是原生 DOM 事件
if (event) {
alert(event.target.tagName) //这里还会弹出个'BUTTON'
}
}
}
})

// F12也可以用 JavaScript 直接调用方法
example2.greet() // => 'Hello Vue.js!' 没有'BUTTON'了

除了直接绑定到一个方法,也可以在内联 JavaScript 语句中调用方法(传入参数就用好了):

1
2
3
4
5
6
7
8
9
10
11
12
<div id="example-3">
<button v-on:click="say('hi')">Say hi</button> <!-- 直接传入参数用了 -->
<button v-on:click="say('what')">Say what</button>
</div>
new Vue({
el: '#example-3',
methods: {
say: function (message) {
alert(message)
}
}
})

有时也需要在内联语句处理器中访问原始的 DOM 事件(这里是button)。可以用特殊变量 $event 把它传入方法:

1
2
3
4
5
6
7
8
9
10
11
<button v-on:click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
// ...
methods: {
warn: function (message, event) {
// 现在我们可以访问原生事件对象 不懂这个什么意思
if (event) event.preventDefault()
alert(message)
}
}

一个小例子说明event.preventDefault()来阻止默认事件的发生,event.stopPropagation()阻止事件的冒泡
比如<a>点击它会发生跳转,用了preventDefault()之后就不跳转了,但会向上冒泡。

1
2
3
<div class="box">
<a href="https://www.baidu.com">跳转</a>
</div>

总结
1.event.stopPropagation()方法
这是阻止事件的冒泡方法,不让事件向documen上蔓延,但是默认事件仍然会执行,当你掉用这个方法的时候,如果点击一个连接,这个连接仍然会被打开,

2.event.preventDefault()方法
这是阻止默认事件的方法,调用此方法是,连接不会被打开,但是会发生冒泡,冒泡会传递到上一层的父元素;

3.return false ;
这个方法比较暴力,他会同时阻止事件冒泡也会阻止默认事件;写上此代码,连接不会被打开,事件也不会传递到上一层的父元素;可以理解为return false就等于同时调用了event.stopPropagation()和event.preventDefault()

不懂event.preventDefault()可以看下这个链接
阻止事件冒泡,阻止默认事件,event.stopPropagation()和event.preventDefault(),return false的区别
浅谈js中事件preventDefault()和addEventListener()

v-on事件修饰符(接着上面的event.preventDefault()和event.stopPropagation())

在事件处理程序中调用 event.preventDefault() 或 event.stopPropagation() 是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。

修饰符是由点开头的指令后缀来表示的,记5个

  • stop (event.stopPropagation())
  • prevent (event.preventDefault())
  • capture
  • self
  • once (这个和前面介绍过的v-once一样,都只一次)
  • passive (尤其能够提升移动端的性能。对应 addEventListener 中的 passive 选项)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!-- 阻止单击事件继续传播 就是event.stopPropagation()-->
<a v-on:click.stop="doThis"></a>

<!-- 提交事件不再重载页面 就是event.preventDefault()-->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修饰符可以串联 注意次序很重要-->
<a v-on:click.stop.prevent="doThat"></a>

<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>

<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理 (先自己,在内部,这什么操作,冒泡呢)-->
<div v-on:click.capture="doThis">...</div>

<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 (不是冒泡过来的,是自身div)-->
<div v-on:click.self="doThat">...</div>

<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>

<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<!-- 不要把 .passive 和 .prevent 一起使用,因为 .prevent 将会被忽略,同时浏览器可能会向你展示一个警告。请记住,.passive 会告诉浏览器你不想阻止事件的默认行为。 -->
<div v-on:scroll.passive="onScroll">...</div>

按键修饰符

记住所有的 keyCode 比较困难,所以 Vue 为最常用的按键提供了别名:

1
2
3
4
5
6
7
8
<!-- 只有在 `keyCode` 是 13 时调用 `vm.submit()` -->
<input v-on:keyup.13="submit">

<!-- 同上 -->
<input v-on:keyup.enter="submit">

<!-- 缩写语法 -->
<input @keyup.enter="submit">

全部的按键别名:

  • enter
  • tab
  • delete (捕获“删除”和“退格”键)
  • esc
  • space
  • up
  • down
  • left
  • right

可以通过全局 config.keyCodes 对象自定义按键修饰符别名:

1
2
// 可以使用 `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112

系统修饰键(这个只要有一个按下了就行,模糊,要精准加.exact修饰符)

请注意修饰键与常规按键不同,在和 keyup 事件一起用时,事件触发时修饰键必须处于按下状态。换句话说,只有在按住 ctrl 的情况下释放其它按键,才能触发 keyup.ctrl。而单单释放 ctrl 也不会触发事件。

可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。

  • ctrl
  • alt
  • shift
  • meta (在 Mac 系统键盘上,meta 对应 command 键 (⌘)。在 Windows 系统键盘 meta 对应 Windows 徽标键 (⊞))

.exact 修饰符

.exact 修饰符允许你控制由精确的系统修饰符组合触发的事件。

1
2
3
4
5
6
7
8
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>

<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>

<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button>

鼠标按钮修饰符

  • left
  • right
  • middle

这些修饰符会限制处理函数仅响应特定的鼠标按钮。


表单输入绑定v-model

也就单行 多行,单选 多选 下拉 5个

用 v-model 指令在表单 <input> 及 <textarea> 元素上创建双向数据绑定。

v-model 会忽略所有表单元素的 value、checked、selected 特性的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。

文本和多行文本

1
2
3
4
5
6
7
8
9
文本
<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>

多行文本
<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<br>
<textarea v-model="message" placeholder="add multiple lines"></textarea>

单选

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
单选按钮    看下面单选多选,一组的 只要v-model绑定的名字一样就是一组的 不用写key,render后也没有key
<div id="example-4">
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<br>
<span>Picked: {{ picked }}</span>
</div>
new Vue({
el: '#example-4',
data: {
picked: ''
}
})

复选

注意,这5种就这个复选除了静态的字符串,还有个布尔值,其他4种静态的只有字符串。后面v-bind解放一切

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
单个复选框,绑定到布尔值:显示的是boolean值 false 和 true,

<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>

多个复选框,绑定到同一个数组:

<div id='example-3'>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names: {{ checkedNames }}</span> //这里显示初始值是空的,而value值是点击选中后才有的
</div>
new Vue({
el: '#example-3',
data: {
checked: false,
checkedNames: []
}
})

选择框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
单选时:

<div id="example-5">
<select v-model="selected">
<option disabled value="">请选择</option> <!-- 提供一个值为空的禁用选项,防止无法选中第一个 -->
<option>A</option>
<option>B</option>
<option value="DD">C</option> <!-- 没有value时选内容,有value就value -->
</select>
<span>Selected: {{ selected }}</span>
</div>
new Vue({
el: '...',
data: {
selected: ''
}
})


多选时 (绑定到一个数组):

<div id="example-6">
<select v-model="selected" multiple style="width: 50px;"> <!-- 多了个multiple -->
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<br>
<span>Selected: {{ selected }}</span>
</div>
new Vue({
el: '#example-6',
data: {
selected: [] <!-- 换成数组 -->
}
})

如果 v-model 表达式的初始值未能匹配任何选项,<select> 元素将被渲染为“未选中”状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change 事件。因此,更推荐像上面这样提供一个值为空的禁用选项。


上面该讲的讲好了,下面做一些补充

用 v-for 渲染的动态选项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<select v-model="selected">
<option v-for="option in options" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
<span>Selected: {{ selected }}</span>
new Vue({
el: '...',
data: {
selected: 'A',
options: [
{ text: 'One', value: 'A' },
{ text: 'Two', value: 'B' },
{ text: 'Three', value: 'C' }
]
}
})

值绑定 v-bind稳的一笔

对于单选按钮,复选框及选择框的选项,v-model 绑定的值通常是静态字符串 (对于复选框也可以是布尔值):

1
2
3
4
5
6
7
8
9
10
<!-- 当选中时,`picked` 为字符串 "a" -->
<input type="radio" v-model="picked" value="a">

<!-- `toggle` 为 true 或 false -->
<input type="checkbox" v-model="toggle">

<!-- 当选中时,`selected` 为字符串 "abc" -->
<select v-model="selected">
<option value="abc">ABC</option>
</select>

但是有时我们可能想把值绑定到 Vue 实例的一个动态属性上,这时可以用 v-bind 实现,并且这个属性的值可以不是字符串。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
单选按钮
<input type="radio" v-model="pick" v-bind:value="a">
// 当选中时
vm.pick === vm.a

复选框
<input
type="checkbox"
v-model="toggle"
true-value="yes"
false-value="no"
>
// 当选中时
vm.toggle === 'yes'
// 当没有选中时
vm.toggle === 'no'
这里的 true-value 和 false-value 特性并不会影响输入控件的 value 特性,因为浏览器在提交表单时并不会包含未被选中的复选框。如果要确保表单中这两个值中的一个能够被提交,(比如“yes”或“no”),请换用单选按钮。

选择框的选项
<select v-model="selected">
<!-- 内联对象字面量 -->
<option v-bind:value="{ number: 123 }">123</option>
</select>
// 当选中时
typeof vm.selected // => 'object'
vm.selected.number // => 123

修饰符 3个(.lazy, .number, .trim)

.lazy
在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。你可以添加 lazy 修饰符,从而转变为使用 change 事件进行同步:

1
2
3
4
5
<!-- 一输入就变化 -->
<p>{{msg}}</p>
<input v-model="msg" >
<!-- 在“change”时而非“input”时更新 比如回车后才变化 或则失去焦点-->
<input v-model.lazy="msg" >

.number
如果想自动将用户的输入值转为数值类型(因为这5种绑定的默认都是静态的字符串啊),可以给 v-model 添加 number 修饰符:
也会自动去除不是数字的内容

1
<input v-model.number="age" type="number">

这通常很有用,因为即使在 type=”number” 时,HTML 输入元素的值也总会返回字符串。

.trim
如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:

1
<input v-model.trim="msg">

哈哈哈 3个一起用 顺序没关系

1
<input v-model.lazy.number.trim="msg">

参考
Vue 2.0学习笔记:v-model

史上最详细VUE2.0全套demo讲解 基础1(模板语法)


路由就是使用 vue-router

vuex 状态保存

axios

element-ui

vscode常用快捷键

发表于 2018-01-27 | 分类于 前端

vscode常用快捷键

按按键个数分
ctrl+o 单一
ctrl+shift+o 组合
ctrl+k, ctrl+o 依赖一个键
ctrl+k, o先依赖一个,再单个
alt 鼠标 键盘+鼠标点击
ctrl 鼠标拖动 键盘+ 鼠标拖动

按 ctrl+k ctrl+s 可以查看键盘快捷方式

jj {

}

PAT乙级

发表于 2018-01-24 | 分类于 前端

PAT乙级(一共80题)

1001. 害死人不偿命的(3n+1)猜想 (15)

卡拉兹(Callatz)猜想:

对任何一个自然数n,如果它是偶数,那么把它砍掉一半;如果它是奇数,那么把(3n+1)砍掉一半。这样一直反复砍下去,最后一定在某一步得到n=1。卡拉兹在1950年的世界数学家大会上公布了这个猜想,传说当时耶鲁大学师生齐动员,拼命想证明这个貌似很傻很天真的命题,结果闹得学生们无心学业,一心只证(3n+1),以至于有人说这是一个阴谋,卡拉兹是在蓄意延缓美国数学界教学与科研的进展……

我们今天的题目不是证明卡拉兹猜想,而是对给定的任一不超过1000的正整数n,简单地数一下,需要多少步(砍几下)才能得到n=1?
·
输入格式:每个测试输入包含1个测试用例,即给出自然数n的值。

输出格式:输出从n计算到1需要的步数。

输入样例:
3
输出样例:
5

思路

读入题目给定的n,之后用while做循环,反复判断n是否为1

  1. 如果n==1,则退出循环,
  2. 如果n!=1,则判断n是否是偶数,是则n除以2;否则n为(3*n+1)除以2.之后令计数器step加1
    程序执行完后,step就是答案

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <cstdio>
int main(){
int n = 0, step = 0; //要初始化,不然vs2008输入数字是终止
scanf("%d", &n);
while(n != 1){
if (n % 2 == 0){ //也可以用(n&1) == 0 但是判断奇偶是不能用 n%2 !=0
n = n / 2;
}else {
n = (3*n + 1)/2;
}
step++;
}
printf("%d", step);
return 0;
}

1009. 说反话 (20)

给定一句英语,要求你编写程序,将句中所有单词的顺序颠倒输出。

输入格式:测试输入包含一个测试用例,在一行内给出总长度不超过80的字符串。字符串由若干单词和若干空格组成,其中单词是由英文字母(大小写有区分)组成的字符串,单词之间用1个空格分开,输入保证句子末尾没有多余的空格。

输出格式:每个测试用例的输出占一行,输出倒序后的句子。

输入样例:
Hello World Here I Come
输出样例:
Come I Here World Hello

思路

使用gets函数读入一整行,从左至右枚举每一个字符,以空格为分隔符对单词进行划分,并按顺序存放到二维字符数组中,最后按单词输入顺序的逆序来输出所有单词。

代码参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <cstdio>
int main(){
int num = 0;
char ans[90][90] = {};
while(scanf("%s", ans[num]) != EOF) {
num++;
}

for(int i = num -1; i >= 0; i--) {
printf("%s", ans[i]);
if(i > 0) {
printf(" ");
}
}
return 0;
}

#include <cstdio>
#include <cstring>
int main() {
char str[90] = {};
gets(str);
int len = strlen(str), r = 0, h = 0;
char ans[90][90] = {};
for(int i = 0; i < len; i++) {
if(str[i] != ' ') { //还没到空格就存入
ans[r][h++] = str[i];
}else {
ans[r][h] = '\0'; //是空格就在末尾加\0 控制%s输出
r++;
h = 0;
}
}

for(int i = r; i >= 0; i--) {
printf("%s", ans[i]);
if(i > 0) {
printf(" ");
}
}
return 0;
}

1022. D进制的A+B (20)

输入两个非负10进制整数A和B(<=230-1),输出A+B的D (1 < D <= 10)进制数。

输入格式:

输入在一行中依次给出3个整数A、B和D。

输出格式:

输出A+B的D进制数。

输入样例:
123 456 8
输出样例:
1103

思路

先计算A+B的按10进制,然后把结果转成D进制,除基取余法。

代码参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <cstdio>
int main(){
int a, b, d;
scanf("%d%d%d", &a, &b, &d);
int sum = a + b;
int ans[31], num = 0; //存放D进制每一位 31表示以2进制表示最多31个位置就够了
do {
ans[num++] = sum % d;
sum /= d;
}while(sum);

for(int i = num-1; i >= 0; i--){ //逆序输出
printf("%d", ans[i]);
}
return 0;
}

1032. 挖掘机技术哪家强(20)

为了用事实说明挖掘机技术到底哪家强,PAT组织了一场挖掘机技能大赛。现请你根据比赛结果统计出技术最强的那个学校。

输入格式:

输入在第1行给出不超过105的正整数N,即参赛人数。随后N行,每行给出一位参赛者的信息和成绩,包括其所代表的学校的编号(从1开始连续编号)、及其比赛成绩(百分制),中间以空格分隔。

输出格式:

在一行中给出总得分最高的学校的编号、及其总分,中间以空格分隔。题目保证答案唯一,没有并列。

输入样例:
6
3 65
2 80
1 100
2 70
3 40
3 0
输出样例:
2 150

思路

  • 用一个数组school[maxn],下标表示学校,内容表示分数,初始为0。对于每读入一个学校的schID和分数score,相应school[schID]+=score;
  • 令变量k记录最高总分学校编号,变量MAX记录最高总分,初值-1.由于学校是连续编号的从1开始,因此枚举编号1~N, 不断更新k和MAX值就好

代码参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <cstdio>
int main(){
const int maxn = 100001;
int school[maxn] = {0}; //初始化

int n = 0, schID = 0, score = 0;
scanf("%d", &n); //不判断n是否合法,因为给定就是在10^5内
for(int i = 0; i < n; i++){
scanf("%d%d", &schID, &score);
school[schID] += score;
}

int k =1, MAX = -1;
for(int i = 1; i <= n; i++){
if(school[i] > MAX){
MAX = school[i];
k = i;
}
}

printf("%d %d", k, MAX);
return 0;
}

1036. 跟奥巴马一起编程(15)

美国总统奥巴马不仅呼吁所有人都学习编程,甚至以身作则编写代码,成为美国历史上首位编写计算机代码的总统。2014年底,为庆祝“计算机科学教育周”正式启动,奥巴马编写了很简单的计算机代码:在屏幕上画一个正方形。现在你也跟他一起画吧!

输入格式:

输入在一行中给出正方形边长N(3<=N<=20)和组成正方形边的某种字符C,间隔一个空格。

输出格式:

输出由给定字符C画出的正方形。但是注意到行间距比列间距大,所以为了让结果看上去更像正方形,我们输出的行数实际上是列数的50%(四舍五入取整)。

输入样例:
10 a
输出样例:
aaaaaaaaaa
a a
a a
a a
aaaaaaaaaa

思路

看到上面正方形是9*10的。在看有字母的,行数是列数的50%(四舍五入)。

第1行和最后一行全输出n个a,2~row-1行奇数行不输出,偶数行就头尾有a

代码参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <cstdio>
int main(){
int row, col;
char c;
scanf("%d %c", &col, &c); //输入列数和使用字符
if(col % 2 == 1) { //四舍五入得row
row = col / 2 +1;
}else{
row = col / 2;
}
//frist row 1 这里就是从1开始 row表示数量,不用当做数组看,col还是按从0开始看
for(int i = 0; i <= col-1; i++ ){
printf("%c", c);
}
printf("\n"); //第一行后的空行

//2~row-1
for(int i = 2; i <= row-1; i++ ){
printf("%c", c); //第一个c
for(int j = 1; j < col-1; j++){
printf(" "); //输出col-2个空格
}
printf("%c\n", c); //最后一个c
}

//最后一行 row
for(int i = 0; i < col; i++ ){
printf("%c", c);
}

return 0;
}

markdown常用语法

发表于 2018-01-22 | 分类于 前端

直接贴一段用 markdown 写好的文章,两边对照的那种看好了

用 stackEdit

目前用的是 vscodve

虽然 vscode 本身有 markdown 的预览,不过并不支持 LaTeX。推荐两款插件 markdown all in one 和 Markdown Preview Enhanced。不仅支持 LaTex 还有导出 pdf 这种功能,画路程图
markdownlint

3 款插件功能

  • markdown all in one
Tables Are Cool
标题 自动生成目录 引用
ctrl + shift + [/]
删除 强调 斜体
ctrl + b ctrl + i <u>
无序列表 有序列表 列表项
tab / backsapce
链接 图片 索引链接
[]() ![]() [][]
代码,代码块 高亮
``` ```c++
表格

总结

1
2
3
4
5
6
7
8
9
10
11
标题 段落 引用
#
删除 强调 斜体

列表

链接 图片

代码

表格
Tables Are Cool
标题 自动生成目录 引用
# heading [toc] >
删除 强调 斜体
~~del~~ **strong** *em* <u>
无序列表 有序列表 列表项
* 1. - [x]
链接 图片 索引链接
[]() ![]() [][]
代码,代码块 高亮
``` ```c++
表格

标题(有 2 种方式)

Setext 方式

1
2
3
4
大标题         <h1>
===
小标题 <h2>
---

大标题

小标题

ATX 方式

1
2
3
4
5
6
# 一级标题          <h1>
## 二级标题 <h2>
### 三级标题 <h3>
#### 四级标题 <h4>
##### 五级标题 <h5>
###### 六级标题 <h6>

一级标题

二级标题

三级标题

四级标题

五级标题
六级标题

引用

使用>
加不加空格都行,推荐加

1
> hello world   <blockquote>

hello world

嵌套的引用
注意前面加一个或多个空格或 tab,还有要 2 个>>

1
2
> hello
>> world

hello

world

删除 强调 斜体

使用 2 个~号包上的表示删除,没有单个~的用法哦
使用 2 个*号或者 2 个_包上的表示强调
单个*或者_包上的表示斜体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
hello world
//删除线 <del>
~~hello world~~
//强调 <em>
**hello world**
**hello world**
//斜体 <strong>
_hello world_
_hello world_
//也存在只需要\*或\_开头或结尾有就行,但不推荐,因为你不知道什么时候会和前面的\*发生闭合
_hello world
hello world_
_hello world
hello world_

hello world
//删除线
hello world
//强调
hello world
hello world
//斜体
hello world
hello world
//不推荐
hello world
hello world

hello world
hello world

分割线

用___下划线及更多就好了,注意不是-减号,这是小标题,虽然有些用减号来作为分割,比如 cmdmarkdown

1
2
3
hello
---
world

hello


world

列表

无序列表

用*或+或-都可以(仍然推荐用* 注意加空格,虽然可以混用)

1
2
3
4
<ul> <li>
* h
+ hi
- hello
  • h
  • hi
  • hello

嵌套

就是加 tab,不建议只加 1 个空格

1
2
3
* h
* hi
* hello
  • h
    • hi
      • hello

有序列表

所有的都要注意加空格,然后就是第一个起始的为参考基准,后面的数字打乱其实没关系,但不建议打乱写。

还有就是怎么重新建立一个新开始的有序列表

1
2
3
4
5
6
7
8
9
<ol> <li>
1. h
2. hi
3. hello


3. h
2. hi
3. hello
  1. h
  2. hi
  3. hello

  4. h

  5. hi
  6. hello

嵌套

1
2
3
1. h
2. hi
3. hello
  1. h
    1. hi
      1. hello

todo list

1
2
3
4
5
- [ ] 支持以 PDF 格式导出文稿
- [ ] 改进 Cmd 渲染算法,使用局部渲染技术提高渲染效率
- [x] 新增 Todo 列表功能
- [x] 修复 LaTex 公式渲染问题
- [x] 新增 LaTex 公式编号功能
  • [ ] 支持以 PDF 格式导出文稿
  • [ ] 改进 Cmd 渲染算法,使用局部渲染技术提高渲染效率
  • [x] 新增 Todo 列表功能
  • [x] 修复 LaTex 公式渲染问题
  • [x] 新增 LaTex 公式编号功能

超链接

1
2
<p><a></a></p>
[www.github.com](www.github.com)

www.github.com

图片

比超链接多一个!
但图片怎么设置大小呢

1
2
<p><img></p>
![GitHub Mark](http://github.global.ssl.fastly.net/images/modules/logos_page/GitHub-Mark.png "GitHub Mark")

GitHub Mark

索引链接

同超链接但是用两个[],也适用于图片

1
2
3
4
5
[github][1]
![octocat][2]

[1]: www.github.com
[2]: http://github.global.ssl.fastly.net/images/modules/logos_page/Octocat.png

github
octocat

自动链接

用<>

1
<www.github.com>

代码(用`)

行内代码

1 个`

1
2
<code>
`hello`

hello

段落代码

2 个``

hello world

3 个`带序号的

1
2
hello
world

3. 高亮一段代码code

1
2
3
4
5
6
7
@requires_authorization
class SomeClass:
pass

if __name__ == '__main__':
# A comment
print 'hello world'

转义字符(加\)

1
2
3
4
5
6
7
8
9
10
11
12
\   反斜线
` 反引号
* 星号
_ 底线
{} 花括号
[] 方括号
() 括弧
# 井字号
+ 加号
- 减号
. 英文句点
! 惊叹号

表格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
| Tables        | Are           | Cool  |
| ------------- | :-----------: | ----: |
| col 3 is | right-aligned | $1600 |
| col 2 is | centered | $12 |
| zebra stripes | are neat | $1 |

tips:

:---:居中
:---左对齐 --- 也是
---:右对齐
-至少有一个,否则默认左对齐;例如::(建议也放3个)

就是怎么设置几行或几列合一,并没有这功能,所以用html写?
Tables Are Cool
col 3 is right-aligned $1600
col 2 is centered $12
zebra stripes are neat $1

其他功能

2. 书写一个质能守恒公式latex

1
$$E=mc^2$$

5. 高效绘制 序列图

1
2
3
Alice->Bob: Hello Bob, how are you?
Note right of Bob: Bob thinks
Bob-->Alice: I am good thanks!

6. 高效绘制 甘特图

1
2
3
4
5
6
7
8
9
10
11
12
13
title 项目开发流程
section 项目确定
需求分析 :a1, 2016-06-22, 3d
可行性报告 :after a1, 5d
概念验证 : 5d
section 项目实施
概要设计 :2016-07-05 , 5d
详细设计 :2016-07-08, 10d
编码 :2016-07-15, 10d
测试 :2016-07-22, 5d
section 发布验收
发布: 2d
验收: 3d

问题

在使用 hexo 中 如果你写表格用的是 html 这种,那么部署完 hexo 后会出现很多行空行 br,这是因为 hexo 本身的问题,所以要么压缩下,写一行,但这么不就更麻烦了么。要么还是用 markdown 的语法

PAT刷题前的准备

发表于 2018-01-22 | 分类于 前端

C/C++快速入门

1
2
3
4
5
6
#include <cstdio>

int main(){

return 0;
}

一个基本的C++程序模板,当然主要是注意头文件,和c语言的<stdio.h>不一样的,这里是<cstdio>
常用的头文件还有

1
2
3
4
#include <string.h>     //字符串
#include <math.h> //数学函数
#include <iostream> //cin cout
using namespace std;

基本数据类型 不用markdown,用html,但br换行

类型 大致范围 输入 输出
整型 int 正负2*10^9用 %d %d
long long 9*10^18 超过了用这个 LL %lld
浮点型 float 精度6-7位 %f %f
double 精度15-16位 %lf
字符型 char -128~127 用0~127 ‘’ %c %c
字符串 就是字符数组 “” %s
布尔型 bool 正负整数为true,0为false,存储按1和0
1
2
3
%m.nf
这个m表示最少输出m位,包括. 右对齐。前面补空格 %0md表示前面补0
n表示小数输出n位,不存在四舍五入
1
2
3
4
5
6
7
8
scanf  printf  getchar putchar gets puts  sscanf sprintf 4对

scanf是用空白符作为结束(包括tab 空格 回车。\f \r \n \t \v一般输入也就空格,tab,回车),
这个要特别注意%d和%c混用,并且用空格来分割两个整数的情况,读取字符串"hi"时和gets一样末尾自动加'\0'。再说下字符数组{'h', 'i'}因为这样初始化后,后面的元素为0,也就是ascii 0 \0
&只有字符串不加,其他加。
getchar是用来输入字符(这个可以接受上面说的结束符,但上面scanf虽然接受了但用来做结束的)
gets输入字符串的,末尾和scanf一样自动加`\0`,getchar不加。这个gets以\n回车为结束
puts输出字符串,自动在末尾加\n
1
2
3
4
从scanf和printf到sscanf和sprintf
scanf("%d", &n); 到 scanf(screen, "%d", &n); 所以sscanf(str, "%d", &n);
printf("%d", n); 到 printf(screen, "%d", n); 所以sprintf(str, "%d", &n);
不过sscanf和sprintf虽然读入的是串,但可以规定按别的格式读入串。同理输出。可以和printf混用,输出到屏幕
1
强制转换(新类型名)变量名

符号常量和const常量

1
#define     const 推荐用const

注释

1
//  /**/

运算符

1
2
3
4
5
6
7
8
9
10
1.算数
+ - * / % ++ --
2.关系
> < >= <= == !=
3.逻辑
&& || !
4.条件
? :
5.位
& | !

顺序结构

常用数学函数 (加math.h)

  1. fabs(double x)
  2. floor(double x) ceil(double x)
  3. round(double x)和java不一样
  4. pow(double r, double p)
  5. sqrt(double x)
  6. log(double x)
  7. sin(double x) cos(double x) tan(double x)
  8. asin(double x) acos(double x) atan(double x)

选择

1
2
3
4
5
6
if  else
switch case break

在使用if的过程中如果条件是
n!=0, 可以直接写if(n) 非空
n==0, 写if(!n) 这个不是非空,而是空

循环

1
2
3
4
while do-while
for(int i = 0; i < n; i++) c++可以这么写。c不行

break continue

数组

一维数组 大小固定 const N
下标0~n-1

初始化的问题

1
2
3
4
5
6
int a[10] = {};
int a[10] = {0};
但不能
int a[10]; //这里就不是0,而是随机数

用memset来对数组元素全设为0或-1,其他的用fill函数

二维数组

int a[5][6] = { {}, {}, {} };

若定义的数组大于10^6,则放在main函数外,不然栈中放不下,静态存储区可以放得下。

字符数组 char

1
2
3
4
5
6
char str[15] = {'A', 'B' };

char str2[15] = "hello"; //也可以用字符串,但仅在初始化可以用

一维数组当字符串,二维的当字符串数组,这里上面两种方式不同,第二种末尾自动加`\0`,第一种没有。
不过说串就是字符数组啊,末尾自动加'\0'。还有就是%s读取用scanf和gets,会在末尾自动加\0

//例子

1
2
3
4
5
6
7
8
9
#include<cstdio>
//通过vs调试可以看到
int main(){
char str[15] = {'h', 'e', 'l', 'l', 'o'}; //这里都输出hello,因为str[5]及以后被初始化0,就是字符串的结束符
char str2[15] = "hello";
printf("%s\n", str );
printf("%s\n", str2 );
return 0;
}

char1

1
2
3
4
5
char str[5] = {'h', 'e', 'l', 'l', 'o'};    //这个字符数组是不会自动加\0,这个没有溢出
char str2[5] = "hello"; //运行时这里报错,溢出,说明字符串是默认后面加个\0,

char str[5] = {'h', 'e', 'l', 'l', 'o'}; // 用%s格式可以输出,但因为末尾没有0,%s所以末尾会输出乱码
char str2[6] = "hello"; //%s输出hello

char2
char3

string.h头文件

4个常用的

  1. strlen(str)
    可以得到字符数组中第一个\0前的字符个数
  2. strcmp(str1, str2)
    按ASCII大小比较,按编辑器不同,返回正数,0,负数。
  3. strcpy(str1, str2)
    str2复制给str1,包括str2的\0,不然怎么结束
  4. kstrcat(str1, str2)
    str1接上str2

sscanf和spritf

函数

指针

一个地址表示存有1个字节,通过数据类型知道要读多少个字节。

&取地址 是个unsigned整型。

定义

1
2
int *p1, p2;    //一个是指针,一个是整型
int *p1, *p2; //两个指针

指针加减法

加法表示跳过n个此种类型的距离。
减法表示以此种类型为基准,相差n个距离。

指针和数组

数组名就是个指针,当数组首地址使用,表示整个数组。

指针与函数

引用

对引用变量的操作就是对原变量的操作。
当你不想通过传地址,可以通过引用来实现交换数据。

1
2
3
4
5
6
7
8
9
10
11
12
#include <cstdio>

void change (int &y){
y = 1;
}

int main(){
int x = 10;
change(x);
printf("%d\n", x);
return 0;
}

就是在函数的参数中,&。
注意参数变量得是变量,不能是const常量

结构体

1
2
3
4
5
6
struct studentInfo {
int id;
char gender;
char name[20];
char major[20];
}Alice, Bob, stu[1000];

上面的定义中,studentInfo是结构体名字,Alice Bob是结构体变量,stu[1000]是数组。
上面studentInfo中不能出现studentInfo类型的,但可以出现定义自身类型的指针变量。

1
2
3
4
struct node {
node n;
struct node *next;
}

访问结构体内元素

要么用. 要么->

1
2
3
4
5
struct studentInfo {
int id;
char name[20];
studentInfo *next;
}stu, *p

可以

1
2
3
stu.id stu.name stu.next
(*p).id (*p).name (*p).next
p->id p->name p->next

结构体初始化(利用构造函数)

同java 没有返回类型,名字同结构体名

1
2
3
4
5
6
7
8
9
10
11
12
13
struct studentInfo {
int id;
char gender;
studentInfo(){};
studentInfo(char x){
gender = x;
};
studentInfo(int x, char y){
id = x;
gender = y;
};

}

补充

cin和cout

加头文件

1
2
#include <iostream>
using namespace std;

不需要指定格式

1
2
3
4
5
6
7
8
9
10
11
12
cin >> n >>db >> c >> str;

//一整行用getline
char str[100];
cin.getline(str, 100)

//string容器
string str;
getling(cin, str);

//换行可以加\n或者endl
cout << n <<"" << c <<"\n" <<endl

浮点数比较大小

利用一个eps 10^-8

大于,小于 大于等于, 小于等于
这些都画个图就知道了

(b-eps,b+eps) a
a > b+eps
即a-b > eps
同理

cos(π) = -1 所以 π = acos(-1.0)

复杂度

时间复杂度一般为10^7~10^8次,所以O(n^2) n=1000可以接受
空间复杂度也不要超过10^7 A[10000][10000]的就不要

黑盒测试

单点测试 PAT

多点测试
3种

  1. while……EOF 题目没有说明有多少数据需要输入时
1
2
3
while(scanf("%d", &n) != EOF) {

}
  1. while……break 题目要求输入的数据满足某个条件时停止输入
1
2
3
4
5
6
7
8
9
10
while(scanf("%d%d", &a, &b) != EOF) {
if(a == 0 && b == 0) break;
printf("%d\n" , a + b);
}

改为更简洁的

while(scanf("%d%d", &a, &b), a || b) {
printf("%d\n" , a + b);
}
  1. while(T—) 题目给出测试数据的组数
1
2
3
1. 正常输出
2. 每行输出后带一个空行
3. 最后一行不输出空行,就是加个判断,当T>0才输出空行。

刷题

发表于 2018-01-21 | 分类于 刷题

先从lintcode开始刷

  1. A+B问题

给出两个整数a和b, 求他们的和, 但不能使用 + 等数学运算符。

1
2


gitpage+jekyll/hexo搭建blog

发表于 2018-01-20 | 分类于 前端

使用github page 和jekyll hexo来搭免费博客

先使用github page来做一个仓库,可以看到显示结果。

github page
当然下个github desktop也会是可以玩玩的这个desktop就是个垃圾

windows上
下个ruby,下个稳定版比如与2.4的
安装完ruby后不用装那个什么c,要啊MSYS2
然后安装rubygems一个包管理的
二、win
  1.安装Ruby windows下Ruby安装包 安装成功以后检查 ruby -v 、gem -v ;

   2.gem install bundle、gem install jekyll 安装成功 检查 bundle -v、jekyll -v 看到各自安装的版本;

  [环境搭建]Windows下安装Ruby和Jekyll

  win下 bundle install的时候需要安装msys2 关于msys2 软件主页:https://sourceforge.net/projects/msys2/

1
2
3
4
5
6
7
//在RubyGems官网上下载压缩包,解压到你的本地任意位置
//在Terminal中
cd yourpath to RubyGems //你解压的位置
ruby setup.rb

//有了gems之后安装jekyll
$ gem install jekyll

我用了 Jacman这个模板放到你的仓库中

1
2
3
4
$ cd you website path //cd到你的网站目录下
$ jekyll serve
//一个开发服务器将会运行在 http://localhost:4000/
//你就能在本地服务器看到你用模板搭建的网站了


用hexo吧 这个是nodejs环境的
hexo官网

这是基于nodejs的,所以安装好nodejs和git就行

然后用npm命令安装hexo-cli而不是hexo咯

1
npm install -g hexo-cli

模板用iissnan
也就是
nextT主题
上面的是themes,不是正文,要区别好。
上面的_ 开头的配置和你用 hexo init建立的不一样。

一开始我也不懂 后来才懂,看下鑫客栈 就是哔哩哔哩视频 你就会懂这个next主题是在themes中,而整个博客的文章是个大的配置

首先要hexo init <name>才会有,进入后在npm install

. ├── _config.yml ├── package.json ├── scaffolds ├── source | ├── _drafts | └── _posts └── themes

在 Hexo 中有两份主要的配置文件,其名称都是 _config.yml。 其中,一份位于站点根目录下,主要包含 Hexo 本身的配置;另一份位于主题目录下,这份配置由主题作者提供,主要用于配置主题相关的选项。为了描述方便,在以下说明中,将前者称为 站点配置文件, 后者称为 主题配置文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//常用
hexo new "postName" #新建文章
hexo new page "pageName" #新建页面
hexo generate #生成静态页面至public目录
hexo server #开启预览访问端口(默认端口4000,'ctrl + c'关闭server)
hexo deploy #将.deploy目录部署到GitHub

//简写
hexo n == hexo new
hexo g == hexo generate
hexo s == hexo server
hexo d == hexo deploy

//复合
hexo deploy -g
hexo server -g

说下怎么用nexT主题

安装 Installation

  1. 在终端切换到hexo 根目录. 在hexo目录下一定有 node_modules, source, themes 和其他文件夹:

    1
    2
    3
    4
    $ hexo init username.github.io
    $ cd username.github.io
    $ ls
    _config.yml node_modules package.json public scaffolds source themes
  2. 从 github 上获取主题 。这里有几种方式来获取主题:
    下载最新发布的版本 Download tagged release version
    在大多数情况下 稳定。 推荐用户下载这个。 还有两种什么标签发布版本,稳定master分支不管

    1
    2
    $ mkdir themes/next
    $ curl -s https://api.github.com/repos/iissnan/hexo-theme-next/releases/latest | grep tarball_url | cut -d '"' -f 4 | wget -i - -O- | tar -zx -C themes/next --strip-components=1

直接git到github上出问题了,不像jekyll直接放上去就好了,不知道什么问题

遇到

问题1

1
2
3
4
5
git remote add origin git@github.com:xuoutput/xuoutput.github.io.git
git push -u origin master
error: src refspec master does not match any.
error: failed to push some refs to 'git@github.com:xuoutput/xuoutput.github.io.git'
//是因为你没有add commit 库里啥也没有

问题2

  • 用git push到github上了但还是看不来。

    因为这个不是和jekyll一样的,直接push就好了,而是用hexo g生成后的publish文件。推荐用hexo d

单独来讲下hexo deploy

  • 在使用git deploy之前,先去 _config.yml 中修改参数
1
2
deploy:
type: git
  • 安装 hexo-deployer-git。
    1
    $ npm install hexo-deployer-git --save

修改配置。

1
2
3
4
5
6
7
8
9
10
11
deploy:
type: git //有空格
repo: <repository url>
branch: [branch]
message: [message]
参数 描述
repo 库(Repository)地址
branch 分支名称。如果您使用的是 GitHub 或 GitCafe 的话,程序会尝试自动检测。
message 自定义提交信息 (默认为 Site updated: {{ now('YYYY-MM-DD HH:mm:ss') }})

//注意空格要加上,

其他方法

Hexo 生成的所有文件都放在 public 文件夹中,您可以将它们复制到您喜欢的地方。不过我没找到public文件夹

每次部署的步骤,可按以下三步来进行。

1
2
3
hexo clean
hexo generate
hexo deploy

再说三遍,不要用git push直接提交整个源blog,用hexo d来提交生成文件,这两个不一样的
再说三遍,不要用git push直接提交整个源blog,用hexo d来提交生成文件,这两个不一样的
再说三遍,不要用git push直接提交整个源blog,用hexo d来提交生成文件,这两个不一样的

还有关于git库的,不需要删了库中的内容重新提交,只要你直接提交,那个库就按你最新的文件内容更改。
比如原来库中是文件abc,你要改成文件456,不需要删了abc,直接本地工作区删了abc,commit文件456,库中就没有abc了,这个库中哦不是append,


写作hexo new 然后做一个例子你就可以区分和hexo new page

先在主题配置文档中打开,这样才能体会new和new page区别

1
2
3
4
5
6
7
8
9
menu:
home: / || home
about: /about/ || user
tags: /tags/ || tags
categories: /categories/ || th
archives: /archives/ || archive
#schedule: /schedule/ || calendar
#sitemap: /sitemap.xml || sitemap
commonweal: /404/ || heartbeat

用一下命令来新建一篇文章,注意新建的文件名是按title名来的

1
$ hexo new [layout] <title>

文件名称

Hexo 默认以标题做为文件名称,但您可编辑 new_post_name 参数来改变默认的文件名称,举例来说,设为 :year-:month-:day-:title.md 可让您更方便的通过日期来管理文章。

Front-matter

Front-matter 是文件最上方以 —- 分隔的区域,用于指定个别文件的变量,举例来说:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
---
title: my new post
date: 2018-01-20 14:54:25
tags:
---

参数 描述 默认值
layout 布局
title 标题
date 建立日期 文件建立日期
updated 更新日期 文件更新日期

tags 标签(不适用于分页)
categories 分类(不适用于分页)

comments 开启文章的评论功能 true
permalink 覆盖文章网址

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
---
layout: post
title: my new post with resource
date: 2018-01-20 15:14:47
updated:
tags:
- git
- hexo
- nodejs
- resource directory
categories:
- diary
comments: true
premalink:
---

hexo下新建页面下如何放多个文章?
上面添加好也可以点开标签tags或categories能看到你的文章,但是你在导航栏中点点tags或categories就找不到。这个就是hexo new page的功能了,其实在主题中的那个menu开启的也是这个功能,这个menu里面的名字可以自定义的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
---
layout: post
title: my new post with resource
date: 2018-01-20 15:14:47
updated:
tags:
- git
- hexo
- nodejs
- resource directory
categories:
- diary
comments: true
premalink:
---

下面实例,建tags和categories页面

这其实就是hexo new [layout]这个layout的属性

1
hexo new page tags

打开 tags/index.md ,并改成:

1
2
3
4
title: 标签
date: 日期
type: "tags" //这个一致,加不加引号无所谓
comments: false

同理categories


资源文件夹

比如图片,文章小,你可以放在source/images中(直接用markdown的语法引用),但一旦文章多了,这就不好管理了。

所以,为每个文章设置一个资源文件夹,首先开启配置

1
2
_config.yml
post_asset_folder: true

然后hexo new就会多一个文件夹

使用3种语法

1
2
3
4
5
{% asset_path slug %}
{% asset_img slug [title] %}
{% asset_link slug [title] %}
//比如图片
{% asset_img example.jpg This is an example image %}

1…121314
Henry x

Henry x

this is description

133 日志
25 分类
135 标签
GitHub E-Mail
Links
  • weibo
© 2019 Henry x