Vuex详解---快速理解

Vuex是什么,个人理解是vue.js中集中管理状态的一种模式。官网解释:Vuex是一个专为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态并以相应的规则保证状态以一种可预测的方式发生变化。如果构建的应用比较简单,没有必要使用vuex,简单的单向数据流就能满足产品需求,体现单向数据流的简洁性,如下: <template> <div>{{count}}</div> <el-button @click=“increment “>count</el-button></template>new Vue({ data () { return { count: 0 } }, methods: { increment () { this.count++ } }})适用场景:如果应用是小型的、组件不多、组件之间的状态依赖不多、结构清晰便于维护,组件之间很少有数据之间的关联,这样的情况,没必要使用vuex。相反,哪些比较大型的应用,多个视图组件共用一个状态,一个组件的修改,需要更新其它关联的组件,此时Vuex比较适用。类似商城系统、外卖系统等。Vuex的使用:安装vuex,在项目中执行命令:npm install vuex –save-dev 或者使用淘宝镜像 cnpm install vuex –save-dev创建文件&&书写代码部分:1、创建文件夹store,然后在里面创建文件store.js,并引入相关文件:import Vue from ‘vue’import Vuex from ‘vuex’import getters from ‘./getters’import * as actions from ‘./actions’import mutations from ‘./mutations’import state from ‘./state’Vue.use(Vuex)// 注册上面引入的各大模块const store = new Vuex.Store({ state, getters, actions, mutations, state})export default store// 导出store并在 main.js中引用注册。2、创建文件state.js,代码如下:const state = { count: 0, countNumber: 10}export default state3、创建文件actions.js,代码如下:export function count ({commit}, name) { return commit(‘count’, name) // 触发mutations中的方法&&传值}4、创建文件getters.js。(我的理解是store的计算属性)const getters = { countNumber (state) { state.countNumber+=2 // state中countNumber字段发生变化,就会触发该方法 return state.countNumber }}5、创建文件mutations.js,代码如下:const mutations = { count (state, num) { // num为dispatch传递的参数 state.count+=num state.countNumber+=num }}export default mutations6、main.js文件,需要引入store:import Vue from ‘vue’import App from ‘./App’import router from ‘./router’import store from ‘./store/store.js’ // 导入文件storeVue.config.productionTip = false/* eslint-disable no-new */new Vue({ el: ‘#app’, router, store, components: { App }, template: ‘<App/>’})7、文件HelloWorld.vue中的相关操作:<template> <div class=“hello”> <div class=“addClsFather”> <div class=“addCls” @click=“addFuc”> Add </div> <div> 未使用vuex:{{count}} </div> <div> 使用vuex:{{this.$store.state.count}} </div> <div> 计算属性getters:{{countNumber}} &yen; </div> </div> </div></template><script>import { mapGetters } from ‘vuex’export default { name: ‘HelloWorld’, data () { return { count: 0 } }, computed: mapGetters([ ‘countNumber’ ]), methods: { addFuc() { this.count++ this.$store.dispatch(‘count’, 5) // 触发action中的方法&&传值 } }}</script><!– Add “scoped” attribute to limit CSS to this component only –><style scoped>.addClsFather { margin: 50px 100px;}.addCls { cursor: pointer; width: 100px; height: 30px; line-height: 30px; background-color: blue; text-align: center; color: #fff; border-radius: 5px;}</style>整体思路:1、在文件HelloWorld.vue中,点击"Add"按钮,通过dispatch触发action并传值;2、在action中,触发mutations中的方法并传值;3、通过mutations改变state中的字段值;4、对于使用了getters的字段,在state中的该字段改变后,会触发getters,并进行相关的处理(getters可以理解为state的计算属性);5、state、getters改变后,渲染到DOM中。注:在使用getters的字段,需要在相关关的(HelloWorld.vue)页面引入mapGetters字段,并添加computed属性,如下(不使用computed属性,不会起作用):computed: mapGetters([ ‘countNumber’]),以上是对vuex整体的摘要,下期分析vuex中的mapState,mapGetters,mapMutations,mapActions ...

January 23, 2019 · 2 min · jiezi

记录vuex module 模块化分割

参考官网例子,加深下vuex的学习。随着项目的复杂度增大,为了方便管理vuex,一般会将其按功能分割成不同的模块,方便日后管理,先看下整体的目录结构:目录结构store里面暂时弄了common和shop两个模块,每个模块拥有自己的 state、mutation、action、getter模块代码示例先列下shop模块代码:state.jsexport default { module: “shop”, name: “shop模块”};mutation-types.jsexport const SET_MODULE = “SET_MODULE”;export const SET_NAME = “SET_NAME”;mutations.jsimport * as types from “./mutation-types”;export default { [types.SET_MODULE](state, data) { state.module = data; }, [types.SET_NAME](state, data) { state.name = data; }};getters.jsexport default { module: state => state.module, name: state => state.name};actions.jsimport * as types from “./mutation-types”;export default { shopAction({ commit }, params) { commit(types.SET_MODULE, params.module); commit(types.SET_NAME, params.name); }};index.jsimport state from “./state”;import mutations from “./mutations”;import getters from “./getters”;import actions from “./actions”;export default { namespaced: true,//增加命名空间 state, getters, mutations, actions};store index.jsimport Vue from “vue”;import Vuex from “vuex”;import common from “./common”;import shop from “./shop”;import createLogger from “vuex/dist/logger”;Vue.use(Vuex);const debug = process.env.NODE_ENV !== “production”;export default new Vuex.Store({ modules: { common, shop }, strict: debug, plugins: debug ? [createLogger()] : []});使用mapGetterscomputed: { …mapGetters(“shop”, { shopModule: “module”, shopName: “name” })}mapMutations…mapMutations(“shop”, { setShopName: “SET_NAME”, setShopModule: “SET_MODULE”})mapActions…mapActions(“shop”, [“shopAction”])//使用shopAction:this.shopAction(params)在线预览:弄了个可以跑的小demo,方便大家查看效果和代码(自备梯子,不然打不开)demo代码<template> <div class=“hello”> <div> <h1>vuex common模块</h1> <p>name:{{ commonName }}</p> <p>module:{{ commonModule }}</p> <div> <div> <input type=“text” v-model=“common.name” placeholder=“请输入name值” /> <input type=“text” v-model=“common.module” placeholder=“请输入module值” /> </div> <button @click=“changeCommonName”>修改name</button> <button @click=“changeCommonModule”>修改module</button> <button @click=“changeCommonAll”>action修改全部</button> </div> </div> <hr /> <div> <h1>vuex shop模块</h1> <p>name:{{ shopName }}</p> <p>module:{{ shopModule }}</p> <div> <input type=“text” v-model=“shop.name” placeholder=“请输入name值” /> <input type=“text” v-model=“shop.module” placeholder=“请输入module值” /> </div> <button @click=“changeShopName”>修改name</button> <button @click=“changeShopModule”>修改module</button> <button @click=“changeShopAll”>全部修改</button> </div> </div></template><script>import { mapMutations, mapGetters, mapActions } from “vuex”;export default { name: “HelloWorld”, data() { return { msg: “Vuex Module”, common: { name: “”, module: "" }, shop: { name: “”, module: "" } }; }, computed: { …mapGetters(“common”, { commonModule: “module”, commonName: “name” }), …mapGetters(“shop”, { shopModule: “module”, shopName: “name” }) }, methods: { …mapMutations(“common”, { setCommonName: “SET_NAME”, setCommonModule: “SET_MODULE” }), …mapMutations(“shop”, { setShopName: “SET_NAME”, setShopModule: “SET_MODULE” }), …mapActions(“common”, { setCommonAction: “commonAction” }), …mapActions(“shop”, [“shopAction”]), changeCommonName() { this.setCommonName(this.common.name); }, changeCommonModule() { this.setCommonModule(this.common.module); }, changeCommonAll() { this.setCommonAction(this.common); }, changeShopName() { this.setShopName(this.shop.name); }, changeShopModule() { this.setShopModule(this.shop.module); }, changeShopAll() { this.shopAction(this.shop); } }};</script><!– Add “scoped” attribute to limit CSS to this component only –><style></style>小技巧建议大家开发中开启vuex的debug,不合理的修改state数据都会有警告,而且可以很直观的看到store数据的变化过程,详见上面store index.js结尾写的不太好,大家见谅,看demo比较直观,配合官网module文档 ...

January 10, 2019 · 2 min · jiezi

[vuex] getters should be function but getters.default is {}.

getters.js文件部分代码:const getters = { resturantName: function (state) { return ‘111’ }}export default gettersindex.js文件部分代码:import Vue from ‘vue’import Vuex from ‘vuex’import * as getters from ‘./getters’ Vue.use(Vuex)const state = { number: 110}const store = new Vuex.Store({ state, getters})export default store// 导出store并在 main.js中引用注册。然后进行编译,会直接报标题错误:[vuex] getters should be function but “getters.default” is {}.分析原因在于:index.js引入的是所有getters中的实例,然后getters中是对象的形式。所以错误原因是引入的方式有问题。正确的形式:import getters from ‘./getters’

January 7, 2019 · 1 min · jiezi

每天学习一点点 - vuex

最近比较闲,抽时间过了一遍vuex的文档。同时写了官网上购物车的那个小demo。下面来总结一下一些vuex的一些知识点一.vuex是什么?官方文档上写:vuex是一个专为vue.js应用程序开发的状态管理模式。这个状态自管理应用包括以下几个部分:state,驱动应用的数据源;view,以声明方式将state映射到视图;actions,响应在view上的用户输入导致的状态变化。二.vuex的核心vuex应用的核心就是store(仓库)。“store"基本上就是一个容器,它包含着你的应用中大部分的状态(state)。vuex的状态是响应式的。即:若store中state发生改变,组件会随之更新。你不能直接修改state,而是使用commit显示地提交mutation。三.State在vue组件中获取状态// 创建一个 Counter 组件const Counter = { template: &lt;div&gt;{{ count }}&lt;/div&gt;, computed: { count () { return store.state.count } }}使用mapState辅助函数在一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些复杂。为了解决这个问题,可以使用mapState辅助函数// 在单独构建的版本中辅助函数为 Vuex.mapStateimport { mapState } from ‘vuex’export default { computed: mapState({ count: state => state.count, // 传入字符串’count’等同于’state => state.count’ countAlias: ‘count’, // 使用模块并且对应模块名称为cart时,获取cart里面对应的count属性 count: state => state.cart.count })}当计算属性的名称与state子节点名称相同时,可以给mapState传入一个字符串数组computed: mapState([ ‘count’])对象展开运算符…mapStatecomputed: { localComputed() { …mapState({ }) }}四.Getter从store中的state派生出一些状态。可以理解为store的计算属性。当getter所依赖的值发生改变时,getter的值会随之改变。import { mapGetters } from ‘vuex’export default { // … computed: { // 使用对象展开运算符将 getter 混入 computed 对象中 …mapGetters([ ‘doneTodosCount’, ‘anotherGetter’, // … ]) }}如果你想将一个 getter 属性另取一个名字,使用对象形式:mapGetters({ // 把 this.doneCount 映射为 this.$store.getters.doneTodosCount doneCount: ‘doneTodosCount’})模块化状态下,getter取值// 模块为cartcomputed: { …mapGetters(‘cart’, { doneCount: ‘doneTodosCount’ })}五.Mutation更改vuex的store中的状态的唯一办法是提交mutation 注意:mutation必须是同步函数,异步需要放在aciton中const store = new Vuex.store({ state: { count:1 }, mutations: { increment (state) { state.count++ } }})store.commit(‘increment’)提交载荷(Payload) 你可以向store.commit传入额外的参数,即mutaition的荷载(payload)mutations: { increment(state, n) { state.count += n }}store.commit(‘increment’, 10)在组件中提交mutationimport { 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') }) }}六.ActionAction类似于mutation。不同在于:Action提交的是mutation,而不是直接改变状态。Action可以包含异步操作const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit(‘increment’) } // 或者也可以用es2015的解构赋值写成下面这种形式 increment({ commit }) { } }})分发ActionAction通过store.dispatch方法触发store.dispatch(‘increment’)在组件中分发Actionimport { mapActions } from ‘vuex’export default { // … methods: { …mapActions([ ‘increment’, //将this.increment()映射为this.$store.dispatch(‘increment’) ]) …mapActions([ add: ‘increment’ // 将 this.add() 映射为 this.$store.dispatch('increment') ]) }}七.ModuleVuex允许我们将store分割成模块。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 的状态默认情况下,模块内部的action、mutation和getter是注册在全局命名空间的。如果你希望你的模块具有更高的封装度和复用度,你可以通过添加namespaced:true的方式使其成为带命名空间的模块。在模块被注册后,它所有getter、action及mutation都会自动根据模块注册的路径调整命名。const store = new Vuex.Store({ modules: { account: { namespaced: true, state: {}, getters: { isAdimin() {…} // getters[‘account/isAdmin’] } } }})对于内部模块,如果你希望获取全局的state和getter,可以通过rootState、rootGetter获取到 若需要在全局命名空间内分发action或者提交mutation,将{root: true}作为第三个参数传给dispatch或者commit即可// 在另外一个模块获取cart模块rootState.cart.alldispatch(‘someOtherAction’, null, { root: true }) // -> ‘someOtherAction’commit(‘someMutation’, null, { root: true }) // -> ‘someMutation’ ...

January 3, 2019 · 2 min · jiezi

PWA项目实战分享

PWA项目实战分享 - BookPlayer 每天听本书App因为自己有个需求,特别的痒,昼夜难免。第二天就开始起手做这个项目,利用业余时间,大概持续了10天时间(因为边学边做),从设计到数据(包括解析物理文件)到前端。总于把我想要的效果做出来了。因为数据涉及到版权问题,所以只搞了部分数据来做演示,哈哈。效果演示传送门项目地址传送门Android App 下载该项目实现了:播放器功能倍速播放连续播放播放列表…听书排序分月/区间浏览已读变灰色查看大图解析xmind文件为树结构文本书籍的搜索历史记录课程和听书类似没有xmind只有图片文稿AppPWA 集成可借助 Lavas 生成 Android App…技术栈vue + vuex + vue-router + vue cli3 + LeanCloud + PWA(可以借助Lavas生成AndroidApp) + 腾讯云对象存储项目运行git clone https://github.com/worklinwu/BookPlayer.gitcd BookPlayernpm i 或 yarnnpm run devLeanCloud 配置先注册 LeanCloud 账号创建应用,命名为 BookPlayer, 或者自己喜欢的进入应用后,在存储的创建 Class旁边有个加号,点击选择数据导入把目录下的 json 文件导入查看侧边栏的设置 -> 应用key,复制替换掉该项目的 .env 的 VUE_APP_LEANCLOUD_APP_ID 和 VUE_APP_LEANCLOUD_APP_KEY重启项目,看看效果吧

December 28, 2018 · 1 min · jiezi

Vuex入门简单易懂系列(一)

在 学习vue 开发中,组件通信一直是一大痛点。当项目是很简单的 SPA 或者多入口项目时,可以靠着 vue 自带的 prop/$emit 进行组件通信;规模再大一些,可以搭配使用 bus 总线进行兄弟组件通信;项目再大一些,出现更复杂的组件关系时,复杂的组件通信可以让你写得怀疑人生。万幸的是, vue 官方出品了 vuex ,通过全局式的状态管理,解决了这一痛点。虽然 vuex 很好用,但是,很多小伙伴和我吐槽 vuex 的文档和 vue-ssr 的文档一样,让人看得一脸懵逼。安装并引入正常情况下,我们使用 vue-cli3 生成项目时,可以选择集成 vuex 到项目中。此时, vue-cli3 会自动安装 vuex ,并在 src 文件夹下生成 store.js 完成 vuex 的引入和配置。但是,很多同学并没有使用 vue-cli3 或者生成项目时没有选择集成 vuex 。此时,就只能手动安装并引入 vuex 了。安装由于 vuex 是用于全局状态管理的,所以,它不仅仅作用于开发环境,而且还要用于生产环境。显而易见,安装 vuex 应该使用 -S 即 –save 命令。npm install vuex -S引入类似于 vue-cli3 生成的项目,我们在 src 文件夹下新建 store.js ,并在其中写入:// store.jsimport Vue from ‘vue’import Vuex from ‘vuex’Vue.use(Vuex)export default new Vuex.Store({})然后,我们只需要在 vue 实例中引入 store.js 中的 Vuex.Store 实例即可:// main.jsimport Vue from ‘vue’import App from ‘./App.vue’import router from ‘./router’import store from ‘./store’Vue.config.productionTip = falsenew Vue({ router, // 引入store store, render: h => h(App)}).$mount(’#app’)Vuex的使用完成了 vuex 的安装和引入,接下来我们进入 Vuex 的使用。vuex 中有三要素: state, mutation 以及 action 。它们之间的关系可以用官网那张著名的图来表示:重点来了 (State)简单来说, state 表示状态,类似于 vue 中的 data (其实本质上就是差不多的, vuex 在 vue 的 beforeCreate 钩子中将 state 混入进 data)。但是,它们又有很大的不同: 在使用者看来, state 是全局的,这得益于 vuex 的设计理念——单一状态树。这些我将在后几篇文章中详细,现在我们只需要知道 state 是类似于全局下的 data 。接下来我们通过一个简单例子来感受下 state :首先,我们需要修改 store.js 文件,配置 state 。可以看到,我们在生成 Vuex.Store 实例时传入了实例化选项对象,对象包含一个 state 属性, state 对象的属性就是我们定义的全局状态。此时,我们定义了一个全局状态——count ,并将其的初始值设为1。// store.jsimport Vue from ‘vue’import Vuex from ‘vuex’Vue.use(Vuex)export default new Vuex.Store({ // 添加state state: { count: 1 }})接下来,我们需要在组件中引用 count,由于它是全局状态,我们可以在任何一个组件中使用。为了展示其威力,我们在两个不同的组件中使用它。首先我们在 App.vue 中使用它:在模板中,我们使用 $store.state.count 引入该全局状态,没错,使用它就是那么简单,只需要 以 $store.state.key 的形式调用。// App.vue<template> <div id=“app”> <div id=“nav”> {{$store.state.count}} <router-link to="/">Home</router-link> | <router-link to="/about">About</router-link> </div> <router-view/> </div></template>可以发现, Home 前多出了一个 1 ,这代表着我们成功引入了全局状态 count 。接下来我们在 Home.vue 的子组件 HelloWorld.vue 中引入 count 。相同的引用方式: $store.state.count// HelloWorld.vue<template> <div class=“hello”> {{$store.state.count}} </div></template>可以发现,success。Mutation但是,上面的示例有个问题,那就是全局状态是静态的。如果在实际应用场景中,一般来说,会经常更改状态。有的同学会说,我们直接在方法中修改 this.$store.state.key 的值不就行了吗?不好意思,当然是不行的。state 和 data 的另一大区别在于,你不能直接改变 state 。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。简而言之,我们把 mutation 当做接收 state 作为参数并修改 state 的自定义事件即可,上一段所说的 commit 就是触发 mutaion 这个自定义事件的方法。光说不练假把式,接下来,我们对为 vuex 添加上 mutation ,实现 state 的动态改变:首先,当然是修改生成 Vuex.Store 示例的选项对象,为其添加 mutations 。import Vue from ‘vue’import Vuex from ‘vuex’Vue.use(Vuex)export default new Vuex.Store({ state: { count: 1 }, // 添加mutation mutations: { increment (state) { state.count++ } }})在上面的代码中,我们添加了一个名为 increment 的 mutation 。完成了自定义事件,接下来,我们只需要在组件中对 mutation 进行触发即可。我们在 HelloWorld.vue 添加一个按钮,每次点击触发一次 increment 这个 mutation 。可以发现,触发方式很简单,只需要调用 store 自带的 commit 方法,其中参数为需要触发的 mutation 的名称。// HelloWorld.vue<template> <div class=“hello”> <div>{{$store.state.count}}</div> <button @click="$store.commit(‘increment’)">修改count</button> </div></template>点击页面中的按钮,你会发现,页面中的两个 count 都同时增加了1,说明我们成功实现了 state 的动态修改。Actionaction 类似于 mutation ,也相当于一种自定义事件。只不过, action 操作的是 mutation 而不是 state 。添加 action 的方法类似,在选项对象中新增 action 属性即可。与 mutation 的参数不同, action 的参数就是当前创建的 Vue.store 对象实例的上下文,一般将其命名为 context 。我们需要使用其自带的 commit 方法来触发 mutation 。下面我通过实际的例子来尝试下 action :首先,修改选项对象,使得新添加的 action 可以触发之前的 mutation :// store.jsimport Vue from ‘vue’import Vuex from ‘vuex’Vue.use(Vuex)export default new Vuex.Store({ state: { count: 1 }, mutations: { increment (state) { state.count++ } }, actions: { increment ({ commit }) { commit(‘increment’) } }})由于我们一般来说仅仅需要 context 中的 commit 方法,所以可以采用解构的方式,直接调用 commit 方法,而不需要以 context.commit 的方式使用它。接下来,只需要修改 HelloWorld.vue ,使其能够在点击按钮时触发即可。action 的触发方式和 mutation 类似,只不过调用的方法是 dispatch 。// HelloWorld.vue<template> <div class=“hello”> <div>{{$store.state.count}}</div> <button @click="$store.dispatch(‘increment’)">修改count</button> </div></template>点击页面按钮,你会发现,实现了和之前相同的效果。总结学会了 vuex 三jian客: state , mutation , action ,我们再回过头看看前面的那张关系图,此时应该很容易理解了吧?组件交互触发 action , 在 action 中进行异步操作(可选)并触发 mutation , mutation 控制 state 的变动, state 修改之后,触发响应式,重新渲染组件。PS(其他的进阶用法,如: getter , module , 简写以及 vuex 项目结构优化,甚至 vuex 源码解析将会在之后的文章一一讲解)最后个人认为对很多新手在入门时(不管学习什么东西),无可厚非最好刚开始是看视频。So,一份付出一份收获。如果你有需要购买学习视频的可以访问该网站加群http://www.wantmore.top,也可以直接添加群号:157285015。偶尔会分享学习干货噢! ...

December 27, 2018 · 2 min · jiezi

Vuex集中存储管理数据

Vuex集中存储和管理应用的所有的组件状态(数据)import vuex from “vuex"Vue.use(vuex)let state = { count:0}let mutations = {increment(){ state.count+=1},decrement(){ state.count-=1}} 这里主要通过this.$store.commit(“increment”)来改变state里的count值let actions ={ acincrement(context){ context.commit(“increment”)}, acdecrement(context){ context.comit(“decrement”)}}通过this.$store.dispatch(“acincrement”)可以带参数的这里的context代表上下文let getters={getroundval(state){ state.count=state.count>0? state.count:0; return state.count;}}这里通过this.$store.getters.getroundval,个人觉得这里主要对数据进行运算逻辑功能const store=new Vue.Store({ state, mutations, actions, getters})export default store;

December 24, 2018 · 1 min · jiezi

入门-vuex

我是一个刚刚学习vuex的小白,这两天主要连着官网和资料还有项目里的vuex在学习,在这里记下自己学习的一些总结,一方面是我自己的学习提升,也希望对你有所帮助。首先我们来理解下为什么要用vuex?我们先来捋下父子组件和非父子组件之间的通信。父子组件:先来定义下有parent.vue 和 childOne.vue 和 childTwo.vue1.属性传值//parent.vue里可以写(写的都是部分代码)<child-one :params=“params”></child-one>//childOne.vue里用props接收//只有开发原型系统能接收props:[‘params’], //稍微好点的写法(我在项目里就这样写的)props:{ params: Object}//更好的写法props:{ params: { type: Object, required: true, // 必填的字符串 //对象或数组默认值必须从一个工厂函数获取 default: function () { return { message: ‘hello’ } } //自定义校验这个值必须匹配下列字符串中的一个 validator: function(value){ return[ ’name’ ‘sex’ ’’ … ’error’ ].indexOf(value) !== -1 } }}//这里扩散了一点props越看越多 要详细看的请去看官网//https://cn.vuejs.org/v2/guide/components-props.html2.$refs 不止传参哦//parent.vue 里面<template> <child-one ref=“child1”></child-one><script> this.$child1.clickChildOne() //调用child1的clickChildOne方法 传参有时候觉得超方便 设置子组件的样式也超方便 安利,哈哈3.$parent.$parent.$parent(请你一定要找对层级 ‘_’)//主要是子组件给父级组件传参数或者调用父组件的方法childOne.vue里<script> methods: { //调用父级的方法getParentDeta() clickHandleDeta() { //几个parent通过自己的层级找吧.. this.$parent.$parent.$parent.getParentDeta() } } 非父子组件传值1.通过共同的父级来一级一级的传上去然后再传下来(就不写代码了)2.集中式的事件中间件就是Bus,来处理 这里详细说下//app.jsvar createBus = { install(Vue,options) { Vue.prototype.$bus = vue }}Vue.use(createBus)//childOne.vue里<script>methods: { getDeta() { this.$bus.$emit(’todoSth’, params); //emit触发 params是传递的参数 }}//childTwo.vue里<script>mounted: { this.$bus.$on(’todoSth’, params); //on接收}vuex 它就是可以用在大中型项目(当然你想用那个地方都能用)用全局单例模式管理来管理多个组件公共的数据或者方法。//一个貌似四肢健全的vuex(有state、getters、actions、mutations、module)const store = new Vuex.Store({ state: { count: 0 }, getters: { doneTodos: state => { return state.todos.filter(todo => todo.done) } } mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit(‘increment’) } }})下面我们按顺序一一来介绍和学习state 用来数据共享数据存储mutation 用来注册改变数据状态getters 用来对共享数据进行过滤操作action 解决异步改变共享数据mutations1.更改 Vuex 的 store 中的状态的唯一方法是提交 mutation2.Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。而调用或者说唤醒mutations 就必须用store.commitstore.commit(‘increment’)你可以向 store.commit 传入额外的参数,字段或者对象都行的。在大多数情况下,参数应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读。store.commit(‘increment’, { amount: 10})但是mutations 是同步执行的,就是说mutations 中的方法是不分组件的 , 假如你在 hello1.js 文件中的定义了add方法 , 在其他文件中的一个add方法 , 那么$store.commit(‘add’) 会执行所有的 add 方法!这个时候就是actions出现的时候了。actions多个state 的操作,使用 mutations 会来触发会比较好维护 , 那么需要执行多个 mutations 就需要用 action 了。const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit(‘increment’) } }})Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,就是说context.commit可以提交一个 mutation,可以调用context.state和context.getters来获取state和getters。使用 $store.dispatch(‘switch_dialog’)来触发action中的increment方法还记得 mutation 必须同步执行这个限制么?Action 就不受约束!我们可以在 action 内部执行异步操作:actions: { incrementAsync ({ commit }) { setTimeout(() => { commit(‘increment’) }, 1000) }}Actions 参数也可以是对象啥的// 以载荷形式分发store.dispatch(‘incrementAsync’, { amount: 10})// 以对象形式分发store.dispatch({ type: ‘incrementAsync’, amount: 10})很多时候 , $store.state.dialog.show 、$store.dispatch(‘switch_dialog’) 这种写法又长又臭 , 很不方便 。使用 mapState、mapGetters、mapActions 就不会这么复杂了。<template> <el-dialog>{{count}}</el-dialog></template><script>import {mapState} from ‘vuex’;export default { computed:{ //这里的三点叫做 : 扩展运算符 …mapState({ count:state => state.dialog.count }), }}</script>相当于<template> <el-dialog>{{count}}</el-dialog></template><script>import {mapState} from ‘vuex’;export default { computed:{ count(){ return this.$store.state.dialog.count; } }}</script>mapGetters、mapActions 和 mapState类似,mapGetters 一般也写在 computed 中 ,mapActions 一般写在 methods 中。 ...

December 21, 2018 · 2 min · jiezi

SAP Fiori + Vue = ?

2017年3月28日,我到国内一个SAP CRM客户那里,同他们的架构师关于二次开发的UI框架选择SAP UI5还是Vue进行了一番探讨。回到SAP研究院之后,我把这个问题扔到了公司的微信群里,引起了大家的热烈讨论。因为出差回来之后,我需要向我老板和老板的老板汇报工作,因此写了这篇blog:https://blogs.sap.com/2017/03…时光飞逝,转眼间2018年也快过完了。今天上午上班路上,忽然看到阳哥在公司微信群里发了一个截图,提供了一个指向公网github仓库的链接:https://github.com/SAP/fundam…看到这个仓库的url,Jerry马上就想起了早些时候在experience.sap.com网站上看到的这条新闻:https://experience.sap.com/ne…我们都知道Fiori代表SAP新一代UI的界面风格,而UI5是Fiori UX(User Experience,用户体验)的具体实现技术。SAP决定将Fiori同具体UI实现技术解耦, 是出于什么考虑呢?众所周知,前端技术发展的速度是非常快的,新理念,新名词,新工具层出不穷,很多前端开发程序猿经常哀叹"学不过来了",那么,如果只绑定于某一种具体的UI实现技术,Fiori UX会缺乏足够的灵活性,很难充分利用业界最新技术来更好地为终端用户服务。同时,这一举动也充分体现了SAP确实在倾听自己生态圈里开发人员的呼声,通过这种解耦允许SAP开发人员根据实际项目需要,灵活选择最佳UI框架来开发Fiori应用。Fiori UX同底层UI实现框架解耦的关键就在于SAP Fiori Fundamentals, 一个轻量级的展现层实现。从技术层面上说,SAP Fiori Fundamentals不是一种新的UI技术或者框架,而是一系列stylesheets和HTML标签的集合,以此来让SAP生态圈里的UI开发人员用其喜欢的UI框架,比如Angular,React,Vue等进行开发,同时自动保证开发出的应用仍具有Fiori的风格和用户体验。SAP Fiori Fundamentals的出现,绝不意味着它会替代UI5,实际上,SAP对于UI5的维护和功能增强一直没有停步。按照Jerry文章的风格,当然是到上代码的时候了。因为Jerry所在的团队进行原型开发,组内同事大多喜欢用Vue,所以我们就来试试SAP Fiori Fundamentals + Vue这对组合。首先我们得有一个能工作的Vue应用,然后在此基础上加工。您可以在我的SAP博客上找到一个Hello World的Vue应用,通过webpack打包之后运行,能在浏览器里看到显示的Hello World:https://blogs.sap.com/2017/12…这个Hello world的Vue应用,项目结构如下:下面我们在其基础上进行加工。1. 在项目文件夹下安装fundamental-vue。这是为SAP Fiori Fundamentals实现的一个轻量级的Vue组件集合。npm install –save fundamental-vue安装完毕后在package.json里能够看到fundamental-vue还在beta版,这一点和SAP在github上的文档描述一致。2. 下面这个链接罗列了SAP Fiori Fundamentals里支持的Vue组件,同时也介绍了如何自定义一个新的Vue组件。https://dist-4d2gqwr8y.now.sh…下图是一个Table组件的运行时效果,大家不难发现这个Table的外观和我们之前用UI5开发的很相似。点击Show Code,会显示这个Table组件的Vue实现源代码,类似我们UI5 Toolkit里显示的控件在UI5 XML View里的源代码,道理是相通的。把这一大堆代码粘贴到我们Vue应用src文件夹下的index.vue里:同样在index.vue里,在module.exports里实现作为Button事件处理函数addCurrentEntry, 以及硬编码一些测试数据:在main.js里加入两行:import FundamentalVue from ‘fundamental-vue’;Vue.use(FundamentalVue);最后一步,在index.html里引入位于CDN上的Fiori Fundamentals的css文件。当然github上也提到了也可以使用npm install –save fiori-fundamentals将其安装到本地使用。至此加工就结束了。用webpack打包之后,运行npm run dev启动wepack-dev-server, 就可以在localhost里看到如下效果:输入新的谋士姓名,点击Add Entry按钮之后能将其输入到表格中。尽管网上有种说法,“郭嘉不死,卧龙不出”,然而孔明永远是Jerry心中的三国演义第一谋士。这个使用Vue组件开发而成的具有Fiori UX风格的应用运行时效果,大家可以查看这个视频体验:<iframe frameborder=“0” width=“677” height=“380.8125” allow=“autoplay; fullscreen” allowfullscreen=“true” src=“https://v.qq.com/txp/iframe/p...;amp;vid=q0814wlsmqn&amp;autoplay=false&amp;full=true&amp;show1080p=false&amp;isDebugIframe=false" style=“margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;"></iframe>或者查看我托管到github上的demo:http://i042416.github.io/Fior…手机上打开上面链接的效果:由于时间关系,Jerry还没能深入了解SAP Fiori Fundamentals的更多技术细节,只是简单给大家展示了它和Vue协同工作的效果。未来如果有机会,Jerry会给大家带来更多深入报道,感谢阅读。相关阅读SAP Fiori应用的三种部署方式Jerry的Fiori原创文章合集SAP成都C4C小李探花:浅谈Fiori Design GuidelinesJerry和您聊聊Chrome开发者工具Jerry的UI5框架代码自学教程Jerry的碎碎念:SAPUI5, Angular, React和VueSAP Cloud for Customer 使用SAP UI5的独特之处当我用UI5诊断工具时我用些什么在Kubernetes上运行SAP UI5应用(上)在Kubernetes上运行SAP UI5应用(下)要获取更多Jerry的原创文章,请关注公众号"汪子熙”: ...

December 21, 2018 · 1 min · jiezi

vue 撸后台笔记一

前言本文是以 花裤衩 大佬的 vue-element-admin 项目为模板、结合公司需求开发的后台管理系统的学习笔记。原项目地址:vue-element-admin参考文章:手摸手用 vue 撸后台系列安装与配置新建 vue-cli 项目,相关安装及配置不多做介绍,有需要可自行搜索。接着是安装项目依赖。基本依赖库:Vue-Router Vue.js 官方的路由管理器Axios 基于promise 的 HTTP 库Element-UI 一套为开发者、设计师和产品经理准备的基于 Vue2.0 的桌面端组件库Vuex 一个专为 Vue.js 应用程序开发的状态管理模式扩展依赖库:node-sass css 扩展语言normalize.css 为默认的 HTML 元素样式上提供跨浏览器的高度一致性js-cookie 一款轻量级的 js 操作 cookie 的插件i18n Vue.js 的国际化插件,它可以轻松地将一些本地化特性集成到 Vue 中driver.js 一款轻量级、无需依赖但功能强大的原生 JavaScript,兼容所有主流浏览器,可帮助你将用户的注意力集中在页面上NProgress 细长的全站进度条SVG sprite loader 用于根据导入的 svg 文件自动生成 symbol 标签并插入 htmlSortable 一款轻量级的拖放排序列表的 js 插件ECharts 一款功能强大的图表和可视化库screenfull 一款全屏插件项目结构├── build // 构建相关├── config // 配置相关├── disk // 打包文件├── node_modules // 依赖项├── src // 源代码│ ├── api // 所有请求│ ├── assets // 主题 字体等静态资源│ ├── components // 全局公用组件│ ├── directive // 全局指令│ ├── waves // 水波纹指令│ ├── icons // 项目所有 svg icons│ ├── lang // 国际化 language│ ├── mock // 项目mock 模拟数据│ ├── roter // 路由│ ├── store // 全局 store管理│ ├── styles // 全局样式│ ├── utils // 全局公用方法│ ├── views // views 所有页面│ ├── account // 账户管理│ ├── court // 法院管理│ ├── dashboard // 功能主页│ ├── device // 设备管理│ ├── errorPage // 错误页面│ ├── layout // 整体布局│ ├── login // 登录页面│ ├── redirect // 重定向页面│ ├── statistics // 数据统计页面│ ├── versions // 版本管理页面│ ├── writs // 文书管理页面│ ├── App.vue // 入口页面│ ├── errorLog.js // 错误日志│ ├── main.js // 入口文件 加载组件 初始化等│ ├── permission.js // 权限管理├── static // 第三方不打包资源├── .babelrc // babel-loader 配置├── .eslintrc.js // eslint 配置项├── .gitignore // git 忽略项├── favicon.ico // favicon 图标├── index.html // html 模板├── package.json // 依赖项目录├── README.MD // 说明文档简单讲下 src 文件夹api 与 views根据项目的业务划分 views 页面展示部分,并将 api 接口请求与 views 一一对应,有利于迭代更新与后期维护。components将全局公用的模块与组件存放在 components 文件夹中,页面级的的组件建议还是放在各自的 views 文件夹下。store在 index 入口文件引入 modules 对象,独立封装各个模块状态。axios在 axios 配置档设置基础 URL,根据环境变量动态切换 api,需要在 config/dev.env.js 文件中配置接口路径。lang将中英文语言包各自封装并在入口 index.js 配置导入在 main.js 使用 i18n。 ...

December 21, 2018 · 2 min · jiezi

Vue全家桶 + webpack 构建单页应用初体验

文章指南主题 承接这上一篇Vue + Webpack 构建模块化开发框架详解,我们知道了如何使用webpack对vue进行打包,从而开始我们的前端模块化开发之路,这一篇在上一篇的基础上讲解 Vue全家桶(vue+vuex+vue-router+axios) + webpack 构建一个单页应用Demo前提 阅读本篇内容之前,除了需要掌握上一篇内容中的前提部分的知识,还需要了解以下内容????Vue全家桶系列,掌握vuex,vue-router以及axios ,不了解请移步官方教程axios-npm,vuex,vue-routervue全家桶简单介绍vuex : vuex是一种集中式状态管理模式,什么意思呢?我们在模块化开发过程中,我们以组件来作为模块单位,模块之间存在于不同的命名空间,作用域互不干预,这样保证了我们模块之间变量函数名称等不会冲突,但是有时候我们我们需要组件之间共享一些数据或者状态,我们通常的做法是传参,但是传参的做法至少有两个弊端,一是麻烦(尤其是当需要传递的参数很多时),二是不好管理且冗余(给多个组件传参就需要多份参数列表,而且容易出错)。vuex提供的集中式管理就解决了这个问题,通过把要共享的数据或状态集中起来管理,别的组件需要时就去访问变更,大大提高了可维护性和开发效率vue-router : vue-router是一个前端路由管理器,这个和后端常听说的路由有些不同(个人觉得),这里的路由管理器更像是一个组件注册器,vue-router为分散的组件注册一个路由或者叫地址也未尝不可,以方便我们控制组件的层级嵌套关系以及隐藏还是显示,这样我们可以很方便高效的构建单页应用axios : axios 和 jquery.ajax/vue-resource一样 , 都是HTTP异步请求的工具,axios和vue-resource的API很像,但是个人觉得,axios的API更丰富一些正文 本文的小Demo大概功能就是一个登陆的功能,我们这里app.vue封装了一个登陆组件,success.vue封装了一个提示面板,通过vuex来集中管理登陆状态,用vue-router来路由提示面板,用axios来异步提交到一个跨域的服务器(这里后台服务是nginx+php提供,所以在dev-server中要设置一个反向代理,也就是nginx来代理我们的dev-server的请求从而解决axios跨域问题)先来看看我们的项目结构 [自定义的]这里的目录设置,并不是一成不变的金科玉律,也不一定是最好的,读者可以根据自己的想法设定文件结构,了解如何配置单入口单出口的webpack.config.js,可以看我的上一篇文章(开头提到了),或者移步webpack官方网站。本篇内容,不再讲解过多webpack配置,和上一篇是基本相同的,下面主要讲解 Vue全家桶相关内容index.jsimport Vue from ‘vue’;import Router from ‘./routers/index-router’;import Store from ‘./stores/index-store’;import App from ‘./components/App.vue’;var app = new Vue({ //创建一个Vue实例 router : Router, //加入路由配置 store : Store, //加入状态管理 components : {App} //加入App组件}).$mount(’#app’); //挂载节点需要注意的是,路由和状态都需要加入一个vue实例中去才有意义。这里有一点很有意思,就是在路由router中注册的组件(success.vue)依然可以访问到状态store实例,而官方教程上并没有特别强调这一点,在下面我们可以看到这个有趣的事情stores/index-store.jsimport Vuex from ‘vuex’;import Vue from ‘vue’;import axios from ‘axios’;Vue.use(Vuex); //在vue中加入Vuex插件const Store = new Vuex.Store({ //实例化一个Store state : { //这里是我们需要集中管理的登陆状态 account : “ads”, password : “123456”, islogin : null }, mutations : { // 这是唯一可以变更state的途径,需要通过一个提交 updateAccount(state,payload){ //具有载荷的mutation state.account = payload.account }, updatePassword(state,payload){ state.password = payload.password }, islogin(state,payload){ if(payload == 1){ state.islogin = true }else if(payload == 0){ state.islogin = false } } }, actions : { //这是可以支持异步执行提交的actions login (context,payload){ //这里是去访问我们的反向代理服务器上的一个php文件 axios.get(’/index.php’,{ params : { account : payload.account, password : payload.password } }).then(function(response){ if(response.data.code == ‘success’){ //变更store状态 context.commit(‘islogin’,1) //这里做了在异步逻辑中的状态提交 }else{ context.commit(‘islogin’,0) } }).catch(function(error){ console.log(error) alert(‘fail’) }) } }})export default Store我们这里的Store有三个属性,state是我们需要集中管理的状态或者数据, mutations是维护变更我们状态的一个方式, actions是为了在异步逻辑中提交状态的一个中转站,需要注意的是:在实例化Store之前,必须先 Vue.use(Vuex) ,先在vue中加入Vuex插件这里需要说一下的是,如何开启dev-server的proxy,让nginx来代理axios的请求,也就是跨域访问了(因为dev-server跑的是8080端口,nginx跑的是80端口,不设置代理的话浏览器将拒绝跨域访问),感觉很深奥,其实很简单,我们需要修改一下webpack.config.js中devServer的配置 : webpack.config.js devServer : { contentBase : ‘./dist’, watchContentBase : true, compress : true, port : 8080, hot : true, inline : true, //开启页面自动刷新 open : true, proxy : { ‘/index.php’ : { target : ‘http://localhost:80/phpinfo.php’, secure : false } } }然后,我们的axios在配置url时使用 /index.php,就可以相当于访问http://localhost:80/phpinfo.phprouters/index-router.jsimport VueRouter from ‘vue-router’;import Vue from ‘vue’;import success from ‘../components/success.vue’;// 组件注册路由,这里success组件注册路由是’/app’var routes = [ { path : ‘/app’,component : success }]Vue.use(VueRouter); //在vue中加入VueRouter插件var Router = new VueRouter({ //实例化一个router routes //这里的写法相当于 routes:routes})export default Router;这里的路由只注册了一个组件,vue-router的能力远不止如此,还可以嵌套定义组件的层级关系,更多到官方文档了解 ,路由器和状态管理器一样都是vue官方核心插件,使用前都需要先 Vue.use(VueRouter) ,我们其实可以看到,这里把路由和状态分别从vue实例中抽离出来,单独管理,通过暴露实例Store Router,加入到vue实例中去,这样可以方便debug和后期维护components/App.vue 和 components/success.vueApp.vue<template> <div> <input type=“text” v-model=“account”> <input type=“password” v-model=“password”> <button type=“button” @click=“login”>show</button> </div></template><script> export default { data : function(){ return { account : “”, password : "" } }, created : function(){ this.account = this.$store.state.account this.password = this.$store.state.password }, watch : { //侦听属性 account : function(){ //当accout改变就立即提交状态 this.$store.commit(‘updateAccount’,{ account : this.account }) }, // 当password改变就立即提交状态 password : function(){ this.$store.commit(‘updatePassword’,{ password : this.password }) } }, methods : { login : function(){ //通过分发到action,来异步请求登录服务,并变更状态 this.$store.dispatch(’login’,{ account : this.account, password : this.password }) this.$router.push(’/app’);//同时显示登陆状态 } } }</script><style scoped> input { height: 40px; width: 300px; border: 1px solid blue; box-shadow: none; }</style>success.vue<template> <div> <p>{{islogin}}</p> </div></template><script> export default { computed : { //计算属性 //动态响应状态的变更 islogin : function(){ var islogin = this.$store.state.islogin if(islogin == null){ return ‘No option’ }else if(islogin == true){ return ‘Login Success’ }else if(islogin == false){ return ‘Login Fail’ } } } }</script><style scoped> p { height: 40px; text-align: center; }</style>templates/index.html<!DOCTYPE html><html><head> <meta charset=“utf-8”> <title>index</title></head><body> <div id=“app”> <!– App组件渲染出口 –> <App></App> <!– 注册路由的组件渲染出口 –> <router-view></router-view> </div></body></html>总结好了,到此为止,代码基本完成,我们来理一理,vue+vuex+vue-router以及组件之间是如何配合工作的?用户在App.vue组件中输入,v-model双向绑定了App组件中的data,App组件中的侦听属性发现data改动,立即向vuex的Store实例提交了状态变更,当用户点击show,App组件中methods.login事件被触发,它向Store实例发起一个分发,并导航到/app,Store的action收到分发调用axios去异步请求位于localhost:80下的php后台服务,得到的登陆状态立即提交,与此同时,Router去渲染/app路由对应的组件到模板中去,于是我们就可以看到一个动态响应式的状态提示了 ...

December 19, 2018 · 2 min · jiezi

关于Vue2一些值得推荐的文章 -- 十二月份

十二月份查看github最新的Vue weekly;请::点击::集web前端最近很火的vue2框架资源;定时更新,欢迎 Star 一下。十二月上半月-幽州思妇十二月,停歌罢笑双蛾摧。(12.01~12.15):北风行北风行 [唐] 李白 烛龙栖寒门,光曜犹旦开。 日月照之何不及此,唯有北风号怒天上来。 燕山雪花大如席,片片吹落轩辕台。 幽州思妇十二月,停歌罢笑双蛾摧。 倚门望行人,念君长城苦寒良可哀。 别时提剑救边去,遗此虎纹金鞞靫。 中有一双白羽箭,蜘蛛结网生尘埃。 箭空在,人今战死不复回。 不忍见此物,焚之已成灰。 黄河捧土尚可塞,北风雨雪恨难裁。Vue中文推荐列表加快Vue项目的开发速度2019年Vue学习路线图Vue 性能优化之深挖数组几种常见的Vue组件间的传参方式前端错误收集(Vue.js、微信小程序)vue3.0 尝鲜 – 摒弃 Object.defineProperty,基于 Proxy 的观察者机制探索说说在 Vue.js 中如何实现组件间通信(高级篇)不吹不黑比对下React与Vue的差异与优劣尤雨溪:React 是不是比 Vue 牛,为什么?初探 Vue3.0 中的一大亮点——Proxyvue权限路由实现方式总结二从零实现Vue的组件库(零)-基本结构以及构建工具Vue 2.0学习笔记:Vue的transition推荐一个很好用的vscode插件:一个可以给出vuex中store定义信息的vscode插件vue轻量高效的前端组件化方案以及MVC MVVM思想vue的.vue文件是怎么run起来的(vue-loader)Vuex和Redux都参照了的Flux模式简单版实现vue中的computed的this指向问题Vue 进阶系列(一)之响应式原理及实现在Vue项目中加载krpano全景图如何用vue封装一个防用户删除的平铺页面的水印组件5个Vuex插件,让你下一个VueJS项目开发速度提升3倍Vue源码中为什么要const _toStr = Object.prototype.toString?初探 Vue3.0 中的一大亮点——Proxy !vue中async-await的使用误区快速利用 vue 或者 react 开发 chrome 插件Vue中的基础过渡动画原理解析浏览器事件循环机制与Vue nextTick的实现Vue 源码(一):响应式原理用 vue + d3 画一棵树为什么我会选择 React + Next.js,而不是 Vue 或 Angular?使用Golang的Gin框架和vue编写web应用Vue项目中使用better-scroll实现一个轮播图小白带你学习Vuex从零实现Vue的Toast插件如何在vue项目中优雅的使用SVG记一次简单的vue组件单元测试你可能需要的一本前端小册:Vue 项目构建与开发入门为什么Proxy可以优化vue的数据监听机制月下载量千万的 npm 包被黑客篡改,Vue 开发者可能正在遭受攻击基于vue-cli理解render函数Vue.js的复用组件开发流程Proxy实现vue MVVM实践Vue调试神器之Vue.js devToolsVue一个案例引发「动画」的使用总结Vue.js 3.0发布更新计划基于Vue组件化的日期联动选择器mpvue 单文件页面配置VueConf 杭州 PPT深入浅出Vue使用中的小技巧手把手教你使用 VuePress 搭建个人博客利用Vue原理实现一个mini版的MVVM框架Vue.js 图标选择组件实践逐行粒度的vuex源码分析Vue一个案例引发「内容分发slot」的最全总结Vue英文推荐列表Stories, Chapters and Paragraphs: Structuring Content with Storyblok and Vue.js – Markus OberlehnerStructuring a Vue project — Authentication – Boris SavicVue Development In 2019: What You Need To Know - Anthony Gore Let’s talk about an unnecessary but popular Vue plugin - heftyheadWorking with the Camera in a NativeScript Vue App – Raymond CamdenGitHub - f/vue-smart-routeGitHub - posva/vue-local-scopePromoted Get all products by Creative Tim including Vue premium dashboards 90% offBest resources to learn Vue.js in 2018The Vue.js Conference in Amsterdam will have everything you hope forOfficial Style Guide for Vue-specific codeLaravel Nova Administration Panel with Vue.jsVuePress: What is it and Why it is a great tool to useVue.js Frameworks & Libraries to use in your next project Integrating content management into your Vue.js projects with PrismicVue.js Amsterdam RecordingsiView UI framework 2.4State of Vue.js 2019 survey7 VueConf Toronto Talks Now Live to WatchVue.JS Components for building Search UIs – All thingsStructuring a Vue project — Authentication – Boris Savic Best Code Editor for Vue.js – Vue Mastery My Favorite Vue.js & Nuxt.js packages for 2019 – Nada Rifki Turn your Vue Web App into a PWA! – Bits and PiecesThe State of Javascript 2018: The View on VueVue 2 + Firebase: How to add Firebase Social Sign In into your Vue application更多推荐查看github最新的Vue weekly;请::点击::集web前端最近很火的vue2框架资源;定时更新,欢迎 Star 一下。 ...

December 18, 2018 · 2 min · jiezi

Vue2.0 + ElementUI 手写权限管理系统后台模板(四)——组件结尾

i18n国际化多语言翻译使用框架采用vue-i18n版本 8.4.0,使用npm安装新建文件夹src/i18n,目录如下i18n.js//i18n.jsimport Vue from ‘vue’import locale from ’element-ui/lib/locale’import VueI18n from ‘vue-i18n’import messages from ‘./lang’Vue.use(VueI18n)const i18n = new VueI18n({ locale: localStorage.lang || ‘cn’, messages})locale.i18n((key, value) => i18n.t(key, value))export default i18ni18n/lang/index.js//index.jsimport en from ‘./en’import cn from ‘./cn’export default { en, cn}i18n/lang/cn.jscn.js和en.js 需要要翻译的内容要一一对照,我这里这是参考示例只写了一部分//cn.jsimport zhLocale from ’element-ui/lib/locale/lang/zh-CN’const cn = { home: ‘主页’, routeNmae: { home: ‘主页’, article: ‘文章管理’, ‘menu2-2’: ‘二级-2’, ‘menu2-3’: ‘二级-3’, }, rightMenu: { close: ‘关闭’, closeOther: ‘关闭其他’, closeAll: ‘全部关闭’ } …zhLocale // 合并element-ui内置翻译}export default cni18n/lang/en.js//en.jsimport enLocale from ’element-ui/lib/locale/lang/en’const en = { home: ‘home’, routeNmae: { home: ‘home’, article: ‘article’, ‘menu2-2’: ‘menu2-2’, ‘menu2-3’: ‘menu2-3’ }, rightMenu: { close: ‘close’, closeOther: ‘closeOther’, closeAll: ‘closeAll’ } …enLocale // 合并element-ui内置翻译}export default en多语言切换组件新建src/components/lang/langSelect.vue<!– langSelect.vue –><template> <el-dropdown class=‘international’ @command=“handleSetLanguage”> <div> <span class=“el-dropdown-link”><i class=“fa fa-language fa-lg”></i>&nbsp;{{language}}<i class=“el-icon-arrow-down el-icon–right”></i> </span> </div> <el-dropdown-menu slot=“dropdown”> <el-dropdown-item command=“cn”>中文</el-dropdown-item> <el-dropdown-item command=“en”>English</el-dropdown-item> </el-dropdown-menu> </el-dropdown></template>main.jsimport Vue from ‘./btnPermission’import ElementUI from ’element-ui’import ’element-ui/lib/theme-chalk/index.css’import ‘font-awesome/css/font-awesome.css’import App from ‘./App.vue’import router from ‘./router’import store from ‘./vuex’import i18n from ‘./i18n/i18n’new Vue({ el: ‘#app’, router, store, i18n, render: h => h(App), components: {App}, template: ‘<App/>’})使用:<!– 翻译使用 –><p>message: {{ $t(‘home’) }}</p><p>message: {{ $t(‘routeNmae.article’) }}</p><!– 多语言切换组件调用 –><langSelect></langSelect>vue中使用ECharts具体使用方法可以查看ECharts官网,需要注意的地方就是响应屏幕大小代码如下,在调用组件的页面 mounted () { this.selfAdaption() }, methods: { // echart自适应 selfAdaption () { let that = this setTimeout(() => { window.onresize = function () { if (that.$refs.echarts) { that.$refs.echarts.chart.resize() } } }, 10) } }编辑器-markdown框架目前只封装了markdown,实时获取markdown,html,text三种格式文本,支持内容回填,默认初始值,可以编辑已发布的文章或者草稿引用的Editor.md,点击查看插件更多的使用方法结束vue-xuAdmin 只注重框架基础功能,这几个组件是我最近用到的,更多的组件内容根据项目需求可以自己去封装。如果你感觉这个框架或者这几篇文章对你有所帮助,请去项目git上给个星点个star,感谢!orz项目地址:github:https://github.com/Nirongxu/v…码云:https://gitee.com/nirongxu/xu… ...

December 17, 2018 · 1 min · jiezi

vuex源码解析

vuex简介能看到此文章的人,应该大部分都已经使用过vuex了,想更深一步了解vuex的内部实现原理。所以简介就少介绍一点。官网介绍说Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。数据流的状态非常清晰,按照 组件dispatch Action -> action内部commit Mutation -> Mutation再 mutate state 的数据,在触发render函数引起视图的更新。附上一张官网的流程图及vuex的官网地址:https://vuex.vuejs.org/zh/Questions在使用vuex的时候,大家有没有如下几个疑问,带着这几个疑问,再去看源码,从中找到解答,这样对vuex的理解可以加深一些。官网在严格模式下有说明:在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。vuex是如何检测状态改变是由mutation函数引起的?通过在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中。为什么所有子组件都可以取到store?为什么用到的属性在state中也必须要提前定义好,vue视图才可以响应?在调用dispatch和commit时,只需传入(type, payload),为什么action函数和mutation函数能够在第一个参数中解构出来state、commit等?带着这些问题,我们来看看vuex的源码,从中寻找到答案。源码目录结构vuex的源码结构非常简洁清晰,代码量也不是很大,大家不要感到恐慌。vuex挂载vue使用插件的方法很简单,只需Vue.use(Plugins),对于vuex,只需要Vue.use(Vuex)即可。在use 的内部是如何实现插件的注册呢?读过vue源码的都知道,如果传入的参数有 install 方法,则调用插件的 install 方法,如果传入的参数本身是一个function,则直接执行。那么我们接下来就需要去 vuex 暴露出来的 install 方法去看看具体干了什么。store.jsexport function install(_Vue) { // vue.use原理:调用插件的install方法进行插件注册,并向install方法传递Vue对象作为第一个参数 if (Vue && _Vue === Vue) { if (process.env.NODE_ENV !== “production”) { console.error( “[vuex] already installed. Vue.use(Vuex) should be called only once.” ); } return; } Vue = _Vue; // 为了引用vue的watch方法 applyMixin(Vue);}在 install 中,将 vue 对象赋给了全局变量 Vue,并作为参数传给了 applyMixin 方法。那么在 applyMixin 方法中干了什么呢?mixin.jsfunction vuexInit() { const options = this.$options; // store injection if (options.store) { this.$store = typeof options.store === “function” ? options.store() : options.store; } else if (options.parent && options.parent.$store) { this.$store = options.parent.$store; } }在这里首先检查了一下 vue 的版本,2以上的版本把 vuexInit 函数混入 vuex 的 beforeCreate 钩子函数中。在 vuexInit 中,将 new Vue() 时传入的 store 设置到 this 对象的 $store 属性上,子组件则从其父组件上引用其 $store 属性进行层层嵌套设置,保证每一个组件中都可以通过 this.$store 取到 store 对象。这也就解答了我们问题 2 中的问题。通过在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,注入方法是子从父拿,root从options拿。接下来让我们看看new Vuex.Store()都干了什么。store构造函数store对象构建的主要代码都在store.js中,是vuex的核心代码。首先,在 constructor 中进行了 Vue 的判断,如果没有通过 Vue.use(Vuex) 进行 Vuex 的注册,则调用 install 函数注册。( 通过 script 标签引入时不需要手动调用 Vue.use(Vuex) )并在非生产环境进行判断: 必须调用 Vue.use(Vuex) 进行注册,必须支持 Promise,必须用 new 创建 store。if (!Vue && typeof window !== “undefined” && window.Vue) { install(window.Vue);}if (process.env.NODE_ENV !== “production”) { assert(Vue, must call Vue.use(Vuex) before creating a store instance.); assert( typeof Promise !== “undefined”, vuex requires a Promise polyfill in this browser. ); assert( this instanceof Store, store must be called with the new operator. );}然后进行一系列的属性初始化。其中的重点是 new ModuleCollection(options),这个我们放在后面再讲。先把 constructor 中的代码过完。const { plugins = [], strict = false } = options;// store internal statethis._committing = false; // 是否在进行提交mutation状态标识this._actions = Object.create(null); // 保存action,_actions里的函数已经是经过包装后的this._actionSubscribers = []; // action订阅函数集合this._mutations = Object.create(null); // 保存mutations,_mutations里的函数已经是经过包装后的this._wrappedGetters = Object.create(null); // 封装后的getters集合对象// Vuex支持store分模块传入,在内部用Module构造函数将传入的options构造成一个Module对象,// 如果没有命名模块,默认绑定在this._modules.root上// ModuleCollection 内部调用 new Module构造函数this._modules = new ModuleCollection(options);this._modulesNamespaceMap = Object.create(null); // 模块命名空间mapthis._subscribers = []; // mutation订阅函数集合this._watcherVM = new Vue(); // Vue组件用于watch监视变化属性初始化完毕后,首先从 this 中解构出原型上的 dispatch 和 commit 方法,并进行二次包装,将 this 指向当前 store。const store = this;const { dispatch, commit } = this;/** 把 Store 类的 dispatch 和 commit 的方法的 this 指针指向当前 store 的实例上. 这样做的目的可以保证当我们在组件中通过 this.$store 直接调用 dispatch/commit 方法时, 能够使 dispatch/commit 方法中的 this 指向当前的 store 对象而不是当前组件的 this.*/this.dispatch = function boundDispatch(type, payload) { return dispatch.call(store, type, payload);};this.commit = function boundCommit(type, payload, options) { return commit.call(store, type, payload, options);};接着往下走,包括严格模式的设置、根state的赋值、模块的注册、state的响应式、插件的注册等等,其中的重点在 installModule 函数中,在这里实现了所有modules的注册。//options中传入的是否启用严格模式this.strict = strict;// new ModuleCollection 构造出来的_mudulesconst state = this._modules.root.state;// 初始化组件树根组件、注册所有子组件,并将其中所有的getters存储到this._wrappedGetters属性中installModule(this, state, [], this._modules.root);//通过使用vue实例,初始化 store._vm,使state变成可响应的,并且将getters变成计算属性resetStoreVM(this, state);// 注册插件plugins.forEach(plugin => plugin(this));// 调试工具注册const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools;if (useDevtools) { devtoolPlugin(this);}到此为止,constructor 中所有的代码已经分析完毕。其中的重点在 new ModuleCollection(options) 和 installModule ,那么接下来我们到它们的内部去看看,究竟都干了些什么。ModuleCollection由于 Vuex 使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。Vuex 允许我们将 store 分割成模块(module),每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。例如下面这样:const childModule = { state: { … }, mutations: { … }, actions: { … }}const store = new Vuex.Store({ state, getters, actions, mutations, modules: { childModule: childModule, }})有了模块的概念,可以更好的规划我们的代码。对于各个模块公用的数据,我们可以定义一个common store,别的模块用到的话直接通过 modules 的方法引入即可,无需重复的在每一个模块都写一遍相同的代码。这样我们就可以通过 store.state.childModule 拿到childModule中的 state 状态, 对于Module的内部是如何实现的呢?export default class ModuleCollection { constructor(rawRootModule) { // 注册根module,参数是new Vuex.Store时传入的options this.register([], rawRootModule, false); } register(path, rawModule, runtime = true) { if (process.env.NODE_ENV !== “production”) { assertRawModule(path, rawModule); } const newModule = new Module(rawModule, runtime); if (path.length === 0) { // 注册根module this.root = newModule; } else { // 注册子module,将子module添加到父module的_children属性上 const parent = this.get(path.slice(0, -1)); parent.addChild(path[path.length - 1], newModule); } // 如果当前模块有子modules,循环注册 if (rawModule.modules) { forEachValue(rawModule.modules, (rawChildModule, key) => { this.register(path.concat(key), rawChildModule, runtime); }); } }}在ModuleCollection中又调用了Module构造函数,构造一个Module。Module构造函数constructor (rawModule, runtime) { // 初始化时为false this.runtime = runtime // 存储子模块 this._children = Object.create(null) // 将原来的module存储,以备后续使用 this._rawModule = rawModule const rawState = rawModule.state // 存储原来module的state this.state = (typeof rawState === ‘function’ ? rawState() : rawState) || {} }通过以上代码可以看出,ModuleCollection 主要将传入的 options 对象整个构造为一个 Module 对象,并循环调用 this.register([key], rawModule, false) 为其中的 modules 属性进行模块注册,使其都成为 Module 对象,最后 options 对象被构造成一个完整的 Module 树。经过 ModuleCollection 构造后的树结构如下:(以上面的例子生成的树结构)模块已经创建好之后,接下来要做的就是 installModule。installModule首先我们来看一看执行完 constructor 中的 installModule 函数后,这棵树的结构如何?从上图中可以看出,在执行完installModule函数后,每一个 module 中的 state 属性都增加了 其子 module 中的 state 属性,但此时的 state 还不是响应式的,并且新增加了 context 这个对象。里面包含 dispatch 、 commit 等函数以及 state 、 getters 等属性。它就是 vuex 官方文档中所说的Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象 这个 context 对象。我们平时在 store 中调用的 dispatch 和 commit 就是从这里解构出来的。接下来让我们看看 installModule 里面执行了什么。function installModule(store, rootState, path, module, hot) { // 判断是否是根节点,跟节点的path = [] const isRoot = !path.length; // 取命名空间,形式类似’childModule/’ const namespace = store._modules.getNamespace(path); // 如果namespaced为true,存入_modulesNamespaceMap中 if (module.namespaced) { store._modulesNamespaceMap[namespace] = module; } // 不是根节点,把子组件的每一个state设置到其父级的state属性上 if (!isRoot && !hot) { // 获取当前组件的父组件state const parentState = getNestedState(rootState, path.slice(0, -1)); // 获取当前Module的名字 const moduleName = path[path.length - 1]; store._withCommit(() => { Vue.set(parentState, moduleName, module.state); }); } // 给context对象赋值 const local = (module.context = makeLocalContext(store, namespace, path)); // 循环注册每一个module的Mutation module.forEachMutation((mutation, key) => { const namespacedType = namespace + key; registerMutation(store, namespacedType, mutation, local); }); // 循环注册每一个module的Action module.forEachAction((action, key) => { const type = action.root ? key : namespace + key; const handler = action.handler || action; registerAction(store, type, handler, local); }); // 循环注册每一个module的Getter module.forEachGetter((getter, key) => { const namespacedType = namespace + key; registerGetter(store, namespacedType, getter, local); }); // 循环_childern属性 module.forEachChild((child, key) => { installModule(store, rootState, path.concat(key), child, hot); });}在installModule函数里,首先判断是否是根节点、是否设置了命名空间。在设置了命名空间的前提下,把 module 存入 store._modulesNamespaceMap 中。在不是跟节点并且不是 hot 的情况下,通过 getNestedState 获取到父级的 state,并获取当前 module 的名字, 用 Vue.set() 方法将当前 module 的 state 挂载到父 state 上。然后调用 makeLocalContext 函数给 module.context 赋值,设置局部的 dispatch、commit方法以及getters和state。那么来看一看这个函数。function makeLocalContext(store, namespace, path) { // 是否有命名空间 const noNamespace = namespace === “”; const local = { // 如果没有命名空间,直接返回store.dispatch;否则给type加上命名空间,类似’childModule/‘这种 dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => { const args = unifyObjectStyle(_type, _payload, _options); const { payload, options } = args; let { type } = args; if (!options || !options.root) { type = namespace + type; if ( process.env.NODE_ENV !== “production” && !store._actions[type] ) { console.error( [vuex] unknown local action type: ${ args.type }, global type: ${type} ); return; } } return store.dispatch(type, payload); }, // 如果没有命名空间,直接返回store.commit;否则给type加上命名空间 commit: noNamespace ? store.commit : (_type, _payload, _options) => { const args = unifyObjectStyle(_type, _payload, _options); const { payload, options } = args; let { type } = args; if (!options || !options.root) { type = namespace + type; if ( process.env.NODE_ENV !== “production” && !store._mutations[type] ) { console.error( [vuex] unknown local mutation type: ${ args.type }, global type: ${type} ); return; } } store.commit(type, payload, options); } }; // getters and state object must be gotten lazily // because they will be changed by vm update Object.defineProperties(local, { getters: { get: noNamespace ? () => store.getters : () => makeLocalGetters(store, namespace) }, state: { get: () => getNestedState(store.state, path) } }); return local;}经过 makeLocalContext 处理的返回值会赋值给 local 变量,这个变量会传递给 registerMutation、forEachAction、registerGetter 函数去进行相应的注册。mutation可以重复注册,registerMutation 函数将我们传入的 mutation 进行了一次包装,将 state 作为第一个参数传入,因此我们在调用 mutation 的时候可以从第一个参数中取到当前的 state 值。function registerMutation(store, type, handler, local) { const entry = store._mutations[type] || (store._mutations[type] = []); entry.push(function wrappedMutationHandler(payload) { // 将this指向store,将makeLocalContext返回值中的state作为第一个参数,调用值执行的payload作为第二个参数 // 因此我们调用commit去提交mutation的时候,可以从mutation的第一个参数中取到当前的state值。 handler.call(store, local.state, payload); });}action也可以重复注册。注册 action 的方法与 mutation 相似,registerAction 函数也将我们传入的 action 进行了一次包装。但是 action 中参数会变多,里面包含 dispatch 、commit、local.getters、local.state、rootGetters、rootState,因此可以在一个 action 中 dispatch 另一个 action 或者去 commit 一个 mutation。这里也就解答了问题4中提出的疑问。function registerAction(store, type, handler, local) { const entry = store._actions[type] || (store._actions[type] = []); entry.push(function wrappedActionHandler(payload, cb) { //与mutation不同,action的第一个参数是一个对象,里面包含dispatch、commit、getters、state、rootGetters、rootState let res = handler.call( store, { dispatch: local.dispatch, commit: local.commit, getters: local.getters, state: local.state, rootGetters: store.getters, rootState: store.state }, payload, cb ); if (!isPromise(res)) { res = Promise.resolve(res); } if (store._devtoolHook) { return res.catch(err => { store._devtoolHook.emit(“vuex:error”, err); throw err; }); } else { return res; } });}注册 getters,从getters的第一个参数中可以取到local state、local getters、root state、root getters。getters不允许重复注册。function registerGetter(store, type, rawGetter, local) { // getters不允许重复 if (store._wrappedGetters[type]) { if (process.env.NODE_ENV !== “production”) { console.error([vuex] duplicate getter key: ${type}); } return; } store._wrappedGetters[type] = function wrappedGetter(store) { // getters的第一个参数包含local state、local getters、root state、root getters return rawGetter( local.state, // local state local.getters, // local getters store.state, // root state store.getters // root getters ); };}现在 store 的 _mutation、_action 中已经有了我们自行定义的的 mutation 和 action函数,并且经过了一层内部报装。当我们在组件中执行 this.$store.dispatch() 和 this.$store.commit() 的时候,是如何调用到相应的函数的呢?接下来让我们来看一看 store 上的 dispatch 和 commit 函数。commitcommit 函数先进行参数的适配处理,然后判断当前 action type 是否存在,如果存在则调用 _withCommit 函数执行相应的 mutation 。 // 提交mutation函数 commit(_type, _payload, _options) { // check object-style commit //commit支持两种调用方式,一种是直接commit(‘getName’,‘vuex’),另一种是commit({type:‘getName’,name:‘vuex’}), //unifyObjectStyle适配两种方式 const { type, payload, options } = unifyObjectStyle( _type, _payload, _options ); const mutation = { type, payload }; // 这里的entry取值就是我们在registerMutation函数中push到_mutations中的函数,已经经过处理 const entry = this._mutations[type]; if (!entry) { if (process.env.NODE_ENV !== “production”) { console.error([vuex] unknown mutation type: ${type}); } return; } // 专用修改state方法,其他修改state方法均是非法修改,在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误 // 不要在发布环境下启用严格模式!严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。 this._withCommit(() => { entry.forEach(function commitIterator(handler) { handler(payload); }); }); // 订阅者函数遍历执行,传入当前的mutation对象和当前的state this._subscribers.forEach(sub => sub(mutation, this.state)); if (process.env.NODE_ENV !== “production” && options && options.silent) { console.warn( [vuex] mutation type: ${type}. Silent option has been removed. + “Use the filter functionality in the vue-devtools” ); } }在 commit 函数中调用了 _withCommit 这个函数, 代码如下。_withCommit 是一个代理方法,所有触发 mutation 的进行 state 修改的操作都经过它,由此来统一管理监控 state 状态的修改。在严格模式下,会深度监听 state 的变化,如果没有通过 mutation 去修改 state,则会报错。官方建议 不要在发布环境下启用严格模式! 请确保在发布环境下关闭严格模式,以避免性能损失。这里就解答了问题1中的疑问。_withCommit(fn) { // 保存之前的提交状态false const committing = this._committing; // 进行本次提交,若不设置为true,直接修改state,strict模式下,Vuex将会产生非法修改state的警告 this._committing = true; // 修改state fn(); // 修改完成,还原本次修改之前的状态false this._committing = committing;}dispatchdispatch 和 commit 的原理相同。如果有多个同名 action,会等到所有的 action 函数完成后,返回的 Promise 才会执行。// 触发action函数 dispatch(_type, _payload) { // check object-style dispatch const { type, payload } = unifyObjectStyle(_type, _payload); const action = { type, payload }; const entry = this._actions[type]; if (!entry) { if (process.env.NODE_ENV !== “production”) { console.error([vuex] unknown action type: ${type}); } return; } // 执行所有的订阅者函数 this._actionSubscribers.forEach(sub => sub(action, this.state)); return entry.length > 1 ? Promise.all(entry.map(handler => handler(payload))) : entry0; }至此,整个 installModule 里涉及到的内容已经分析完毕。我们在 options 中传进来的 action 和 mutation 已经在 store 中。但是 state 和 getters 还没有。这就是接下来的 resetStoreVM 方法做的事情。resetStoreVMresetStoreVM 函数中包括初始化 store._vm,观测 state 和 getters 的变化以及执行是否开启严格模式等。state 属性赋值给 vue 实例的 data 属性,因此数据是可响应的。这也就解答了问题 3,用到的属性在 state 中也必须要提前定义好,vue 视图才可以响应。function resetStoreVM(store, state, hot) { //保存老的vm const oldVm = store._vm; // 初始化 store 的 getters store.getters = {}; // _wrappedGetters 是之前在 registerGetter 函数中赋值的 const wrappedGetters = store._wrappedGetters; const computed = {}; forEachValue(wrappedGetters, (fn, key) => { // 将getters放入计算属性中,需要将store传入 computed[key] = () => fn(store); // 为了可以通过this.$store.getters.xxx访问getters Object.defineProperty(store.getters, key, { get: () => store._vm[key], enumerable: true // for local getters }); }); // use a Vue instance to store the state tree // suppress warnings just in case the user has added // some funky global mixins // 用一个vue实例来存储store树,将getters作为计算属性传入,访问this.$store.getters.xxx实际上访问的是store._vm[xxx] const silent = Vue.config.silent; Vue.config.silent = true; store._vm = new Vue({ data: { $$state: state }, computed }); Vue.config.silent = silent; // enable strict mode for new vm // 如果是严格模式,则启用严格模式,深度 watch state 属性 if (store.strict) { enableStrictMode(store); } // 若存在oldVm,解除对state的引用,等dom更新后把旧的vue实例销毁 if (oldVm) { if (hot) { // dispatch changes in all subscribed watchers // to force getter re-evaluation for hot reloading. store._withCommit(() => { oldVm._data.$$state = null; }); } Vue.nextTick(() => oldVm.$destroy()); }}开启严格模式时,会深度监听 $$state 的变化,如果不是通过this._withCommit()方法触发的state修改,也就是store._committing如果是false,就会报错。function enableStrictMode(store) { store._vm.$watch( function() { return this._data.$$state; }, () => { if (process.env.NODE_ENV !== “production”) { assert( store._committing, do not mutate vuex store state outside mutation handlers. ); } }, { deep: true, sync: true } );}让我们来看一看执行完 resetStoreVM 后的 store 结构。现在的 store 中已经有了 getters 属性,并且 getters 和 state 都是响应式的。至此 vuex 的核心代码初始化部分已经分析完毕。源码里还包括一些插件的注册及暴露出来的 API 像 mapState mapGetters mapActions mapMutation等函数就不在这里介绍了,感兴趣的可以自行去源码里看看,比较好理解。这里就不做过多介绍。总结vuex的源码相比于vue的源码来说还是很好理解的。分析源码之前建议大家再细读一遍官方文档,遇到不太理解的地方记下来,带着问题去读源码,有目的性的研究,可以加深记忆。阅读的过程中,可以先写一个小例子,引入 clone 下来的源码,一步一步分析执行过程。 ...

December 13, 2018 · 8 min · jiezi

vue 组件间传值

1、父组件给子组件传值父组件:<template> <child :name=“name”></child></template><script>import child from “./child.vue” export default { components: {child}, data(){ return {name:“二哈”} } }</script>子组件:<template> <div>{{name}}</div></template><script> export default { props:[“name”] }</script>2、子组件给父组件父组件:<template> <child @childToParent=“reviceSondata”></child></template><script>import child from “./child.vue” export default { components: {child}, methods:{ reviceSondata(data){ console.log(data); } } }</script>子组件:<template> <button @click=“dataTofather”>点击</button></template><script> export default { data () { return { message: ‘啦啦啦啦’ } }, methods:{ dataTofather(){ this.$emit(“childToParent”,this.message,true); } } }</script>3、vuexstore.js:import Vue from ‘vue’import Vuex from ‘vuex’Vue.use(Vuex)const store = new Vuex.Store({ // 定义状态 state: { headImg: JSON.parse(sessionStorage.getItem(‘headImg’)) || "" }, mutations: { newImg(state, msg){ sessionStorage.setItem(‘headImg’, JSON.stringify(msg)) state.headImg = msg; } }})export default storemain.js中引入vueximport Vue from ‘vue’;import Vuex from ‘vuex’;import store from ‘./vuex/store’;Vue.use(Vuex);var v = new Vue({ el: ‘#app’, router, store, components: {index}, template: ‘<index/>’, created: function () { }})传值:this.$store.commit(“newImg”, value);取值:this.$store.state.headImggithub地址:vue传值 ...

December 13, 2018 · 1 min · jiezi

Vue调试神器之Vue.js devTools

Vue项目中使用Vue.js devTools这款调试神器,可以极大程度的提高我们的开发效率。安装1、打开Chrome网上应用商店安装插件(自墙),直接搜索devTools安装即可。贵宾传送阵,请戳这里→Chrome网上应用商店2、从github上下载到本地。贵宾传送阵,请戳这里→vue-devtools1)、进入到/vue-devtools目录下(npm install或者cnpm install)安装项目所需依赖。2)、安装完依赖后(npm run build或者cnpm run build)编译项目。3)、编译完项目后,在/vue-devtools/shell/chrome/manifest.json中修改persistent参数false为true。4)、在chrome扩展程序页面,打开“开发者模式”,点击“加载已解压的扩展程序”按钮,选择你本地的…/vue-devtools/shell/chrome文件夹, 添加完成后,勾选允许文件地址是否访问。安装完成后devTools的图标会出现在浏览器的菜单栏中。以上两种安装方式告一段落。下面让我们在vue项目中体验一下这款调试神器吧。使用运行Vue项目,在Chrome浏览器中展示项目(浏览器菜单栏中的devTools图标会亮起),按F12打开开发者工具,在开发者工具的菜单栏最后面会看到Vue,选中它就可以尽情的使用了。当然这样一顿行云流水的操作下来,有些人会一次成功,而有些人则不会成功,浏览器菜单中的devTools图标亮滴鸭批,但是就是在开发者工具的菜单栏最后面看不到Vue。嗯~~~,怎么肥事小老弟?请查看项目的index.html中,对vue.js的引用。你肯定使用了压缩版的vue.min.js,使用vue.min.js默认为生产环境,会导致devTools不显示,改为vue.js即可。

November 29, 2018 · 1 min · jiezi

深入了解最新的Vue Devtools v5.0

早些时候发布了Vue devtools 5.0beta版,为已经调试过的强大工具带来了惊人的新功能。一些新功能包括性能分析,路线跟踪,Vuex store的实时编辑以及新的设置面板。这些新的功能我觉得是肯定会出来的,在使用它们几周后,我分享一下对我们最直观感受的东西。让我们来了解一些新功能和在使用它们在调试过程中的新见解。路由Routing选项卡是devtools套件的全新选项。这里有两个不同的视图,“历史记录”和“路径”,可以通过单击“路由”选项卡标题进行交换。如果您在应用程序中使用vue-router,这些都会提供有用的信息。 历史视图有两个面板。左侧面板显示已经前往的路线的历史记录。单击其中一个历史记录条目将在右侧面板中显示该路径更改的详细信息。这些详细信息显示用户导航和导航的路线,以及任何伴随的路线参数。 路由视图还有两个面板,左侧面板显示应用程序中所有路径的映射。单击其中一条路线将在右侧面板中显示其详细信息。此处的详细信息与上一个视图略有不同,而是显示路径,任何子项(嵌套)路由和任何路由参数。VuexVuex已经是老功能了,但它有一个惊人的新功能; 您现在可以从devtools更新应用程序状态!这个功能一直期待已久。在更新之前,改变状态的过程要繁琐得多。您必须重新给一个真确的的Mutation以获得您想要的状态,或者您必须手动更新Vuex模块文件中的默认值。现在,您只需单击任何状态值,然后从那里更新或删除。此外,您甚至可以在现有对象上添加新属性!性能与路由选项卡一样,性能选项卡也是一个新增功能。此选项卡由两部分组成,“每秒帧数”和“组件渲染”。第一个选项卡“每秒帧数”显示一个实时源图表,其中包含应用程序的当前fps。这可用于查找减慢应用程序速度的某些操作或组件。在下图中,您可以看到图表中的第一个红色凹陷在其顶部有“M”,“E”和“R”图标。“M”表示发生Mutation,“E”表示事件被触发,“R”表示路径发生变化。我们可以预判应用程序的fps会暂时降低路径变化,但如果这是意外下降,那我们更加容易查出哪些组件消耗了比较多的资源。“Component Render”选项卡的第二张图片显示了组件生命周期方法的详细运行时间统计信息。左窗格显示组件的总渲染时间,而右窗格按生命周期方法提供细分。任何极慢的组件都会在这个左侧标签中脱颖而出,这再次为调查性能问题提供了一个良好的起点。设置最后但并非最不重要的是,有一个新的设置菜单!目前,此菜单包括以下选项:将显示密度更改为更紧凑的布局规范化组件名称(my-component将变为MyComponent)更新主题 - 打开或关闭新的黑暗主题选项结论Vue核心团队及其社区再一次提供了令人惊叹的开发人员工具体验。Vue devtools一直是开发人员体验中不可或缺的一部分,而且这次更新是朝着正确方向发展的巨大推动力。此版本的devtools与最近发布的vue-cli GUI相结合,提供了从创建到完成,令人惊讶的开发体验。下载地址Chrome官方地址(需自备梯子)

November 24, 2018 · 1 min · jiezi

vue面试题总结

写在前面参考答案在看云平台发布,如果大家想阅读参考答案,可直接购买,看云平台 50 个免费开通权限 已经使用完毕,感谢大家的支持!有什么意见与建议欢迎您及时联系作者或留言回复!Vue开发交流微信群:(二维码定期更新,长期有效!或添加群主微信hanxuming888进群)本文档基于vue-cli技术栈总结了vue-cli工程vue.js核心知识vue-router路由vuex状态管理器axios等http请求移动端适配Tab切换等常用功能vue与原生app混合交互vue生产环境部署vue各项技术源码解析MVVM设计模式vue-cli工程深入拓展等12个关于vue-cli开发的方面,共98道面试题。不仅可以帮你一次性详细阅读所有关于vue的面试题、更可以帮你拓展关于vue开发的视野。关于vue的试题,看这一篇文档就够了!vue-cli工程1、构建的 vue-cli 工程都到了哪些技术,它们的作用分别是什么?2、vue-cli 工程常用的 npm 命令有哪些?3、请说出vue-cli工程中每个文件夹和文件的用处4、config文件夹 下 index.js 的对于工程 开发环境 和 生产环境 的配置5、请你详细介绍一些 package.json 里面的配置参考答案: https://www.kancloud.cn/hanxu...vue核心知识点1、对于Vue是一套渐进式框架的理解2、vue.js的两个核心是什么?3、请问 v-if 和 v-show 有什么区别4、vue常用的修饰符5、v-on可以监听多个方法吗?6、vue中 key 值的作用7、vue-cli工程升级vue版本8、vue事件中如何使用event对象?9、$nextTick的使用10、Vue 组件中 data 为什么必须是函数11、v-for 与 v-if 的优先级12、vue中子组件调用父组件的方法13、vue中 keep-alive 组件的作用14、vue中如何编写可复用的组件?15、什么是vue生命周期和生命周期钩子函数?16、vue生命周期钩子函数有哪些?17、vue如何监听键盘事件中的按键?18、vue更新数组时触发视图更新的方法19、vue中对象更改检测的注意事项20、解决非工程化项目初始化页面闪动问题21、v-for产生的列表,实现active的切换22、v-model语法糖的组件中的使用23、十个常用的自定义过滤器24、vue等单页面应用及其优缺点25、什么是vue的计算属性?26、vue-cli提供的几种脚手架模板27、vue父组件如何向子组件中传递数据?28、vue-cli开发环境使用全局常量29、vue-cli生产环境使用全局常量30、vue弹窗后如何禁止滚动条滚动?31、计算属性的缓存和方法调用的区别32、vue-cli中自定义指令的使用参考答案: https://www.kancloud.cn/hanxu...vue-router1、vue-router如何响应 路由参数 的变化?2、完整的 vue-router 导航解析流程3、vue-router有哪几种导航钩子( 导航守卫 )?4、vue-router的几种实例方法以及参数传递5、vue-router的动态路由匹配以及使用6、vue-router如何定义嵌套路由?7、<router-link></router-link>组件及其属性8、vue-router实现路由懒加载( 动态加载路由 )9、vue-router路由的两种模式10、history路由模式与后台的配合参考答案: https://www.kancloud.cn/hanxu...vuex1、什么是vuex?2、使用vuex的核心概念3、vuex在vue-cli中的应用4、组件中使用 vuex 的值和修改值的地方?5、在vuex中使用异步修改6、pc端页面刷新时实现vuex缓存参考答案: https://www.kancloud.cn/hanxu...http请求1、Promise对象是什么?2、axios、fetch与ajax有什么区别?3、什么是JS的同源策略和跨域问题?4、如何解决跨域问题?5、vue-cli中如何使用JSON数据模拟?6、vue-cli中http请求的统一管理。7、axios有什么特点?参考答案: https://www.kancloud.cn/hanxu...UI样式1、.vue组件的scoped属性的作用2、如何让CSS只在当前组件中起作用?3、vue-cli中常用的UI组件库4、如何适配移动端?【 经典 】5、移动端常用媒体查询的使用6、垂直居中对齐7、vue-cli中如何使用背景图片?8、使用表单禁用时移动端样式问题9、多种类型文本超出隐藏问题参考答案: https://www.kancloud.cn/hanxu…常用功能1、vue中如何实现tab切换功能?2、vue中如何利用 keep-alive 标签实现某个组件缓存功能?3、vue中实现切换页面时为左滑出效果4、vue中父子组件如何相互调用方法?5、vue中央事件总线的使用参考答案: https://www.kancloud.cn/hanxu…混合开发1、vue如何调用 原生app 提供的方法?2、原生app 调用 vue 提供的方法,并将值传递到 .vue 组件中参考答案: https://www.kancloud.cn/hanxu…生产环境1、vue打包命令是什么?2、vue打包后会生成哪些文件?3、如何配置 vue 打包生成文件的路径?4、vue如何优化首屏加载速度?参考答案: https://www.kancloud.cn/hanxu...MVVM设计模式1、MVC、MVP与MVVM模式2、MVC、MVP与MVVM的区别3、常见的实现MVVM几种方式4、Object.defineProperty()方法5、实现一个自己的MVVM(原理剖析)6、 ES6中类和定义7、JS中的文档碎片8、解构赋值9、Array.from与Array.reduce10、递归的使用11、Obj.keys()与Obj.defineProperty12、发布-订阅模式13、实现MVVM的思路分析参考答案: https://www.kancloud.cn/hanxu…源码剖析1、vue内部与运行机制:Vue.js 全局运行机制响应式系统的基本原理什么是 Virtual DOM?如何编译template 模板?diff算法批量异步更新策略及 nextTick 原理?proxy代理?2、vuex工作原理详解Vue.mixinVue.use参考答案: https://www.kancloud.cn/hanxu…深入拓展1、vue开发命令 npm run dev 输入后的执行过程2、vue的服务器端渲染3、从零写一个npm安装包4、vue-cli中常用到的加载器5、webpack的特点参考答案: https://www.kancloud.cn/hanxu…

November 22, 2018 · 1 min · jiezi

Weex系列(3) —— 单页面还是多页面

目录Weex系列(序) —— 总要知道原生的一点东东(iOS)Weex系列(序) —— 总要知道原生的一点东东(Android)Weex系列(1) —— Hello World项目Weex系列(2) —— 页面跳转和通信Weex系列(3) —— 单页面还是多页面[Weex系列(4) —— 老生常谈的三端统一][Weex系列(5) —— 封装原生组件和模块][Weex系列(6) —— css相关小结][Weex系列(7) —— web组件和webview][Weex系列(8) —— 是时候简析一下流程原理了][Weex系列(9) —— 踩坑填坑的集锦][Weex系列(10) —— 先这么多吧想到再写。。。]时间总是过得那么快,一周又过去了。天越来越冷了,感觉跟要冬眠似的,越来越懒得动脑了,哈哈哈,下面开始进入我们的主题吧。单页面应用单页面应用(single page web application,SPA),大家应该很熟悉了,现在好多页面都采用的是这种模式,优缺点网上一搜一箩筐,支持的框架也有很多,react全家桶、vue全家桶等。Weex的上层语言有vue,所以我们是不是也可以用vue全家桶来打造一个App,官网的回答是可以的。用weex脚手架初始化项目,选项vue-router后面竟然跟了一个(not recommended)不推荐的。demo如下图,这个例子很简单,就不上传代码了,其实官网有一个很典型的例子weex-hackernews(https://github.com/weexteam/w…,用了vuex和vue-router,感觉入了weex这个坑的(doge),应该都看过研究过这个例子吧。官网有一个 使用 Vuex 和 vue-router ,大家也可以点进去看一下。然后我们来简单分析一下吧一个bundlejs上面的例子,虽然有三个tab,还有一个page3,感觉好多页面的样子,像web一样,最后打包只有一个js,是不是感觉到一丝不对的气息,是啊,这么一个大的app就这么一个js。1、首次打开白屏时间长2、不能按需加载对应页面js3、整个app使用相同的执行环境,隐患很多等一般app都是越做越大,越做越复杂,想想是不是有点可怕呢。所以官网也是引导我们集成Weex到已有的app。多页面应用其实原生app本就是多页面的场景,好比浏览器可以开很多窗口,上面那个例子就只是在一个窗口里来回折腾。说了这么多,那上面那个例子的底部tab1、2、3怎么实现呢,对,这就是多页面的成本,应该有好多跟我们一样,完全用Weex开发出一个从无到有的app,考虑了很多,底部这块我们还是决定用原生去做,这块我们是找了原生开发同学去做了一些支持的,这块据说是原生开发很基础很基础的一部分,大概半天就能搞定,可是后续的扩展性、性能优化、延展性等就好说多了,下面仅提供我们这边的一个思路。iOS: UITabBarController + UIViewController 把tab1、2、3.js的路径分别赋值给UIViewController,之前也有分析过WXDemoViewController大家可以去看看。UIViewController * weexVC = [[WXDemoViewController alloc] init];((WXDemoViewController *)weexVC).url = url;Android: 这个用的是Fragment,网上搜weex Fragment,会出来好多有参考价值的文章,大家可以去了解一下,我就不截图了,怕有版权之类的。navigator感觉这个词在我前面的文章里也是多次出现过了。是啊,底部tab1对应tab1.js渲染完页面,怎么进去到相应的page.js呢,就是我上一篇讲的了,用的基本就是navigator了,而且在page.js对应的页面,我们也是可以使用vue-router的。这个当然是用原生的模块组件封装的,有兴趣的可以看看WXNavigatorModule.m这个文件,所以页面的进退、切换等效果也都是极佳的,个人感觉完全超过单页面应用。小结读完文章的不难发现,我的观点就是偏向于多页面应用。各有所需,大家完全可以根据自己的场景来选择,如果你的app页面不多、轻量等,完全也是可以用单页面模式的。最后如果大家有一点点喜欢,对你有一点点的帮助,欢迎点赞收藏啊。

November 22, 2018 · 1 min · jiezi

GitHub上最热门的十大Vue.js项目

在过去的一个月里,Mybridge从将近150个Vue.js开源项目中精选出十个热门项目,旨在帮助开发者找到自己需要的Vue.js开源项目上榜开源项目所获得Star数平均为:268涉及的领域包括:系统设计,移动组件,图像过滤器,图表,Nuxtjs,组件,录音机,表格,谷歌地图,悬停效果下面就是上榜的开源项目详情:1.Vue-argon-design-systemhttps://github.com/creativeti… Star 164Vue-argon-design-system是基于Design System for Bootstrap 4开发,它由100多个独立的组件构成,用户可以自由选择和组合,所有组件都可以采用颜色变化,你可以使用SASS文件轻松修改。2.Vue-yduihttps://github.com/ydcss/vue-… Star 1974Vue-ydui 是 YDUI Touch 的一个Vue2.x实现版本,专为移动端打造,在追求完美视觉体验的同时也保证了其性能高效。在开始使用 vue-ydui 之前,有必要先了解 Vue 的相关基础知识以及Vue 组件,并了解移动端相关特性。3.Imagvuehttps://github.com/runkids/Im… Star 210Imagvue是Vue.js的图像组件,具有以下特性:● Imagvue 提供基本的图像处理道具(大小,模糊,对比度,灰度等)。● 支持图像延迟加载。● 所有属性都可以与数据绑定4.Vue-apexchartshttps://github.com/apexcharts… Star 101ApexCharts的Vue.js组件,用Vue.js对ApexCharts进行封装,实现在vue中构建交互式可视化5.Nuxpresshttps://github.com/galvez/nux… Star 90一个基于Nuxt的博客引擎和样板6.Vuihttps://github.com/vui-kit/vui Star 47一个基于VUE的简易框架,用于Vue2单页面应用程序的轻量级组件库,此项目的创建主要是为了深入学习vue7.Vue-audio-recorderhttps://github.com/grishkovel… Star 70适用于VueJS应用程序的简易录音机,它允许在服务器上创建,播放,下载和存储记录。8.Vue-genesis-formshttps://github.com/BlackMix/v… Star 50Vue-genesis-forms可以帮助你在Vue.js中轻松创建表单9.Vue2-gmap-custom-markerhttps://github.com/eregnier/v… Star 27该组件允许用户使用Overlay在地图上显示自定义html内容。这个组件是google map V3 overlay code的改编,其中一些创意来自angularjs谷歌地图10.Vue-good-linkshttps://github.com/xaksis/vue… Star 60给你的超链接添加一个漂亮动画的vue插件,如翻转、跳跃、滚动……原文链接:https://www.jianshu.com/write…

November 22, 2018 · 1 min · jiezi

Vue 全家桶实现一个移动端酷狗音乐

Vue 已经用了不少时间,最近抽空把以前的未完成的酷狗音乐做完了,过来分享下,也可以直接点这里预览,注意切换成手机模式。技术栈: vue-router、eventBus、vuex、vue-awesome-swiper整体功能 vs 酷狗官网:总体模拟官网,原来的亮点保留,如:图片懒加载除此之外,增加了加了全局的 Loading 组件,根据不同页面调整 Loading 尺寸搜索页面做了优化,可以在刷新时保留之前的搜索结果播放页面单独做了一个路由,可以在刷新时保留当前歌曲页面播放器的常驻以及滚动时最小化,避免遮挡歌曲名称部分可以重用,极少更新的数据,譬如主页四大栏,避免了数据的二次请求。增加了主页四栏手势滑动切换歌词滚动等…如果参考网易云,后续可以加的新功能还有一些,不过暂时我要先去做其他事了。CSS 觉得不难,都是手写的,采用的是 BEM 规范,js 是 ESLint。总体难度适中,只不过,如果规范化,组件化抽象,任务还是不少的,具体的坑我就不说了,源代码都在这里,推荐想要熟悉 vue 的同学也自己试下。作为一个练手项目,vue 全家桶都覆盖到了,当然,如果你只用 vue 和 vue-router 去实现,也不是不行,实现到一大半,就会明白为什么要全家桶了。至于酷狗的接口以及跨域的问题,解决方案都在 README 里,都是借用的其他作者的分享与整理,在此还是要感谢下 ecitlm 和 JsonBird。src/ 文件目录:|__ App.vue |__ assets |__ css |__ base.less |__ constant.less |__ iconfont.css |__ reset.css |__ images |__ logo__grey.png |__ logo__text.png |__ logo__theme.png |__ js |__ api.js |__ bus.js |__ globalEvent.js |__ mobileLayout.js |__ utils.js |__ components |__ Main.vue |__ new_song |__ NewSong.vue |__ Slider.vue |__ player |__ NextButton.vue |__ PlayButton.vue |__ PlayerLyrics.vue |__ PlayerMax.vue |__ PlayerMed.vue |__ PlayerProgress.vue |__ PrevButton.vue |__ public |__ AppHeader.vue |__ AppLoading.vue |__ AppMusicList.vue |__ AppNav.vue |__ PubList.vue |__ PubModuleDes.vue |__ PubModuleHead.vue |__ PubModuleTitle.vue |__ rank |__ RankInfo.vue |__ RankList.vue |__ search |__ Search.vue |__ singer |__ SingerCategory.vue |__ SingerInfo.vue |__ SingerList.vue |__ song |__ SongList.vue |__ SongListInfo.vue |__ main.js |__ mixins |__ index.js |__ loading.js |__ router |__ index.js |__ store |__ device.js |__ images.js |__ index.js |__ loading.js |__ newSong.js |__ player.js |__ rank.js |__ search.js |__ singer.js |__ song.js ...

November 16, 2018 · 1 min · jiezi

【vuejs面试题】务必熟知的vuejs面试题「务必收藏」

如果能帮到你,点个赞吧,务必熟知的vuejs面试题「务必收藏」vuejs 基础必备1、active-class 是哪个组件的属性?嵌套路由怎么定义 (1)、active-class 是 vue-router 模块的 router-link 组件的属性 (2)、使用 children 定义嵌套路由2、怎么定义 vue-router 的动态路由? 怎么获取传过来的值 在 router 目录下的 index.js 文件中,对 path 属性加上 /:id。 使用 router 对象的 params.id 获取3、vue-router 有哪几种导航钩子? 三种, (1)、全局导航钩子 router.beforeEach(to, from, next), router.beforeResolve(to, from, next), router.afterEach(to, from ,next) (2)、组件内钩子 beforeRouteEnter, beforeRouteUpdate, beforeRouteLeave (3)、单独路由独享组件 beforeEnter4、v-model 是什么?怎么使用? vue中标签怎么绑定事件 v-model 可以实现双向绑定, 绑定事件:<input @click=“rdhub.cn” />5、axios 是什么?怎么使用?描述使用它实现登录功能的流程 axios 是请求后台资源的模块。 npm i axios -S 如果发送的是跨域请求,需在配置文件中 config/index.js 进行配置6、vuex 是什么?怎么使用?哪种功能场景使用它 vuex 是专门为 vue 开发的数据状态管理模式。组件之间数据状态共享 使用场景:音乐播放、登录状态、购物车// 新建 store.jsimport vue from ‘vue’import vuex form ‘vuex’vue.use(vuex)export default new vuex.store({ //…rdhub.cn}) //main.jsimport store from ‘./store’…7、mvvm 框架是什么?它和其他框架(jquery) 的区别是什么?哪些场景适合 mvvm 是 model + view + viewmodel 框架,通过 viewmodel 连接数据模型model 和 view 区别:vue 是数据驱动,通过数据来显示视图层而不是节点操用 场景:数据操作比较多的场景,更加快捷8、自定义指令(v-check, v-focus) 的方法有哪些? 它有哪些钩子函数? 还有哪些钩子函数参数 全局定义指令:在 vue 对象的 directive 方法里面有两个参数, 一个是指令名称, 另一个是函数。 组件内定义指令:directives 钩子函数: bind(绑定事件出发)、inserted(节点插入时候触发)、update(组件内相关更新) 钩子函数参数: el、binding9、说出至少 4 种 vue 当中的指令和它的用法 v-if(判断是否隐藏)、v-for(把数据遍历出来)、v-bind(绑定属性)、v-model(实现双向绑定)10、vue-router 是什么?它有哪些组件 vue-router 是 vue 的路由插件, 组件:router-link router-view11、vue 的双向绑定的原理是什么 vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。 具体步骤: 第一步:需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter 这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化 第二步:compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图 第三步:Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是: 1、在自身实例化时往属性订阅器(dep)里面添加自己 2、自身必须有一个update()方法 3、待属性变动dep.notice()通知时,能调用自身的 update() 方法,并触发Compile中绑定的回调,则功成身退。 第四步:MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。12、请详细说下你对vue生命周期的理解 总共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后。 创建前/后 在beforeCreated阶段,vue实例的挂载元素$el和数据对象data都为undefined,还未初始化。 在created阶段,vue实例的数据对象data有了,$el还没有。 载入前/后 在beforeMount阶段,vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。 在mounted阶段,vue实例挂载完成,data.message成功渲染。 更新前/后 当data变化时,会触发beforeUpdate和updated方法。 销毁前/后 在执行destroy方法后,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在vuex 面试题1、有哪几种属性 有 5 种,分别是 state、getter、mutation、action、module2、vuex 的 store 特性是什么 (1) vuex 就是一个仓库,仓库里放了很多对象。其中 state 就是数据源存放地,对应于一般 vue 对象里面的 data (2) state 里面存放的数据是响应式的,vue 组件从 store 读取数据,若是 store 中的数据发生改变,依赖这相数据的组件也会发生更新 (3) 它通过 mapState 把全局的 state 和 getters 映射到当前组件的 computed 计算属性3、 vuex 的 getter 特性是什么 (1) getter 可以对 state 进行计算操作,它就是 store 的计算属性 (2) 虽然在组件内也可以做计算属性,但是 getters 可以在多给件之间复用 (3) 如果一个状态只在一个组件内使用,是可以不用 getters4、vuex 的 mutation 特性是什么 action 类似于 muation, 不同在于:action 提交的是 mutation,而不是直接变更状态 action 可以包含任意异步操作5、vue 中 ajax 请求代码应该写在组件的methods中还是vuex 的action中 如果请求来的数据不是要被其他组件公用,仅仅在请求的组件内使用,就不需要放入 vuex 的 state 里 如果被其他地方复用,请将请求放入 action 里,方便复用,并包装成 promise 返回5、不用 vuex 会带来什么问题 可维护性会下降,你要修改数据,你得维护3个地方 可读性下降,因为一个组件里的数据,你根本就看不出来是从哪里来的 增加耦合,大量的上传派发,会让耦合性大大的增加,本来Vue用Component就是为了减少耦合,现在这么用,和组件化的初衷相背生命周期面试题1、什么是 vue 生命周期 vue 实例从创建到销毁的过程就是生命周期。 也就是从开始创建、初始化数据、编译模板、挂在 dom -> 渲染、更新 -> 渲染、写在等一系列过程2、vue生命周期的作用是什么 生命周期中有多个事件钩子,让我们在控制整个 vue 实例的过程时更容易形成好的逻辑3、vue生命周期总共有几个阶段 8个阶段:创建前/后、载入前/后、更新前/后、销毁前/后4、第一次页面加载会触发哪几个钩子 第一次加载会触发 beforeCreate、created、beforeMount、mounted5、DOM 渲染在哪个周期中就已经完成 mounted6、简述每个周期具体适合哪些场景 生命周期钩子的一些使用方法: beforecreate : 可以在这加个loading事件,在加载实例时触发 created : 初始化完成时的事件写在这里,如在这结束loading事件,异步请求也适宜在这里调用 mounted : 挂载元素,获取到DOM节点 updated : 如果对数据统一处理,在这里写上相应函数 beforeDestroy : 可以做一个确认停止事件的确认框 nextTick : 更新数据后立即操作dom![image.png | left | 300x390]关于 ...

October 23, 2018 · 2 min · jiezi

从头开始学习Vuex

一、前言当我们的应用遇到多个组件共享状态时,会需要多个组件依赖于同一状态抑或是来自不同视图的行为需要变更同一状态。以前的解决办法:a.将数据以及操作数据的行为都定义在父组件;b.将数据以及操作数据的行为传递给需要的各个子组件(有可能需要多级传递)传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。在搭建下面页面时,你可能会对 vue 组件之间的通信感到崩溃 ,特别是非父子组件之间通信。此时就应该使用vuex,轻松可以搞定组件间通信问题。二、什么是VuexVuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。这里的关键在于集中式存储管理。简单来说,对 vue 应用中多个组件的共享状态进行集中式的管理(读/写)。三、Vuex的原理是什么1.简要介绍Vuex原理Vuex实现了一个单向数据流,在全局拥有一个State存放数据,当组件要更改State中的数据时,必须通过Mutation进行,Mutation同时提供了订阅者模式供外部插件调用获取State数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走Action,但Action也是无法直接修改State的,还是需要通过Mutation来修改State的数据。最后,根据State的变化,渲染到视图上。2.简要介绍各模块在流程中的主要功能:Vue Components:Vue组件。HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应。dispatch:操作行为触发方法,是唯一能执行action的方法。actions:操作行为处理模块,由组件中的$store.dispatch(‘action 名称’, data1)来触发。然后由commit()来触发mutation的调用 , 间接更新 state。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以支持action的链式触发。commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。mutations:状态改变操作方法,由actions中的commit(‘mutation 名称’)来触发。是Vuex修改state的唯一推荐方法。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些hook暴露出来,以进行state的监控等。state:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新。getters:state对象读取方法。图中没有单独列出该模块,应该被包含在了render中,Vue Components通过该方法读取全局state对象。四、什么时候使用Vuex虽然 Vuex 可以帮助我们管理共享状态,但也附带了更多的概念和框架。这需要对短期和长期效益进行权衡。如果您的应用够简单,您最好不要使用 Vuex,因为使用 Vuex 可能是繁琐冗余的。一个简单的 global event bus 就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。五、Vuex安装(限定开发环境为 vue-cli)首先要安装vue-cli脚手架,对于大陆用户,建议将npm的注册表源设置为国内的镜像(淘宝镜像),可以大幅提升安装速度。npm config set registry https://registry.npm.taobao.orgnpm config get registry//配置后可通过下面方式来验证是否成功npm install -g cnpm –registry=https://registry.npm.taobao.org//cnpm安装脚手架cnpm install -g vue-clivue init webpack my-vuecd my-vuecnpm installcnpm run dev脚手架安装好后,再安装vuexcnpm install vuex –save六、如何使用Vuex### 1.如何通过Vue来实现如下效果?这个小demo很容易用vue实现,核心代码如下: <div class=“hello”> <p>click {{count}} times,count is {{evenOrOdd}}</p> <button @click=“increment”>+</button> <button @click=“decrement”>-</button> <button @click=“incrementIfOdd”>increment if odd</button> <button @click=“incrementAsync”>increment async</button> </div> …… export default { name: “HelloWorld”, data() { return { count: 0 }; }, computed: { evenOrOdd() { return this.count % 2 === 0 ? “偶数” : “奇数”; } }, methods: { increment() { this.count = this.count + 1; }, decrement() { this.count = this.count - 1; }, // 只有是奇数才加1 incrementIfOdd() { if (this.count % 2 === 1) { this.count = this.count + 1; } }, // 过两秒才加1 incrementAsync() { setInterval(() => { this.count = this.count + 1; }, 2000); } }}2.如何通过Vuex来改造上面代码?①创建一个store.js文件import Vue from ‘vue’import Vuex from ‘vuex’Vue.use(Vuex)const store = new Vuex.Store({ state: { count: 0 }, mutations: {// 包含了多个直接更新state函数的对象 INCREMENT(state) { state.count = state.count + 1; }, DECREMENT(state) { state.count = state.count - 1; } }, getters: { // 当读取属性值时自动调用并返回属性值 evenOrOdd(state) { return state.count % 2 === 0 ? “偶数” : “奇数”; } }, actions: { // 包含了多个对应事件回调函数的对象 incrementIfOdd({ commit, state }) { // 带条件的action if (state.count % 2 === 1) { commit(‘INCREMENT’) } }, incrementAsync({ commit }) { //异步的action setInterval(() => { commit(‘INCREMENT’) }, 2000); } }})export default store //用export default 封装代码,让外部可以引用②在main.js文件中引入store.js文件import store from ‘./store’new Vue({ el: ‘#app’, router, store,//注册上vuex的store: 所有组件对象都多一个属性$store components: { App }, template: ‘<App/>’})③新建一个模板HelloWorld.vue<template> <div class=“hello”> <p>click {{count}} times,count is {{evenOrOdd}}</p> <button @click=“increment”>+</button> <button @click=“decrement”>-</button> <button @click=“incrementIfOdd”>increment if odd</button> <button @click=“incrementAsync”>increment async</button> </div></template><script>export default { name: “HelloWorld”, computed: { count() { return this.$store.state.count; }, evenOrOdd() { return this.$store.getters.evenOrOdd; } }, methods: { increment() { this.$store.commit(“INCREMENT”); }, decrement() { this.$store.commit(“DECREMENT”); }, // 只有是奇数才加1 incrementIfOdd() { this.$store.dispatch(“incrementIfOdd”); //触发store中对应的action调用 }, // 过两秒才加1 incrementAsync() { this.$store.dispatch(“incrementAsync”); } }};</script>由于 store 中的状态是响应式的,当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。在组件中调用 store 中的状态简单到仅需要在计算属性中返回即可。改变store 中的状态的唯一途径就是显式地提交 (commit) mutations。3.如何通mapState等辅助函数优化上面代码?import { mapActions, mapGetters, mapState, mapMutations } from “vuex”;… computed: { …mapState([“count”]), …mapGetters([“evenOrOdd”]) } methods: { …mapActions([“incrementIfOdd”, “incrementAsync”]), …mapMutations([“increment”, “decrement”]) }有点必须要注意:HelloWorld.vue文件中increment函数名称要跟store.js文件mutations中一致,才可以写成 …mapMutations([“increment”, “decrement”]),同样的道理,incrementIfOdd和incrementAsync也要和store.js文件actions保持一致。七、使用Vuex的注意点1.如何在Mutations里传递参数先store.js文件里给add方法加上一个参数n mutations: { INCREMENT(state,n) { state.count+=n; }, DECREMENT(state){ state.count–; } }然后在HelloWorld.vue里修改按钮的commit( )方法传递的参数 increment() { return this.$store.commit(“INCREMENT”,2); }, decrement() { return this.$store.commit(“DECREMENT”); }2.如何理解gettersgetters从表面是获得的意思,可以把他看作在获取数据之前进行的一种再编辑,相当于对数据的一个过滤和加工。getters就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。例如:要对store.js文件中的count进行操作,在它输出前,给它加上100。首先要在store.js里Vuex.Store()里引入gettersgetters:{ count:state=>state.count+=100 }然后在HelloWorld.vue中对computed进行配置,在vue 的构造器里边只能有一个computed属性,如果你写多个,只有最后一个computed属性可用,所以要用展开运算符”…”对上节写的computed属性进行一个改造。 computed: { …mapGetters([“count”])}3.actions和mutations区别actions和上面的Mutations功能基本一样,不同点是,actions是异步的改变state状态,而Mutations是同步改变状态。同步的意义在于这样每一个 mutation 执行完成后都可以对应到一个新的状态(和 reducer 一样),这样 devtools 就可以打个 snapshot 存下来,然后就可以随便 time-travel 了。如果你开着 devtool 调用一个异步的 action,你可以清楚地看到它所调用的 mutation 是何时被记录下来的,并且可以立刻查看它们对应的状态—-尤雨溪ps:如果想访问源代码,请猛戳git地址如果觉得文章对你有些许帮助,欢迎在我的GitHub博客点赞和关注,感激不尽!参考文章vuex官方文档Vuex 2.0 源码分析 ...

October 23, 2018 · 2 min · jiezi

Vue学习—— Vuex学习笔记

组件是Vue最强大的功能之一,而组件实例的作用域是相互独立的,意味着不同组件之间的数据是无法相互使用。组件间如何传递数据就显得至关重要,这篇文章主要是介绍Vuex。尽量以通俗易懂的实例讲述这其中的差别,希望对小伙伴有些许帮助。一、Vuex 是什么?Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。二、什么是“状态管理模式”?一个简单的 Vue 计数应用开始:new Vue({ // state data () { return { count: 0 } }, // view template: &lt;div&gt;{{ count }}&lt;/div&gt; , // actions methods: { increment () { this.count++ } }})这个状态自管理应用包含以下几个部分:state,驱动应用的数据源;view,以声明方式将 state 映射到视图;actions,响应在 view 上的用户输入导致的状态变化。state的数据会在 view上显示出来,用户会根据 view 上的内容进行操作,从而触发 actions,接着再去影响 state(vue 是单向数据流的方式驱动的)。当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏。下面的图,是把组件的共享状态抽取出来,以一个全局单例模式管理。三、核心概念1. statestate:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取。 <div> {{ $store.state.count }} </div> console.log(this.$store.state.count)2. gettersgetters:Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。(getters从表面是获得的意思,可以把他看作在获取数据之前进行的一种再编辑,相当于对数据的一个过滤和加工。getters就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。)定义getter: getters: { done(state) { return state.count + 1; }, }3. mutationsmutations:更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:const store = new Vuex.Store({ state: { count: 1 }, mutations: { increment (state) { // 变更状态 state.count++ } }})组件通过commit提交mutations的方式来请求改变statethis.$store.commit(‘increment’)提交载荷(Payload)mutations方法中是可以传参的,具体用法如下: mutations: { // 提交载荷 Payload add(state, n) { state.count += n } },this.$store.commit(‘add’, 10)4.ActionAction:类似于 mutation,不同在于Action 提交的是 mutation,而不是直接变更状态;Action 可以包含任意异步操作。const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit(‘increment’) } }})不同于mutations使用commit方法,actions使用dispatch方法。this.$store.dispatch(‘incrementAsync’)contextcontext是与 store 实例具有相同方法和属性的对象。可以通过context.state和context.getters来获取 state 和 getters。以载荷形式分发incrementAsyncWithValue (context, value) { setTimeout(() => { context.commit(‘add’, value) }, 1000)}this.$store.dispatch(‘incrementAsyncWithValue’, 5)5.Module由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,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 的状态模块的局部状态对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。const moduleA = { state: { count: 0 }, mutations: { increment (state) { // 这里的 state 对象是模块的局部状态 state.count++ } }, getters: { doubleCount (state) { return state.count * 2 } }}Vuex计数器的例子:在src目录下创建一个store文件夹。store/store.jsimport Vue from ‘vue’import Vuex from ‘vuex’Vue.use(Vuex)const store = new Vuex.Store({ state: { count: 0, show: ’’ }, getters: { counts: (state) => { return state.count } }, mutations: { increment: (state) => { state.count++ }, decrement: (state) => { state.count– }, changVal: (state, v) => { state.show = v } }})export default storestate就是我们的需要的状态,状态的改变只能通过提交mutations,例如:increase() { this.$store.commit(‘increment’) }带有载荷的提交方式:changObj () { this.$store.commit(‘changVal’, this.obj) }载荷也可以是一个对象,这样可以提交多个参数。changObj () { this.$store.commit(‘changVal’, { key:’’ }) }在main.js中引入store.jsimport store from ‘./store/store’export default new Vue({ el: ‘#app’, router, store, components: { App }, template: ‘<App/>’})在组件中使用在组建可以通过$store.state.count获得状态更改状态只能以提交mutation的方式。<div class=“store”> <p> {{$store.state.count}} </p> <button @click=“increase”><strong>+</strong></button> <button @click=“decrease”><strong>-</strong></button> <hr> <h3>{{$store.state.show}}</h3> <input placeholder=“请输入内容” v-model=“obj” @change=“changObj” clearable> </input></div></template><script>export default { data () { return { obj: ’’ } }, methods: { increase() { this.$store.commit(‘increment’) }, decrease() { this.$store.commit(‘decrement’) }, changObj () { this.$store.commit(‘changVal’, this.obj) } }}</script>Vuex官方文档Vue面试中,经常会被问到的面试题 ...

October 15, 2018 · 2 min · jiezi

带你五步学会Vue SSR

前言SSR大家肯定都不陌生,通过服务端渲染,可以优化SEO抓取,提升首页加载速度等,我在学习SSR的时候,看过很多文章,有些对我有很大的启发作用,有些就只是照搬官网文档。通过几天的学习,我对SSR有了一些了解,也从头开始完整的配置出了SSR的开发环境,所以想通过这篇文章,总结一些经验,同时希望能够对学习SSR的朋友起到一点帮助。我会通过五个步骤,一步步带你完成SSR的配置:纯浏览器渲染服务端渲染,不包含Ajax初始化数据服务端渲染,包含Ajax初始化数据服务端渲染,使用serverBundle和clientManifest进行优化一个完整的基于Vue + VueRouter + Vuex的SSR工程如果你现在对于我上面说的还不太了解,没有关系,跟着我一步步向下走,最终你也可以独立配置一个SSR开发项目,所有源码我会放到github上,大家可以作为参考。正文1. 纯浏览器渲染这个配置相信大家都会,就是基于weback + vue的一个常规开发配置,这里我会放一些关键代码,完整代码可以去github查看。目录结构- node_modules- components - Bar.vue - Foo.vue- App.vue- app.js- index.html- webpack.config.js- package.json- yarn.lock- postcss.config.js- .babelrc- .gitignoreapp.jsimport Vue from ‘vue’;import App from ‘./App.vue’;let app = new Vue({ el: ‘#app’, render: h => h(App)});App.vue<template> <div> <Foo></Foo> <Bar></Bar> </div></template><script>import Foo from ‘./components/Foo.vue’;import Bar from ‘./components/Bar.vue’;export default { components: { Foo, Bar }}</script>index.html<!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <meta name=“viewport” content=“width=device-width, initial-scale=1.0”> <meta http-equiv=“X-UA-Compatible” content=“ie=edge”> <title>纯浏览器渲染</title></head><body> <div id=“app”></div></body></html>components/Foo.vue<template> <div class=“foo”> <h1>Foo Component</h1> </div></template><style>.foo { background: yellowgreen;}</style>components/Bar.vue<template> <div class=“bar”> <h1>Bar Component</h1> </div></template><style>.bar { background: bisque;}</style>webpack.config.jsconst path = require(‘path’);const VueLoaderPlugin = require(‘vue-loader/lib/plugin’);const HtmlWebpackPlugin = require(‘html-webpack-plugin’);const ExtractTextPlugin = require(’extract-text-webpack-plugin’);module.exports = { mode: ‘development’, entry: ‘./app.js’, output: { path: path.resolve(__dirname, ‘dist’), filename: ‘bundle.js’ }, module: { rules: [ { test: /.js$/, use: ‘babel-loader’ }, { test: /.css$/, use: [‘vue-style-loader’, ‘css-loader’, ‘postcss-loader’] // 如果需要单独抽出CSS文件,用下面这个配置 // use: ExtractTextPlugin.extract({ // fallback: ‘vue-style-loader’, // use: [ // ‘css-loader’, // ‘postcss-loader’ // ] // }) }, { test: /.(jpg|jpeg|png|gif|svg)$/, use: { loader: ‘url-loader’, options: { limit: 10000 // 10Kb } } }, { test: /.vue$/, use: ‘vue-loader’ } ] }, plugins: [ new VueLoaderPlugin(), new HtmlWebpackPlugin({ template: ‘./index.html’ }), // 如果需要单独抽出CSS文件,用下面这个配置 // new ExtractTextPlugin(“styles.css”) ]};postcss.config.jsmodule.exports = { plugins: [ require(‘autoprefixer’) ]};.babelrc{ “presets”: [ “@babel/preset-env” ], “plugins”: [ // 让其支持动态路由的写法 const Foo = () => import(’../components/Foo.vue’) “dynamic-import-webpack” ]}package.json{ “name”: “01”, “version”: “1.0.0”, “main”: “index.js”, “license”: “MIT”, “scripts”: { “start”: “yarn run dev”, “dev”: “webpack-dev-server”, “build”: “webpack” }, “dependencies”: { “vue”: “^2.5.17” }, “devDependencies”: { “@babel/core”: “^7.1.2”, “@babel/preset-env”: “^7.1.0”, “babel-plugin-dynamic-import-webpack”: “^1.1.0”, “autoprefixer”: “^9.1.5”, “babel-loader”: “^8.0.4”, “css-loader”: “^1.0.0”, “extract-text-webpack-plugin”: “^4.0.0-beta.0”, “file-loader”: “^2.0.0”, “html-webpack-plugin”: “^3.2.0”, “postcss”: “^7.0.5”, “postcss-loader”: “^3.0.0”, “url-loader”: “^1.1.1”, “vue-loader”: “^15.4.2”, “vue-style-loader”: “^4.1.2”, “vue-template-compiler”: “^2.5.17”, “webpack”: “^4.20.2”, “webpack-cli”: “^3.1.2”, “webpack-dev-server”: “^3.1.9” }}命令启动开发环境yarn start构建生产环境yarn run build最终效果截图:完整代码查看github2. 服务端渲染,不包含Ajax初始化数据服务端渲染SSR,类似于同构,最终要让一份代码既可以在服务端运行,也可以在客户端运行。如果说在SSR的过程中出现问题,还可以回滚到纯浏览器渲染,保证用户正常看到页面。那么,顺着这个思路,肯定就会有两个webpack的入口文件,一个用于浏览器端渲染weboack.client.config.js,一个用于服务端渲染webpack.server.config.js,将它们的公有部分抽出来作为webpack.base.cofig.js,后续通过webpack-merge进行合并。同时,也要有一个server来提供http服务,我这里用的是koa。我们来看一下新的目录结构:- node_modules- config // 新增 - webpack.base.config.js - webpack.client.config.js - webpack.server.config.js- src - components - Bar.vue - Foo.vue - App.vue - app.js - entry-client.js // 新增 - entry-server.js // 新增 - index.html - index.ssr.html // 新增- package.json- yarn.lock- postcss.config.js- .babelrc- .gitignore在纯客户端应用程序(client-only app)中,每个用户会在他们各自的浏览器中使用新的应用程序实例。对于服务器端渲染,我们也希望如此:每个请求应该都是全新的、独立的应用程序实例,以便不会有交叉请求造成的状态污染(cross-request state pollution)。所以,我们要对app.js做修改,将其包装为一个工厂函数,每次调用都会生成一个全新的根组件。app.jsimport Vue from ‘vue’;import App from ‘./App.vue’;export function createApp() { const app = new Vue({ render: h => h(App) }); return { app };}在浏览器端,我们直接新建一个根组件,然后将其挂载就可以了。entry-client.jsimport { createApp } from ‘./app.js’;const { app } = createApp();app.$mount(’#app’);在服务器端,我们就要返回一个函数,该函数的作用是接收一个context参数,同时每次都返回一个新的根组件。这个context在这里我们还不会用到,后续的步骤会用到它。entry-server.jsimport { createApp } from ‘./app.js’;export default context => { const { app } = createApp(); return app;}然后再来看一下index.ssr.htmlindex.ssr.html<!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <meta name=“viewport” content=“width=device-width, initial-scale=1.0”> <meta http-equiv=“X-UA-Compatible” content=“ie=edge”> <title>服务端渲染</title></head><body> <!–vue-ssr-outlet–> <script type=“text/javascript” src="<%= htmlWebpackPlugin.options.files.js %>"></script></body></html><!–vue-ssr-outlet–>的作用是作为一个占位符,后续通过vue-server-renderer插件,将服务器解析出的组件html字符串插入到这里。<script type=“text/javascript” src="<%= htmlWebpackPlugin.options.files.js %>"></script>是为了将webpack通过webpack.client.config.js打包出的文件放到这里(这里是为了简单演示,后续会有别的办法来做这个事情)。因为服务端吐出来的就是一个html字符串,后续的Vue相关的响应式、事件响应等等,都需要浏览器端来接管,所以就需要将为浏览器端渲染打包的文件在这里引入。用官方的词来说,叫客户端激活(client-side hydration)。所谓客户端激活,指的是 Vue 在浏览器端接管由服务端发送的静态 HTML,使其变为由 Vue 管理的动态 DOM 的过程。在 entry-client.js 中,我们用下面这行挂载(mount)应用程序:// 这里假定 App.vue template 根元素的 id="app"app.$mount(’#app’)由于服务器已经渲染好了 HTML,我们显然无需将其丢弃再重新创建所有的 DOM 元素。相反,我们需要"激活"这些静态的 HTML,然后使他们成为动态的(能够响应后续的数据变化)。如果你检查服务器渲染的输出结果,你会注意到应用程序的根元素上添加了一个特殊的属性:<div id=“app” data-server-rendered=“true”>Vue在浏览器端就依靠这个属性将服务器吐出来的html进行激活,我们一会自己构建一下就可以看到了。接下来我们看一下webpack相关的配置:webpack.base.config.jsconst path = require(‘path’);const VueLoaderPlugin = require(‘vue-loader/lib/plugin’);module.exports = { mode: ‘development’, resolve: { extensions: [’.js’, ‘.vue’] }, output: { path: path.resolve(__dirname, ‘../dist’), filename: ‘[name].bundle.js’ }, module: { rules: [ { test: /.vue$/, use: ‘vue-loader’ }, { test: /.js$/, use: ‘babel-loader’ }, { test: /.css$/, use: [‘vue-style-loader’, ‘css-loader’, ‘postcss-loader’] }, { test: /.(jpg|jpeg|png|gif|svg)$/, use: { loader: ‘url-loader’, options: { limit: 10000 // 10Kb } } } ] }, plugins: [ new VueLoaderPlugin() ]};webpack.client.config.jsconst path = require(‘path’);const merge = require(‘webpack-merge’);const HtmlWebpackPlugin = require(‘html-webpack-plugin’);const base = require(’./webpack.base.config’);module.exports = merge(base, { entry: { client: path.resolve(__dirname, ‘../src/entry-client.js’) }, plugins: [ new HtmlWebpackPlugin({ template: path.resolve(__dirname, ‘../src/index.html’), filename: ‘index.html’ }) ]});注意,这里的入口文件变成了entry-client.js,将其打包出的client.bundle.js插入到index.html中。webpack.server.config.jsconst path = require(‘path’);const merge = require(‘webpack-merge’);const HtmlWebpackPlugin = require(‘html-webpack-plugin’);const base = require(’./webpack.base.config’);module.exports = merge(base, { target: ’node’, entry: { server: path.resolve(__dirname, ‘../src/entry-server.js’) }, output: { libraryTarget: ‘commonjs2’ }, plugins: [ new HtmlWebpackPlugin({ template: path.resolve(__dirname, ‘../src/index.ssr.html’), filename: ‘index.ssr.html’, files: { js: ‘client.bundle.js’ }, excludeChunks: [‘server’] }) ]});这里有几个点需要注意一下:入口文件是 entry-server.js因为是打包服务器端依赖的代码,所以target要设为node,同时,output的libraryTarget要设为commonjs2这里关于HtmlWebpackPlugin配置的意思是,不要在index.ssr.html中引入打包出的server.bundle.js,要引为浏览器打包的client.bundle.js,原因前面说过了,是为了让Vue可以将服务器吐出来的html进行激活,从而接管后续响应。那么打包出的server.bundle.js在哪用呢?接着往下看就知道啦package.json{ “name”: “01”, “version”: “1.0.0”, “main”: “index.js”, “license”: “MIT”, “scripts”: { “start”: “yarn run dev”, “dev”: “webpack-dev-server”, “build:client”: “webpack –config config/webpack.client.config.js”, “build:server”: “webpack –config config/webpack.server.config.js” }, “dependencies”: { “koa”: “^2.5.3”, “koa-router”: “^7.4.0”, “koa-static”: “^5.0.0”, “vue”: “^2.5.17”, “vue-server-renderer”: “^2.5.17” }, “devDependencies”: { “@babel/core”: “^7.1.2”, “@babel/preset-env”: “^7.1.0”, “autoprefixer”: “^9.1.5”, “babel-loader”: “^8.0.4”, “css-loader”: “^1.0.0”, “extract-text-webpack-plugin”: “^4.0.0-beta.0”, “file-loader”: “^2.0.0”, “html-webpack-plugin”: “^3.2.0”, “postcss”: “^7.0.5”, “postcss-loader”: “^3.0.0”, “style-loader”: “^0.23.0”, “url-loader”: “^1.1.1”, “vue-loader”: “^15.4.2”, “vue-style-loader”: “^4.1.2”, “vue-template-compiler”: “^2.5.17”, “webpack”: “^4.20.2”, “webpack-cli”: “^3.1.2”, “webpack-dev-server”: “^3.1.9”, “webpack-merge”: “^4.1.4” }}接下来我们看server端关于http服务的代码:server/server.jsconst Koa = require(‘koa’);const Router = require(‘koa-router’);const serve = require(‘koa-static’);const path = require(‘path’);const fs = require(‘fs’);const backendApp = new Koa();const frontendApp = new Koa();const backendRouter = new Router();const frontendRouter = new Router();const bundle = fs.readFileSync(path.resolve(__dirname, ‘../dist/server.js’), ‘utf-8’);const renderer = require(‘vue-server-renderer’).createBundleRenderer(bundle, { template: fs.readFileSync(path.resolve(__dirname, ‘../dist/index.ssr.html’), ‘utf-8’)});// 后端ServerbackendRouter.get(’/index’, (ctx, next) => { // 这里用 renderToString 的 promise 返回的 html 有问题,没有样式 renderer.renderToString((err, html) => { if (err) { console.error(err); ctx.status = 500; ctx.body = ‘服务器内部错误’; } else { console.log(html); ctx.status = 200; ctx.body = html; } });});backendApp.use(serve(path.resolve(__dirname, ‘../dist’)));backendApp .use(backendRouter.routes()) .use(backendRouter.allowedMethods());backendApp.listen(3000, () => { console.log(‘服务器端渲染地址: http://localhost:3000’);});// 前端ServerfrontendRouter.get(’/index’, (ctx, next) => { let html = fs.readFileSync(path.resolve(__dirname, ‘../dist/index.html’), ‘utf-8’); ctx.type = ‘html’; ctx.status = 200; ctx.body = html;});frontendApp.use(serve(path.resolve(__dirname, ‘../dist’)));frontendApp .use(frontendRouter.routes()) .use(frontendRouter.allowedMethods());frontendApp.listen(3001, () => { console.log(‘浏览器端渲染地址: http://localhost:3001’);});这里对两个端口进行监听,3000端口是服务端渲染,3001端口是直接输出index.html,然后会在浏览器端走Vue的那一套,主要是为了和服务端渲染做对比使用。这里的关键代码是如何在服务端去输出html字符串。const bundle = fs.readFileSync(path.resolve(__dirname, '../dist/server.bundle.js'), 'utf-8');const renderer = require('vue-server-renderer').createBundleRenderer(bundle, { template: fs.readFileSync(path.resolve(__dirname, '../dist/index.ssr.html'), 'utf-8')});可以看到,server.bundle.js在这里被使用啦,因为它的入口是一个函数,接收context作为参数(非必传),输出一个根组件app。这里我们用到了vue-server-renderer插件,它有两个方法可以做渲染,一个是createRenderer,另一个是createBundleRenderer。const { createRenderer } = require('vue-server-renderer')const renderer = createRenderer({ /* 选项 */ })const { createBundleRenderer } = require('vue-server-renderer')const renderer = createBundleRenderer(serverBundle, { /* 选项 */ })createRenderer无法接收为服务端打包出的server.bundle.js文件,所以这里只能用createBundleRenderer。serverBundle 参数可以是以下之一:绝对路径,指向一个已经构建好的 bundle 文件(.js 或 .json)。必须以 / 开头才会被识别为文件路径。由 webpack + vue-server-renderer/server-plugin 生成的 bundle 对象。JavaScript 代码字符串(不推荐)。这里我们引入的是.js文件,后续会介绍如何使用.json文件以及有什么好处。renderer.renderToString((err, html) =&gt; { if (err) { console.error(err); ctx.status = 500; ctx.body = '服务器内部错误'; } else { console.log(html); ctx.status = 200; ctx.body = html; }});使用createRenderer和createBundleRenderer返回的renderer函数包含两个方法renderToString和renderToStream,我们这里用的是renderToString成功后直接返回一个完整的字符串,renderToStream返回的是一个Node流。renderToString支持Promise,但是我在使用Prmoise形式的时候样式会渲染不出来,暂时还不知道原因,如果大家知道的话可以给我留言啊。配置基本就完成了,来看一下如何运行。yarn run build:client // 打包浏览器端需要bundleyarn run build:server // 打包SSR需要bundleyarn start // 其实就是 node server/server.js,提供http服务最终效果展示:访问http://localhost:3000/index我们看到了前面提过的data-server-rendered="true"属性,同时会加载client.bundle.js文件,为了让Vue在浏览器端做后续接管。访问http://localhost:3001/index还和第一步实现的效果一样,纯浏览器渲染,这里就不放截图了。完整代码查看github3. 服务端渲染,包含Ajax初始化数据如果SSR需要初始化一些异步数据,那么流程就会变得复杂一些。我们先提出几个问题:服务端拿异步数据的步骤在哪做?如何确定哪些组件需要获取异步数据?获取到异步数据之后要如何塞回到组件内?带着问题我们向下走,希望看完这篇文章的时候上面的问题你都找到了答案。服务器端渲染和浏览器端渲染组件经过的生命周期是有区别的,在服务器端,只会经历beforeCreate和created两个生命周期。因为SSR服务器直接吐出html字符串就好了,不会渲染DOM结构,所以不存在beforeMount和mounted的,也不会对其进行更新,所以也就不存在beforeUpdate和updated等。我们先来想一下,在纯浏览器渲染的Vue项目中,我们是怎么获取异步数据并渲染到组件中的?一般是在created或者mounted生命周期里发起异步请求,然后在成功回调里执行this.data = xxx,Vue监听到数据发生改变,走后面的Dom Diff,打patch,做DOM更新。那么服务端渲染可不可以也这么做呢?答案是不行的。在mounted里肯定不行,因为SSR都没有mounted生命周期,所以在这里肯定不行。在beforeCreate里发起异步请求是否可以呢,也是不行的。因为请求是异步的,可能还没有等接口返回,服务端就已经把html字符串拼接出来了。所以,参考一下官方文档,我们可以得到以下思路:在渲染前,要预先获取所有需要的异步数据,然后存到Vuex的store中。在后端渲染时,通过Vuex将获取到的数据注入到相应组件中。把store中的数据设置到window.__INITIAL_STATE__属性中。在浏览器环境中,通过Vuex将window.__INITIAL_STATE__里面的数据注入到相应组件中。正常情况下,通过这几个步骤,服务端吐出来的html字符串相应组件的数据都是最新的,所以第4步并不会引起DOM更新,但如果出了某些问题,吐出来的html字符串没有相应数据,Vue也可以在浏览器端通过Vuex注入数据,进行DOM更新。更新后的目录结构:- node_modules- config - webpack.base.config.js - webpack.client.config.js - webpack.server.config.js- src - components - Bar.vue - Foo.vue - store // 新增 store.js - App.vue - app.js - entry-client.js - entry-server.js - index.html - index.ssr.html- package.json- yarn.lock- postcss.config.js- .babelrc- .gitignore先来看一下store.js:store/store.jsimport Vue from ‘vue’;import Vuex from ‘vuex’;Vue.use(Vuex);const fetchBar = function() { return new Promise((resolve, reject) => { setTimeout(() => { resolve(‘bar 组件返回 ajax 数据’); }, 1000); });};function createStore() { const store = new Vuex.Store({ state: { bar: ’’ }, mutations: { ‘SET_BAR’(state, data) { state.bar = data; } }, actions: { fetchBar({ commit }) { return fetchBar().then((data) => { commit(‘SET_BAR’, data); }).catch((err) => { console.error(err); }) } } }); if (typeof window !== ‘undefined’ && window.INITIAL_STATE) { console.log(‘window.INITIAL_STATE’, window.INITIAL_STATE); store.replaceState(window.INITIAL_STATE); } return store;}export default createStore;typeof window如果不太了解Vuex,可以去Vuex官网先看一些基本概念。这里fetchBar可以看成是一个异步请求,这里用setTimeout模拟。在成功回调中commit相应的mutation进行状态修改。这里有一段关键代码:if (typeof window !== ‘undefined’ && window.INITIAL_STATE) { console.log(‘window.INITIAL_STATE’, window.INITIAL_STATE); store.replaceState(window.INITIAL_STATE);}因为store.js同样也会被打包到服务器运行的server.bundle.js中,所以运行环境不一定是浏览器,这里需要对window做判断,防止报错,同时如果有window.__INITIAL_STATE__属性,说明服务器已经把所有初始化需要的异步数据都获取完成了,要对store中的状态做一个替换,保证统一。components/Bar.vue<template> <div class=“bar”> <h1 @click=“onHandleClick”>Bar Component</h1> <h2>异步Ajax数据:</h2> <span>{{ msg }}</span> </div></template><script> const fetchInitialData = ({ store }) => { store.dispatch(‘fetchBar’); }; export default { asyncData: fetchInitialData, methods: { onHandleClick() { alert(‘bar’); } }, mounted() { // 因为服务端渲染只有 beforeCreate 和 created 两个生命周期,不会走这里 // 所以把调用 Ajax 初始化数据也写在这里,是为了供单独浏览器渲染使用 let store = this.$store; fetchInitialData({ store }); }, computed: { msg() { return this.$store.state.bar; } } }</script><style>.bar { background: bisque;}</style>这里在Bar组件的默认导出对象中增加了一个方法asyncData,在该方法中会dispatch相应的action,进行异步数据获取。需要注意的是,我在mounted中也写了获取数据的代码,这是为什么呢? 因为想要做到同构,代码单独在浏览器端运行,也应该是没有问题的,又由于服务器没有mounted生命周期,所以我写在这里就可以解决单独在浏览器环境使用也可以发起同样的异步请求去初始化数据。components/Foo.vue<template> <div class=“foo”> <h1 @click=“onHandleClick”>Foo Component</h1> </div></template><script>export default { methods: { onHandleClick() { alert(‘foo’); } },}</script><style>.foo { background: yellowgreen;}</style>这里我对两个组件都添加了一个点击事件,为的是证明在服务器吐出首页html后,后续的步骤都会被浏览器端的Vue接管,可以正常执行后面的操作。app.jsimport Vue from ‘vue’;import createStore from ‘./store/store.js’;import App from ‘./App.vue’;export function createApp() { const store = createStore(); const app = new Vue({ store, render: h => h(App) }); return { app, store, App };}在建立根组件的时候,要把Vuex的store传进去,同时要返回,后续会用到。最后来看一下entry-server.js,关键步骤在这里:entry-server.jsimport { createApp } from ‘./app.js’;export default context => { return new Promise((resolve, reject) => { const { app, store, App } = createApp(); let components = App.components; let asyncDataPromiseFns = []; Object.values(components).forEach(component => { if (component.asyncData) { asyncDataPromiseFns.push(component.asyncData({ store })); } }); Promise.all(asyncDataPromiseFns).then((result) => { // 当使用 template 时,context.state 将作为 window.INITIAL_STATE 状态,自动嵌入到最终的 HTML 中 context.state = store.state; console.log(222); console.log(store.state); console.log(context.state); console.log(context); resolve(app); }, reject); });}我们通过导出的App拿到了所有它下面的components,然后遍历,找出哪些component有asyncData方法,有的话调用并传入store,该方法会返回一个Promise,我们使用Promise.all等所有的异步方法都成功返回,才resolve(app)。context.state = store.state作用是,当使用createBundleRenderer时,如果设置了template选项,那么会把context.state的值作为window.__INITIAL_STATE__自动插入到模板html中。这里需要大家多思考一下,弄清楚整个服务端渲染的逻辑。如何运行:yarn run build:clientyarn run build:serveryarn start最终效果截图:服务端渲染:打开http://localhost:3000/index可以看到window.__INITIAL_STATE__被自动插入了。我们来对比一下SSR到底对加载性能有什么影响吧。服务端渲染时performance截图:纯浏览器端渲染时performance截图:同样都是在fast 3G网络模式下,纯浏览器端渲染首屏加载花费时间2.9s,因为client.js加载就花费了2.27s,因为没有client.js就没有Vue,也就没有后面的东西了。服务端渲染首屏时间花费0.8s,虽然client.js加载扔花费2.27s,但是首屏已经不需要它了,它是为了让Vue在浏览器端进行后续接管。从这我们可以真正的看到,服务端渲染对于提升首屏的响应速度是很有作用的。当然有的同学可能会问,在服务端渲染获取初始ajax数据时,我们还延时了1s,在这个时间用户也是看不到页面的。没错,接口的时间我们无法避免,就算是纯浏览器渲染,首页该调接口还是得调,如果接口响应慢,那么纯浏览器渲染看到完整页面的时间会更慢。完整代码查看github4. 使用serverBundle和clientManifest进行优化前面我们创建服务端renderer的方法是:const bundle = fs.readFileSync(path.resolve(__dirname, ‘../dist/server.js’), ‘utf-8’);const renderer = require(‘vue-server-renderer’).createBundleRenderer(bundle, { template: fs.readFileSync(path.resolve(__dirname, ‘../dist/index.ssr.html’), ‘utf-8’)});serverBundle我们用的是打包出的server.bundle.js文件。这样做的话,在每次编辑过应用程序源代码之后,都必须停止并重启服务。这在开发过程中会影响开发效率。此外,Node.js 本身不支持 source map。vue-server-renderer 提供一个名为 createBundleRenderer 的 API,用于处理此问题,通过使用 webpack 的自定义插件,server bundle 将生成为可传递到 bundle renderer 的特殊 JSON 文件。所创建的 bundle renderer,用法和普通 renderer 相同,但是 bundle renderer 提供以下优点:内置的 source map 支持(在 webpack 配置中使用 devtool: ‘source-map’)在开发环境甚至部署过程中热重载(通过读取更新后的 bundle,然后重新创建 renderer 实例)关键 CSS(critical CSS) 注入(在使用 *.vue 文件时):自动内联在渲染过程中用到的组件所需的CSS。更多细节请查看 CSS 章节。使用 clientManifest 进行资源注入:自动推断出最佳的预加载(preload)和预取(prefetch)指令,以及初始渲染所需的代码分割 chunk。preload和prefetch有不了解的话可以自行查一下它们的作用哈。那么我们来修改webpack配置:webpack.client.config.jsconst path = require(‘path’);const merge = require(‘webpack-merge’);const HtmlWebpackPlugin = require(‘html-webpack-plugin’);const VueSSRClientPlugin = require(‘vue-server-renderer/client-plugin’);const base = require(’./webpack.base.config’);module.exports = merge(base, { entry: { client: path.resolve(__dirname, ‘../src/entry-client.js’) }, plugins: [ new VueSSRClientPlugin(), // 新增 new HtmlWebpackPlugin({ template: path.resolve(__dirname, ‘../src/index.html’), filename: ‘index.html’ }) ]});webpack.server.config.jsconst path = require(‘path’);const merge = require(‘webpack-merge’);const nodeExternals = require(‘webpack-node-externals’);const HtmlWebpackPlugin = require(‘html-webpack-plugin’);const VueSSRServerPlugin = require(‘vue-server-renderer/server-plugin’);const base = require(’./webpack.base.config’);module.exports = merge(base, { target: ’node’, // 对 bundle renderer 提供 source map 支持 devtool: ‘#source-map’, entry: { server: path.resolve(__dirname, ‘../src/entry-server.js’) }, externals: [nodeExternals()], // 新增 output: { libraryTarget: ‘commonjs2’ }, plugins: [ new VueSSRServerPlugin(), // 这个要放到第一个写,否则 CopyWebpackPlugin 不起作用,原因还没查清楚 new HtmlWebpackPlugin({ template: path.resolve(__dirname, ‘../src/index.ssr.html’), filename: ‘index.ssr.html’, files: { js: ‘client.bundle.js’ }, excludeChunks: [‘server’] }) ]});因为是服务端引用模块,所以不需要打包node_modules中的依赖,直接在代码中require引用就好,所以配置externals: [nodeExternals()]。两个配置文件会分别生成vue-ssr-client-manifest.json和vue-ssr-server-bundle.json。作为createBundleRenderer的参数。来看server.jsserver.jsconst serverBundle = require(path.resolve(__dirname, ‘../dist/vue-ssr-server-bundle.json’));const clientManifest = require(path.resolve(__dirname, ‘../dist/vue-ssr-client-manifest.json’));const template = fs.readFileSync(path.resolve(__dirname, ‘../dist/index.ssr.html’), ‘utf-8’);const renderer = createBundleRenderer(serverBundle, { runInNewContext: false, template: template, clientManifest: clientManifest});效果和第三步就是一样的啦,就不截图了,完整代码查看github。5. 配置一个完整的基于Vue + VueRouter + Vuex的SSR这里和第四步不一样的是引入了vue-router,更接近于实际开发项目。在src下新增router目录。router/index.jsimport Vue from ‘vue’;import Router from ‘vue-router’;import Bar from ‘../components/Bar.vue’;Vue.use(Router);function createRouter() { const routes = [ { path: ‘/bar’, component: Bar }, { path: ‘/foo’, component: () => import(’../components/Foo.vue’) // 异步路由 } ]; const router = new Router({ mode: ‘history’, routes }); return router;}export default createRouter;这里我们把Foo组件作为一个异步组件引入,做成按需加载。在app.js中引入router,并导出:app.jsimport Vue from ‘vue’;import createStore from ‘./store/store.js’;import createRouter from ‘./router’;import App from ‘./App.vue’;export function createApp() { const store = createStore(); const router = createRouter(); const app = new Vue({ router, store, render: h => h(App) }); return { app, store, router, App };}修改App.vue引入路由组件:App.vue<template> <div id=“app”> <router-link to="/bar">Goto Bar</router-link> <router-link to="/foo">Goto Foo</router-link> <router-view></router-view> </div></template><script>export default { beforeCreate() { console.log(‘App.vue beforeCreate’); }, created() { console.log(‘App.vue created’); }, beforeMount() { console.log(‘App.vue beforeMount’); }, mounted() { console.log(‘App.vue mounted’); }}</script>最重要的修改在entry-server.js中,entry-server.jsimport { createApp } from ‘./app.js’;export default context => { return new Promise((resolve, reject) => { const { app, store, router, App } = createApp(); router.push(context.url); router.onReady(() => { const matchedComponents = router.getMatchedComponents(); console.log(context.url) console.log(matchedComponents) if (!matchedComponents.length) { return reject({ code: 404 }); } Promise.all(matchedComponents.map(component => { if (component.asyncData) { return component.asyncData({ store }); } })).then(() => { // 当使用 template 时,context.state 将作为 window.INITIAL_STATE 状态,自动嵌入到最终的 HTML 中 context.state = store.state; // 返回根组件 resolve(app); }); }, reject); });}这里前面提到的context就起了大作用,它将用户访问的url地址传进来,供vue-router使用。因为有异步组件,所以在router.onReady的成功回调中,去找该url路由所匹配到的组件,获取异步数据那一套还和前面的一样。于是,我们就完成了一个基本完整的基于Vue + VueRouter + VuexSSR配置,完成代码查看github。最终效果演示:访问http://localhost:3000/bar:完整代码查看github后续上面我们通过五个步骤,完成了从纯浏览器渲染到完整服务端渲染的同构,代码既可以运行在浏览器端,也可以运行在服务器端。那么,回过头来我们在看一下是否有优化的空间,又或者有哪些扩展的思考。1. 优化我们目前是使用renderToString方法,完全生成html后,才会向客户端返回,如果使用renderToStream,应用bigpipe技术可以向浏览器持续不断的返回一个流,那么文件的加载浏览器可以尽早的显示一些东西出来。const stream = renderer.renderToStream(context)返回的值是 Node.js stream:let html = ‘‘stream.on(‘data’, data => { html += data.toString()})stream.on(’end’, () => { console.log(html) // 渲染完成})stream.on(’error’, err => { // handle error…})在流式渲染模式下,当 renderer 遍历虚拟 DOM 树(virtual DOM tree)时,会尽快发送数据。这意味着我们可以尽快获得"第一个 chunk",并开始更快地将其发送给客户端。然而,当第一个数据 chunk 被发出时,子组件甚至可能不被实例化,它们的生命周期钩子也不会被调用。这意味着,如果子组件需要在其生命周期钩子函数中,将数据附加到渲染上下文(render context),当流(stream)启动时,这些数据将不可用。这是因为,大量上下文信息(context information)(如头信息(head information)或内联关键 CSS(inline critical CSS))需要在应用程序标记(markup)之前出现,我们基本上必须等待流(stream)完成后,才能开始使用这些上下文数据。因此,如果你依赖由组件生命周期钩子函数填充的上下文数据,则不建议使用流式传输模式。webpack优化webpack优化又是一个大的话题了,这里不展开讨论,感兴趣的同学可以自行查找一些资料,后续我也可能会专门写一篇文章来讲webpack优化。2. 思考是否必须使用vuex?答案是不用。Vuex只是为了帮助你实现一套数据存储、更新、获取的机制,入股你不用Vuex,那么你就必须自己想一套方案可以将异步获取到的数据存起来,并且在适当的时机将它注入到组件内,有一些文章提出了一些方案,我会放到参考文章里,大家可以阅读一下。是否使用SSR就一定好?这个也是不一定的,任何技术都有使用场景。SSR可以帮助你提升首页加载速度,优化搜索引擎SEO,但同时由于它需要在node中渲染整套Vue的模板,会占用服务器负载,同时只会执行beforeCreate和created两个生命周期,对于一些外部扩展库需要做一定处理才可以在SSR中运行等等。结语本文通过五个步骤,从纯浏览器端渲染开始,到配置一个完整的基于Vue + vue-router + Vuex的SSR环境,介绍了很多新的概念,也许你看完一遍不太理解,那么结合着源码,去自己手敲几遍,然后再来看几遍文章,相信你一定可以掌握SSR。最后,本文所有源代码都放在我的github上,如果对你有帮助的话,就来点一个赞吧参考链接https://ssr.vuejs.org/zh/https://zhuanlan.zhihu.com/p/…http://www.cnblogs.com/qingmi...https://juejin.im/entry/590ca...https://github.com/youngwind/… ...

October 11, 2018 · 8 min · jiezi

vue组件通信全面总结

写在前面组件间的通信是是实际开发中非常常用的一环,如何使用对项目整体设计、开发、规范都有很实际的的作用,我在项目开发中对此深有体会,总结下vue组件间通信的几种方式,讨论下各自的使用场景文章对相关场景预览父->子组件间的数据传递子->父组件间的数据传递兄弟组件间的数据传递组件深层嵌套,祖先组件与子组件间的数据传递文章相关技术预览prop、emit、bus、vuex、路由URL、provide/inject注:以下介绍与代码环境:vue2.0+、vue-cli2父->子组件间的数据传递父子组件的通信是开发是最常用的也是最重要的,你们一定知道父子通信是用prop传递数据的,像这样://父组件,传递数据<editor :inputIndex=“data” :inputName=“王文健”></editor>//子组件,接受数据,定义传递数据的类型type与默认值default props: { inputIndex: { type: Object, default: function(){ return {} } }, inputName: { type: String, default: ’’ },注意项:父组件传递数据时类似在标签中写了一个属性,如果是传递的数据是data中的自然是要在传递属性前加v-bind:,如果传递的是一个已知的固定值呢字符串是静态的可直接传入无需在属性前加v-bind数字,布尔,对象,数组,因为这些是js表达式而不是字符串,所以即使这些传递的是静态的也需要加v-bind,把数据放到data中引用,如果prop传到子组件中的数据是一个对象的话,要注意传递的是一个对象引用,虽然父子组件看似是分离的但最后都是在同一对象下如果prop传到子组件的值只是作为初始值使用,且在父组件中不会变化赋值到data中使用如果传到子组件的prop的数据在父组件会被改变的,放到计算属性中监听变化使用。因为如果传递的是个对象的话,只改变下面的某个属性子组件中是不会响应式更新的,如果子组件需要在数据变化时响应式更新那只能放到computed中或者用watch深拷贝deep:true才能监听到变化当然如果你又需要在子组件中通过prop传递数据的变化做些操作,那么写在computed中会报警告,因为计算属性中不推荐有任何数据的改变,最好只进行计算。如果你非要进行数据的操作那么可以把监听写在watch(注意deep深拷贝)或者使用computed的get和set如下图:但问题又来了,如果你传进来的是个对象,同时你又需要在子组件中操作传进来的这个数据,那么在父组件中的这个数据也会改变,因为你传递的只是个引用, 即使你把prop的数据复制到data中也是一样的,无论如何赋值都是引用的赋值,你只能对对象做深拷贝创建一个副本才能继续操作,你可以用JSON的方法先转化字符串在转成对象更方便一点,所以在父子传递数据时要先考虑好数据要如何使用,否则你会遇到很多问题或子组件中修改了父组件中的数据,这是很隐蔽并且很危险的子->父组件间的数据传递在vue中子向父传递数据一般用$emit自定义事件,在父组件中监听这个事件并在回调中写相关逻辑// 父组件监听子组件定义的事件 <editor :inputIndex=“index” @editorEmit=‘editorEmit’></editor>// 子组件需要返回数据时执行,并可以传递数据this.$emit(’editorEmit’, data)那么问题来了,我是不是真的有必要去向父组件返回这个数据,用自定义事件可以在当子组件想传递数据或向子组件传递的数据有变化需要重新传递时执行,那么另外一种场景,父组件需要子组件的一个数据但子组件并不知道或者说没有能力在父组件想要的时候给父组件,那么这个时候就要用到组件的一个选项ref:<editor ref=“editor” @editorEmit=‘editorEmit’></editor>父组件在标签中定义ref属性,在js中直接调用this.$refs.editor就是调用整个子组件,子组件的所有内容都能通过ref去调用,当然我们并不推荐因为这会使数据看起来非常混乱,所以我们可以在子组件中定义一种专供父组件调用的函数,,比如我们在这个函数中返回子组件data中某个数据,当父组件想要获取这个数据就直接主动调用ref执行这个函数获取这个数据,这样能适应很大一部分场景,逻辑也更清晰一点另外,父向子传递数据也可以用ref,有次需要在一个父组件中大量调用同一个子组件,而每次调用传递的prop数据都不同,并且传递数据会根据之后操作变化,这样我需要在data中定义大量相关数据并改变它,我可以直接用ref调用子组件函数直接把数据以参数的形式传给子组件,逻辑一下子清晰了如果调用基础组件可以在父组件中调用ref执行基础组件中暴露的各种功能接口,比如显示,消失等兄弟组件间的数据传递vue中兄弟组件间的通信是很不方便的,或者说不支持的,那么父子组件中都有什么通信方式呢路由URL参数在传统开发时我们常常把需要跨页面传递的数据放到url后面,跳转到另外页面时直接获取url字符串获取想要的参数即可,在vue跨组件时一样可以这么做,// router index.js 动态路由{ path:’/params/:Id’, component:Params, name:Params}// 跳转路由<router-link :to="/params/12">跳转路由</router-link>在跳转后的组件中用$route.params.id去获取到这个id参数为12,但这种只适合传递比较小的数据,数字之类的Bus通信在组件之外定义一个bus.js作为组件间通信的桥梁,适用于比较小型不需要vuex又需要兄弟组件通信的bus.js中添加如下 import Vue from ‘vue’ export default new Vue组件中调用bus.js通过自定义事件传递数据 import Bus from ‘./bus.js’ export default { methods: { bus () { Bus.$emit(‘msg’, ‘我要传给兄弟组件们’) } } }兄弟组件中监听事件接受数据 import Bus from ‘./bus.js’ export default { mounted() { Bus.$on(‘msg’, (e) => { console.log(e) }) } }注:以上两种使用场景并不高所以只是简略提一下,这两点都是很久以前写过,以上例子网上直接搜集而来如有错误,指正Vuex集中状态管理vuex是vue的集中状态管理工具,对于大型应用统一集中管理数据,很方便,在此对vuex的用法并不过多介绍只是提一下使用过程中遇到的问题规范:对于多人开发的大型应用规范的制定是至关重要的,对于所有人都会接触到的vuex对其修改数据调用数据都应有一个明确严格的使用规范vuex分模块:项目不同模块间维护各自的vuex数据限制调用:只允许action操作数据,getters获取数据,使用mapGetters,mapActions辅助函数调用数据对于vuex的使用场景也有一些争论,有人认为正常组件之间就是要用父子组件传值的方式,即使子组件需要使vuex中的数据也应该由父组件获取再传到子组件中,但有的时候组件间嵌套很深,只允许父组件获取数据并不是一个方便的方法,所以对于祖先元组件与子组件传值又有了新问题,vue官网也有一些方法解决,如下祖先组件与子组件间的数据传递provide/inject除了正常的父子组件传值外,vue也提供了provide/inject这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效官网实例// 父级组件提供 ‘foo’var Provider = { provide: { foo: ‘bar’ }, // …}// 子组件注入 ‘foo’var Child = { inject: [‘foo’], created () { console.log(this.foo) // => “bar” }}provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的属性。一个字符串数组,或一个对象,对象的 key 是本地的绑定名,value 是:在可用的注入内容中搜索用的 key (字符串或 Symbol),或 一个对象,该对象的:from 属性是在可用的注入内容中搜索用的 key (字符串或 Symbol)default 属性是降级情况下使用的 value提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。具体细节移步vue相关介绍https://cn.vuejs.org/v2/api/#…provide/inject还未在项目中应用过,后面会做尝试写在结尾文章只是整理一下笔记,谈一谈遇到的问题和经验,并没有严谨的措辞和详细的过程,如有错误望指正原创文章转载引用请注明原文链接http://blog.wwenj.com/index.p… ...

October 10, 2018 · 1 min · jiezi

Vue基于vuex、axios拦截器实现loading效果及axios的安装配置

准备利用vue-cli脚手架创建项目进入项目安装vuex、axios(npm install vuex,npm install axios)axios配置项目中安装axios模块(npm install axios)完成后,进行以下配置: main.js//引入axiosimport Axios from ‘axios’//修改原型链,全局使用axios,这样之后可在每个组件的methods中调用$axios命令完成数据请求Vue.prototype.$axios=Axios loading组件我这里就选择使用iview提供的loading组件,npm install iviewmain.jsimport iView from ‘iview’;import ‘iview/dist/styles/iview.css’;Vue.use(iView);安装引入后,将loading写成一个组件loading.vueVuex state状态设置控制loading的显隐store.js(Vuex)export const store = new Vuex.Store({ state:{ isShow:false }})在state中定义isShow属性,默认false隐藏v-if=“this.$store.state.isShow"为loading组件添加v-if绑定state中的isShow组件使用axios请求数据<button @click=“getData”>请求数据</button>methods:{ getData(){ this.$axios.get(‘https://www.apiopen.top/journalismApi') .then(res=>{ console.log(res)//返回请求的结果 }) .catch(err=>{ console.log(err) }) } }我这里使用一个按钮进行触发事件,利用get请求网上随便找的一个api接口,.then中返回请求的整个结果(不仅仅包括数据)Axios拦截器配置main.js//定义一个请求拦截器Axios.interceptors.request.use(function(config){ store.state.isShow=true; //在请求发出之前进行一些操作 return config})//定义一个响应拦截器Axios.interceptors.response.use(function(config){ store.state.isShow=false;//在这里对返回的数据进行处理 return config})分别定义一个请求拦截器(请求开始时执行某些操作)、响应拦截器(接受到数据后执行某些操作),之间分别设置拦截时执行的操作,改变state内isShow的布尔值从而控制loading组件在触发请求数据开始时显示loading,返回数据时隐藏loading特别注意:这里有一个语法坑(我可是来来回回踩了不少次)main.js中调取、操作vuex state中的数据不同于组件中的this.$store.state,而是直接store.state 同上面代码效果展示本文作者:茅野zhy博客链接:www.zhysama.xyz版权声明: 该文章由博主编辑 , 转发请注明出处谢谢!

September 5, 2018 · 1 min · jiezi

[译]Vuex中使用localStorage实现数据的持久化保存

Vuex中数据的全局化管理可以使得我们数据更加便于管理,但存在一个缺点就是当页面刷新也就是实例重新创建时,数据就会丢失,而使用localStorage就可以使得数据持久化保存,本文就此提供一种解决思路。1. localStorage基础localStorage是一种缓存机制,即时浏览器关闭后依然可以获取到。这使得你可以在页面上保存一些数据,后续的操作也可以继续访问的到。MDN 网站上有详细介绍,在此不再赘述,下面介绍下它的基本用法,非常简单。下面中的key表示获取访问数据的标识符// 存储localStorage.setItem(‘key’,‘value’);// 获取let val = localStorage.getItem(‘key’);注意: localStorage只能是字符串,意味着你想要存储对象和数组的话,localStorage只能先转换成JSON格式的字符串,然后获取的时候,再转换回原理的格式。// 保存数据localStorage.setItem(‘key’, JSON.stringify(object));// 数据获取,需转换let obj = JSON.parse(localStorage.getItem(‘key’));2. VuexVuex是Vue的一个中央数据管理工具,提供了一种全局存储中心,可以保存组件的数据,并且能够获取,更新和响应式变化。除非你做一个很小的应用,还是强力建议你使用Vuex来管理你的数据和状态。最近在开发一个Vueapp应用,碰到个需求,就是,我想要一些数据能够存在localStorage中,无论什么时候更新的时候,这就意味着,当用户关闭它们的浏览器或者电脑的时候,然后重新打开页面,数据依然能够显示在页面上。例如电商应用中,当用户添加商品到购物车的时候,页面关闭再打开是,购物车的物品依然能够展示在页面上展示。2.1 初始化store一个store实例的典型配置// 初始化 storeconst store new Vuex.Store({ state: { count: 1 }, mutations: {}, getters: {}});2.2 存储数据我们希望无论state对象任何时候更新,数据都能够缓存下来,我们可以在actions提交mutation来改变state中的数据。// Subscribe store.subscribe((mutation, state) => { // 保存数据对象为JSON字符串 localStorage.setItem(‘store’, JSON.stringify(state));});2.3 获取数据创建一个方法来获取`localStorage保存的数据,首先需要检查数据是否已经存在const store new Vuex.Store({ state: { count: 1 }, mutations: { initialiseStore(state) { // 检查ID是否存在 if(localStorage.getItem(‘store’)) { } } }, getters: {}});如果数据存在的话我们需要使用replaceState来替换数据。const store new Vuex.Store({ state: { count: 1 }, mutations: { initialiseStore(state) { // 检查ID是否存在 if(localStorage.getItem(‘store’)) { // 用存储的数据替换掉state 对象 this.replaceState( Object.assign(state, JSON.parse(localStorage.getItem(‘store’))) ); } } }, getters: {}});最后一步就是当实例创建的时候提交相应的mutation,我们想要在实例创建的最开始就执行,所以,我们选择在beforeCreate的时候触发。new Vue({ el: ‘#app’, store, beforeCreate() { this.$store.commit(‘initialiseStore’); }});3. 缓存的有效性在最近的一个项目中使用它时,数据的变化并没有引起相应的变化。 这是因为在不清除localStorage的情况下改变了store的结构。 添加值的时候是正常的,但重命名,编辑或更改值时,对应值的类型没有改变。当使用semver进行版本控制的话,可以调用版本号来检查store是否是最新版本。如果在加载时,用户与我的应用版本号相同,则进行缓存处理,否则清除缓存并加载新版本。注意: 会清除整个缓存,因此如果您依赖它来获取用户首选项或类似内容,您可能偏向选择性删除。第一步是加载我们的版本号。 我们存储在package.json中,因此,使用ES6的话很简单:import {version} from ‘./package.json’;但是,如果从git标记缓存您的内容或将其作为变量放在某处,它需要与存储分开访问,以便缓存不会完全覆盖它。接下来,在store中创建一个空字符串,以便在验证后保存该版本号。state: { // Cache version version: ‘’, count: 1},我们现在可以更新initialiseStore,以使用缓存中的版本号检查版本,并根据返回结果执行不同操作initialiseStore(state) { // 检查 store 是否存在 if(localStorage.getItem(‘store’)) { let store = JSON.parse(localStorage.getItem(‘store’)); // 检查存储的版本号是否与当前相同,不同的话加载缓存中的版本 if(store.version == version) { this.replaceState( Object.assign(state, store) ); } else { state.version = version; } }}4. 选择性缓存当只从store中缓存一些元素,可以通过在订阅功能中创建一个新对象并存储它来实现。我们已经在store加载时将缓存与当前存储状态合并,因此不需要更改。store.subscribe((mutation, state) => { let store = { version: state.version, count: 1 }; localStorage.setItem(‘store’, JSON.stringify(store));});参考Vue: Using localStorage with Vuex storeawesome-vue ...

September 3, 2018 · 1 min · jiezi

Vue.js状态管理模式 Vuex

uex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。安装、使用 vuex首先我们在 vue.js 2.0 开发环境中安装 vuex :npm install vuex –save然后 , 在 main.js 中加入 :import vuex from ‘vuex’Vue.use(vuex);const store = new vuex.Store({//store对象 state:{ show:false, count:0 }})再然后 , 在实例化 Vue对象时加入 store 对象 :new Vue({ el: ‘#app’, router, store,//使用store template: ‘<App/>’, components: { App }})现在,你可以通过 store.state 来获取状态对象,以及通过 store.commit 方法触发状态变更:store.commit(‘increment’)console.log(store.state.count) // -> 1State在 Vue 组件中获得 Vuex 状态从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:// 创建一个 Counter 组件const Counter = { template: &lt;div&gt;{{ count }}&lt;/div&gt;, computed: { count () { return this.$store.state.count } }}mapState 辅助函数当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性:// 在单独构建的版本中辅助函数为 Vuex.mapStateimport { mapState } from ‘vuex’export default { // … computed: mapState({ // 箭头函数可使代码更简练 count: state => state.count, // 传字符串参数 ‘count’ 等同于 state =&gt; state.count countAlias: ‘count’, // 为了能够使用 this 获取局部状态,必须使用常规函数 countPlusLocalState (state) { return state.count + this.localCount } })}当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组:computed: mapState([ // 映射 this.count 为 store.state.count ‘count’])Gettergetters 和 vue 中的 computed 类似 , 都是用来计算 state 然后生成新的数据 ( 状态 ) 的,就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。Getter 接受 state 作为其第一个参数: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 对象,你可以以属性的形式访问这些值:store.getters.doneTodos // -> [{ id: 1, text: ‘…’, done: true }]Getter 也可以接受其他 getter 作为第二个参数:getters: { // … doneTodosCount: (state, getters) => { return getters.doneTodos.length }}store.getters.doneTodosCount // -> 1组件中使用:computed: { doneTodosCount () { return this.$store.getters.doneTodosCount }}注意,getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的。通过方法访问通过方法访问你也可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用:getters: { // … getTodoById: (state) => (id) => { return state.todos.find(todo => todo.id === id) }}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’, // … ]) }}如果你想将一个 getter 属性另取一个名字,使用对象形式:mapGetters({ // 把 this.doneCount 映射为 this.$store.getters.doneTodosCount doneCount: ‘doneTodosCount’})Mutation更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。注册:const store = new Vuex.Store({ state: { count: 1 }, mutations: { increment (state) { // 变更状态 state.count++ } }})调用:store.commit(‘increment’)提交载荷(Payload)你可以向 store.commit 传入额外的参数,即 mutation 的 载荷(payload):// …mutations: { increment (state, n) { state.count += n }}store.commit(‘increment’, 10)如果提交多个参数,必须使用对象的形式进行提交// …mutations: { increment (state, payload) { state.count += payload.amount }}store.commit(‘increment’, { amount: 10})注:mutations里的操作必须是同步的;ActionAction 类似于 mutation,不同在于:Action 提交的是 mutation,而不是直接变更状态。Action 可以包含任意异步操作。const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit(‘increment’) } }})Action 通过 store.dispatch 方法触发:store.dispatch(‘increment’)在 action 内部执行异步操作:actions: { incrementAsync ({ commit }) { setTimeout(() => { commit(‘increment’) }, 1000) }}对象形式传参:// 以载荷形式分发store.dispatch(‘incrementAsync’, { amount: 10}) ...

September 2, 2018 · 2 min · jiezi