关于vuex:vue3复合式写法中监听Pinia和vuex状态的变化

vue3中,pinia如果应用选项式进行状态治理,则和vue2中应用vuex的办法一样,间接通过mapState和mapActions等办法放到组件外部即可。 vue3中复合式的话组件外部怎么监听呢?应用Vue3中的API watchEffect办法, <script setup> import {watchEffect} from 'vue' ... watchEffect(() => { // 监听pinia或者vuex变动当前得解决放在该处 }) ...</script>

September 27, 2023 · 1 min · jiezi

关于vuex:P34-vuex-状态管理

App.vue <script>import store from "./store";import Home from "./views/Home.vue";export default { provide:{ store, }, components:{ Home, }}</script><template> <Home /></template>Home.vue <template> <div> <div>{{store.state.msg}}</div> <button @click="updateMsg">扭转msg</button> </div></template><script>export default { name: "Home", inject: ['store'], methods: { updateMsg: function () { this.store.updateMsg(); } },}</script><style scoped></style>index.js //实现响应式//集中管理//如何在app组件中通过provide提供// ref ractive --> 对象中存储的状态 msg,gae,conuter等import {reactive} from 'vue'const store={ state:reactive({ msg:"hellokugou" }), updateMsg:function (){ this.state.msg='你好,类好呀' }}export default store

February 1, 2023 · 1 min · jiezi

关于vuex:vuex数据持久化处理

次要利用插件vuex-persistedstate import Vue from "vue";import Vuex from "vuex";import storeSidebar from "./storeSidebar";import storeConsole from "./storeConsole";import storeLogin from "./storeLogin";import storeUrl from "./storeUrl";import storeUserFliters from "./storeUserFliters";import storeRoleFliters from "./storeRoleFliters";import storeUser from "./storeUser";import storeRole from "./storeRole";import storeWidget from "./storeWidget";import storeOrgan from "./storeOrgan";import storeUserImage from "./storeUserImage";import CreatePersistedState from "vuex-persistedstate";Vue.use(Vuex);export default new Vuex.Store({ state: {}, mutations: {}, actions: {}, modules: { storeSidebar, storeConsole, storeLogin, storeUrl, storeUserFliters, storeUser, storeWidget, storeRoleFliters, storeRole, storeOrgan, storeUserImage }, plugins: [ CreatePersistedState({ reducer(val) { return { assessmentData: val.storeLogin, storeSidebar: val.storeSidebar, storeUserImage: { userImage: val.storeUserImage.userImage }, storeUrl: { url: val.storeUrl.url, MenuList: val.storeUrl.MenuList, RelationList: val.storeUrl.RelationList, appList: val.storeUrl.appList } }; } }) ]});

August 17, 2022 · 1 min · jiezi

关于vuex:vuex多种获取数据和调用方法的总结

1.获取state数据1.一般获取:在computed属性中以一个函数的办法返回state中的数据computed:{cont(){rteurn this.$store.state.num}}2.胡子语法中获取state数据:<p>{{$store.state.num}}</p>3.函数辅助中获取state数据:在...mapstate(中传入一个字符串数组)...mapstate(['num'])4.modules拆分的模块,对象包着,键值对形式,值是箭头函数形式...mapstate({num:state=>state.num})2.调用mutation办法1.一般调用store.commit()//在组件中store.commit('办法名')2.函数辅助的调用,在...mapmutations中传入一个字符串办法名数组//1.没有传参...mapmutation(['add'])//2.如果须要传参在事件中this.add(要传的参数)3.modules拆分进去的模块化中调用,对象包着,键值对形式...mapmutations({属性名:'文件夹名/办法名'})//如果要传参,在事件中this.属性名(要传的值)3.调用actions办法1.一般调用通过$store.dispatch()办法//在vuex中mutations:{getadd(state,payoad){state.num=payoad}}actios只是异步提交mutations办法actions:{add(context,payoad){//异步代码settimeout(()=>{//提交mutations,通过commitcomtext.commit('mutations办法名',payoad)},1000)}}//在组件中调用this.$store.dispatch('异步办法名',要传的值)2.函数辅助调用,给...mapactions传入一个字符串办法名数组//1.没有传参...mapactions(['异步办法名'])//2.如果要传参,在事件中调用this.异步办法名(要传的参数)3.modules拆分进去的模块,对象包着,以键值对形式...mapActions({属性名:'文件夹名/办法名'})//如果要传参,在事件中this.属性名(要传的值)

May 15, 2022 · 1 min · jiezi

关于vuex:uniapp使用vuex管理登录状态

小程序登录后应用vuex来治理登录状态和退出,小程序登录之后将后端发送过去的token存储到本地,而后应用vuex读取并且扭转登录状态。 import vue from 'vue'import vuex from 'vuex'Vue.use(vuex)export default new Vue.store({ state:{ //登录状态 loginStatus: flase, //存储token token: false }, mutations:{ //登录胜利扭转状态和存储token login(state,userToken){ state.loginStatus = true, state.token = userToken, uni.setStorageSync('userToken',JSON.stringify(userToken)) }, //退出操作:更改状态,删除token logout(state){ state.loginStatue = false, token = false, uni.removeStorageSync('userToken) } }, actions:{ //再定义个初始化登录的办法,放弃登录状态 initUser({state}){ let userToken = uni.getStorageSync('userToken') if(userToken){ state.loginStatus = true, state.token = JSON.parse(userToken) } } }})这样咱们就能够在登录页面间接调用这个办法和状态了!

April 25, 2022 · 1 min · jiezi

关于vuex:源码vuex402

之前看vuex 的 3.x 版本源码,当初看下4.x版本源码有哪些不同?还是针对外围源码 Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式 + 库。它采纳集中式存储管理利用的所有组件的状态,并以相应的规定保障状态以一种可预测的形式发生变化。 所以跟3.x版本, 官网示例代码import { createApp } from 'vue'import { createStore } from 'vuex'// 创立一个新的 store 实例const store = createStore({ state () { return { count: 0 } }, mutations: { increment (state) { state.count++ } }})const app = createApp({ /* 根组件 */ })// 将 store 实例作为插件装置app.use(store)createStoreexport function createStore (options) { return new Store(options)}createStore函数很简略,间接返回一个Sotre示例。 Sotre 类app.use会调用传入参数对象的install函数,所以先来剖析install函数 install函数export const storeKey = 'store'install (app, injectKey) { // 设置sotre实例到利用范畴中的所有组件 app.provide(injectKey || storeKey, this) // 增加$store属性作为全局的property,相当于Vue.prototype.$store app.config.globalProperties.$store = this // 疏忽 const useDevtools = this._devtools !== undefined ? this._devtools : __DEV__ || __VUE_PROD_DEVTOOLS__ if (useDevtools) { addDevtools(app, this) }}install函数首先应用Vue的依赖注入provide来挂载sotre实例,为什么要这么做?因为在setup办法中无法访问this。 ...

March 16, 2022 · 2 min · jiezi

关于vuex:源码vuex363

install 函数let Vue;export function install(_Vue) { // 防止反复加载 if (Vue && _Vue === Vue) { if (__DEV__) { console.error( "[vuex] already installed. Vue.use(Vuex) should be called only once." ); } return; } Vue = _Vue; applyMixin(Vue);}

March 7, 2022 · 1 min · jiezi

关于vuex:vuex模块化使用

vuex主动引入modlues模块在vuex模块化开发中。如果多个modules的应用,每次须要import导入有些麻烦。咱们能够应用主动导入的形式。 废话不多间接上干货! 文件夹构造|- store |- index.js //入口文件 |- modules //文件夹 |- app.jsindex.js 入口文件代码import Vue from 'vue'import Vuex from 'vuex'import getters from './getters'Vue.use(Vuex)// you do not need `import app from './modules/app'`// it will auto require all vuex module from modules fileconst modulesFiles = require.context('./modules', true, /\.js$/)const modules = modulesFiles.keys().reduce((modules, modulePath) => { // set './app.js' => 'app' const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1') const value = modulesFiles(modulePath) modules[moduleName] = value.default return modules}, {})export default new Vuex.Store({ getters, modules})app.js 编写形式const state = {}const mutations = { }const actions = { }export default { namespaced: true, state, mutations, actions}此时退出一个新需要,开发过程中vuex数据刷新就会重置,所以有些数据咱们是缓存在浏览器。 那咱们怎么将浏览器的缓存主动导入在vuex中呢? ...

March 2, 2022 · 1 min · jiezi

关于vuex:VuexVueRouter

vuex本节vuex须要把握,state、getters、mutations、actions、module的含意、定义方法、调用办法 state全局状态 const count = computed(()=>store.state.count) //应用computed为其保留响应式getters从state中派生出的状态,相似于vue中的计算属性computed 定义:接管state、getters、rootState、rootGetters参数(留神参数有程序)、且可通过属性或办法的形式拜访 getters: { doneTodosCount: (state, getters, rootState, rootGetters) => { //通过属性拜访,参数有程序 return getters.doneTodos.length }, getTodoById: (state, getters, rootState, rootGetters) => (id) => {//通过办法拜访 return state.todos.find(todo => todo.id === id) }}调用: store.getters.doneTodosCount //通过属性拜访store.getters.getTodoById(2) //通过办法拜访mutations扭转state全局状态值的惟一办法,举荐应用常量代替mutation事件类型 mutation必须是同步函数:devtools工具会记录mutation的日记,捕捉到每一个mutation办法执行时的前一状态和后一状态的快照,如果mutation执行异步操作,就无奈追踪到数据变动 定义:接管state、payload参数,必须是同步函数 mutations: { increment (state, payload) { state.count++ }}调用: store.commit( 'xx', {params_1:xx,params_2:xx} ) //调用办法1store.commit({ type:'xx', params_1:xx, params_2:xx }) //调用办法2actionsaction提交的是mutation,而不是间接变更状态,action能够蕴含异步操作 定义:可接管context、payload参数,其中context可解构为state、rootState、getters、rootGetters、commit、dispatch actions: { actionA ({ state,rootState,getters,rootGetters,commit,dispatch }, payload) { return new Promise((resolve, reject) => { setTimeout(() => { commit('someMutation') resolve() }, 1000) }) }}调用: ...

January 29, 2022 · 1 min · jiezi

关于vuex:vue2vuex

vuexvuex 是一个专门为vue.js利用程序开发的状态管理模式。 vuex中,有默认的五种根本的对象: state:存储状态(变量)getters:对数据获取之前的再次编译,能够了解为state的计算属性。咱们在组件中应用 $sotre.getters.fun()mutations:批改状态,并且是同步的。在组件中应用$store.commit('',params)。这个和咱们组件中的自定义事件相似。actions:异步操作。在组件中应用是$store.dispath('')modules:store的子模块,为了开发大型项目,不便状态治理而应用的。这里咱们就不解释了,用起来和下面的一样。装置应用装置vuex npm install vuex --savevuex调用新建store/store.js文件,引入vuex import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)const state = { count: 1}const mutations = { increment (state) { state.count++ }}export default new Vuex.Store({ state, mutations})main.js引入store import store from './store/store'new Vue({ el: '#app', router, store, components: { App }, template: '<App/>'})页面调用count状态 {{ $store.state.count }}用mutations和actions 持续操作store状态 <button @click="add()">add</button>methods: { add () { this.$store.commit('increment') //一般提交封装 } }mutations携带参数<button @click="add(10)">add</button> methods: { add (count) { // this.$store.commit('increment',count) this.$store.commit({ //对象提交封装 type:'increment', count }) } }store.js文件:const mutations = { increment (state,count) { state.counter+=count }} //对象提交count更改 payload increment (state) { state.counter+=payload.count}actions异步操作const actions = { // 异步操作 acincrement (context) { state.count++ }应用dispath来触发 ...

October 27, 2021 · 1 min · jiezi

关于vuex:两种方式解决页面刷新vuex中数据丢失问题详细讲解

问题形容首先,对于 页面刷新vuex中数据失落问题,其实换种形式去形容就是:页面刷新vuex中的数据又初始化问题 集体愚见:vuex的数据并不是弄丢了,而是初始化了,回到初始值,回到原点了 对于vuex的用法本篇文章不赘述,详情能够看之前的文章:https://segmentfault.com/a/11...vuex能够了解为是一个公共的空间,所有的组件都能够共享这个空间的状态数据,大家都能够(批改or读取)这个空间的数据,然而,这个空间的数据状态是有初始值的,举例页面上的菜单数据信息,因为菜单信息须要存储在vuex中,作为全局的共用共享的数据。 state仓库中有一个menuArr,初始值为一个空数组,这个空数组寄存的就是后端传递过去的动静菜单的数组数据,对于动静菜单能够看上一篇文章:https://segmentfault.com/a/11...拜访我的项目初始页面为登录页,用户登录胜利当前,会发申请获取后端返回的动静菜单的数据,而后把这个数据寄存在vuex中的state中的menuArr外面,同时进行页面跳转到我的项目首页这时,页面上的左侧菜单栏组件读取vuex中的menuArr数据并渲染进去,用户就能够失常操作了。然而!当用户一刷新的时候(比方在首页刷新),路由不会变,还是在首页。然而:无论是 .vue组件中的data中的数据,还是vuex中的state中的数据,都会回归到初始状态所以vuex中的menuArr数组又会变成空数组,又因为vue是响应式的,当menuArr为空数组的时候,左侧菜单栏组件就渲染不进去货色,所以就没了,所以看着就像:页面刷新数据失落剖析分明了问题产生的起因,接下来就是想方法去解决。 解决方案一 本地存储一份login页面在login.vue组件中的登录中咱们去触发action,申请的接口能够写在action外面,也能够写在组件外面。这里因为须要复用action,所以申请我就写在action外面了。 /* login.vue页面 */<template> <div class="loginBox"> <el-button type="primary" @click="loginIn">点击登录</el-button> </div></template><script>export default { name: "CodeLogin", methods: { loginIn() { this.$store.dispatch("naviBar/getMenu") // 告诉action去发申请 this.$router.push({ path: "/" }) // 跳转到首页 }, },};</script>vuex中的naviBar模块/* vuex中的naviBar模块 */// 为了发申请,须要有一个vue实例import Vue from 'vue'const that = new Vue()/* vuex数据刷新失落,解决方案一,本地存一份*/const naviBar = { state: { menuArr: JSON.parse(sessionStorage.getItem('menuArr')) ? JSON.parse(sessionStorage.getItem('menuArr')) : [] }, mutations: { setMenu(state, payload) { state.menuArr = payload }, }, actions: { async getMenu({ commit }, menu) { let res = await that.$api.getMenuData() console.log('菜单数据', res); sessionStorage.setItem('menuArr', JSON.stringify(res.data)) // 本地存储一份 commit("setMenu", res.data) // 提交mutation }, }, namespaced: true}export default naviBar在须要应用vuex中的数据的中央,也就是home.vue首页的中央,间接应用计算属性取到相应vuex数据,在html中应用即可。computed: { menuArr(){ return this.$store.state.naviBar.menuArr } } ...

October 19, 2021 · 1 min · jiezi

关于vuex:vuex-的使用

vuex 的简介vuex 是全局状态管理器,在 state 中定义一个全局变量,能够在任何一个组件中进行获取、批改,并且能够将批改的内容同步到全局vuex 的装置npm install vuex --savevuex 的配置我的项目中新建一个名为 store 的文件夹,用来寄存所有配置文件或设置全局变量的文件 import { createStore } from 'vuex'export default createStore({ // 要设置的全局拜访的state对象 state: { count: 0 }, mutations: { // 进行数据更新,扭转数据状态 countType(state, action) { state.count = state.count + action.addNum } }, actions: { // 执行动作,将数据发散到须要的地位 addCount(context) { let action = { addNum: 20 } context.commit('countType', action) } }, getters: { // 获取到最终的数据后果 getCount(state) { console.log('getters-store中获取到了state', state); return state.count } }})vuex 的全局注入入口文件(个别为 main.js)中引入 store,而后全局注入import { createApp } from 'vue'import App from './App.vue'import store from './store'const app = createApp(App)app.use(store)app.mount('#app')vuex 的应用扭转全局 state 中的变量,这里用的是 vue3 的写法<template> <div id="useVuex"> <div class="autoMsg">{{ data.count }}</div> <div class="button"> <el-button type="warning" plain @click="add" >触发vuex扭转的事件</el-button > </div> <div class="result scroll"></div> </div></template><script>import { reactive } from "vue";import { useStore } from "vuex";export default { nameL: "useVuex", setup() { let store = useStore(); let data = reactive({ count: 0, }); let add = () => { // 点击的时候,被动去触发store的事件 store.dispatch("addCount"); // store中的事件扭转全局的值,赋给count data.count = store.getters.getCount; }; return { data, add, }; },};获取全局变量,在另一个组件中获取<template> <div id="transDef"> <div class="autoMsg">{{ data.count }}</div> <div class="button"></div> <div class="result scroll"></div> </div></template><script>import { reactive } from "vue";import { useStore } from "vuex";export default { nameL: "transDef", setup() { let store = useStore(); let data = reactive({ count: 0, }); data.count = store.getters.getCount; return { data, }; },};</script>

September 15, 2021 · 1 min · jiezi

关于vuex:增强Vuex-4x的typescript-类型智能提示与检查

vuex 4.x TS加强更好的反对智能提醒及TS查看,在不影响已有TS反对的性能状况下, 加强 state, getters 有限层级属性提醒,并反对只读校验; 加强commit、dispache办法感知所有操作类型名称并对载荷参数查看,类型名称反对namespaced配置进行拼接。 装置$ yarn add vuex-ts-enhanced应用import { createStore} from 'vuex'import { ExCreateStore } from 'vuex-ts-enhanced'class State { count: number = 1}export const store = (createStore as ExCreateStore)({ state: new State() ...})或者应用笼罩申明形式, 在你的我的项目文件夹中增加一个d.ts文件: // vuex.d.tsdeclare module 'vuex' { export { createStore } from 'vuex-ts-enhanced'}这样就能够不改变任何原有的Vuex应用办法。 不反对的操作: 不反对对象形式分法或提交,因为没有限度载荷必须为对象类型;不反对在带命名空间的模块注册全局 action,不举荐这种用法;不反对动静注册的模块, 须要应用 (store.dispatch as any)('doSomething') 的形式来跳过查看;不兼容的应用办法 createStore<State>({...}) 无需手动指定<State>,默认将会主动从 state 选项中推断;当须要自定义类型时,请应用 class 进行定义并设置初始值,而后在state配置项中创立一个实例;class State { name = '' count = 1 list?:string[] = []}const store = createStore({ state: new State(), ...}全局类型补充 将 store 装置到 Vue 利用时,会挂载this.$store属性,同时将 store 注入为利用级依赖,在未指定 InjectionKey 时将应用 "store" 作为默认 key, 因而咱们能够在组合式 API 中应用inject('store')来拿到 store 实例,然而却无奈感知返回的数据类型,为此咱们能够应用上面的形式给 store 进行类型补充: ...

September 14, 2021 · 1 min · jiezi

关于vuex:简单的vuex实现

实现一个vuex插件pvuex 初始化: Store申明、install实现,vuex.js: let Vue;// 定义install办法 实现赋值与注册function install(_Vue) { Vue = _Vue; Vue.mixin({ beforeCreate() { if (this.$options.store) { Vue.prototype.$store = this.$options.store; } } });}// 定义sotre类,治理响应式状态 stateclass Store { constructor(options = {}) { this._vm = new Vue({ data: { $$state:options.state } }); } get state() { return this._vm._data.$$state } set state(v) { return v } }function install(_Vue) { Vue = _Vue; Vue.mixin({ beforeCreate() { if (this.$options.store) { Vue.prototype.$store = this.$options.store; } } }); } export default { Store, install };实现 commit :依据用户传入type获取并执行对应 mutation ...

August 24, 2021 · 1 min · jiezi

关于vuex:vuex中更新对象或数组的值页面不更新的问题

在Vuex中,如果store中数据是数组或者是对象,操作之后,vuex 数值曾经扭转了,但页面展现的对应数值却没有扭转。 相似的状况,大部分呈现在这几个场景 state: { obj: { a:1, }, arr: [0,1,2]}1. 扭转数组的某一项state.arr[0] = 1;2. 对象赋值新属性state.obj.b = 2;...导致起因 Vue2 Object.defineProperty的自身的机制问题,拓展https://cn.vuejs.org/v2/guide... 解决方案 优先应用Vue.set,应用JSON.parse(JSON.stringify(state.obj)触发对象更新 或者 state.arr.push() 触发数组更新 也能够; import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({ state: { obj: { a:1, }, arr: [0,1,2] }, mutations: { SET_STATE_DATA(state){ // state.obj.b= '233'; // 谬误 页面不会更新 // JSON.parse(JSON.stringify(state.obj) //不举荐此办法触发更新 显得很LOWB Vue.set(state.obj,'b','233') // 正确的办法 // state.arr[0] = 233; // 谬误 页面不会更新 // state.arr.push() //不举荐此办法触发更新 Vue.set(state.arr,0,233) // 正确的办法 } }, actions: { }})

May 31, 2021 · 1 min · jiezi

关于vuex:简单实现Vuex

github,blogVuexVuex集中式存储管理利用的所有组件的状态,并以相应的规定保障状态以可预测的形式发生变化。 装置Vuexvue add vuex外围概念state:状态、数据mutations:更改状态的函数action:异步操作store:蕴含以上概念的容器状态 - statestate保留利用状态 export default new Vuex.Store({ state: { counter: 0 }})<h1> {{$store.state.counter}}</h1>状态变更 - mutationsmutations用于批改状态 export default new Vuex.Store({ mutations:{ add(state){ state.counter++ } }})<h1 @click="$store.commit('add')"> {{$store.state.counter}}</h1>派生状态 - getters从state派生进去新状态,相似计算属性 export default new Vuex.Store({ getters:{ doubleCounter(state){ return state.counter * 2; } }})<h1> {{$store.getters.doubleCounter}}</h1>动作 - actions增加业务逻辑,相似于controller export default new Vuex.Store({ actions:{ add({commit}){ setTimeout(() => commit('add'), 1000); } }})<h1 @tap="$store.dispatch('add')"> {{$store.state.counter}}</h1>Vuex原理解析任务分析实现插件 实现Store类 维持一个响应式状态state实现commit()实现dispatch()实现getters挂载$store创立新的插件在Vue2.x我的项目中的src门路下,复制一份store文件,重命名为ou-store。 而后在ou-store门路下新建一个ou-vuex.js文件,并将index.js文件中的Vuex引入改为ou-vuex.js。 import Vuex from './ou-vuex'同时将main.js中的router引入也批改一下。 import router from './ou-vuex'创立vue的插件回头看一下store/index.js,首先是应用Vue.use()注册了Vuex,而后再实例化了Vuex.Store这个类,因而Vuex这个对象里含有一个install办法以及一个Store的类。 ...

April 27, 2021 · 2 min · jiezi

关于vuex:vuex-全面解析看完即上手

vuex是采纳集中式治理组件依赖的共享数据的一个工具,能够解决不同组件数据共享问题。批改state状态必须通过mutationsmutations只能执行同步代码,相似ajax,定时器之类的代码不能在mutations中执行执行异步代码,要通过actions,而后将数据提交给mutations才能够实现state的状态即共享数据能够在组件中援用组件中能够调用actionvuex根底-初始化性能建设一个新的脚手架我的项目, 在我的项目中利用vuex$ vue create demo开始vuex的初始化建设,抉择模式时,抉择默认模式初始化: 第一步:npm i vuex --save => 装置到运行时依赖 => 我的项目上线之后仍然应用的依赖 ,开发时依赖 => 开发调试时应用开发时依赖 就是开开发的时候,须要的依赖,运行时依赖,我的项目上线运行时仍然须要的第二步: 在main.js中 import Vuex from 'vuex'第三步:在main.js中 Vue.use(Vuex) => 调用了 vuex中的 一个install办法第四步:const store = new Vuex.Store({...配置项})第五步:在根实例配置 store 选项指向 store 实例对象import Vue from 'vue'import Vuex from 'vuex'Vue.use(vuex)const store = new Vuex.Store({})new Vue({ el: '#app', store})vuex根底-statestate是搁置所有公共状态的属性,如果你有一个公共状态数据 , 你只须要定义在 state对象中 定义state // 初始化vuex对象const store = new Vuex.Store({ state: { // 治理数据 count: 0 }})如何在组件中获取count?原始模式- 插值表达式 App.vue 组件中能够应用 this.$store 获取到vuex中的store对象实例,可通过state属性属性获取count, 如下 ...

April 27, 2021 · 3 min · jiezi

关于vuex:记录vuex

这里记录一下vuex的应用和vuex的繁难实现首先创立对应的store目录和对应的入口index.js import Vue from 'vue'import Vuex from 'vuex'import products from './modules/products'import cart from './modules/cart'Vue.use(Vuex)export default new Vuex.Store({ strict: process.env.NODE_ENV !== 'production', state: { count: 0, msg: 'Hello Vuex' }, getters: { reverseMsg (state) { return state.msg.split('').reverse().join('') } }, mutations: { increate (state, payload) { state.count += payload } }, actions: { increateAsync (context, payload) { setTimeout(() => { context.commit('increate', payload) }, 2000) } }, modules: { products, cart }})首先注册vuex的插件开发阶段开启strict严格模式配置初始的state配置对应的getters配置对应的mutations 无副作用的函数更新配置actions在此做异步解决最初配置模块模块中配置: ...

January 6, 2021 · 1 min · jiezi

关于vuex:关于是否使用vuex的一张图-关于怎么用protals存数据

November 19, 2020 · 0 min · jiezi

关于vuex:Vuex的使用

新建下列目录构造 index.js文件 import Vue from 'vue'import Vuex from 'vuex'import mutations from './mutations'import actions from './action'Vue.use(Vuex);const state = { username: '',//登录用户名}export default new Vuex.Store({ state, mutations, actions})actions.js文件 /** * 商城Vuex-actions*/export default { saveUserName(context,username){ context.commit('saveUserName', username) }}mutations.js文件 /** * 商城Vuex-mutations*/export default{ saveUserName(state, username) { state.username = username; }}在main.js中引入 import store from './store'new Vue({ store, render: h => h(App),}).$mount('#app')存储 //第一种办法let username = 'jack'this.$store.dispatch('saveUserName',username)//第二种办法import {mapActions} from 'vuex'methods:{ ...mapActions(['saveUserName']), setName(){ let username = 'jack' this.saveUserName(username); }}获取 ...

October 20, 2020 · 1 min · jiezi

关于vuex:手写vuex

vuexvuex 集中式存储管理利用的所有组件的状态,并以相应的规定保障状态以可预测的形式发生变化 应用vuex(1) 导入vuex vue add vuex(2) 外围概念state: 状态数据mutations: 批改状态的函数action: 异步操作 (3) 状态store -- 保留利用状态 export default new Vuex.store({ state: { counter: 0 }})(4) mutation -- 状态变更 export default new Vuex.store({ state: { counter: 0 }, mutations: { add(state){ state.counter++ } }})(5) getters -- 从state中派生出的状态,相似计算属性 export default new Vuex.store({ state: { counter: 0 }, mutations: { add(state){ state.counter++ } }, getters: { doubleCounter(state) { return state.counter * 2 } }})(6) actions -- 增加业务逻辑 ...

October 9, 2020 · 3 min · jiezi

关于vuex:Vuex模块开启命名空间map

转载出处:https://www.cnblogs.com/sea-b...

July 24, 2020 · 1 min · jiezi

关于vuex:手写vuex原理解析

应用vue过程中难免会用到vuex,然而很多时候咱们只晓得如何应用它但却不明确外部运行机制,上面咱们来简略手写一下vuex的实现过程。 简介Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式。其集中式的存储了利用所有组件的状态,并且制订了绝对应的规定以便保障组件状态能够朝着可预测的方向发生变化。 首先须要理解vuex的几个外围概念: State 繁多状态树,惟一数据源Mutation 状态更改函数Action 蕴含任意异步操作Getter state中派生出一些状态,相似计算属性Module store模块宰割机制,store 蕴含以上概念的容器对这些概念不分明的能够观看vuex官网进行进一步理解:https://vuex.vuejs.org/zh/ vuex原理解析首先vuex是一个插件,咱们须要去申明一个store类,还要有个install办法去挂载$store。store的具体实现过程: 1、创立响应式的state,保留mutations,actions与getters。2、实现commit依据用户传入type执行对应mutation。3、实现dispatch依据传入的type生成对应的action,同时传递数据。4、实现getters,依照getters定义对state派生数据。首先设计vuex根本数据: import Vue from 'vue'import Vuex from './vuex'Vue.use(Vuex)export default new Vuex.Store({ state: { counter: 0, }, mutations: { add(state) { state.counter++ } }, actions: { // 参数怎么来的? add({ commit }) { // 业务逻辑组合或者异步 setTimeout(() => { commit('add') }, 1000); } }, getters: { doubleCounter: state => { return state.couter*2; }, }, modules: { }})1.对store类的申明,install办法实现。 ...

July 22, 2020 · 2 min · jiezi

Vuex-modules-模式下-mapStatemapMutations-的操作实例

当我们使用 Vuex 实现全局状态维护时,可能需要将状态值划分多个模块,比如一些 root 级的用户登录状态,token,用户级的用户信息,购物车级的购物车信息。 下面我们实例演示下如何在多模块下使用 mapState/mapMutations。 modules 只作用于属性,属性会归属在相应的模块名的命名空间下。mutations, actions, getter 没有命名空间的限定,所以要保证全局的唯一性,否则后者会覆盖前者store/index.jsimport Vue from 'vue'import Vuex from 'vuex'import user from './user'import order from './order'Vue.use(Vuex)const store = new Vuex.Store({ modules: { user, order }, state: { hasLogin: false, token: "" }, mutations: { setHasLogin(state, hasLogin) { state.hasLogin = hasLogin }, setToken(state, token) { state.token = token } }})export default storestore/user.jsconst state = { name: "sqrtcat", age: 25}const mutations = { setUserName(state, name) { state.name = name }, setUserAge(state, age) { state.age = age }}const actions = {}const getters = {}export default { state, mutations, actions, getters}store/order.jsconst state = { name: "cart", count: 0}const mutations = { setOrderName(state, name) { state.name = name }, setOrderCount(state, count) { state.count = count }}const actions = {}const getters = {}export default { state, mutations, actions, getters}Vue 引入import Vue from 'vue'import App from './App'import store from './store'Vue.config.productionTip = falseVue.prototype.$store = store // 注入仓库const app = new Vue({ store// 注入仓库})app.$mount()index.vue<template> <view> <button class="primary" @click="setUserName('big_cat')">setUserName</button> <button class="primary" @click="setUserAge(27)">setUserAge</button> <button class="primary" @click="setOrderName('yes')">setOrderName</button> <button class="primary" @click="setHasLogin(true)">setHasLogin</button> <button class="primary" @click="setToken('tokentokentokentoken')">setToken</button> <view class=""> {{userName}} </view> <view>{{userAge}}</view> <view>{{orderName}}</view> <view>{{hasLogin}}</view> <view>{{token}}</view> </view></template><script> import { mapState, mapMutations } from "vuex" export default { data() { return {} }, computed: { // 原生 hasLogin() { return this.$store.state.hasLogin }, token() { return this.$store.state.token } // 仓库root属性 可以直接 magic 赋值 // ...mapState(["hasLogin", "token"]), // 因为 modules 下的属性使用了命名空间 所以不能使用数组方式的 magic ...mapState({ userName: state => state.user.name, userAge: state => state.user.age, orderName: state => state.order.name }), // 更多示例 ...mapState({ hasLogin(state) { return state.hasLogin }, token(state) { return state.token } }), ...mapState({ hasLogin: (state) => { return state.hasLogin }, token: (state) => { return state.token } }), }, methods: { // vuex 在使用了 modules 模式时 // mutation依然没有命名空间的概念 所以在定义 mutations 时要注意全局的唯一性 // 否则后者会覆盖前者 ...mapMutations(["setHasLogin", "setToken"]), // magic style1 ...mapMutations(["setUserName", "setUserAge", "setOrderName"]), // magic style2 ...mapMutations({ setUserName(commit, userName) { commit("setUserName", userName) }, setUserAge(commit, userAge) { commit("setUserAge", userAge) }, setOrderName(commit, orderName) { commit("setOrderName", orderName) } }), // 原生写法 setUserName(userName) { this.$store.commit("setUserName", userName) }, setUserAge(userAge) { this.$store.commit("setUserAge", userAge) }, setOrderName(orderName) { this.$store.commit("setOrderName", orderName) } } }</script>

October 17, 2019 · 2 min · jiezi

mapState-mapMutations-的使用方法

Vue 引入 Vuex// store/index.jsimport Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)const store = new Vuex.Store({ state: { account: "", password: "", age: 0 }, mutations: { account(state, account) { state.account = account; }, account(state, password) { state.password = password; }, account(state, age) { state.age = age; }, }})export default store挂载至 Vueimport Vue from 'vue'import App from './App'import store from './store'Vue.config.productionTip = falseVue.prototype.$store = storeconst app = new Vue({ store, ...App})app.$mount()页面引用 Vuex使用 mapState, mapMutations 魔术方法导入 ...

October 15, 2019 · 2 min · jiezi

Vue-开发必须知道的-36-个技巧近1W字

前言Vue 3.x 的Pre-Alpha 版本。后面应该还会有 Alpha、Beta 等版本,预计至少要等到 2020 年第一季度才有可能发布 3.0 正式版;所以应该趁还没出来加紧打好 Vue2.x 的基础;Vue基本用法很容易上手,但是有很多优化的写法你就不一定知道了,本文从列举了 36 个 vue 开发技巧;后续 Vue 3.x 出来后持续更新. 1.require.context()1.场景:如页面需要导入多个组件,原始写法: import titleCom from '@/components/home/titleCom'import bannerCom from '@/components/home/bannerCom'import cellCom from '@/components/home/cellCom'components:{titleCom,bannerCom,cellCom}2.这样就写了大量重复的代码,利用 require.context 可以写成 const path = require('path')const files = require.context('@/components/home', false, /\.vue$/)const modules = {}files.keys().forEach(key => { const name = path.basename(key, '.vue') modules[name] = files(key).default || files(key)})components:modules这样不管页面引入多少组件,都可以使用这个方法 3.API 方法 实际上是 webpack 的方法,vue 工程一般基于 webpack,所以可以使用require.context(directory,useSubdirectories,regExp)接收三个参数:directory:说明需要检索的目录useSubdirectories:是否检索子目录regExp: 匹配文件的正则表达式,一般是文件名2.watch2.1 常用用法1.场景:表格初始进来需要调查询接口 getList(),然后input 改变会重新查询 ...

October 9, 2019 · 9 min · jiezi

vue高级进阶

Vuex1.state的使用首先在src文件夹下面建一个store文件夹作为仓库store里创建一些js文件作为相对应的存储空间例如 store.js import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.store({state: { //状态管理 count: 1},getters: { //获取状态值 getCount: function(state){ return state.count * 5 }},mutations: { //操作状态 add: function(state){ state.count += 1 }},actions: { //异步操作状态 addFun: function(context){ context.commit('add') }}})在vue组件中使用 在插值表达式里 {{$store.state.count}}computed:{count(){return this.$store.state.count}}更新state的值methods: { change(){ this.$store.commit('add',args) //同步操作,可以有选择性的传参 }, asyncChange() { this.$store.dispatch('addFun') //异步操作 }}2.mapState的使用1.在.vue组件中引入,在js块中引入 import { mapState } from 'vuex'2.在.vue组件中使用,一般放在computed里可以监听到状态改变 computed:{ ...mapState([ //mapState本是一个函数,在里面写一个数组,记得加... ‘num’ , //存的数据 ‘id’ ])}或computed: { ...mapState({ num: (state)=>state.num, //这种用法可以看作为起别名 id: (state)=>state.id })}mapAction的使用正常action的使用this.$store.dispatch('function',args)mapActionimport {mapActions} from 'vuex' methods: {...mapActions(['fun1','fun2','fun3'])或...mapActions({ fun1: 'fun1', fun2: 'fun2' })}mapMutations的使用 //关于传参问题,直接在调用的地方传参正常mutation的使用this.$store.commit('function',args)mapMutations的使用import {mapMutations} from 'vuex'methods:{...mapMutations(['fun1','fun2'])或...mapMutations({ fun1: 'fun1', fun2: 'fun2'})}混入 (mixin)混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。**组件的思想主要是用来解决重复码有相似功能的代码,并使其标准化,统一化,但在前端更多是体现在界面上的视觉效果,如果要实现功能大体相同,界面需要个性化,但又不想传入过多的props怎么办呢这时mixin便有了其用武之地,可以使用相同的js逻辑,template和css自定义就好了** ...

October 8, 2019 · 1 min · jiezi

vue-数据监听

关于数据监听,vue提供了两种方式watch和computed(计算属性) watch常规用法watch:{ id(newVal,oldVal){ // do somethings ... }}深层监听// 深度监听,可监听到对象、数组的变化watch:{ obj:{ deep: true, // 监听多层对象或者数组 immediate: true, // 立即生效 handler: (newVal, oldVal) => { // do somethings ... }, }}配合生命周期// 有的时候我们会使用使用多个组件进行传值,在watch到变化的时候往往监听的组件还没有加载完成这时候就需要配合生命周期使用mounted(){ this.$watch('obj', (newVal,oldVal) => { // do somethings... }, { deep: true, immediate: true })}配合computed(计算属性)监听vuex变化watch:{ nodesList: { immediate: true, deep: true, handler (val) { // do somethings... } },},computed:{ obj() { return this.$store.state.obj },}

September 19, 2019 · 1 min · jiezi

实现一个简易版的vuex持久化工具

背景最近用uni-app开发小程序项目时,部分需要持久化的内容没法像其他vuex中的state那样调用,所以想着自己实现一下类似vuex-persistedstate插件的功能,貌似代码量也不会很大 初步思路首先想到的实现方式自然是vue的watcher模式。对需要持久化的内容进行劫持,当内容改变时,执行持久化的方法。先弄个dep和observer,直接observer需要持久化的state,并传入get和set时的回调: function dep(obj, key, options) { let data = obj[key] Object.defineProperty(obj, key, { configurable: true, get() { options.get() return data }, set(val) { if (val === data) return data = val if(getType(data)==='object') observer(data) options.set() } })}function observer(obj, options) { if (getType(obj) !== 'object') throw ('参数需为object') Object.keys(obj).forEach(key => { dep(obj, key, options) if(getType(obj[key]) === 'object') { observer(obj[key], options) } })}然而很快就发现问题,若将a={b:{c:d:{e:1}}}存入storage,操作一般是xxstorage('a',a),接下来无论是改了a.b还是a.b.c或是a.b.c.d.e,都需要重新执行xxstorage('a',a),也就是无论a的哪个后代节点变动了,重新持久化的都是整个object树,所以监测到某个根节点的后代节点变更后,需要先找到根节点,再将根节点对应的项重新持久化。接下来的第一个问题就是,如何找到变动节点的父节点。 state树的重新构造如果沿着state向下找到变动的节点,并根据找到节点的路径确认变动项,复杂度太高。如果在observer的时候,对state中的每一项增添一个指向父节点的指针,在后代节点变动时,是不是就能沿着指向父节点的指针找到相应的根节点了?为避免新增的指针被遍历到,决定采用Symbol,于是dep部分变动如下: function dep(obj, key, options) { let data = obj[key] if (getType(data)==='object') { data[Symbol.for('parent')] = obj data[Symbol.for('key')] = key } Object.defineProperty(obj, key, { configurable: true, get() { ... }, set(val) { if (val === data) return data = val if(getType(data)==='object') { data[Symbol.for('parent')] = obj data[Symbol.for('key')] = key observer(data) } ... } })}再加个可以找到根节点的方法,就可以改变对应storage项了 ...

September 11, 2019 · 3 min · jiezi

推荐一篇学习vuex的文章

https://segmentfault.com/a/11...

September 10, 2019 · 1 min · jiezi

Vuex-20-源码分析

Vuex 2.0 源码分析在一般情况之下, 我们普遍使用 global event bus 来解决全局状态共享, 组件通讯的问题, 当遇到大型应用的时候, 这种方式将使代码变得难以维护, Vuex应运而生, 接下来我将从源码的角度分析Vuex的整个实现过程. 目录结构整个Vuex的目录结构还是非常清晰地, index.js 是整个项目的入口, helpers.js 提供Vuex的辅助方法>, mixin.js 是$store注入到vue实例的方法, util.js 是一些工具函数, store.js是store类的实现 等等, 接下来就从项目入口一步步分析整个源码. 项目入口首先我们可以从index.js看起: export default { Store, install, version: '__VERSION__', mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers }可以看到, index.js就是导出了一个Vuex对象, 这里可以看到Vuex暴露的api, Store就是一个Vuex提供的状态存储类, 通常就是使用 new Vuex.Store(...)的方式, 来创建一个Vuex的实例. 接下来看, install 方法, 在store.js中; export function 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 applyMixin(Vue) }install 方法有个重复install的检测报错, 并将传入的_Vue赋值给自己定义的Vue变量, 而这个Vue变量已经变导出, 整个项目就可以使用Vue, 而不用安装Vue; ...

August 28, 2019 · 10 min · jiezi

面试被问到Vue想进一步提升那就停下来看一下吧

Vue作为最近最炙手可热的前端框架,其简单的入门方式和功能强大的API是其优点。而同时因为其API的多样性和丰富性,所以他的很多开发方式就和一切基于组件的React不同,如果没有对Vue的API(有一些甚至文档都没提到)有一个全面的了解,那么在开发和设计一个组件的时候有可能就会绕一个大圈子,所以我非常推荐各位在学习Vue的时候先要对Vue核心的所有API都有一个了解。这篇文章我会从实践出发,遇到一些知识点会顺带总结一下。 进入正题,我相信不论什么项目几乎都会有一个必不可少的功能,就是用户操作反馈、或者提醒.像这样(简单的一个demo) 其实在vue的中大型项目中,这些类似的小功能会更加丰富以及严谨,而在以Vue作为核心框架的前端项目中,因为Vue本身是一个组件化和虚拟Dom的框架,要实现一个通知组件的展示当然是非常简单的。但因为通知组件的使用特性,直接在模板当中书写组件并通过v-show或者props控制通知组件的显示显然是非常不方便的并且这样意味着你的代码结构要变,当各种各样的弹层变多的时候,我们都将其挂载到APP或者一个组件下显然不太合理,而且如果要在action或者其他非组件场景中要用到通知,那么纯组件模式的用法也无法实现。那么有没有办法即用到Vue组件化特性方便得实现一个通知组件的展现,那么我们可否用一个方法来控制弹层组件的显示和隐藏呢? 目标一实现一个简单的反馈通知,可以通过方法在组件内直接调用。比如Vue.$confirm({...obj}) 首先,我们来实现通知组件,相信这个大部分人都能写出来一个像模像样的组件,不啰嗦,直接上代码 <template> <div :class="type" class="eqc-notifier"> <i :class="iconClass" class="icon fl"/> <span>{{ msg }}</span> <!-- <span class="close fr eqf-no" @click="close"></span> --> </div></template><script>export default { name: 'Notification', props: { type: { type: String, default: '' }, msg: { type: String, default: '' } }, computed: { iconClass() { switch (this.type) { case 'success': return 'eqf-info-f' case 'fail': return 'eqf-no-f' case 'info': return 'eqf-info-f' case 'warn': return 'eqf-alert-f' } } }, mounted() { setTimeout(() => this.close(), 4000) }, methods: { close() { } }}</script><style lang="scss"> .eqc-notifier { position: fixed; top: 68px; left: 50%; height: 36px; padding-right: 10px; line-height: 36px; box-shadow: 0 0 16px 0 rgba(0, 0, 0, 0.16); border-radius: 3px; background: #fff; z-index: 100; // 层级最高 transform: translateX(-50%); animation: fade-in 0.3s; .icon { margin: 10px; font-size: 16px; } .close { margin: 8px; font-size: 20px; color: #666; transition: all 0.3s; cursor: pointer; &:hover { color: #ff296a; } } &.success { color: #1bc7b1; } &.fail { color: #ff296a; } &.info { color: #1593ff; } &.warn { color: #f89300; } &.close { animation: fade-out 0.3s; } }</style>在这里需要注意,我们定义了一个close方法,但内容是空的,虽然在模板上有用到,但是似乎没什么意义,在后面我们要扩展组件的时候我会讲到为什么要这么做。 ...

August 20, 2019 · 4 min · jiezi

8-道高频出现的-Vue-面试题及答案

前言本文讲解 8 道高频出现的 Vue 面试题及答案。 复习前端面试的知识,是为了巩固前端的基础知识,最重要的还是平时的积累!注意:文章的题与题之间用下划线分隔开,答案仅供参考。 前端硬核面试专题的完整版在此:前端硬核面试专题,包含:HTML + CSS + JS + ES6 + Webpack + Vue + React + Node + HTTPS + 数据结构与算法 + Git 。 Vue对 MVC、MVP 、MVVM 的理解 MVC 模式的意思是,软件可以分成三个部分。 视图(View):用户界面。控制器(Controller):业务逻辑。模型(Model):数据保存。各部分之间的通信方式如下。 View 传送指令到 ControllerController 完成业务逻辑后,要求 Model 改变状态Model 将新的数据发送到 View,用户得到反馈所有通信都是单向的(逆时针)。MVP 模式将 Controller 改名为 Presenter,同时改变了通信方向。 各部分之间的通信,都是双向的(顺时针)。View 与 Model 不发生联系,都通过 Presenter 传递。View 非常薄,不部署任何业务逻辑,称为 "被动视图"(Passive View),即没有任何主动性,而 Presenter非常厚,所有逻辑都部署在那里。MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。 ...

August 18, 2019 · 3 min · jiezi

vuex笔记

所有用Vuex管理的组件中都多了一个属性$store,他就是一个store对象属性: state: 注册的state对象getters: 注册的getters对象 方法: dispatch(actionName,data): 分发调用action关系图

July 14, 2019 · 1 min · jiezi

vuex的简单todolist例子

一个简单的vuex应用的小例子,一段自己的学习记录。todolist就是一个简单的输入框,一个按钮,一个文本显示区域,可以逐条进行删除。 1.在用vue-cli生成好的HelloWorld.vue文件中直接写代码,先删除所有的自带代码 <template> <div class="hello"> <input type="text"> <button>增加事项</button> <ul> <li>item</li> </ul> </div></template>要把`input`中的值在经过`button`点击后,显示在`li`中,`input`有`v-model`属性进行值的绑定,让`li`的数据是一个数组。相当于在数组中push input的值。2.在src目录下,新建一个store文件夹,创建一个index.js文件 import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)const store = new Vuex.Store({ state: { inputVal: 'lily', list: ['1', '2', '3'] }, mutations: { changeListValue(state, inputVal) { state.list.push(inputVal) state.inputVal = '' }, handleDel(state, idx) { state.list.splice(idx, 1) } }, actions: { changeListValue: ({commit}, inputVal) => { return commit('changeListValue', inputVal) }, handleDel: ({commit}, idx) => { return commit('handleDel', idx) } }})export default store3.回到HelloWorld.vue ...

July 14, 2019 · 1 min · jiezi

为什么要使用状态管理

我们平时开发的大部分项目,由于复杂度不够, 很少使用 Vuex、Redux 等状态管理库,就算引入了 Vuex 这些库,也只是当作一个全局数据引用,并非对应用状态进行管理。 但一旦页面的复杂度比较高,必然要引入状态管理,今天就聊聊我理解中的状态管理。 如果涉及到举例,由于我对Vuex更熟悉,团队内也大多比较熟悉Vue,因此会使用Vuex作例子。 到底什么时候应该使用状态管理举例几个需要用 Vuex 的例子: 例子一// Page.vue<page> <component-a /> <component-b /></page>// ComponentA.vue<div> <component-a-child /></div>比如这个例子中,<component-a-child />想和<component-b />通信,使用事件传递来解决非常麻烦。 当然也可以使用 EventBus,加一个全局的 vue 实例解决,但用 EventBus 还需要去关心事件的绑定解绑,需要手动处理事件,当这类组件,就会变得非常麻烦。 最好的解决办法就是抽象出通用的组件状态,放到 state 里面,接着通过 action/mutation 改变通用状态,而需要这些状态的组件则自己调用(mapState/mapGetter),不需要去关注组件之间的关系。 例子二// Page.vue<page><topic-list :list="list" :activity="activity" :user="user" /></page>// TopicList.vue<div> <topic-header :list="list" :activity="activity" :user="user" /> <template v-for="item in list"> <topic :list="list" :data="item" :activity="activity" :user="user" /> </template></divt这个例子里:list="list" :activity="activity" :user="user"在被不断的传递,实际里面的组件可能只需要里面的一两个属性。 当然,例子里面的代码比较简单,也可以通过合理的组件设计来解决。 但一旦碰到这种某几个状态数据不断被其子组件以及后代组件使用的状况,可以考虑使用状态管理来解耦,可能使代码更加简洁。 状态管理解决了什么 最主要是解耦,把组件与组件之间的复杂关系解耦为数据与数据的关系,组件仅作单纯的数据渲染,而且由于是单一数据源,整体上非常便于维护。以前是: 现在是: 由于单一数据源+数据不可变,带来了应用状态的快照,可以很方便的实现前进/后退以及历史记录管理。可测试性,可以分别针对视图和数据进行测试,而不是混淆在一起,导致测试难度极大。状态管理带来的新问题最主要是由于解决使得单个组件复杂度的提升,但相比整体复杂度的降低以及更高的可维护性,这点代价是完全值得。

July 13, 2019 · 1 min · jiezi

关于Vue2一些值得推荐的文章-七月初

七月初 vue2 推荐集合查看github最新的Vue weekly;请::点击::集web前端最近很火的vue2框架资源;定时更新,欢迎 Star 一下。 七月: 不在山,不在岸,采我之时七月半。!七月-银潢七月秋浪高,黄昏欲渡未成桥。(07.01~至今): 王子乔 [唐] 宋之问 王子乔,爱神仙,七月七日上宾天。白虎摇瑟凤吹笙, 乘骑云气吸日精。吸日精,长不归,遗庙今在而人非。 空望山头草,草露湿人衣。 学习vue源码我们一起写一个Vue的Loading插件吧大白话理解和初步使用vue-routervue使用总结Vue nextTick 变迁史vuex中的四大金刚提前使用Vue 3.0新特性,vue-function-api尝鲜使用vue中的混入mixin优化表单验证插件一张图教你快速玩转vue-cli3学习vue源码—mvvmvue-router 源代码全流程分析「长文」探索Angular,React,Vue的趋势比较深入理解vue响应式原理你不知道的Vue.nextTick源码系列Vue手把手带你撸项目系列之动态面包屑为vue3学点typescript(1), 体验typescript使用 Typescript 加强 Vuex 使用体验前端规范之vue 项目规范大白话理解和初步使用vuexVue 面试知识点总结Vue 项目功能实现:刷新当前页面精读《Vue3.0 Function API》Vue入门学习之技术分享-2(深入理解Vue组件)为vue3学点typescript, 基础类型和入门高级类型vuex了解一下?Vue 面试知识点总结(二)【持续更新中~】【一文学会】vue.js入门到放弃从源码解读Vue生命周期,让面试官对你刮目相看Vue入门学习之技术分享-3(Vue中的动画特效)Vue中jsx不完全应用指南vue打包后vendor.js文件过大解决方案带你了解vue计算属性的实现原理以及vuex的实现原理记录一次vue练习的填坑记录Vue2 weekly 上Why You Should Start Front-End by Learning Vue.js Integrating content management into your Vue.js projects with PrismicVue.js Amsterdam RecordingsiView UI framework 2.4Promoted - 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 forLaravel 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 projectVueCamp: Vue.js Barcamp BerlinAmendment proposal to Function-based Component API · Issue #63 · vuejs/rfcs Why every Vue developer should be excited by Quasar 1.0 – Razvan StoenescuVue's Darkest Day – Daniel ElkingtonVue2 weekly 中What does the Vue function API feel like - Abdelrahman Awad3 Key Insights from Vue’s new functional API RFC – Kevin BallVue without View - An Introduction to Renderless Components – Jason Yu How to use cookies in VuePress - Dan VegaIn Vue, When Do I Actually Need the :key Attribute and Why? — Marina MostiWhat is VueFront? - VueFrontVue.js functional components: what, why, and when? – Austin GMigrating from Vuetify to Quasar - Stanislav Valasek10 Things You Should Know Before Writing Your Next Vuejs Component - Edithson Abelard GitHub - jamesdruhan/vue-jd-tableHow To Upgrade Your VuePress Site To v1.0 - Florimond MancaUse Fragments to Avoid Obsolete GraphQL Fields in Vue.js Applications – Markus OberlehnerReading Image Sizes and Dimensions with Vue.js – Raymond CamdenFrom JSX to Vue: my favorite templating tips – briwa A beginner-friendly guide to unit testing the Vue.js application – Vladislav BulyukhinVue2 weekly 下tiptap – a renderless rich-text editor for Vue.js VueFrontVuePress 1.x Released! – ULIVZNuxtJS: From Terminal to Browser - Sébastien ChopinTriggering events from Vue Router views - Dan VegaBuild An Intersection Observer Directive In Vue - Alex ReganBuild Decoupled Vue.js Applications with Hooks - Markus OberlehnerHow to Build a Group Chat App with Vue.js - Oscar CastroGitHub - kai-oswald/vue-svg-transitionGitHub - wokes/Laravel-Vue-SPA-template更多推荐查看github最新的Vue weekly;请::点击::集web前端最近很火的vue2框架资源;定时更新,欢迎 Star 一下。 ...

July 6, 2019 · 2 min · jiezi

vuex-的动态注册方法-registerModule

Vuex(2.3.0+)可以用store.registerModule方法在进入路由时进行注册,离开路由时候销毁 actions, mutations, getters, state,在一定范围内相同名称不会被覆写 例子index.jsmodule.exports = { install(_this) { _this.$store.registerModule(['abc'], { namespaced: true, state: { rightTest: 999 }, actions: { setTest: ({commit}, val) => { commit('putTest', val) } }, mutations: { putTest: (state, val) => { state.rightTest = val; } } }) }, uninstall(_this) { _this.$store.unregisterModule(['abc']) }};调用方法时应该在创建完实例之后的钩子中,未创建实例调用会找不到 store。在install、uninstall时,传递this过去,可以在上面中直接调用。dispath 时,如果设置了命名空间,则一定要加上,我这个因为没使用较复杂的命名,注册时的名字就在命名空间那用了。 test.vueimport abc from '../../store/test';...created() { // 挂载对应的 store abc.install(this); console.log(this.$store, 'install');},destroyed() { // 销毁对应的 store abc.uninstall(this); console.info(this.$store, 'uninstall');},methods: { test(){ this.$store.dispatch('abc/setTest', Math.random()); }总结当范围内使用动态方法注册 actions 时还是比较爽的,而且在destroyed 钩子中销毁可以节省一部分资源;配置命名空间也可以避免覆盖问题,算是多一种手段吧(感觉还是应用在多模块,全局注册时用到这个);当没有父子关系时,但还需要多页面共享状态,可以用动态注册就不太方便了;(我好像还是没解决全局注册时方法过多的问题。。。) ...

July 3, 2019 · 1 min · jiezi

vuex对ts的支持太弱一个让-vuex-更好的支持-typescript-的解决方案

传统 vuex 编码让人觉得麻烦的一点就是 state、getters、mutation、dispatch 在调用时无法获得编辑器的智能提示,必须切换文件去查找。本以为用上 typescript 后这个问题可以得到解决,却发现vuex官方提供的types并没有那么强大... 在找寻了一会儿各种解决方案后,觉得都存在这样或那样的问题(类型需要重复定义、侵入严重,和原本写法完全不一样),所以便自己写了这么一个解决方案,在获得了typescript的智能提示支持下却不需要重复写各种type和interface,和vuex原本写法保持一致,对业务代码侵入极小。 demo 项目由 vue-cli 3 生成,IDE 为 VSCODE 效果展示1. state state 会显示所有的 module、里面的属性及属性的类型 /src/store/modules/auth.ts const moduleState = { token: '', tokenExpire: 0,}2. gettersgetter 可以显示值类型 /src/store/modules/auth.ts const moduleGetters = { isLogin(state: State, getters: any, rootState: Store['state'], rootGetters: any) { return !!state.token && (!!rootState.user.userId || !!rootState.user.accountId); },};3. commit commit 会显示所有注册的 mutation 以及对应 payload 的类型, 如果 payload 类型不对还会报错 ...

July 1, 2019 · 4 min · jiezi

Vue项目总结基于饿了么组件封装

vue项目中,组件是项目的基石,每个页面都是组件来组装起来,我司没有自己的组件库,选用的是ElementUI组件库,在它的基础上再次封装。 可编辑表格由于是后台管理项目,各种单据漫天飞,而且单据列表要可编辑,可验证,基于业务封装了可编辑表格组件 业务需求: 每列可以编辑每列输入的值需要被验证每列可编辑,则需要每列的字段需要一个可编辑的属性edit来确定是否可以编辑,输入的值可以被验证,需要我们传入验证规则。 确认需要传入的propsprops: { // 表格数据 tableData: { type: Array, default: () => [] }, // 需要验证的列字段 columns: { type: Array, default: () => [] }, // 是否可编辑 defaultEdit: { type: Boolean, default: true }, // 验证集合 verifyRules: { type: Object, default: () => {} }}表格传入数据后,初始化可编辑状态阅读el-table源码,可以看到,表格组件拥有自己的store,一些需要通信的状态都是它来维护的,我们也可创建一个自有的table-store.js来维护编辑状态 // 初始化数据this.store = new TableStore({ list: this.tableData, columns: this.columns, defaultEdit: this.defaultEdit});可编辑列edit-table-cell利用slot插槽来传递编辑状态和验证规则 <slot v-else :cellState="cellState" :validateCell="validateCell"></slot>...computed: { isInput() { return this.slotName === 'input'; }, rowState() { const states = this.editTable.store.states; const rowState = states.get(this.row); return rowState; }, cellState() { const cellState = this.rowState.get(this.prop); return cellState; }},methods: { // 验证 validateCell(cb) { this.editTable .verifyTableCell(this.row, this.prop, this.cellState) .then(errMsg => { const valid = !errMsg; typeof cb === 'function' && cb(valid); }); }}使用组件// edit-table.vue<page-edit-table ref="editTable" v-model="tableData" :columns="['categoryName', 'name', 'purchaseDate']" :default-edit="true" :verify-rules="verifyRules"> <el-table ref="table" v-loading="loading" :data="tableData" tooltip-effect="dark" highlight-current-row border stripe style="width: 100%" > <el-table-column align="center" label="序号" width="50"> <template slot-scope="scope">{{ scope.$index + 1 }}</template> </el-table-column> <el-table-column label="品目名称" prop="categoryName" show-overflow-tooltip> <template slot-scope="{ row }"> <edit-table-cell :row="row" prop="categoryName"> <template slot-scope="{ cellState, validateCell }"> <el-select v-if="cellState.edit" v-model="row.categoryName" clearable placeholder="请选择品目" @change="validateCell" > <el-option label="你" value="1"></el-option> <el-option label="好" value="2"></el-option> <el-option label="呀" value="3"></el-option> </el-select> <span v-if="!cellState.edit">{{ row.categoryName }}</span> </template> </edit-table-cell> </template> </el-table-column>.... 效果如下 ...

June 28, 2019 · 2 min · jiezi

vuecli的npm包

为了方便在项目的中使用的vue框架,自己搭建了一套vue-cli,欢迎大家使用,并提出问题,谢谢 1.安装npm包cnpm(npm) install create-frame-vue -g 2.使用create-frame-vue指令就可以创建项目 使用指令后你可以看到这么的 指令完成之后创建的项目 npm install && npm run serve 即可启动项目。 欢迎大家使用,希望大家提供好的建议或意见,谢谢大家

June 27, 2019 · 1 min · jiezi

vuex结构图及用法

June 24, 2019 · 0 min · jiezi

Vuex学习整理

Vuex为Vue.js应用程序开发的状态管理模式解决的问题由 多个组件共享状态 引起的 1. 多个视图依赖于统一状态2. 不同视图的行为需要变更统一状态 Store每个应用只包含一个Store实例Vuex 的状态存储是响应式的。store 中的state状态更新立即触发组件更新不能直接修改state。 两种方式: 可以通过 this.$store.state.变量 = xxx; 进行修改显式地提交 (commit) mutation this.$store.dispatch(actionType, payload) => mapActions 使用action进行更改,异步this.$store.commit(commitType, payload) => mapMutations 使用mutation进行更改,同步可以通过 this.$store.state.变量 = xxx; 进行修改strict模式使用第一种方法vue会throw error : [vuex] Do not mutate vuex store state outside mutation handlers。异同点 共同点:都能触发视图更新(响应式修改)不同点: strict模式下,使用第一种方法vue会throw error : [vuex] Do not mutate vuex store state outside mutation handlers。使用commit的优点: vuex能够记录每一次state的变化记录,保存状态快照,实现时间漫游/回滚之类的操作。核心概念State 单一状态树全局的应用层级状态 -- 唯一数据源通过this.$store.state可以访问mapState 辅助函数:帮助在组件中引入state相关属性,作为组件的计算属性Getter state派生状态可以认为是 store的计算属性 : getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算getters: { // 可以通过第二个参数getters来使用其他getter getterA: (state, getters) => { return getters.getterB.length }, getterB: (state) => { return state.todoList; }, getterC: (state) => (id) => { return state.todoList[id]; }}通过属性访问:this.$store.getters.getterA ...

June 19, 2019 · 2 min · jiezi

手摸手带你用vue实现后台管理权限系统及顶栏三级菜单显示

手摸手,带你用vue实现后台管理权限系统及顶栏三级菜单显示 效果演示地址项目demo展示 重要功能总结权限功能的实现权限路由思路:根据用户登录的roles信息与路由中配置的roles信息进行比较过滤,生成可以访问的路由表,并通过router.addRoutes(store.getters.addRouters)动态添加可访问权限路由表,从而实现左侧和顶栏菜单的展示。 实现步骤: 1.在router/index.js中,给相应的菜单设置默认的roles信息; 如下:给"权限设置"菜单设置的权限为:meta:{roles: ['admin', 'editor']},及不同的角色都可以看到; 给其子菜单"页面权限",设置权限为:meta:{roles: ['admin']},及表示只有"admin"可以看到该菜单; 给其子菜单"按钮权限"设置权限为:meta:{roles: ['editor']},及表示只有"editor"可以看到该菜单。 2.通过router.beforeEach()和router.afterEach()进行路由过滤和权限拦截; 代码如下: // permission judge functionfunction hasPermission(roles, permissionRoles) { if (roles.indexOf('admin') >= 0) return true // admin permission passed directly if (!permissionRoles) return true return roles.some(role => permissionRoles.indexOf(role) >= 0)}const whiteList = ['/login'] // 不重定向白名单router.beforeEach((to, from, next) => { NProgress.start() // 设置浏览器头部标题 const browserHeaderTitle = to.meta.title store.commit('SET_BROWSERHEADERTITLE', { browserHeaderTitle: browserHeaderTitle }) // 点击登录时,拿到了token并存入了vuex; if (getToken()) { /* has token*/ if (store.getters.isLock && to.path !== '/lock') { next({ path: '/lock' }) NProgress.done() } else if (to.path === '/login') { next({ path: '/' }) // 会匹配到path:'',后面的path:'*'还没有生成; NProgress.done() } else { if (store.getters.roles.length === 0) { store.dispatch('GetInfo').then(res => { // 拉取用户信息 const roles = res.roles store.dispatch('GenerateRoutes', { roles }).then(() => { // 根据roles权限生成可访问的路由表 router.addRoutes(store.getters.addRouters) // 动态添加可访问权限路由表 next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 }) }).catch((err) => { store.dispatch('FedLogOut').then(() => { Message.error(err || 'Verification failed, please login again') next({ path: '/' }) }) }) } else { // 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓ if (hasPermission(store.getters.roles, to.meta.roles)) { next()// } else { next({ path: '/401', replace: true, query: { noGoBack: true }}) } } } } else { if (whiteList.indexOf(to.path) !== -1) { next() } else { // 点击退出时,会定位到这里 next('/login') NProgress.done() } }})router.afterEach(() => { NProgress.done() // 结束Progress setTimeout(() => { const browserHeaderTitle = store.getters.browserHeaderTitle setTitle(browserHeaderTitle) }, 0)})用户点击登录之后的业务逻辑分析: ...

June 19, 2019 · 3 min · jiezi

Vue项目总结后台管理项目总结

公司做的大部分都是后台管理项目,剔除每个项目的业务逻辑,其实都可以用通用的一套模版来做。 登录逻辑每个系统都有自己的登录登出逻辑,而我们前端所要做的其实是请求后台,拿到登录权限,带上登录权限,获取用户信息和菜单信息。在vue项目开发当中,我们一般都是在全局路由钩子做这一系列判断。 router.beforeEach(async(to, from, next) => { NProgress.start(); await store.dispatch('SetConfigApi'); // 获取配置 await store.dispatch('SetApi'); // 设置基本配置 const token = await store.dispatch('getToken'); // 获取token if (token) { // 用户信息不存在 if (!store.getters.userInfo) { await store.dispatch('GetUser'); // 获取用户信息 const menuList = await store.dispatch('GetMenu', localRoute); // 获取菜单 await store.dispatch('GenerateRoutes', localRoute); router.addRoutes(store.getters.addRoutes); ... } else { next(); } } else { if (whiteList.includes(to.path)) { // 在免登录白名单,直接进入 next(); } else { window.location.href = store.getters.api.IPORTAL_LOCAL_API; NProgress.done(); } }});当用户进入系统的时候,先获取系统的配置信息,这个配置信息可以是前端json文件,或者是后台接口;用这种方式可以灵活的修改项目中的配置,而不用每次都打包死进入项目,直接可以要运维童靴修改对应的配置信息,就可以了。 ...

June 18, 2019 · 4 min · jiezi

vuex-开发-测试-生产环境-配置axios

1、vue2使用 vue-cli安装的项目 config目录下面都有 dev.env.js/test.env.js/prod.env.js文件,做相应的修改,添加API_ROOT module.exports = merge(prodEnv, { NODE_ENV: '"development"', API_ROOT: '"http://www.eastgrain.cn"'})2、src下面添加api目录添加api.js文件 import axios from 'axios'// 创建配置const Axios = axios.create({ baseURL: process.env.API_ROOT, timeout: 20000, headers: { 'Content-Type': 'application/json' }})// request 拦截器 请求开始显示loading等等Axios.interceptors.request.use((config) => { console.log(config, 'config axios配置') // 显示loading... return config}, (error) => { return Promise.reject(error)})// response 拦截器Axios.interceptors.response.use((response) => { console.log(response, 'axios response配置') // 这里可以做处理,response.data.code 错误码不同显示不同错误信息 return response.data}, (error) => { return Promise.reject(error)})export default Axios3、在文件中调用 import Axios from '@/api/api'methods: { // 访问接口 getFormData () { Axios.post('customer/modifyUserInfo.json', {phone: 15001209233}).then((success) => { console.log(success)// 这里可以出发vuex中的mutations、actions来修改vuex state中的数据 }).then((err) => { console.log(err) }) },

June 18, 2019 · 1 min · jiezi

vuex-mapState-mapMutations-mapActions

mapStatemapMutationsmapActionsvuex在表单中的使用因为vuex state定义的变量只能在 mutations中修改,所以表单中的v-model绑定就会有问题,解决方案 ...<input type="text" v-model="username" />...computed: { username: { // 双向绑定 input 由于vuex中的数据只能在 mutations中修改,所以当username数据发生变化的时候调用vuex文件中的方法 get () { return this.$store.username }, set (value) { this.$store.commit('mutations_username', value) } } ...store/index.js文件 ...state: { username: '', state: { show: true, dialog: true, formInfo: { username: ['zxc', 'hp'], email: '', password: '', passwordSure: '', address: '', number: 0, total: 0, search: '', searchResult: [] } }, ...},mutations: { mutations_username (state) { state.username = 'zhangxuchao' } ... 使用 mapState mapMutations mapActions ...

June 18, 2019 · 1 min · jiezi

vue-状态管理一

vue 状态管理(一)父子组件之间往往使用props和 &dollar;emit 实现数据共享,任意组件可通过bus(一个vue实例)作为桥梁,实现数据共享。当项目中组件越来越多时,组件之间的数据共享变得复杂,难以维护。使用 Vuex 可集中管理组件之间的数据(状态),使组件之间的数据共享变得简单。 父子组件间通信父→(props)子组件;子→(&dollar;meit)父组件,即子组件自定义一个事件,在父组件中监听该事件。 自定义输入组件: <template> <input @input="handleInput" :value="value" :placeholder="placeholder" /></template><script> export default { name: "CustomInput", //props 接收父组件传递过来的数据 props: { value: { type: [Number, String], required: true, default: "" }, placeholder: { type: String, default: "提示文本" } }, methods: { handleInput(event) { let val = event.target.value; // 子组件的事件监听函数中触发一个自定义事件 this.$emit("customInput", val); } } };</script>使用组件: <template> <div class="store"> <!-- props 传递值 --> <custom-input :value="value" @customInput="handleInput" :placeholder="placeholder" /> <p v-text="value"></p> </div></template><script> import CustomInput from '_c/CustomInput.vue' export default { name: 'Store', components: { CustomInput }, data() { return { value: '', placeholder: '自定义事件传递值' } }, methods: { // 自定义事假处理器 handleInput(val) { this.value = val } } }</script>因为 v-model 指令是双向绑定的,我们也可以用其来实现值的传递: ...

June 16, 2019 · 3 min · jiezi

vuex之store源码简单解析

关于vuex的基础部分学习于https://blog.csdn.net/qq_3865... 使用Vuex的时候,通常会实例化Store类,然后传入一个对象,包括我们定义好的actions、getters、mutations、state等。store的构造函数: export class Store { constructor (options = {}) { // 若window内不存在vue,则重新定义Vue if (!Vue && typeof window !== 'undefined' && window.Vue) { install(window.Vue) } if (process.env.NODE_ENV !== 'production') { // 断言函数,来判断是否满足一些条件 // 确保 Vue 的存在 assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`) // 确保 Promsie 可以使用 assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`) assert(this instanceof Store, `store must be called with the new operator.`) } // 解构赋值,拿到options里的plugins和strict const { plugins = [], strict = false } = options // 创建内部属性 // 标志一个提交状态,作用是保证对 Vuex 中 state 的修改只能在 mutation 的回调函数中,而不能在外部随意修改 state this._committing = false // 用来存储用户定义的所有的actions this._actions = Object.create(null) this._actionSubscribers = [] // 用来存储用户定义所有的mutatins this._mutations = Object.create(null) // 用来存储用户定义的所有getters this._wrappedGetters = Object.create(null) // 用来存储所有的运行时的 modules this._modules = new ModuleCollection(options) this._modulesNamespaceMap = Object.create(null) // 用来存储所有对 mutation 变化的订阅者 this._subscribers = [] // 一个 Vue对象的实例,主要是利用 Vue 实例方法 $watch 来观测变化的 this._watcherVM = new Vue() // 把Store类的dispatch和commit的方法的this指针指向当前store的实例上 const store = this const { dispatch, commit } = 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) } // 是否开启严格模式 this.strict = strict const state = this._modules.root.state // Vuex的初始化的核心,其中,installModule方法是把我们通过options传入的各种属性模块注册和安装; // resetStoreVM 方法是初始化 store._vm,观测 state 和 getters 的变化;最后是应用传入的插件。 installModule(this, state, [], this._modules.root) resetStoreVM(this, state) plugins.forEach(plugin => plugin(this)) const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools if (useDevtools) { devtoolPlugin(this) } }Vuex本身是单一状态树,应用的所有状态都包含在一个大对象内,随着我们应用规模的不断增长,这个Store变得非常臃肿。为了解决这个问题,Vuex允许我们把store分模块。每一个模块包含各自的state、mutations、actions和getters,甚至还可以嵌套模块。接下来看installModule方法: ...

June 13, 2019 · 2 min · jiezi

vuex的数据变化被watch监听如此简单

vuex 的数据变化被watch 监听到watch:{ '$store.state.mqttSign':function(newFlag, oldFlag){ // 需要执行的代码 }}没错,就是这么简单

June 12, 2019 · 1 min · jiezi

vue-Appvue里的公共组件改变值触发其他组件或vue页面监听

业务场景重现 现在我的App.vue里面有一个头部的公共组件,头部组件里有一个输入框,当我输入词条时,将词条传进App.vue里的<router-view>里的.vue页面,并进行查询获取数据解决思路如下: 1.如何拿到头部的词条2.当词条改变时如何触发.vue里的请求数据事件解决方案我是用vuex数据仓库来存储词条的,当词条改变时,修改数据仓库里的词条然后在.vue页面里监听这个词条,当词条改变时触发请求数据的事件 代码数据仓库store.js import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({ state: { searchKey: '' //存储词条的变量 }, mutations: { //修改数据仓库的事件 changeSearchKey(state,value){ state.searchKey = value } }, actions: { //推荐使用的异步修改数据仓库 setSearchKey(context,value){ context.commit('changeSearchKey',value) } }})App.vue里的header组件 goSearch: function(){ if(this.value){ this.$store.dispatch('setSearchKey',this.value) //当输入词条时,将词条更新到数据仓库 } },vue页面里监听词条 computed: { //监听词条 getSearchKey(){ return this.$store.state.searchKey } }, watch: { getSearchKey: { handler(newValue,oldValue){ //当词条改变时执行事件 this.recordis(newValue) // console.log('new',newValue) // console.log('old',oldValue) } } },

May 30, 2019 · 1 min · jiezi

Vue-组件间通信方式

Vue 是数据驱动的视图框架,那么组件间的数据通信是必然的事情,那么组件间如何进行数据传递呢? 首先组件间通信有父子组件、兄弟组件、堂兄弟组件、叔侄组件等,分类太多可能不好理解,我们暂且分为: 父子组件通信子父组件通信非父子组件通信 兄弟组件通信非兄弟组件通信(不是直属关系,如堂兄组件、叔侄组件等)后续的组件间通信方式的例子就会根据这些分类进行说明。 Vue 本身提供哪几种通信方式?首先 Vue 灵感源于 angular,支持双向绑定,Vue 本质还是单向数据流。跟 React 一样,组件间最基本的数据流是通过 prop 向子组件传递数据。 这里列举一下 Vue 本身支持的通信方式: prop$emit这个其实类似 React 的 props 回调。 provide / inject如果你熟悉 React,这与 React 的 context 特性很相似。 那么有人说 $attrs 、$listener 呢?这些严格意义上不能归纳为数据流的通信方式,这些只是辅助属性,本人也不建议过多的使用这些 $ 属性,除了一些有必要的场景。 propprop 是 Vue 三大核心概念之一,prop 在组件中无处不在。prop 只可以从上一级组件传递到下一级组件(父子组件),即所谓的单向数据流。而且 prop 只读,不可被修改,所有修改都会失效并警告。 可以先阅读官网的 通过 Prop 向子组件传递数据 的教程。 父子组件通信这里也编写了一个简单的例子 http://jsrun.net/wXyKp/edit。 子父组件通信不是说是单向数据流吗,怎么还可以使用 prop 进行子父组件通信?这样想是对的,prop 是无法向上传递数据,但是我们可以使用回调啊。数据流的确向上走了,但是这并不违反单向数据流的思想,这个并不会使得数据流混乱,还是比较清晰。 这个 prop 回调方式,在 React 会经常使用。但是在 Vue 却很少使用,因为组件可以自定义事件,即后面的 $emit 组件间通信方式(其实就是订阅发布模式)。 ...

May 23, 2019 · 1 min · jiezi

vuecli构建的小说阅读器

项目介绍主要页面1、首页home.vue分类展示书籍,幻灯片展示热门推荐2、搜索search.vue,上拉加载更多3、书籍详情book.vue加入书架、立即阅读,展示评论,同类书籍推荐4、书籍内容read.vue,获取目录,存储翻阅的章节位置,5、书架bookrack.vue,获取加入书架的书单 技术栈vue、vue-cli、axios、vue-router、vuex、localStorege 入口页面app.vue分成底部导航 跟 主视图容器 router-view首页tabbar/Home包含: components/sub/item 和 components/sub/search 、components/sub/header结构: banner切换 与 搜索 和 小说分类楼层 小说楼层单独定义了组件 components/sub/item , home循环楼层分类名称,并将楼层分类id传给item组件 :booklistId='{id:item._id}' , item组件用props: ["booklistId"] 接收分类id, 并根据分类id获取对应的数据item.vue mouted: this.getlist(this.booklistId.id);methods: getlist(id) { //每个分类id下对应的数据 子组件接收父组件传过来的id 获取对应的数据 bootd(id).then(res => { var arrdata = res.data.data; arrdata.map(item => { this.booklist.push(item.book); }); }); }小说详情页components/book/Book.vue包含: components/sub/yuedu 、mulu、pinglun、结构: 小说概况与简介,是否加入书架或者继续阅读 ,目录、评论、同类书籍推荐加入书架/立即阅读(yuedu.vue)组件 加入书架,获取书籍信息,并把相关书籍信息存放在书架中、存localbook.vue computed: { ...mapState(['calbook','shuajiabook']) //书籍信息 书架数据[] }, methods:{ addbook(){ this.flag=!this.flag var book= this.calbook; // calbook 是store里存储的书籍信息【 SHEFLBOOK 】 var carbook = JSON.parse(window.localStorage.getItem('book') || '{}') if(!this.flag){ //加入书架 carbook[book._id] = { cover: book.cover, flag:!this.flag, title: book.title, lastChapter:book.lastChapter, id: book._id, author:book.author, chapterIndexCache: 0, bookSource: 0, pageIndexCache: 0, } this.setbook(false) window.localStorage.setItem('book', JSON.stringify(carbook)) }else{ delete carbook[book._id] this.setbook(true) //设置的布尔值 window.localStorage.setItem('book', JSON.stringify(carbook)) } } }立即阅读时进入小说章节 `this.$router.push({name:'read',params:{id:this.booklinks}})`目录组件components/sub/mulu.vue ...

May 13, 2019 · 2 min · jiezi

一张思维导图辅助你深入了解-Vue-VueRouter-Vuex-源码架构

1.前言本文内容讲解的内容:一张思维导图辅助你深入了解 Vue | Vue-Router | Vuex 源码架构。 项目地址:https://github.com/biaochenxuying/vue-family-mindmap 文章的图文结合版 Vue-family.md Vue-family.pdf 2. Vue 全家桶先来张 Vue 全家桶 总图: 3. Vue细分如下 源码目录 源码构建,基于 Rollup  Vue 本质:构造函数 数据驱动 组件化 深入响应式原理 编译 扩展 4. Vue-Router introduction 路由注册 VueRouter 对象 matcher 路径切换 5. Vuex introduction Vuex 初始化 API 插件 6. 已完成与待完成已完成: 思维导图待完成: 继续完善 思维导图添加 流程图因为该项目都是业余时间做的,笔者能力与时间也有限,很多细节还没有完善。 如果你是大神,或者对 vue 源码有更好的见解,欢迎提交 issue ,大家一起交流学习,一起打造一个像样的 讲解 Vue 全家桶源码架构 的开源项目。 7. 总结以上内容是笔者最近学习 Vue 源码时的收获与所做的笔记,本文内容大多是开源项目 Vue.js 技术揭秘 的内容,只不过是以思维导图的形式来展现,内容有省略,还加入了笔者的一点理解。 ...

May 12, 2019 · 1 min · jiezi

使用vuex的问题记录解决刷新页面state数据消失

在实际的vue项目中,当我们的应用遇到多个组件之间的共享问题时,通常会用到Vuex(一个状态管理的插件,可以解决不同组件之间的数据共享和数据持久化),解决组件之间同一状态的共享问题。 因子: Vuex优势:相比sessionStorage,存储数据更安全,sessionStorage可以在控制台被看到。Vuex劣势:在刷新页面后,vuex会重新更新state,所以,存储的数据会丢失。言而总之:实际问题:在vue项目中,使用Vuex做状态管理时,调试页面时,刷新后state上的数据消失了,该如何解决? 解决思路:将state中的数据放在浏览器sessionStorage和localStorage解决办法: 存储到localStorage通过监听页面的刷新操作,即beforeunload前存入本地localStorage,页面加载时再从本地localStorage读取信息在App.vue中加入下面代码 created(){ //在页面刷新时将vuex里的信息保存到localStorage里 window.addEventListener("beforeunload",()=>{ localStorage.setItem("messageStore",JSON.stringify(this.$store.state)) }) //在页面加载时读取localStorage里的状态信息 localStorage.getItem("messageStore") && this.$store.replaceState(Object.assign(this.$store.state,JSON.parse(localStorage.getItem("messageStore")))); }使用vuex-persistedstate 插件安装插件:npm install vuex-persistedstate --save配置:在store的index.js中,手动引入插件并配置 import createPersistedState from "vuex-persistedstate"const store = new Vuex.Store({ // ... plugins: [createPersistedState()]})该插件默认持久化所有state,当然也可以指定需要持久化的state: import createPersistedState from "vuex-persistedstate"const store = new Vuex.Store({ // ... plugins: [createPersistedState({ storage: window.sessionStorage, reducer(data) { return { // 设置只储存state中的myData myData: data.myData } } })]

May 8, 2019 · 1 min · jiezi

vuex-使用总结详解

###### 如果之前未使用过 vuex 请务必先看一下参考 参考: vue中store存储store.commit和store.dispatch的区别及使用vuex的安装和简单使用什么情况下应该使用 Vuex?Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。 如果不打算开发大型单页应用,应用够简单,最好不要使用 Vuex。一个简单的 store 模式就足够了。但是,如果需要构建一个中大型单页应用,就要考虑如何更好地在组件外部管理状态,Vuex 是不错的选择。 使用在 Vue 的单页面应用中使用,需要使用Vue.use(Vuex)调用插件。将其注入到Vue根实例中。 import Vuex from 'vuex'Vue.use(Vuex)const store = new Vuex.Store({ state: { count: 0 }, getter: { doneTodos: (state, getters) => { return state.todos.filter(todo => todo.done) } }, mutations: { increment (state, payload) { state.count++ } }, actions: { addCount(context) { // 可以包含异步操作 // context 是一个与 store 实例具有相同方法和属性的 context 对象 } }})// 注入到根实例new Vue({ el: '#app', // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件 store, template: '<App/>', components: { App }})然后改变状态: ...

May 5, 2019 · 4 min · jiezi

页面刷新后vuex中数据丢失清空的解决方案-vuexpersistedstate

场景之一应用API进行用户身份验证,将登录状态保存为Vuex状态中的布尔值。当用户登录时,设置了 登录状态 并相应地有条件地显示 登录/注销 按钮。 但是当刷新页面时,vue应用程序的状态将丢失并重置为默认值。 这导致的问题就是:即使用户登录了,但刷新页面时,登录状态 也会设置为false, 这样即使用户保持登录状态,也会显示登录按钮而不是注销按钮.... 怎么做才能防止这种行为 解决方案可以使用 vuex-persistedstate 。这是一个用于 vuex 在页面刷新之间处理和存储状态的插件。 示例代码: import createPersistedState from 'vuex-persistedstate'const store = new Vuex.Store({ // ... plugins: [ createPersistedState() ]})以上是将状态保存在 localStorage ,也可以使用 js-cookie 将状态保存在cookie import { Store } from 'vuex'import createPersistedState from 'vuex-persistedstate'import * as Cookies from 'js-cookie' const store = new Store({ // ... plugins: [ createPersistedState({ storage: { getItem: key => Cookies.get(key), // 参考 https://github.com/js-cookie/js-cookie#json setItem: (key, value) => Cookies.set(key, value, { expires: 3, secure: true }), removeItem: key => Cookies.remove(key) } }) ]})总结: ...

May 5, 2019 · 1 min · jiezi

VueCLI2x全家桶架构支持打包后自动部署到服务器构建案例

今天有时间分享一些平时自己开发上的一些构建配置,我们以Vue-CLI-2.x来构建开发环境。好,我们先来看一下我们要做哪些工作。现附上源码地址,https://github.com/749264345/... 1.Vue,Vuex,vue-router,axios通过CDN引入;优化打包后文件过大的问题2.开发环境区分开发,测试,生产;提高开发效率3.打包区分测试与生产4.实现项目打包后自动部署到远程服务器5.模块化构建vuex状态管理6.打包后自动清除console.log()7.项目默认基于Element-ui,对相关组件二次封装8.模块化构建axios请求响应拦截器一寸光阴一寸金,寸金难买寸光阴~废话不多说,直接撸起袖子就是干~~ 1.Vue,Vuex,vue-router,axios通过CDN引入在过去我们都习惯使用npm或者cnpm安装依赖,因为这种操作非常方便,但是有利必有弊,有些小伙伴在将项目打包后会发现打包后的js文件非常庞大,有些甚至达到了几兆,所以最终导致应用打开时首屏加载非常慢,因此我们可以使用传统的引入方式,也就是CDN引入。Vue,Vuex,vue-router,axios,这些均为静态资源,我们从它们官网下载适合的版本后放入项目根目录【static】文件夹,然后修改几个地方:首先,index.html <!--兼容ie--> <script src="./static/libs/polyfill.min.js"></script> <script src="./static/libs/eventsource.js"></script> <!--vue--> <script src="./static/libs/vue.js"></script> <script src="./static/libs/vue-router.min.js"></script> <script src="./static/libs/element-ui/element-ui.js"></script> <script src="./static/libs/axios.js"></script> <script src="./static/libs/vuex.js"></script>然后修改,build/webpack.base.conf.js module.exports = { externals: { 'vue': 'Vue', 'vuex': 'Vuex', 'vue-router': 'VueRouter', 'axios': 'axios' }, ...}到这里基本配置已经完成,最后一步需要在main.js中删除原先对这些模块的import操作即可。这样后再打包项目,你会发现页面非常丝滑,几乎秒开。 2.开发环境区分开发,测试,生产;提高开发效率在调试接口,或者debug的时候我们经常会切换环境,而且在打包的时候又要改成生产的接口,这样没有效率,所以我们可以做如下配置。在config/dev.env.js文件中, const TARGET = process.env.npm_lifecycle_event;//开发环境if (TARGET === 'dev') { var data = { NODE_ENV: '"dev"', API: '"http://www.dev.com"' }}//测试环境if (TARGET === 'test') { var data = { NODE_ENV: '"test"', API: '"http://www.test.com"' }}//生产环境if (TARGET === 'prod') { var data = { NODE_ENV: '"prod"', API: '"http://www.prod.com"' }}我们从process.env.npm_lifecycle_event中获得当前node执行的环境来区分项目运行的环境,这样我们可以用来区分接口环境,因此我们添加相关指令。在根目录package.json文件中, ...

May 5, 2019 · 3 min · jiezi

前端性能优化02vue性能优化

一、template语义化标签,避免乱嵌套,合理命名属性等等标准推荐的东西就不谈了。 模板部分帮助我们展示结构化数据,vue 通过数据驱动视图,主要注意一下几点 v-show,v-if 用哪个?在我来看要分两个维度去思考问题,第一个维度是权限问题,只要涉及到权限相关的展示无疑要用 v-if ,第二个维度在没有权限限制下根据用户点击的频次选择,频繁切换的使用 v-show ,不频繁切换的使用 v-if ,这里要说的优化点在于减少页面中 dom 总数,我比较倾向于使用 v-if ,因为减少了 dom 数量,加快首屏渲染,至于性能方面我感觉肉眼看不出来切换的渲染过程,也不会影响用户的体验。不要在模板里面写过多的表达式与判断 v-if="isShow && isAdmin && (a || b)" ,这种表达式虽说可以识别,但是不是长久之计,当看着不舒服时,适当的写到 methods 和 computed 里面封装成一个方法,这样的好处是方便我们在多处判断相同的表达式,其他权限相同的元素再判断展示的时候调用同一个方法即可。循环调用子组件时添加 key。 key 可以唯一标识一个循环个体,可以使用例如 item.id 作为 key,假如数组数据是这样的 ['a' , 'b', 'c', 'a'],使用 :key="item" 显然没有意义,更好的办法就是在循环的时候 (item, index) in arr ,然后 :key="index" 来确保 key 的唯一性。在列表数据进行遍历渲染时,给每一项item设置唯一key值,会方便vuejs内部机制精准找到该条列表数据。当state更新时,新的状态值和旧的状态值对比,较快地定位到diff。二、style将样式文件放在 vue 文件内还是外?讨论起来没有意义,重点是按模块划分,我的习惯是放在 vue 文件内部,方便写代码是在同一个文件里跳转上下对照,无论内外建议加上 <style scoped> 将样式文件锁住,目的很简单,再好用的标准也避免不了多人开发的麻烦,约定命名规则也可能会冲突,锁定区域后尽量采用简短的命名规则,不需要 .header-title__text 之类的 class,直接 .title 搞定。为了和上一条作区分,说下全局的样式文件,全局的样式文件,尽量抽象化,既然不在每一个组件里重复写,就尽量通用,这部分抽象做的越好说明你的样式文件体积越小,复用率越高。建议将复写组件库如 Element 样式的代码也放到全局中去。不使用 float 布局,之前看到很多人封装了 .fl -- float: left 到全局文件里去,然后又要 .clear,现在的浏览器还不至于弱到非要用 float 去兼容,完全可以 flex,grid 兼容性一般,功能其实 flex 布局都可以实现,float 会带来布局上的麻烦,用过的都知道。至于其他通用的规范这里不赘述。三、script这部分也是最难优化的点,说下个人意见吧。 ...

April 30, 2019 · 2 min · jiezi

关于vuex-数据缓存问题

描述: 页面中的页签使用vuex进行数据存储,当切换路由时,进行添页签渲染问题: 当页面刷新后,存储在store的数据就会被初始化,变为空解决: 最开始想法是用本地缓存来进行数据存储,但感觉并不是太方便,于是就有了vuex-along,话不多数,上代码 引入vuex-alongwatch() 方法里面存入的数组是你要进行数据保存的的,默认是全部数据缓存,不过有的是是不需要的,就可以在里面设置watchSession()和watch()存储类似,不过warchSession是存储在session中,区别是关闭浏览器watch() 方法存储的数据不会丢失,watchSession,关闭浏览器数据就会丢失onlySession()方法,是关闭当前页面,数据就会丢失

April 29, 2019 · 1 min · jiezi

从0到1搭建element后台框架之权限篇

前言首先还是谢谢各位童鞋的大大的赞赞,你们的支持是我前进的动力!上周写了一篇从0到1搭建element后台框架,很多童鞋留言提到权限问题,这一周就给大家补上。GitHub 一、jwt授权认证现在大多数项目都是采用jwt授权认证,也就是我们所熟悉的token登录身份校验机制,jwt的好处多多,由于jwt是由服务端生成,中间人修改密串后,服务端会校验不过,安全有效。一般呆在请求头上的Authorization里面。前端童鞋一般获取token后通过vuex存储起来,随后数据持久化存到session中。 路由跳转验证token首先在路由跳转的时候需要验证vuex是否存储了token,如果没有token的话直接跳到登陆页面获取token。 if (to.path !== '/login' && !store.state.token) { next('/login') NProgress.done() // 结束Progress } else { next(); }请求拦截带上token详细请看项目中的router.js本地存在token之后,我们在每次请求接口的时候都需要带上token来验证token的合法性。 //在请求前拦截 if (store.state.token) { config.headers["Authorization"] = "Bearer " + store.state.token; }如果token不合法,全局错误处理,直接跳到登陆页面 case 401: messages("warning", "用户登陆过期,请重新登陆"); store.commit('COMMIT_TOKEN','') setTimeout(() => { router.replace({ path: "/login", query: { redirect: router.currentRoute.fullPath } }); }, 1000); break;详细代码看项目中的request.js 二、菜单权限本项目中,我主要是通过后端传过来的角色类型来判断导航菜单的显示与隐藏。也就是说首先前端请求接口,后端返回token,以及对应的角色,比如项目中用admin登陆的话,roles=['admin'],用user登陆的话roles=['user']。接下来我这边设计了一份菜单表和一份路由表,路由表主要是为了注册路由,不需要考虑层级关系。而菜单表需要考虑层级关系,里面可以配置主菜单,子菜单,图标等等一系列的东西,当然菜单表最好是通过接口数据从后端传过来。值得注意的是无论是菜单表,还是路由表,里面都有一个meta配置项。里面可以配置我们的角色权限。路由表对应的菜单表角色权限需要一致。没有配置角色权限的菜单默认都开放。 menu.js { icon: "el-icon-question", index: "premission", title: "权限测试", subs: [{ index: "permission", title: "菜单测试", meta: { roles: ['admin'] } }, { index: "permissionBtn", title: "按钮权限", }, ] }router.js { path: '/permission', component: getComponent('permission', 'permission'), meta: { title: '菜单权限', roles: ['admin'] } },根据角色过滤菜单现在我们开始编写菜单逻辑,进入Aside.vue,首先根据角色过滤菜单表menu.js ...

April 26, 2019 · 2 min · jiezi

用vue26实现一个抖音很火的时间轮盘屏保小DEMO

写在前面:前段时间看抖音,有人用时间轮盘作为动态的桌面壁纸,一时间成为全网最火的电脑屏保,后来小米等运用市场也出现了【时间轮盘】,有点像五行八卦,感觉很好玩,于是突发奇想,自己写一个网页版小DEMO玩玩,先看看效果: 当然实现这个效果,前端的角度来说,有很多,这里介绍最简单的,达到这个效果纯粹是元素圆性布局,如果仅仅是这样肯定没有达到各位老铁心理需求,所以既然,做了肯定是要做一个麻雀虽小五脏俱全的小demo,于是就把vue全家桶用上带设置的小项目。接下来就一步一步带各位从0到1构建这个小东西。 一、项目需求:功能描述:实现一个带设置的并兼容移动端的时间罗盘(设置包含:多语言切换,垂直水平位置,缩放大小,旋转角度,文字颜色,背景颜色等) 二、预备基础知识点: 1、圆形布局,如下效果图 解析:1、圆心:O点、半径r ,我这里用transform: translateX值来设置半径值;2、圆心角:∠BOM;3、需要布局的元素:A、B、C、D、E、F、G、H绝对定位的元素;4、绝对定位时的元素的坐标点,可以用transform:rotate值,按照秒、分、小时、上下午、星期、日期、月等份旋转角度来控制各个元素在圆心的位置 有了这些信息,我们就开始写代码了(vue构建项目这里就略了 ),简单的直接用vue-cli3 三、项目布局效果开发3.1布局首先我们看到时间轮盘分别由 秒、分、小时、上下午、星期、日期、月,这几项组成,于是把他们都封装在一个小模块组件里 <template> <div class="home"> <Second :second="second" /><Minute :minute="minute" /><Hour :hour="hour" /><Apm :apm="apm" /><Week :week="week" /><Day :day="day" /><Month :month="month" /></div></template>而且同一圆心,所以公共部分的样式可以共用 <style lang="scss">.home { ul { list-style-type: none;padding: 0;position: absolute;top: 0;bottom: 0;left: 0;right: 0;margin: auto;height: 60px;width: 60px;li { position: absolute; height: 60px; width: 60px; color: #fff; text-align: center; font-size: 14px; line-height: 20px;}}}</style>这里圆布局,我们以星期为例看下面代码 Week.vue <template> <ul> <li v-for="(item, index) in weekList" :key="index"> {{ item }}</li></ul></template> ...

April 23, 2019 · 3 min · jiezi

vue移动端列表组件

效果图子组件:<template> <div id=“list”> <ul> <li v-for="(data,index) in listData" :key=“index” @click=“toRouter(data.url)"> <img class=“img” :src=“data.icon”> <div class=“content vux-1px-b”> <span class=“title”>{{data.title}}</span> <span class=“arr-r”></span> <span v-if=“data.num” class=“tg-n”>{{data.num}}</span> </div> </li> </ul> </div></template><script>export default { name: “list”, props:[’listData’], methods: { toRouter(url) { this.$router.push(url); } }};</script><style lang=“less” scoped>#list { ul { padding: 0 .24rem; li { height: 1rem; display: flex; display: -webkit-flex; line-height: 1rem; .img { width: 0.44rem; height: 0.44rem; margin: 0.28rem 0; overflow: hidden; } .content { width: 100%; margin-left: 0.32rem; .title { display: inline-block; line-height: 1rem; text-decoration: none; } .tg-n { float: right; display: flow-root; width: .34rem; height: .34rem; color: #fff; background-color: red; font-size: .24rem; border-radius: 50%; text-align: center; margin: .29rem .1rem 0 0; line-height: 0.4rem; } .arr-r { width: 0.2rem; height: 0.2rem; display: inline-block; float: right; border-right: 0.04rem solid #bbb; border-top: 0.04rem solid #bbb; margin: 0.4rem 0.2rem 0 0; transform: rotate(45deg); -ms-transform: rotate(45deg); -moz-transform: rotate(45deg); -webkit-transform: rotate(45deg); -o-transform: rotate(45deg); } } } }}</style>父组件<!– 管理中心 –><template> <div id=“management”> <list :listData=‘listData’></list> </div></template><script>import list from “../../../components/list”;let caseHouse = require(”./../../../assets/icon/management/case.png");let feedBack = require("./../../../assets/icon/management/feedback.png");let local = require("./../../../assets/icon/management/local.png");let material = require("./../../../assets/icon/management/material.png");export default { components: { list }, name: “info”, data() { return { listData: [ { icon: local, title: “麻辣香锅”, url: “/info/managerCenter”}, { icon: material, title: “油焖大虾”, url: “/info/managerCenter”}, { icon: caseHouse, title: “焦糖玛奇朵”, url: “/info/managerCenter”}, { icon: feedBack, title: “黑糖玛奇朵”, url: “/info/managerCenter”} ] }; },};</script>总结主要是父组件传递主要是传递 listData 子组件,第一次写这种东西,乱七八糟的,谢谢! ...

April 17, 2019 · 2 min · jiezi

[集成框架]基于VueCli3的微信公众号项目前端框架

概述基础环境:Node.js最新稳定版Vue2.xVueCli3开发:跟普通的vue单页应用开发方式一致。 npm i //安装依赖 npm run serve //启动开发环境 npm run build// 打包调试:开发环境下,可以借助chrome进行调试,需要注意的是,如果涉及到微信验证的接口,要手动在sessionStorage中注入 wx_info类似的验证,具体的wx_info配置可以询问后端开发人员如果要进行真实账号测试或者是生产环境下,可以通过微信开发者工具进行调试,调试方式跟chrome类似。vuex的调试,请先科学上网,然后在chrome拓展工具里搜索安装 Vue.js devtools ,安装完成后打开开发者工具,在最右侧vue栏里进行vuex调试。具体使用路由管理采用树形结构管理路由每一个大模块单独设置一个路由,再汇总到总路由文件const Demo=() => import(/* webpackChunkName: “demo-page” / ‘@views/demo/index’)const DemoDetail =() => import(/ webpackChunkName: “demo-page” */ ‘@views/demo/detail/index’)const DEMO_ROUTERS =[ { path: ‘/demo’, name: ‘订单1’, component: Demo, meta: {requireAuth: false,index:1}, }, { path: ‘/detail’, name: ‘订单详情’, component: DemoDetail, meta: {requireAuth: false,index:2}, },]export default DEMO_ROUTERS如果该模块较为复杂,页面很多,可以视情况再细分路由。结合 Vue 的异步组件和 Webpack 的代码分割功能,实现路由组件的懒加载serve管理serve 下的 base.js用于全局配置api请求的域名或者ip地址等可以快速的切换测试环境和生产环境const base = { dv:‘http://test’, // 测试环境 pr: ‘http://prodect’, // 生产环境 mock:’ https://easy-mock.com/mock/5cb4762ae14be30f81aee1a6/mock' // mock环境}export default base;推荐一个好用的在线生成前端mock数据的网站,easy-mock注册后把mock这边的地址改成你的mock地址即可对接mock接口get(url, params) { return new Promise((resolve, reject) => { axios({ method: ‘get’, url: url, params: params }).then(res => { // console.log(res); if (res.data.errCode !== 0) Toast.failed(res.data.errMsg); else resolve(res.data); }, err => { reject(err); Toast.failed(err.message); }); }); },此处的 errMsg是我自己配的mock接口状态码,请根据自己的项目自行更改,如果不希望在全局使用toast 把此处的toast注释掉即可serve下的 http.js 用于封装axios如果是微信公众号的项目,需要token验证,在此处配置token的获取,并且添加到请求头数据管理简单项目无需使用vuex,反而加重项目数据交互较多,难以管理时,使用vuex提高效率按照 store 下的demo 这个module作为参考,分模块编写。已经集成了vuex数据持久化插件,默认为所有state数据都存入session如果要更改存储位置,或者希望局部存入,自行修改plugin配置,配置方法vuex-persistedstate自适应方案(rem+vw)参考凹凸实验室发表一篇的布局方案 利用视口单位实现适配布局在static/style/global下配置// 主题色配置$theme-color:#4fc08d;// rem 单位换算:定为 75px 只是方便运算,750px-75px、640-64px、1080px-108px,如此类推$vw_fontsize: 75; // iPhone 6尺寸的根元素大小基准值@function rem($px) { @return ($px / $vw_fontsize ) * 1rem;}// 根元素大小使用 vw 单位$vw_design: 750;html { font-size: ($vw_fontsize / ($vw_design / 2)) * 100vw; // 同时,通过Media Queries 限制根元素最大最小值 @media screen and (max-width: 300px) { font-size: 64px; } @media screen and (min-width: 750px) { font-size: 108px; }}// body 也增加最大最小宽度限制,避免默认100%宽度的 block 元素跟随 body 而过大过小body { max-width: 750px; min-width: 300px;}默认配置以750作为设计稿基准同时可以配置最大最小适配范围上述功能都集成在view/demo项目里,可以参考demo具体了解开发优化alias:在vue.config查看alias 路径,自行添加或者修改mixins: 已经全局配置,在static 下的mixin 进行样式函数的编写用户体验使用路由懒加载,提高加载速度全局动态配置了路由切换动画,为了正确显示切换动画,在配置路由时一定要写明路由层级,切换动画时会根据层级比较作为判断结语没啥干货,权当服务一下前端初学者和伸手党,同时把自己不成熟的框架拿出来供大家检阅,发现不足之处。项目地址Github如果有帮助到您,厚着脸皮希望给个小星星 ...

April 17, 2019 · 1 min · jiezi

vuex持久化插件-解决浏览器刷新数据消失问题

众所周知,vuex的一个全局状态管理的插件,但是在浏览器刷新的时候,内存中的state会释放,通常的解决办法就是用本地存储的方式保存数据,然后再vuex初始化的时候再赋值给state,手动存再手动取会觉得很麻烦,这个时候就可以使用vuex的插件vuex-solidification插件地址: vuex-solidification , 欢迎star插件原理vuex有一个hook方法:store.subscribe((mutation, state) => {}) 每次在mutation方法执行完之后都会调用这个回调函数,返回执行完毕之后的state使用方法安装npm install –save vuex-solidification引入及配置import Vue from ‘vue’import Vuex from ‘vuex’import count from ‘./count/index.js’;import createPersistedState from ‘vuex-solidification’;Vue.use(Vuex);const store = new Vuex.Store({ state: { count: { value: 0, num: 1 }, pos: 1 } plugins: [ // 默认存储所有state数据到localstorage createPersistedState() ]});插件参数说明createPersistedState({options}) : Functionoptions里面可以有🔑 String 存储到localStorage, sessionStorage 中对象的key,默认为vuexlocal: Object 和 session: Object, 分别代表localStorage的配置和sessionStorage的配置local 和 session 里面可以有: include: Array 和 exclude: Array配置例子createPersistedState({ local: { include: [‘count.value’] }})/* hook钩子触发之后,localstorage里面存储的对象为: { count: { value: 0, } }/createPersistedState({ local: { exclude: [‘count.value’] }})/ hook钩子触发之后,localstorage里面存储的对象为: { count: { num: 1 }, pos: 1 }/createPersistedState({ session: { include: [‘count.value’] }})/ hook钩子触发之后,sessionstorage里面存储的对象为: { count: { value: 0, } }/createPersistedState({ session: { exclude: [‘count.value’] }})/ hook钩子触发之后,sessionstorage里面存储的对象为: { count: { num: 1 }, pos: 1 }/createPersistedState({ session: { include: [‘count’] }, local: { include: [‘pos’] }})/ hook钩子触发之后, sessionstorage里面存储的对象为: { count: { value: 0, num: 1 }, } sessionstorage里面存储的对象为: { pos: 0 }*/代码例子Check out the example on CodeSandbox.写在最后欢迎交流,提issue和pr, ...

April 12, 2019 · 1 min · jiezi

vuex源码浅析

前言当前版本是3.1.0,这个版本主要引入了mutations的异步前后的两个钩子debug的项目是官方例子里的shopping-cart,这个项目的有两个modules,可以看的比较直观。个人理解vuex就是维护一个Store对象的状态树。而他下一级如果直接就是state的话,那么当数据量大时就会很影响性能,通过分治的思想,引入了modules。源码constructor主要是进行一些初始化的操作if (!Vue && typeof window !== ‘undefined’ && window.Vue) { install(window.Vue) }这里主要是判断是不是全局引入的vue及是不是浏览器环境,全局引入就自动注册进vue里,就是用npm引入的那种vue.use(vuex),install主要实现了applyMixin方法export default function (Vue) { const version = Number(Vue.version.split(’.’)[0]) if (version >= 2) { Vue.mixin({ beforeCreate: vuexInit }) } else { // override init and inject vuex init procedure // for 1.x backwards compatibility. const _init = Vue.prototype._init Vue.prototype._init = function (options = {}) { options.init = options.init ? [vuexInit].concat(options.init) : vuexInit _init.call(this, options) } } /** * Vuex init hook, injected into each instances init hooks list. */ function 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的版本,如果是vue2就调用mixin,把vuexInit插入到beforeCreate钩子之前,vue1就不用说了….回到constructor const { plugins = [], strict = false } = options // store internal state this._committing = false this._actions = Object.create(null) this._actionSubscribers = [] this._mutations = Object.create(null) this._wrappedGetters = Object.create(null) this._modules = new ModuleCollection(options) this._modulesNamespaceMap = Object.create(null) this._subscribers = [] this._watcherVM = new Vue()这里主要是初始化store内部的一些对象,关键是这个ModuleCollection,他把modules初始化之后,之后mutations改变state直接照这个_modules就好了这里我们直接看代码可能会很难看懂,可以直接运行官方例子进行加深理解。这里的options是new Vuex.Store({ modules: { cart, products }, strict: debug, plugins: debug ? [createLogger()] : []})我们进入ModuleCollection,constructor (rawRootModule) { // register root module (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) { this.root = newModule } else { const parent = this.get(path.slice(0, -1)) parent.addChild(path[path.length - 1], newModule) } // register nested modules if (rawModule.modules) { forEachValue(rawModule.modules, (rawChildModule, key) => { this.register(path.concat(key), rawChildModule, runtime) }) } }这里的逻辑是给options里的每一个modules new一个modules对象,放在_children下,options放在_rawModule下,此时的this。_modulesModuleCollection {root: Module}root: Moduleruntime: falsestate: {}_children:cart: Module {runtime: false, _children: {…}, rawModule: {…}, state: {…}}products: Module {runtime: false, children: {…}, rawModule: {…}, state: {…}}rawModule:modules: {cart: {…}, products: {…}}plugins: [ƒ]strict: true__proto: Objectnamespaced: (…)proto: Object__proto: Object回到constructor const store = this const { dispatch, commit } = 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) }这里是为了强制this指向的是store先看commit,在vuex里,改变state的唯一方法是提交commit来触发_mutations,actions最后也是通过_mutations来改变的 commit (_type, _payload, _options) { // check object-style commit const { type, payload, options } = unifyObjectStyle(_type, _payload, _options) const mutation = { type, payload } const entry = this._mutations[type] if (!entry) { if (process.env.NODE_ENV !== ‘production’) { console.error([vuex] unknown mutation type: ${type}) } return } this._withCommit(() => { entry.forEach(function commitIterator (handler) { handler(payload) }) }) 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’ ) } }先通过_type来找到_mutations,然后改变state之后,触发_subscribers,通知订阅者,实现数据的双向绑定。dispatchdispatch (_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 } try { this._actionSubscribers .filter(sub => sub.before) .forEach(sub => sub.before(action, this.state)) } catch (e) { if (process.env.NODE_ENV !== ‘production’) { console.warn([vuex] error in before action subscribers: ) console.error(e) } } const result = entry.length > 1 ? Promise.all(entry.map(handler => handler(payload))) : entry0 return result.then(res => { try { this._actionSubscribers .filter(sub => sub.after) .forEach(sub => sub.after(action, this.state)) } catch (e) { if (process.env.NODE_ENV !== ‘production’) { console.warn([vuex] error in after action subscribers: ) console.error(e) } } return res }) }这里由于_actions是异步的,所以会判断他是不是Promise,不是就new 一个Promise给他,这里需要的是_actionSubscribers运行了两次,这是这个版本加上的两个action的勾子函数。回到constructorinstallModule(this, state, [], this._modules.root)这是注册_mutations, _actions等数据的resetStoreVM(this, state)这是注册订阅者的 // apply plugins plugins.forEach(plugin => plugin(this)) const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools if (useDevtools) { devtoolPlugin(this) }这是装载插件,例如vue-devtools ...

April 12, 2019 · 3 min · jiezi

如何理解Vuex的设计哲学? —— 这,分明就是人生呀!

Veux的哲学,实质上是人生的哲学。看一看这张图。释义:魂: 你的灵魂,你的心中所想。形: 你的身体,你的外在形态。人间: 你的所作所为,所见所闻。想一想,人活着不就是如此吗? 你的灵魂,控制着你的身体。你的身体,与世界进行互动,改变并影响着人间。人间所发生的一切,又时时刻刻塑造着你的灵魂。闭上眼睛,跟随自己去体验:你心中起了一个想法,你的身体也随着这个念头去做出行动。 而这个行动,又改造了你周边的世界。同时,你也听见、看见了人间发生的事,你的思想你的灵魂在随之改变。形、魂、人间,即是Vue单向数据流。我们看一个小型Vue应用。new Vue({ // 魂(State):你的心中所想 data () { return { money: 0 } }, // 形(View): 你的外在形态 template: &lt;div&gt;我有这么多钱:{{ money }}&lt;/div&gt; , // 人间(Action):你的所做所为所见所闻 methods: { earn_money () { this.money++ } }})现代人似乎把一切都简化了——只为“钱”。人活着就沦为了这样的东西:魂,即state: 心里只有钱,即money。形:即view: 有多少钱都炫富出来。<div>我有这么多钱:{{ money }}</div>人间: 所做的只有一件事,即赚钱:earn_money () { this.money++}闭上眼睛,跟随自己去体验:你是一个纯粹为了钱的人。 你心中所想,只有钱。 你的外在表现,就是你有多少钱。你的一切行为,都是为了钱。人生,远不止是钱。这时,你需要Veux。如果人真的能活得这么纯粹,反倒是好事。但真实的人生,往往更复杂:赚钱不简单,你可能需要完成一系列复杂的工作,才能拿到钱。有钱,也无法表现出来。但你可以通过穿着、房子、车子,表现出来。思想更复杂,你心里远不止是钱,钱是为了守住更多的东西,例如尊严、爱情、亲情。当面对捉摸不定的思想、深藏不露的人性、变幻莫测的人间…你需要一套处世哲学。当面对大批量的State,无法直接取值的View,耦合严重的Action的时候,你就需要Vuex了。如何管理,一个更复杂的人生?人生,就是一个大型应用。Vuex就是人的处世哲学。当你的人生乱成一团糟时,你可以试试用Veux的方式,来梳理自己的生活。第一步: 想一想,你现在心里在意哪些事?State,就相当于你心中在意的事。 那如何去维护这些State呢? 钱、父母的健康、爱情,并不是轻轻松松可以得到的,你需要缕清楚之间的关系。Vuex的解决方案: 分解为目标和行动。Vuex把复杂的“人间”,拆解成了行动(Action)与目标(Mutation)。Mutation,即目标,它必须是同步函数的。 它的功能必须是直截了当的,可以简单到“让XX更多”或"让XX归零"的程度。Action,即行动,在其中可以包含异步操作(如Ajax获取数据),并组合一个个小目标。View,只能发起一个个行动(dispatch)。Action,在这里可以执行多个同步/异步函数、发起多个行动(dispatch),达成(commit)一个个小目标。Mutation, 只能改变state(matate)。State, 直接影响我的形态,这是vue的工作,不作表述。当然,我们有时提取出一些更“有用”的状态,相当于state的计算参数,即Getters,State的计算属性。所以, Action + Mutation + state, 以及dispatch和commit两个函数,就构成了Veux的逻辑。我们也可以这样来管理生活第二步: Mutation: 写下目标第三步: Action: 写下行动第四步: Getters: 写下其它指标代码化store.jsconst store = new Vuex.Store({ state: { money: 10000000, energy: 60, love: 30, parent_health:50 }, mutations: { earn_money (state) { state.money += 1000 }, pay_money (state,payload) { state.money -= payload.money_cost if(state.money < 0) state.money = 0 }, restore_energy(state, payload){ state.energy = state.energy + payload.sleep_hour*10 if(state.energy > 100) state.energy = 100 }, use_energy(state,payload){ state.energy -= payload.energy_cost if(state.energy < 0) state.energy = 0 }, be_romantic(state){ state.love += 10 }, enhance_harmony(state){ state.love += 5 parent_health += 10 } }, actions: { async work({commit}){ commit(‘use_energy’) await wait(8) commit(’earn_money’) }, send_gift({commit}){ commit(‘pay_money’,{money_cost:10000}) commit(‘be_romantic’) }, async family_walk({commit}){ commit(‘use_energy’,{energy_cost = 10}) await wait(1) commit(’enhance_harmony’) } async sleep({commit}) { await wait(8) commit(‘restore_energy’) }, async dating({dispatch, state}){ dispatch(‘send_gift’) if(state.love >80){ await dispatch(‘sleep’) } } }, getters(){ location: (state)=>{ return state.money>10000000 ? 4 : state.money>5000000 ? 5: null }, walk_time: (state)=>{ return Math.min(energy, parent_health) } }})module.exports = store在Vue项目中使用Vuex首先,在app.js中导入vueximport Vue from ‘vue’import App from ‘./components/App.vue’import store from ‘./store’new Vue({ el: ‘#app’, store, render: h => h(App)})之后,在编写.vue文件时,利用mapState, mapGetters, mapActions, 可以访问到StateGettersActions注意:mapState和mapGetters必须在computed中访问,因为它们返回的是对象,所以需要用拓展符…进行展开。mapActions则是将Action挂载到methods上,也需要用拓展符…进行展开。<script>import { mapState, mapGetters, mapActions } from ‘vuex’export default { computed: { …mapState([‘money’,’love’]), …mapGetters([’location’] } methods: { …mapActions([‘family_walk’,‘dating’]) }, created () { this.$store.dispatch(‘work’) }}</script>结语成功学中有两个很重要的概念,叫“目标导向” “阶段性执行”,回头来看,不正是Veux的哲学吗?Mutation目标导向: 设定简单的目标,改变StateAction阶段性执行: 执行一个个Matation、异步函数、和其它阶段性执行。现在,你不仅完全理解了Veux的设计哲学,你更懂得了如何管理人生。你可以问自己三个问题:梳理思绪: 我心中最在意的事。设立目标:这些最在意的事,会发生怎样改变?创建行动:连接一个个小目标,形成你要做的行动。然后,行动吧! 只要做正确的事,你所期待的,就一定会发生! ...

April 7, 2019 · 2 min · jiezi

Vuex源码学习(八)模块的context如何被创建以及它的作用

你不知道action与mutation怎么被调用的?赶紧回去看啊Vuex源码学习(七)action和mutation如何被调用的(调用篇))上两个小节已经讲述了commit与dispatch如何调用mutation与action的,但是action中有几个参数感觉涉及到了一些我们遗漏(故意不讲)的点。模块的context在installModule的时候 给每个模块绑定了一个属性context。通过makeLocalContext函数创建的,在注册action、mutation和getters都有使用。这个context是什么呢?makeLocalContext函数创建了一个什么东西返回值local对象 由两个方法、两个属性构成的。这个目的是什么?创建局部模块的dispatch、commit、getters、state也就是这个东西我们按照类型分析dispatch与commit// 查看全名,如果没有全名 可能是根模块或者没有设置命名空间const noNamespace = namespace === ‘’;// 如果没有全名 就使用全局(store)上的disptach// 有全名的话 构建一个新的dispatch // 这个新的dispatch仍然接收三个参数(与store上的dispatch一样)// dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => { //unifyObjectStyle 对额外传入的_options没有任何处理 只是确定一下位置 const args = unifyObjectStyle(_type, _payload, _options) // options 值没有发生变化 const { payload, options } = args let { type } = args // 在disptach的时候是否指定选择root(根) // 如果options设置为{root : true} 那么就会跳过下面 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 } } // 调用(补全名字后)的action return store.dispatch(type, payload) },这段代码我们可以看出来,local对象(也就是模块的context属性)中的dispacth会在未指定使用根模块名字时,会把dispatch调用的名字强行加上这个模块的全名,用这个dispatch调用的action都会变成你这个模块下的action所以local中的dispatch与store中的disptach有什么不同通俗的讲我们想要调用A模块(有命名空间的某个action B)需要做的是this.$store.dispatch(‘A模块的全名/B的名字’); 在A模块的action中想要使用dispatch来做一些事情。actions.jsexport const ajaxGetUserName = ({dispatch})=>{ // 这个时候用dispatch调用自己模块内的其余的action不需要加上全名 dispatch(‘ajaxGetUserAge’); // 想要变成和根模块一样的dispatch 需要加上一个options注明{root : true} // 这个时候dispatch就会变成全局的 不会主动帮你拼接全名了}export const ajaxGetUserAge = () => { // do something}同理local对象下的commit也是做了同样的事情,这里就不多加解释了,相信聪明的你早就可以举一反三了。两个方法说完了,下面该讲两个属性了getters与state这两个属性就是我们的getters与state,但是这是我们local对象中,也就是局部模块下的getters与state。getters与state如何创建的getters首先判断全名是不是为空,为空就返回store对象的getters,有的话就创建局部getters。与其说是创建不如说是代理如何创建局部的getters? 代理的方式makeLocalGetters源码function makeLocalGetters (store, namespace) { // 设计思想 //其实我们并不需要创建一套getters, // 只要我们在local中通过getters来获取一些局部模块的值的时候, // 可以被代理到真正存放这些getters的地方。 // 创建代理对象 const gettersProxy = {} // 找到切割点 const splitPos = namespace.length Object.keys(store.getters).forEach(type => { // skip if the target getter is not match this namespace // 得去getters里面找一下有没有这个namespace为前缀的getter。 // 没有就找不到了 if (type.slice(0, splitPos) !== namespace) return // extract local getter type // 拿到模块内注册的那个局部的getter名字 // 全名是set/getName // localType就是getName const localType = type.slice(splitPos) // Add a port to the getters proxy. // Define as getter property because // we do not want to evaluate the getters in this time. // 完成代理任务, // 在查询局部名字是被代理到对应的store.getters中的(全名)getter Object.defineProperty(gettersProxy, localType, { get: () => store.getters[type], enumerable: true }) }) //返回代理对象 return gettersProxy}创建局部的getters就是一个代理的过程,在使用模块内使用(没有加上命名空间的)getters的名字,会被代理到,store实例上那个真正的(全名的)getters。state这个相对来说就简单很多了与代理类似,只是state只需要代理到state中对应那个模块的state,这个就比较简单了。创建完毕context是如何被创建的大家已经比较了解了。context的作用是什么?(local就是contenxt)之前说过注册mutation、action、getters都用到了local。用他们干什么?一一介绍1. 注册mutation我们注册的mutation在被commit调用时,使用的state是局部的state,当前模块内的state,所以不用特殊方式mutation无法更新父(祖先)模块和兄弟模块的内容。2. 注册dispatchdispatch是可以调用到模块内的mutation、disptach,也就是说它有更新模块内数据的能力,但是只给了dispatch传入了store的getters与state(虽然有了这俩你想要什么放在vuex的数据都能得到),并没有给store的dispatch与mutation。这就说名dispatch可以查看store中的所有数据,你放在vuex里面的数据我都可以看,但是你想改不使用特殊手段,不好意思只能改自己模块的。3. 注册gettersgetters并没有改变数据的能力,你愿意怎么操作数据都可以,模块内的数据,全模块的数据都可以给你,你愿意怎么计算都可以。在注册中我们可以看到,vuex对这个改变数据的权限控制的很严格,但是查看数据控制的很松,改只能改自己模块的,查你愿意怎么看都OK。总结context(也是local)是经过一个makeLocalContext的函数创建的,里面有局部的dispatch、commit方法和getters、state属性。局部的方法属性都是只能访问局部模块内的,除非在使用时额外传入options({root:true})来解开局部模块的限制。局部的getters是通过makeLocalGetters来实现的,主要思想是依靠代理的方式,把局部的名字的getter代理到store的getters中那个全名的getter。context 的作用可以帮助dispatch与commit控制更新数据的权限,帮助模块内getters拿到局部与全模块的数据。这个章节结束,我们所有和模块有关的内容就已经完结了。对于模块做一个小的总结。模块的意义模块与模块链接把Vuex初始化传入的内容,整理成一个方便处理的模块树(方便)模块让action、mutation、getters、state都有了自己的全名(设置namespaced为true),起名字不再被约束,减少了命名冲突。模块还给action、mutation、getters提供了局部上下文(context)让模块内的这些方法和属性,可以方便的修改模块内的数据以及获取全模块与模块内的数据。dispatch与commit也对模块进行了全力的支持(不支持不白做了吗),所以模块为Vuex提供了很多方便,方便的去获取数据、修改数据。那么Vuex真正的数据仓库在哪里?数据都存储在哪里?我们下一章见我是一个应届生,最近和朋友们维护了一个公众号,内容是我们在从应届生过渡到开发这一路所踩过的坑,已经我们一步步学习的记录,如果感兴趣的朋友可以关注一下,一同加油~ ...

April 2, 2019 · 2 min · jiezi

DIY 一个 Vuex 持久化插件

在做 Vue 相关项目的时候,总会遇到因为页面刷新导致 Store 内容丢失的情况。复杂的项目往往涉及大量的状态需要管理,如果仅因为一次刷新就需要全部重新获取,代价也未免太大了。那么我们能不能对这些状态进行本地的持久化呢?答案是可以的,社区里也提供了不少的解决方案,如 vuex-persistedstate,vuex-localstorage 等插件,这些插件都提供了相对完善的功能。当然除了直接使用第三方插件以外,我们自己来 DIY 一个也是非常容易的。这个持久化插件主要有2个功能:能够选择需要被持久化的数据。能够从本地读取持久化数据并更新至 Store。接下来我们会从上述两个功能点出发,完成一个 Vuex 持久化插件。Gist地址:https://gist.github.com/jrain…在线体验地址:https://codepen.io/jrainlau/p…一、学习写一个 Vuex 插件引用 Vuex 官网 的例子:Vuex 的 store 接受 plugins 选项,这个选项暴露出每次 mutation 的钩子。Vuex 插件就是一个函数,它接收 store 作为唯一参数:const myPlugin = store => { // 当 store 初始化后调用 store.subscribe((mutation, state) => { // 每次 mutation 之后调用 // mutation 的格式为 { type, payload } })}然后像这样使用:const store = new Vuex.Store({ // … plugins: [myPlugin]})一切如此简单,关键的一点就是在插件内部通过 store.subscribe() 来监听 mutation。在我们的持久化插件中,就是在这个函数内部对数据进行持久化操作。二、允许用户选择需要被持久化的数据首选初始化一个插件的主体函数:const VuexLastingPlugin = function ({ watch: ‘’, storageKey: ‘VuexLastingData’}) { return store => {}}插件当中的 watch 默认为全选符号 ,允许传入一个数组,数组的内容为需要被持久化的数据的 key 值,如 [‘key1’, ‘key2’] 等。接着便可以去 store.subscribe() 里面对数据进行持久化操作了。const VuexLastingPlugin = function ({ watch: ‘’}) { return store => { store.subscribe((mutation, state) => { let watchedDatas = {} // 如果为全选,则持久化整个 state // 否则将只持久化被列出的 state if (watch === ‘’) { watchedDatas = state } else { watch.forEach(key => { watchedDatas[key] = state[key] }) } // 通过 localStorage 持久化 localStorage && localStorage.setItem(storageKey, JSON.stringify(watchedDatas)) }) }}按照 Vuex 的规范,有且只有通过 mutation 才能够修改 state,于是按照上面的步骤,我们便完成了对数据进行实时持久化的工作。这里也有一个小问题,就是写入 watch 参数的数组元素必须是 state 当中的最外层 key ,不支持形如 a.b.c 这样的嵌套 key。这样的功能显然不够完善,所以我们希望可以增加对嵌套 key 的支持。新建一个工具函数 getObjDeepValue():function getObjDeepValue (obj, keysArr) { let val = obj keysArr.forEach(key => { val = val[key] }) return val}该函数接收一个对象和一个 key 值数组, 返回对应的值,我们来验证一下:var obj = { a: { name: ‘aaa’, b: { name: ‘bbb’, c: { name: ‘ccc’ } } }}getObjDeepValue(obj, ‘a.b.c’.split(’.’))// => { name: “ccc” }验证成功以后,便可以把这个工具函数也放进 store.subscribe() 里使用了: store.subscribe((mutation, state) => { let watchedDatas = {} if (watch === ‘’) { watchedDatas = state } else { watch.forEach(key => { // 形如 a.b.c 这样的 key 会被保存为 deep_a.b.c 的形式 if (data.split(’.’).length > 1) { watchedDatas[deep_${key}] = getObjDeepValue(state, key.split(’.’)) } else { watchedDatas[key] = state[key] } }) } localStorage && localStorage.setItem(storageKey, JSON.stringify(watchedDatas)) })经过这一改造,通过 watch 写入的 key 值将支持嵌套的形式,整个插件将会更加灵活。三、从本地读取持久化数据并更新至 Store从上面的步骤我们已经能够灵活监听 store 里的数据并持久化它们了,接下来的工作就是完成如何在浏览器刷新之后去读取本地持久化数据,并把它们更新到 store。为插件添加一个默认为 true 的选项 autoInit,作为是否自动读取并更新 store 的开关。从功能上来说,刷新浏览器之后插件应该自动读取 localStorage 里面所保存的数据,然后把它们更新到当前的 store。关键的点就是如何把 deep_${key} 的值正确赋值到对应的地方,所以我们需要再新建一个工具函数 setObjDeepValue():function setObjDeepValue (obj, keysArr, value) { let key = keysArr.shift() if (keysArr.length) { setObjDeepValue(obj[key], keysArr, value) } else { obj[key] = value }}该函数接收一个对象,一个 key 值数组,和一个 value ,设置对象对应 key 的值,我们来验证一下:var obj = { a: { name: ‘aaa’, b: { name: ‘bbb’, c: { name: ‘ccc’ } } }}setObjDeepValue(obj, [‘a’, ‘b’, ‘c’], 12345)/**obj = { a: { name: ‘aaa’, b: { name: ‘bbb’, c: 12345 } }}/有了这个工具方法,就可以正式操作 store 了。 if (autoInit) { const localState = JSON.parse(storage && storage.getItem(storageKey)) const storeState = store.state if (localState) { Object.keys(localState).forEach(key => { // 形如 deep_a.b.c 形式的值会被赋值到 state.a.b.c 中 if (key.includes(‘deep_’)) { let keysArr = key.replace(‘deep_’, ‘’).split(’.’) setObjDeepValue(storeState, keysArr, localState[key]) delete localState[key] } }) // 通过 Vuex 内置的 store.replaceState 方法修改 store.state store.replaceState({ …storeState, …localState }) } }上面这段代码会在页面初始化的时候读取 storage 的值,然后把形如 deep_a.b.c 的值提取并赋值到 store.state.a.b.c 当中,最后通过 store.replaceState() 方法更新整个 store.state 的值。这样便完成了从本地读取持久化数据并更新至 Store 的功能。四、案例测试我们可以写一个案例,来测试下这个插件的运行情况。在线体验:https://codepen.io/jrainlau/p…App.vue<template> <div id=“app”> <pre>{{$store.state}}</pre> <button @click=“updateA”>updateA</button> <button @click=“updateX”>UpdateX</button> </div></template><script>export default { name: ‘app’, methods: { updateA () { let random = Math.random() this.$store.commit(‘updateA’, { name: ‘aaa’ + random, b: { name: ‘bbb’ + random, c: { name: ‘ccc’ + random } } }) }, updateX () { this.$store.commit(‘updateX’, { name: Math.random() }) } }}</script>store.jsimport Vue from ‘vue’import Vuex from ‘vuex’import VuexPlugin from ‘./vuexPlugin’Vue.use(Vuex)export default new Vuex.Store({ plugins: [VuexPlugin({ watch: [‘a.b.c’, ‘x’] })], state: { a: { name: ‘aaa’, b: { name: ‘bbb’, c: { name: ‘ccc’ } } }, x: { name: ‘xxx’ } }, mutations: { updateA (state, val) { state.a = val }, updateX (state, val) { state.x = val } }})从案例可以看出,我们针对 state.a.b.c 和 state.x 进行了数据持久化。在整个 state.a 都被修改的情况下,仅仅只有 state.a.b.c 被存入了 localStorage ,数据恢复的时候也只修改了这个属性。而 state.x 则整个被监听,所以任何对于 state.x 的改动都会被持久化并能够被恢复。尾声这个 Vuex 插件仅在浏览器环境生效,未曾考虑到 SSR 的情况。有需要的同学可以在此基础上进行扩展,就不再展开讨论了。如果发现文章有任何错误或不完善的地方,欢迎留言和我一同探讨。 ...

April 2, 2019 · 3 min · jiezi

Vuex源码学习(七)action和mutation如何被调用的(调用篇)

前置篇不会那可不行!Vuex源码学习(六)action和mutation如何被调用的(前置准备篇)在前置准备篇我们已经知道被处理好的action与mutation都被集中放置在哪里了。下面就要看dispacth和commit如何去调用它们。dispatch与commit的实现commit:首先呢我们要校正参数,把传入的参数整理下主要是处理这种形式// 接收一个对象this.$store.commit({type : ‘setName’,name : ‘xLemon’});this.$store.commit(‘setName’,{name : ‘xLemon’});这两个基本等价。只是第一种方式mutation接收的payload会比第二种多一个type属性,整理的部分并不关键type是我们要找的mutation的名字,如何找到mutation呢?通过 this._mutations[type] 找到要执行的mutation所以type一定要是mutation的全名所以我们通过commit找mutation的时候有命名空间的时候就要输入全名,(那种带很多/的)。没有这个名字的mutation容错处理,然后在withCommit函数的包裹下,完成了mutation的执行(所有mutation啊,全名相同的mutation都会被执行)。然后呢遍历_subscribers里面的函数进行执行。_subscribers这是什么?在一开始我们可以注册一些内容(函数),在commit完成时被通知执行。(观察者模式)如何注册在这一章就不多讲了!后面章节会统一讲述。这就是commit做的事情。dispatch呢?与commit大同小异,也有一个_actionSubscribers的属性,在dispatch执行之前全部调用。对于dispatch Vuex推荐的是放置异步任务,在注册action的时候已经被强制promise化了,所以有多个同名action会通过Promise.all来处理。在action的前后都有对应的钩子函数执行。固定disptach与commit的this指向//在vue组件内一个方法如果多次使用dispatch和commit,就会很麻烦。this.$store.dispatch(‘xxx’,payload);this.$store.commit(‘xxx’,payload);const {dispatch,commit} = this.$store;//这相当于创建变量,然后把this.$store的dispatch与commit赋值给它们。//有经验的都应该知道,函数dispatch和commit的this指向在严格模式下指向undefined。// 非严格模式下指向window,// 刚才的源码中我们也看到了,dispatch和commit都依赖于Store实例。怎么办??解决方法如下:dispatch和commit是Store原型链上的方法,在constructor内注册了构造函数内的方法,把原型上的dispatch和commit进行了一个this指向的强制绑定,通过call让两个方法永远指向Store的实例上,保证了dispatch和commit不会因为赋值给其余变量等操作导致this指向改变从而发生问题action与mutation函数的参数都有哪些?怎么来的?看一个简单的mutation:export const setName = function(state,payload) { state.name = payload;};这个时候不经意间有了一个疑惑?state哪里来的。这就要从mutation被注册的函数内找原因了handle是我们要被注册的一个mutation,entry是这个同名mutation的容器(存储所有的这个名字的mutation,一般只有一个)在吧handle放入entry的过程中,我们发现,entry是被一个函数包裹起来,然后将local.store和payload绑定到这个handle的参数上,然后把handle的this指向锁定到了Store实例上,所以mutation在被commit调用的时候只传入了一个参数payload,但是mutation函数执行的时候就有了两个参数。下面看一下action:按照刚才的分析action在被dispatch调用的时候会接收一个参数,但是action执行的时候会接收两个参数,第一个参数是个对象里面有六项,真的是包罗万象啊。。。我们看一下这个对象的六项{ dispatch : local.dispatch, commit:local.commit, getter: local.getters, state: local.state, rootGetters:store.getters, rootState:store.state}分为两种一种是local的、一种是store的。mutation中好像也有使用local,那么local的意义是什么呢?我们下一节会讲述local的含义以及makeLocalContext、makeLocalGetters两个函数的作用。还是要给个小线索,在模块树的层级很高的时候,我们在使用state的时候要一层一层找寻吗?总结dispatch与commit在执行的时候,都是根据传入的全名action、mutation去Store实例的_actions、_mutations中找到对应的方法,进行执行的。dispatch和commit中都使用了this(指向Store实例),为了防止this的指向改变从而出现问题,就把原型上的dispatch与commit在constructor中处理到了实例本身,这个过程做了this指向的绑定(call)。action和mutation在执行的时候,参数都是在注册的时候就绑定了一部分,所以action与mutation在使用的时候可以访问到state等很多内容。下一章;离开action与mutation 来讨论一下local的含义以及makeLocalContext、makeLocalGetters两个函数的作用我是一个应届生,最近和朋友们维护了一个公众号,内容是我们在从应届生过渡到开发这一路所踩过的坑,已经我们一步步学习的记录,如果感兴趣的朋友可以关注一下,一同加油~

April 1, 2019 · 1 min · jiezi

Vuex源码学习(六)action和mutation如何被调用的(前置准备篇)

module与moduleCollection你一定要会啊!Vuex源码学习(五)加工后的module在组件中使用vuex的dispatch和commit的时候,我们只要把action、mutation注册好,通过dispatch、commit调用一下方法名就可以做到。使用方式vue组件内//in vue componentthis.$store.commit(‘setName’,{name : ‘xLemon’});this.$store.commit(’list/setName’,{name : ‘xLemon’});vuex的mutation中// in mutation.jsexport const setName = function(state,payload){ state.name = payload.name;}// in list mutation.js 在名为list的模块下的mutation(有自己的命名空间)export const setName = function(state,payload){ state.name = payload.name;}我们传递的只是一个字符串,commit是如何找到注册mutation时的同名方法的呢?有命名空间的这种mutation是如何被找到并且执行的呢?上源码属性的意义_actions用于存放所有注册的action_mutations用于存放所有注册的mutation被注册的action和mutation如何被放到对应的属性中的呢?轮到installModule函数要出马了。installModule的意义是初始化根模块然后递归的初始化所有模块,并且收集模块树的所有getters、actions、mutations、以及state。看一下installModule的代码,installModule并不是在类的原型上,并不暴露出来,属于一个私有的方法,接收五个参数。store(Vuex.store的实例对象。rootState (根结点的state数据)。path 被初始化模块的path(前两张讲过path的意义)。module 被初始化的模块。hot 热更新(并不关键)function installModule (store, rootState, path, module, hot) { const isRoot = !path.length // 获取全名 const namespace = store._modules.getNamespace(path) // register in namespace map if (module.namespaced) { // 设置命名空间对照map store._modulesNamespaceMap[namespace] = module //console.log(store._modulesNamespaceMap); } // set state if (!isRoot && !hot) { const parentState = getNestedState(rootState, path.slice(0, -1)) const moduleName = path[path.length - 1] // 把子模块的state(数据)绑定到父模块上(按照层级) store._withCommit(() => { Vue.set(parentState, moduleName, module.state) }) } const local = module.context = makeLocalContext(store, namespace, path) // 使用模块暴露出来的方法来注册mutation、action、getter module.forEachMutation((mutation, key) => { const namespacedType = namespace + key registerMutation(store, namespacedType, mutation, local) }) module.forEachAction((action, key) => { const type = action.root ? key : namespace + key const handler = action.handler || action registerAction(store, type, handler, local) }) module.forEachGetter((getter, key) => { const namespacedType = namespace + key registerGetter(store, namespacedType, getter, local) }) module.forEachChild((child, key) => { installModule(store, rootState, path.concat(key), child, hot) })}这个函数虽然只有40多行,但处于一个承上启下的关键点,这一章只会分析如何收集mutation与action其余的内容会再下一章讲述。installModule首先获取一下这个模块的命名(我称之为全名)依赖_modules(ModuleCollection实例对象)的getNamespace方法。根据模块的path,path有从根结点到当前节点这条路径上按顺序排序的所有模块名(根结点没有模块名,并没有设置在path,所以根模块注册时是个空数组,他的子模块的path就是[子模块的名字])。那么Vuex如何整理模块名的呢?效果:如果这个模块有自己的命名空间(namespaced为true)这个模块的全名就是父模块的全名+自己的模块名,如果这个模块没有自己的命名空间(namespaced为false)这个模块的全名就是父模块的全名为什么会是这样?分析一下代码getNamespace (path) { let module = this.root //根模块 return path.reduce((namespace, key) => { //根模块的path是个空数组不执行 // path的第一项是根模块的儿子模块。 // 获取儿子模块 并且将替换module (path的下一项就是儿子模块中的子模块了) // 下次累加 就是这个module(轮到子模块了)去找它(子模块)的子模块 module = module.getChild(key) // 查看儿子模块是不是设置了命名空间 //如果设置了这个模块的全名就增加自己的模块名和一个’/‘分割后面的模块名, //没有的话返回一个’’, // reduce累加可以把这个名称进行累加 return namespace + (module.namespaced ? key + ‘/’ : ‘’) }, ‘’)}获取完模块的全名了,之后我们看一下这两个函数module.forEachActionmodule.forEachMutation在上一章节module提供了遍历自己内部的action、mutation的方法。 module.forEachMutation((mutation, key) => { const namespacedType = namespace + key registerMutation(store, namespacedType, mutation, local)})module.forEachAction((action, key) => { const type = action.root ? key : namespace + key const handler = action.handler || action registerAction(store, type, handler, local) })const namespacedType = namespace + key 这句话 就是拼接出真正的mutation、action的名字模块全名+mutation/action的名字。也就是一开始我举例的list/setName是这个mutation的全名(被调用的时候用)this.$store.commit(’list/setName’,{name : ‘xLemon’});名称已经获取到了,下一步怎么办?把这些函数按照对应名字放到之前说的_actions、_mutations属性中啊看一下这个名字的mutation有没有被注册过,没有就声明一下,然后push进去。如果这个名字的mutation被注册过,就push进去。action同理小彩蛋 设置两个不同模块的同名mutation(全名一样哦)这两个mutation都会执行,action也是一样的。总结action和mutation在被dispatch和commit调用前,首先遍历模块树获取每个模块的全名。把模块内的action和mutation加上模块全名,整理成一个全新的名字放入_actions 、 _mutations属性中。dispacth和commit如何调用aciton和mutation 的将在下章讲述下一章:我们讨论action和mutation如何被调用的(调用篇)。我是一个应届生,最近和朋友们维护了一个公众号,内容是我们在从应届生过渡到开发这一路所踩过的坑,已经我们一步步学习的记录,如果感兴趣的朋友可以关注一下,一同加油~ ...

March 31, 2019 · 2 min · jiezi

记录一次vue后台管理系统解决keep-alive页面路由参数变化时缓存问题

场景描述:一个后台管理系统,一个列表页A路由配置需要缓存,另一个页面B里面有多个跳转到A路由的链接。问题描述:首先访问/A?id=1页面,然后到B页面再点击访问A?id=2的页面,发现由于页面A设置了缓存,数据还是id=1的数据,并没有更新。路由跳转情况:/B –> /A?id=1 –> /B –> /A?id=2ps: 本文记录了一下处理问题的思路,如需解决方案可直接查看文章最后。解决思路分析1. 尝试思路一:watch监听watch 监听query参数变化,判断参数是否发生变化。watch: { ‘$route.query.id’: function(newVal, oldVal){ console.log(newVal, oldVal); }}未解决问题的原因: watch只能在离开页面时才能取到oldVal的值,如果非要以此方法处理就必须要在vuex里面保存这个值,下次再进行判断,处理比较繁琐。2. 尝试思路二: deactivated 及 activated 配合判断由于第一步我们发现,oldVal在离开组件时才能获取到,故而想到了使用deactivated来存这个query值。然后再次进入这个路由时,用activated 去判断试试。computed: { id() { return this.$route.query.id }},data () { return { oldId: ‘’, //旧参数,用于判断是否缓存 }},activated() { // 比较你的id: if(this.id != this.oldId){ // 重新刷新数据… }},deactivated() { console.log(‘保存旧值’,this.id) // this.oldVin = this.id; this.oldVin = this.$route.query.id;},然而,理想很丰满,实际却发现在deactivated中,this.id 及 this.$route.query.id的值都为null。也就是deactivated获取不到computed计算的值和this.$route.query的值。3. 思路三:keep-alive的include属性(尚未实践)利用vuex动态改变keep-alive的include属性的值,实现动态设置路由是否缓存。但是这个地方有个问题,如果侦听到id参数变化了,那么需要把路由A设置为不缓存,那么违背了路由A原本需要缓存的需求,此处还希望大家探讨一下。。。解决方案给router-view设置key为路由的完整路径即可。最后找到这种实现方案,能够解决我的问题,实在简单!<keep-alive :include=“cacheList”> <router-view :key="$route.fullPath"></router-view></keep-alive>最后,文笔不好,喜欢大佬们海涵,共勉。

March 29, 2019 · 1 min · jiezi

Vuex源码学习(五)加工后的module

没有看过moduleCollection那可不行!Vuex源码学习(四)module与moduleCollection代码块和截图的区别代码块部分希望大家按照我的引导一行行认真的读代码的截图是希望大家能记住图中的结构与方法,下面会对整体进行一个分析,而不会一行一行的分析。但是以后的文章会更偏向于使用代码块,希望大家喜欢。上一章我们讲述了ModuleCollection类的作用,帮助我们把伪(未加工的)模块变成真正的模块,然后把每个模块按照父子与兄弟关系链接起来。那么真正的模块相比于伪(未加工的)模块多了哪些能力呢?module提供的方法这是module暴露出来的所有方法,以及一个属性。先看一下constructorconstructor (rawModule, runtime) { this.runtime = runtime // Store some children item // 创建一个容器存放该模块所有的子模块 this._children = Object.create(null) // Store the origin module object which passed by programmer // 存放自己未被加工的模块内容。 this._rawModule = rawModule const rawState = rawModule.state // Store the origin module‘s state // 创建这个模块的数据容器 this.state = (typeof rawState === ‘function’ ? rawState() rawState) || {}}模块的初始化主要是做了以下三件事情创建_children属性用于存放子模块创建_rawModule属性存储自己模块的伪(未被加工)模块时的内容创建state属性存储自己模块的数据内容 每个模块都有自己的state。模块的初始化并没有做什么事情,模块提供的方法和属性才是它的核心,模块提供了一个namespaced的属性,以及很多方法,我将模块提供的方法分成两类。先说属性get namespaced () { // 获取模块的namespaced属性 确定这个模块有没有自己的命名空间 return !!this._rawModule.namespaced}判断是否有命名空间有什么用?在以后设置getters、mutation、actions时有很大作用,以后再讲。再说方法模块提供的所有方法都是为了给外部的调用,这些方法没有一个是让模块在自己的内部使用的。所以我把方法划分的纬度是,按照这个方法是用于构建模块树还是用于抽取模块中的内容,构建模块树的方法:1.addChild:给模块添加子模块。addChild (key, module) { this._children[key] = module}这个方法实现上很简单,它是在哪里被调用的呢?大家可以翻开上一章的moduleCollection的内容,在ModuleCollection中完成模块之间的链接,就是使用这个方法给父模块添加子模块。removeChild:移除子模块 Vuex初始化的时候未使用,但可以给你提供灵活的处理模块的能力removeChild (key) { delete this._children[key]}getChild:获取子模块 获取子模块的意义是什么?在以后配置模块的名字时,需要获取模块的是否设置了命名空间,获取命名空间的属性模块提供了,再提供一个获取子模块就都Ok了getChild (key) { return this._children[key]}updateChild:更新模块的_ra wModule属性(更新模块的未加工前的模块内容),Vuex中未使用update (rawModule) { this._rawModule.namespaced = rawModule.namespaced if (rawModule.actions) { this._rawModule.actions = rawModule.actions } if (rawModule.mutations) { this._rawModule.mutations = rawModule.mutations } if (rawModule.getters) { this._rawModule.getters = rawModule.getters }}Vuex在链接与整合模块的时候使用了其中两个方法,addChild、getChild。类ModuleCollection在链接时需要找到模块(getChild)然后给模块添加子模块(addChild)的功能,所以这两个方法是在整合模块时最重要的。抽取模块中的内容上面的一组方法,是为了更好的完成模块的链接,给散落的单一模块整理成一个模块树可以提供便捷的封装方法,下面要说的方法什么叫做抽取模块中的内容?将这些方法暴露给外面可以方便的去获取这个模块内的一些内容来使用。forEachValue是Vuex封装的工具方法,用于遍历对象的。export function forEachValue (obj, fn) { Object.keys(obj).forEach(key => fn(obj[key], key))}这四个方法作用:forEachChild : 遍历模块的子模块forEachGetter : 遍历模块中_rawModule.getters 这块就应该知道 _rawModule的作用了,我把模块未加工时会有getters属性,存放这个模块所有的getters方法(vuex的基本用法就不多讲了),然后遍历,forEachMutation : 和forEachGetter类似,只是换成了遍历mutationsforEachAction : 和forEachGetter类似,只是换成了遍历actions这四个方法就是遍历这些内容,有意义吗?意义很大,目前_rawModule上这些getters、mutations、actions属性并不会生效,只是单纯的一个属性,如何让他们可以成为那种,被dispatch、commit使用的那种方法呢?先给大家一个小提示,mutations、actions都是要被注册的,注册之前总要获取到这些内容,具体的实现方式后面的章节会详细讲述,总结加工后真正的module(我们称由Module这个类实例化出来为真正的module)只是缓存了一些内容,并且给外界提供了一堆方便高效的方法。这些方便高效的方法为之后的注册action、mutation。整合state都起了很关键的作用。所以说module这个小单元为下面的代码提供了很大便利,额外思考我们对一段内容需要频繁的处理并且处理方式大同小异的时候,是不是可以像module一样整理成一个对象,然后给外界提供一些方法。(有一种面向对象思想)下一章讲述action和mutation是如何调用的我是一个应届生,最近和朋友们维护了一个公众号,内容是我们在从应届生过渡到开发这一路所踩过的坑,已经我们一步步学习的记录,如果感兴趣的朋友可以关注一下,一同加油~ ...

March 27, 2019 · 1 min · jiezi

Vue技术分类

vue-cli1、vue-cli 工程常用的 npm 命令有哪些?$ npm install vue-cli //1$ vue init webpack vue-project //2$ cd vue-project //3$ npm install //4$ npm run dev 2、请说出vue-cli工程中每个文件夹和文件的用处3、config文件夹 下 index.js 的对于工程 开发环境 和 生产环境 的配置module.exports => dev => proxyTable 开发时请求代理module.exports => port 开发时使用端口module.exports => build => 规定打包后文件的结构以及名称4、详细介绍一些 package.json 里面的配置name: 项目名称,version: 版本号,description: 项目说明,author: 作者信息,dependencies: 开发环境和生产环境都需要的依赖包devDependencies: 生产环境的依赖包vue知识点1、对于Vue是一套渐进式框架的理解 Vue核心功能是一个视图模板引擎,但不是说Vue就不能成为一个框架。可以通过添加组件系统、客户端路由、大规模状态管理来构建一个完整的框架。这些功能相互独立,可以在核心功能的基础上任意选用其他的部件,不一定要全部整合在一起。这就是“渐进式”,就是Vue的使用方式。2、vue.js的两个核心是什么?数据驱动、组件系统。3、请问 v-if 和 v-show 有什么区别? v-if判断条件是否渲染,是惰性的,初始渲染时条件为假时什么也不做;v-show是 display: block/none;元素始终都会渲染;在项目中如果需要频繁的切换则使用v-show较好,运行条件很少改变,则使用v-if。4、vue常用的修饰符.prevent 提交事件不再重载页面;.stop 阻止单击事件冒泡;.self 当事件发生在该元素本身而不是子元素时触发;.capture 添加事件监听器时使用事件捕获模式;.once 只会触发一次 按键修饰符 :keyup.enter :keyup.tab5、v-on可以监听多个方法吗? 可以。6、vue中 key 值的作用v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,主要是为了高效的更新虚拟DOM。7、vue-cli工程升级vue版本手动修改 package.json 里面vue的版本,同时修改 vue-template-compiler 为相同的版本;后者在devDependencies里面,然后npm install。8、vue事件中如何使用event对象? @click=“EventName($event)“9、$nextTick的使用在修改数据之后立即使用这个方法,获取更新后的 DOM。10、Vue 组件中 data 为什么必须是函数每用一次组件,就会有一个新实例被创建。每个实例可以维护一份被返回对象的独立的拷贝,每个对象都是独立互不影响的。11、v-for 与 v-if 的优先级v-for 具有比 v-if 更高的优先级。v-if 将分别重复运行于每个 v-for 循环中。vue风格指南提示永远不要把 v-if 和 v-for 同时用在同一个元素上。12、vue中子组件调用父组件的方法第一种:this.$parent.xxx;第二种:通过props传递父组件函数名,子组件接受,接受类型为Function;第三种:创建eventBus。13、vue中 keep-alive 组件的作用keep-alive是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。include(包含的组件缓存) 与 exclude(排除的组件不缓存,优先级大于include) 。include - 字符串或正则表达式,只有名称匹配的组件会被缓存;exclude反之亦然。 include=“a,b” :include="/a|b/” :include=”[‘a’, ‘b’]“14、vue中如何编写可复用的组件?1.规范化命名:组件的命名应该跟业务无关,而是依据组件的功能命名。2.数据扁平化:定义组件接口时,尽量不要将整个对象作为一个 prop 传进来。每个 prop 应该是一个简单类型的数据。这样做有下列几点好处: (1) 组件接口清晰。加粗文字 (2) props 校验方便。 (3) 当服务端返回的对象中的 key 名称与组件接口不一样时,不需要重新构造一个对象。 扁平化的 props 能让我们更直观地理解组件的接口。3.可复用组件只实现 UI 相关的功能,即展示、交互、动画,如何获取数据跟它无关,因此不要在组件内部去获取数据。4.可复用组件应尽量减少对外部条件的依赖。5.组件在功能独立的前提下应该尽量简单,越简单的组件可复用性越强。6.组件应具有一定的容错性。15、什么是vue生命周期和生命周期钩子函数?Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。总共分为8个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后。让我们在控制整个Vue实例的过程时更容易形成好的逻辑。16、vue生命周期钩子函数有哪些?beforeCreate(创建前) 在数据观测和初始化事件还未开始created(创建后) 完成数据观测,属性和方法的运算,初始化事件,$el属性还没有显示出来; beforeMount(载入前) 在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上;mounted(载入后) 在el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互;beforeUpdate(更新前) 在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程;updated(更新后) 在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用;beforeDestroy(销毁前) 在实例销毁之前调用。实例仍然完全可用;destroyed(销毁后) 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。17、vue如何监听键盘事件中的按键?监听keyup事件并添加按键修饰符,对一些常用按键vue提供了别名,或者使用keyCode,vue也支持复合按键。18、vue更新数组时触发视图更新的方法Vue.set(arr, key, value) Vue.set(object, key, value)19、vue中对象更改检测的注意事项Vue 不能检测对象属性的添加或删除;不能动态添加根级别的响应式属性。使用 Vue.set(object, key, value) 方法向嵌套对象添加响应式属性。20、解决非工程化项目初始化页面闪动问题vue页面在加载的时候闪烁花括号{}},v-cloak指令和css规则如[v-cloak]{display:none}一起用时,这个指令可以隐藏未编译的Mustache标签直到实例准备完毕。/css样式/[v-clock] { display: none;}21、v-for产生的列表,实现active的切换<ul class=“ul” > <li v-on:click=“currentIndex = index” class=“item” v-bind:class="{clicked: index === currentIndex}” v-for="(items, index) in arr"> <a>{{items}}</a> </li></ul>data() { return{ currentIndex: 0 }}22、v-model语法糖的组件中的使用1:用于表单上数据的双向绑定;2:修饰符: .lazy- 取代input监听change事件 .number- 输入字符串转为数字 .trim- 输入首尾空格过滤 23、十个常用的自定义过滤器// 全局方法 Vue.filter() 注册一个自定义过滤器Vue.filter(“sum”, function(value) { return value + 4;});// 局部new Vue({ el: “.test”, data: { message:12 }, filters: { sum: function (value) { return value + 4; } }})24、vue等单页面应用及其优缺点优点——数据驱动、组件化、轻量简洁高效,通过尽可能简单的API实现响应的数据绑定和组合的视图组件;缺点:不支持低版本的浏览器,不利于SEO优化,可以使用服务器端渲染,首次加载耗时长。25、什么是vue的计算属性?在模板中放入太多的逻辑会让模板过重且难以维护,在需要对数据进行复杂处理,且可能多次使用的情况下,尽量采取计算属性的方式。计算属性基于它们的依赖进行缓存的;只在相关依赖发生改变时它们才会重新求值。31、计算属性的缓存和方法调用的区别两种方式的最终结果确实是完全相同的。不同的是计算属性是基于它们的依赖进行缓存的,只在相关依赖发生改变时它们才会重新求值。只要相关依赖还没有发生改变,多次访问计算属性会立即返回之前的计算结果,而不必再次执行函数如果不希望有缓存,请用方法来替代。 26、vue-cli提供的几种脚手架模板vue-cli的脚手架项目模板有webpack-simple 和 webpack;区别在于webpack-simple 没有包括Eslint 检查等功能。27、vue父组件如何向子组件中传递数据? 通过父组件v-bind传递数据子组件props接收数据28、vue-cli开发环境使用全局常量①少量Vue.prototype.baseUrl = function () { return ‘https://segmentfault.com/';};Vue.prototype.getTitle = { title:’’, isBack: true, isAdd: false,};②配置文件形式在项目的src 目录里面 新建一个 lib目录,lib目录里创建一个 config.js文件。export default { install(Vue,options) { Vue.prototype.baseUrl = function () { return ‘111’; }; Vue.prototype.getTitle = { title:’’, isBack: true, isAdd: false, }; Vue.prototype.showFootTab = { isShow:false, active:0, }}最后导入 import config from ‘./lib/config.js’; Vue.use(config);使用 <template> <div> {{getTitle.title}} </div> </template> this.getTitle29、vue-cli生产环境使用全局常量30、vue弹窗后如何禁止滚动条滚动? /滑动限制/ stop(){ var mo=function(e){e.preventDefault();}; document.body.style.overflow=‘hidden’; document.addEventListener(“touchmove”,mo,false);//禁止页面滑动 }, /取消滑动限制/ move(){ var mo=function(e){e.preventDefault();}; document.body.style.overflow=’’;//出现滚动条 document.removeEventListener(“touchmove”,mo,false); } // 如果不是Vue,可以直接给html设置overflow:hidden32、vue-cli中自定义指令的使用directives: { focus: { // 指令的定义 inserted: function (el) { el.focus() }![图片描述][1] }}Vue.directive(‘color-swatch’, function (el, binding) { el.style.backgroundColor = binding.value}) vue-router1、vue-router如何响应 路由参数 的变化?当使用路由参数时,例如从 /user/foo 导航到 /user/bar,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。同时意味着组件的生命周期钩子不会再被调用。复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch (监测变化) $route 对象:watch: { ‘$route’ (to, from) { // 对路由变化作出响应… }}2、完整的 vue-router 导航解析流程导航被触发。在失活的组件里调用离开守卫。调用全局的 beforeEach 守卫。在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。在路由配置里调用 beforeEnter。解析异步路由组件。在被激活的组件里调用 beforeRouteEnter。调用全局的 beforeResolve 守卫 (2.5+)。导航被确认。调用全局的 afterEach 钩子。触发 DOM 更新。用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。3、vue-router有哪几种导航钩子( 导航守卫 )?全局的, 单个路由独享的, 组件级的。全局守卫:router.beforeEach router.beforeResolve(2.5+) router.afterEachconst router = new VueRouter({ … })router.beforeEach((to, from, next) => { // …})router.afterEach((to, from) => { // 这些钩子不会接受 next 函数也不会改变导航本身: …})路由独享的守卫: beforeEnter 这些守卫与全局前置守卫的方法参数是一样的。const router = new VueRouter({ routes: [ { path: ‘/foo’, component: Foo, beforeEnter: (to, from, next) => { // … } } ]})组件内的守卫beforeRouteEnterbeforeRouteUpdate (2.2 新增)beforeRouteLeaveconst Foo = { template: ..., beforeRouteEnter (to, from, next) { // 在渲染该组件的对应路由被 confirm 前调用 // 不!能!获取组件实例 this // 因为当守卫执行前,组件实例还没被创建 next(vm => { // 通过 vm 访问组件实例 }) }, beforeRouteUpdate (to, from, next) { // 在当前路由改变,但是该组件被复用时调用 // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候, // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 // 可以访问组件实例 this }, beforeRouteLeave (to, from, next) { // 导航离开该组件的对应路由时调用 // 可以访问组件实例 this }}每个守卫方法接收三个参数:to: Route: 即将要进入的目标 路由对象from: Route: 当前导航正要离开的路由next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。4、vue-router的几种实例方法以及参数传递编程式导航 this.$router.push({ name: ’news’, params: { userId: 123 }}); // this.$route.params.userId this.$router.push({ path: ‘/news’, query: { userId: 123 }}); // this.$route.query.userId this.$router.replace();声明式导航 <router-link :to="{ name: ’news’, params: { userId: 1111}}">click to news page</router-link> <router-link :to="{ path: ‘/news’, query: { userId: 1111}}">click to news page</router-link>5、vue-router的动态路由匹配以及使用需要把某种模式匹配到的所有路由,全都映射到同个组件const User = { template: ‘<div>User{{ $route.params.id }}</div>’}const router = new VueRouter({ routes: [ // 动态路径参数 以冒号开头 { path: ‘/user/:id’, component: User } ]})复用组件时,想对路由参数的变化作出响应的话,使用watch (监测变化) $route 对象 watch: { ‘$route’ (to, from) { // 对路由变化作出响应… } }想匹配任意路径,我们可以使用通配符 (){ // 会匹配所有路径 path: ‘’}, { // 会匹配以 /user- 开头的任意路径 path: ‘/user-*’}6、vue-router如何定义嵌套路由?在router.js使用children数组来定义子路由,并在模板中使用<router-view>定义嵌套路由。如果没有匹配到合适的子路由,可以提供一个 空的 子路由 routes: [ { path: ‘/user/:id’, component: User, children: [ // 当 /user/:id 匹配成功, // UserHome 会被渲染在 User 的 <router-view> 中 { path: ‘’, component: UserHome }, // …其他子路由 ] } ]7、<router-link></router-link>组件及其属性<router-link> 组件支持用户在具有路由功能的应用中 (点击) 导航。 通过 to 属性指定目标地址,默认渲染成带有正确链接的 <a> 标签,可以通过配置 tag 属性生成别的标签。to: <!– 字符串 –> <router-link to=“home”>Home</router-link> <!– 渲染结果 –> <a href=“home”>Home</a> <!– 使用 v-bind 的 JS 表达式 –> <router-link v-bind:to="‘home’">Home</router-link> <!– 不写 v-bind 也可以,就像绑定别的属性一样 –> <router-link :to="‘home’">Home</router-link> <!– 同上 –> <router-link :to="{ path: ‘home’ }">Home</router-link> <!– 命名的路由 –> <router-link :to="{ name: ‘user’, params: { userId: 123 }}">User</router-link> <!– 带查询参数,下面的结果为 /register?plan=private –> <router-link :to="{ path: ‘register’, query: { plan: ‘private’ }}">Register</router-link>replace: 会调用 router.replace() 而不是 router.push(),于是导航后不会留下 history 记录。<router-link :to="{ path: ‘/abc’}" replace></router-link>append: 在当前 (相对) 路径前添加基路径tag: 渲染成某种标签active-class: 设置 链接激活时使用的 CSS 类名。默认值可以通过路由的构造选项 linkActiveClass 来全局配置。8、vue-router实现路由懒加载( 动态加载路由 )component: () => import(‘comp/AlbumlibMore’)9、vue-router路由的两种模式vue-router 默认 hash 模式 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。HTML5 History 模式 充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。 要玩好,还需要后台配置支持; 因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 40410、history路由模式与后台的配合在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面;然后在给出一个 404 页面。vuex1、什么是vuex?专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。读取的状态集中放在store中; 改变状态的方式是提交mutations,这是个同步的事物; 异步逻辑应该封装在action中。在main.js引入store,注入。新建了一个目录store,export 。场景有:单页应用中,组件之间的状态、音乐播放、登录状态、加入购物车2、使用vuex的核心概念state => 基本数据 getters => 从基本数据派生的数据 mutations => 提交更改数据的方法,同步! actions => 像一个装饰器,包裹mutations,使之可以异步。 modules => 模块化Vuex(2019.03.26)3、vuex在vue-cli中的应用4、组件中使用 vuex 的值和修改值的地方?5、在vuex中使用异步修改6、pc端页面刷新时实现vuex缓存http请求1、Promise对象是什么?2、axios、fetch与ajax有什么区别?3、什么是JS的同源策略和跨域问题?4、如何解决跨域问题?5、vue-cli中如何使用JSON数据模拟?6、vue-cli中http请求的统一管理。7、axios有什么特点?UI样式1、.vue组件的scoped属性的作用2、如何让CSS只在当前组件中起作用?3、vue-cli中常用的UI组件库4、如何适配移动端?【 经典 】5、移动端常用媒体查询的使用6、垂直居中对齐7、vue-cli中如何使用背景图片?8、使用表单禁用时移动端样式问题9、多种类型文本超出隐藏问题常用功能1、vue中如何实现tab切换功能?2、vue中如何利用 keep-alive 标签实现某个组件缓存功能?3、vue中实现切换页面时为左滑出效果4、vue中父子组件如何相互调用方法?5、vue中央事件总线的使用混合开发1、vue如何调用 原生app 提供的方法?2、原生app 调用 vue 提供的方法,并将值传递到 .vue 组件中生产环境1、vue打包命令是什么?2、vue打包后会生成哪些文件?3、如何配置 vue 打包生成文件的路径?4、vue如何优化首屏加载速度?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的思路分析源码剖析1、vue内部与运行机制:Vue.js 全局运行机制响应式系统的基本原理什么是 Virtual DOM?如何编译template 模板?diff算法批量异步更新策略及 nextTick 原理?proxy代理?2、vuex工作原理详解Vue.mixinVue.use深入拓展1、vue开发命令 npm run dev 输入后的执行过程2、vue的服务器端渲染3、从零写一个npm安装包4、vue-cli中常用到的加载器5、webpack的特点 ...

March 26, 2019 · 4 min · jiezi

Vuex源码学习(四)module与moduleCollection

如果你还不知道Vuex是怎么安装的,请移步Vuex源码学习(三)install都做了哪些事情整合模块这一节该分析模块的是怎么被整合的,以及要整合成什么样子。在Vuex的constructor中比较靠前的位置有这么两行代码,_modules属性是ModuleCollection的实例对象,之后 _modules属性被频繁使用,这块就是对Vuex的模块进行了一次整合,整合出一个可以被使用的 _modules,而_moduleNamespaceMap是一个空对象该怎么整合模块?先看一下我们我们项目中Store的结构store/index.jsmoduleList:moduleSet:结构就是这样的在以上代码中的modules下的数据,我都称它是伪(未加工)模块,因为它还不具有模块的功能。当我们实例化Vuex.Store这个类的时候接收的参数options就会直接交给moduleCollection来处理。参数options是什么呢?就是上面图中这样结构的数据,想要处理成什么样子?下面看一下ModuleCollection是怎么处理的export default class ModuleCollection { constructor (rawRootModule) { // register root module (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) { this.root = newModule } else { // path.slice(0,-1)就可以拿到父模块的path。 // get方法可以根据path来找到对应的模块。 const parent = this.get(path.slice(0, -1)) // 将子模块挂载到父模块上 parent.addChild(path[path.length - 1], newModule) } // register nested modules if (rawModule.modules) { // 遍历每个模块的modules(目的是获取所有子模块) forEachValue(rawModule.modules, (rawChildModule, key) => { // 为什么要path.concat(key)? // 依次注册子模块。 this.register(path.concat(key), rawChildModule, runtime) }) } }}在Vuex与vue-router的源码中,命名变量是很有规律的,在开发人员使用这两个框架的时候,传递进去的参数,在使用时命名的变量名都是raw开头的,代表是未经过加工的。将未经过加工的伪模块处理成真正可以使用的模块。在初始化的时候直接开始注册模块,moduleCollection的这个类的任务是把生成的模块合理的链接起来,而模块的生成交给了Module这个类。所以register方法就是把根模块以及所有的子模块从一个伪(未加工)模块变成一个真正的模块并且链接起来。遍历树形结构用什么方法? 递归!register都做了什么?筛选出不符合规则的模块,报错提示。将伪(未加工)模块加工成一个真正的模块。将加工好的模块挂载在它的父模块上。如果这个模块有modules属性(模块有自己的子模块)让每个子模块重复以上操作递归的出口:rawModule.modules为false(模块没有子模块) ,也就是每个模块都没有子模块需要注册了,那就代表全部加工与链接完毕。分析register的三个参数register接收三个参数,path、rawModule、hot。hot这个参数目前看来不关键。rawModule是伪(未加工)模块那path的作用是什么呢?path的作用很大,大家类比下前端页面的dom树的Xpath,如果我想知道这个节点的位置,需要知道这个父节点的位置,然后一层一层的向上知道根结点,有了Xpath就可以直接找到这个节点,Vuex也是一样的想知道某个模块的位置,只需要提供根结点到他的一个path,path按顺序存储着根模块到它本身的所有祖先模块(根模块没有名字,又不能把第一个放一个空,所以path里 面没有根模块),在每次注册的时候,这个模块有子模块,就把它的path加上(concat)子模块的名字,在子模块执行register方法时,path就比它的父模块多一个父模块的名字,所以根模块注册的时候传入path就是[](空数组)了。ModuleCollection的get方法可以根据path来获取指定的模块,在挂载的时候十分有用,,使用reduce的方法,按照数组的顺序,一层一层的找目标模块。path对以后要讲的设置命名空间也很有帮助。总结ModuleCollection这个类,主要完成了模块的链接与整合,生成模块的任务交给了Module这个类。模块的链接与整合通过递归完成。path可以让moduleCollection快速找到对应模块。下一章讲述生成的module具体可以做什么我是一个应届生,最近和朋友们维护了一个公众号,内容是我们在从应届生过渡到开发这一路所踩过的坑,已经我们一步步学习的记录,如果感兴趣的朋友可以关注一下,一同加油~ ...

March 26, 2019 · 1 min · jiezi

Vue技术点②

1:Vue实现数据双向绑定的原理?答:Object.defineProperty(),采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。<body> <div id=“app”> <input type=“text” id=“txt”> <p id=“show”></p></div></body><script type=“text/javascript”> var obj = {} Object.defineProperty(obj, ’txt’, { get: function () { return obj }, set: function (newValue) { document.getElementById(’txt’).value = newValue document.getElementById(‘show’).innerHTML = newValue } }) document.addEventListener(‘keyup’, function (e) { obj.txt = e.target.value })</script>2:Vue的路由实现:hash模式 和 history模式答:hash模式:在浏览器中符号“#”,#以及#后面的字符称之为hash,用window.location.hash读取;特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.xxx.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。history模式:history采用HTML5的新特性;且提供了两个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.xxx.com/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”3:vuex是什么?怎么使用?哪种功能场景使用它?答:专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。读取的状态集中放在store中; 改变状态的方式是提交mutations,这是个同步的事物; 异步逻辑应该封装在action中。在main.js引入store,注入。新建了一个目录store,….. export 。场景有:单页应用中,组件之间的状态、音乐播放、登录状态、加入购物车4:vue-router如何响应 路由参数 的变化?答:当使用路由参数时,例如从 /user/foo 导航到 /user/bar,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。同时意味着组件的生命周期钩子不会再被调用。复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch (监测变化) $route 对象: watch: { ‘$route’ (to, from) { // 对路由变化作出响应… } }5:vue-router有哪几种导航钩子答:全局的, 单个路由独享的, 或者组件级的。全局守卫:router.beforeEach router.beforeResolve(2.5+) router.afterEachconst router = new VueRouter({ … })router.beforeEach((to, from, next) => { // …})router.afterEach((to, from) => { // 这些钩子不会接受 next 函数也不会改变导航本身: …})路由独享的守卫: beforeEnter 这些守卫与全局前置守卫的方法参数是一样的。const router = new VueRouter({ routes: [ { path: ‘/foo’, component: Foo, beforeEnter: (to, from, next) => { // … } } ]})组件内的守卫beforeRouteEnterbeforeRouteUpdate (2.2 新增)beforeRouteLeaveconst Foo = { template: ..., beforeRouteEnter (to, from, next) { // 在渲染该组件的对应路由被 confirm 前调用 // 不!能!获取组件实例 this // 因为当守卫执行前,组件实例还没被创建 next(vm => { // 通过 vm 访问组件实例 }) }, beforeRouteUpdate (to, from, next) { // 在当前路由改变,但是该组件被复用时调用 // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候, // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 // 可以访问组件实例 this }, beforeRouteLeave (to, from, next) { // 导航离开该组件的对应路由时调用 // 可以访问组件实例 this }}每个守卫方法接收三个参数:to: Route: 即将要进入的目标 路由对象from: Route: 当前导航正要离开的路由next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。6:完整的 vue-router 导航解析流程答:导航被触发。在失活的组件里调用离开守卫。调用全局的 beforeEach 守卫。在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。在路由配置里调用 beforeEnter。解析异步路由组件。在被激活的组件里调用 beforeRouteEnter。调用全局的 beforeResolve 守卫 (2.5+)。导航被确认。调用全局的 afterEach 钩子。触发 DOM 更新。用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。 ...

March 25, 2019 · 2 min · jiezi

vue组件通信的几种方式

写在前面vue 的组件化应该是其最核心的思想了,无论是一个大的页面还是一个小的按钮,都可以被称之为组件。基于 Vue 的开发,就是在写一个个组件,无论是基础组件还是业务组件,最后都是要拼装在一起。按照组件的层级关系,可以把组件之间的关系归纳为:父子关系、隔代关系、兄弟关系,无关联关系。$ref 、$parent 、$children基于当前上下文的,可以通过 $ref 、$parent 、$children 访问组件实例,可以直接调用组件的方法或访问数据。其中 $parent 可以访问当前组件的父组件,$children 可以访问当前组件的所有子组件。虽然 $parent 和 $children 都可以获取组件实例,但是它们无法在跨级或兄弟间通信,这是它们的缺点。provide 、injectprovide / inject 是 Vue 在 2.2.0 版本后新增的 API。这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。也就是在父组件中提供一个值,并且在需要使用的子孙组件中注入改值,即:// Parent.vueexport default { provide() { return { name: ‘Stone’ } }}// Child.vueexport default { inject: [’name’], mounted() { console.log(this.name) }}不仅仅是 Child.vue ,只要是 Parent.vue 的子组件,无论隔多少代,都可以通过这个的方式注入。 这种多级组件透传的方式可以保证单向数据流的清晰性,例如像用户信息这样的全局信息,就可以借助这两个 API 来完成,而没有引入第三方库 vuex。替代 Vuexvuex 是把数据集中管理,然后哪里需要就在哪里获取,按照这个思路,我们可以在根组件 App.vue 中注入全局信息,并且在页面的任何地方使用。// App.vue<template> <div> <router-view></router-view> </div></template>export default { provide() { return { userInfo: this.user } }, data() { return { user: {} } }, methods: { getUserInfo () { $.ajax(’/user/info’, (data) => { this.user = data }) } }}把整个 App.vue 的实例 this 对外提供, 这样其他页面就可以通过 this.userInfo 来获取用户信息。<template> <div> {{ userInfo }} </div></template><script> export default { inject: [‘userInfo’] }</script>$dispatch 、 $broadcast这两个 API 是 Vue 1.0 版本的,$dispatch 用于向上级派发事件,$broadcast 用于向下级广播事件的,它们在 2.0 版本中已经被废弃了。因为基于组件树结构的事件流方式有时让人难以理解,并且在组件结构扩展的过程中会变得越来越脆弱。不过我们可以自行实现 dispatch 和 broadcast 方法,用于组件的跨层级通信。它们实现的关键在于如何正确找到所要通信的组件,也就是通过匹配组件的 name 选项向下或向上查找组件。这两个方法都需要传递 3 个参数,第一个参数是要通信组件的 name 选项值,第二个是自定义事件名称,第三个是要给通信组件传递的值。在 dispatch 里,通过 while 循环不断向上查找父组件,直到查找到 componentName 与某个父级组件的 name 选项匹配时结束,此时改父组件就是要通信的组件。 broadcast 方法与 dispatch 类似,是通过 forEach 循环向下查找子组件。 最后封装一个 mixins 来便于复用。// emitter.js function broadcast(componentName, eventName, params) { this.$children.forEach(child => { const name = child.$options.name; if (name === componentName) { child.$emit.apply(child, [eventName].concat(params)); } else { broadcast.apply(child, [componentName, eventName].concat([params])); } });}export default { methods: { dispatch(componentName, eventName, params) { let parent = this.$parent || this.$root; let name = parent.$options.name; while (parent && (!name || name !== componentName)) { parent = parent.$parent; if (parent) { name = parent.$options.name; } } if (parent) { parent.$emit.apply(parent, [eventName].concat(params)); } }, broadcast(componentName, eventName, params) { broadcast.call(this, componentName, eventName, params); } }};通过 mixins 混入组件,实现组件间的通信。<!– Parent.vue –><template> <button @click=“handleClick”> 触发事件 <Child></Child> </button></template><script>import Emitter from “../assets/js/emitter.js”;import Child from “./Child.vue”;export default { name: “Parent”, mixins: [Emitter], created() { this.$on(“on-message”, message => { console.log(message); }); }, components: { Child }, methods: { handleClick() { this.broadcast(“Child”, “on-message”, “Hello, I am Parent Component”); } }};</script><!– Child.vue –><template> <div></div></template><script>import Emitter from “../assets/js/emitter.js”;export default { name: “Child”, mixins: [Emitter], mounted() { this.$on(“on-message”, message => { console.log(message); this.dispatch(“Parent”, “on-message”, “Copy that, I am Child Component”); }); }};</script>相比 Vue 1.0 版本内置的两个 API,自行实现的方法有以下不同:需要额外传入组件的 name 作为第一个参数;匹配到组件就会停止循环,不会冒泡;传递的值只能是一个参数,如果需要传递多个参数,只能通过对象或数组的形式;其他方法在 vue 中组件的通信还有其他的手法,例如:props 、$emit<!– Parent.vue –><template> <Child :info=“info” @update=“onUpdateName”></Child></template><script>import Child from “./Child.vue”;export default { data() { return { info: { name: “stone” } }; }, components: { Child }, methods: { onUpdateName(name) { this.info.name = name; } }};</script><!– Child.vue –><template> <div> <div>{{info.name}}</div> <button @click=“handleUpdate”>update</button> </div></template><script>export default { props: { info: { type: Object, default: {} } }, methods: { handleUpdate() { this.$emit(“update”, “new-name”); } }};</script>父组件将自己的方法传递给子组件,子组件调用方法传数据给父组件使用event bus事件总线$attrs 、$listenersVue 2.4 新增的 API。$attrs 包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。$listeners 包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。Vuex 集中式状态管理写在最后不同的通信方法适用于不同的场景,既可以通过 Vue 内置的 API 来通信,也可以通过自定义事件的方式实现通信方法,当应用足够复杂情况下,就可以使用 Vuex 进行数据管理。 ...

March 18, 2019 · 2 min · jiezi

前端如何进行单元测试?

MochaMocha是JavaScript的测试框架, 浏览器和Node端均可以使用。但是Mocha本身并不提供断言的功能, 需要借助例如: Chai, 这样的断言库完成测试的功能。Mocha API 速览Mocha的API快速浏览, 更多细节请参考文档⚠️ 注意:Mocha不推荐使用箭头函数作为Callback的函数不要用例中什么都不做, 推荐使用skip跳过不需要的测试Mocha简单的示例describe(‘unit’, function () { it(’example’, function () { return true })})Mocha测试异步代码Mocha支持Promise, Async, callback的形式// callbackdescribe(‘异步测试Callback’, function () { it(‘Done用例’, function (done) { setTimeout(() => { done() }, 1000) })})// promisedescribe(‘异步测试Promise’, function () { it(‘Promise用例’, function () { return new Promise((resolve, reject) => { resolve(true) }) })})// asyncdescribe(‘异步测试Async’, function () { it(‘Async用例’, async function () { return await Promise.resolve() })})钩子before, 全部的测试用例之前执行after, 全部的测试用例结束后执行beforeEach, 每一个测试用例前执行afterEach, 每一个测试用例后执行// before, beforeEach, 1, afterEach, beforeEach, 2, afterEach, afterdescribe(‘MochaHook’, function () { before(function () { console.log(‘before’) }) after(function () { console.log(‘after’) }) beforeEach(function () { console.log(‘beforeEach’) }) afterEach(function () { console.log(‘afterEach’) }) it(’example1’, function () { console.log(1) }) it(’example2’, function () { console.log(2) })})异步钩子Mocha Hook可以是异步的函数, 支持done,promise, async全局钩子(root hook)如果beforeEach在任何descride之外添加, 那么这个beforeEach将被视为root hook。beforeEach将会在任何文件, 任何的测试用例前执行。beforeEach(function () { console.log(‘root beforeEach’)})describe(‘unit1’, function () { //…})DELAYED ROOT SUITE如果需要在任何测试用例前执行异步操作也可以使用(DELAYED ROOT SUITE)。使用"mocha –delay"执行测试脚本。“mocha –delay"会添加一个特殊的函数run()到全局的上下文。当异步操作完成后, 执行run函数可以开始执行测试用例function deplay() { return new Promise((resolve, reject) => { setTimeout(function () { resolve() }, 1000) })}deplay().then(function () { // 异步操作完成后, 开始执行测试 run()})describe(‘unit’, function () { it(’example’, function () { return true })})skipdescribe, 或者it之后添加skip。可以让Mocha忽略测试单元或者测试用例。使用skip, 测试会标记为待处理。重试测试设置测试失败后, 测试重试的次数describe(‘retries’, function () { it(‘retries’, function () { // 设置测试的重试次数 this.retries(3) const number = Math.random() if (number > 0.5) throw new Error() else return true })})动态生成测试对于一些接口的测试, 可以使用动态生产测试用例, 配置请求参数的数组, 循环数组动态生成测试用例describe(‘动态生成测试用例’, function () { let result = [] for(let i = 0; i < 10; i ++) { result.push(Math.random()) } result.forEach((r, i) => { // 动态生成测试用例 it(测试用例${i + 1}, function () { return r < 1 }) })})slow如果测试用例, 运行时间超过了slow设置的时间, 会被标红。describe(‘unit’, function () { it(’example’, function (done) { this.slow(100) setTimeout(() => { done() }, 200) })})timeout设置测试用例的最大超时时间, 如果执行时间超过了最大超时时间,测试结果将为错误describe(‘unit’, function () { it(’example’, function (done) { this.timeout(100) setTimeout(() => { done() }, 200) })})ChaiChai是Node和浏览器的BDD/TDD断言库。下面将介绍, BDD风格的API, expect。should兼容性较差。Chai API 速览Chai的API快速浏览, 更多细节请参考文档not对断言结果取反it(’not’, function () { const foo = 1 expect(foo).to.equal(1) expect(foo).to.not.equal(2)})deep进行断言比较的时候, 将进行深比较it(‘deep’, function () { const foo = [1, 2, 3] const bar = [1, 2, 3] expect(foo).to.deep.equal(bar) expect(foo).to.not.equal(bar)})nested启用点和括号表示法it(’nested’, function () { const foo = { a: [ { a: 1 } ] } expect(foo.a[0].a).to.equals(1)})own断言时将会忽略对象prototype上的属性it(‘own’, function () { Object.prototype.own = 1 const a = {} expect(a).to.not.have.own.property(‘own’) expect(a).to.have.property(‘own’)})orderedordered.members比较数组的顺序是否一致, 使用include.ordered.members可以进行部分比较, 配合deep则可以进行深比较it(‘order’, function () { const foo = [1, 2, 3] const bar = [1, 2, 3] const faz = [1, 2] const baz = [{a: 1}, {b: 2}] const fzo = [{a: 1}, {b: 2}] expect(baz).to.have.deep.ordered.members(fzo) expect(foo).to.have.ordered.members(bar) expect(foo).to.have.include.ordered.members(faz)})any要求对象至少包含一个给定的属性it(‘any’, function () { const foo = {a: 1, b: 2} expect(foo).to.have.any.keys(‘a’, ‘b’, ‘c’)})all要求对象包含全部给定的属性it(‘all’, function () { const foo = {a: 1, b: 2} expect(foo).to.not.have.all.keys(‘a’, ‘c’)})a, an用来断言数据类型。推荐在进行更多的断言操作前, 首先进行类型的判断it(‘a’, function () { expect(‘123’).to.a(‘string’) expect(false).to.a(‘boolean’) expect({a: 1}).to.a(‘object’) expect(‘123’).to.an(‘string’) expect(false).to.an(‘boolean’) expect({a: 1}).to.an(‘object’)})includeinclude断言是否包含, include可以字符串, 数组以及对象(key: value的形式)。同时可以配合deep进行深度比较。it(‘include’, function () { expect(’love fangfang’).to.an(‘string’).to.have.include(‘fangfang’) expect([‘foo’, ‘bar’]).to.an(‘array’).to.have.include(‘foo’, ‘bar’) expect({a: 1, b: 2, c: 3}).to.an(‘object’).to.have.include({a: 1}) expect({a: {b: 1}}).to.an(‘object’).to.deep.have.include({a: {b: 1}}) expect({a: {b: [1, 2]}}).to.an(‘object’).to.nested.deep.have.include({‘a.b[0]’: 1})})ok, true, false, null, undefined, NaNok断言, 类似”==“true断言, 类似”===“flase断言, 与false进行”===“比较null断言, 与null进行”===“比较undefined断言, 与undefined进行”===“比较NaN断言, 与NaN进行”===“比较empty断言数组, 字符串长度为空。或者对象的可枚举的属性数组长度为0equal进行”===“比较的断言eql可以不使用deep进行严格类型的比较above, least, below, mostabove大于断言least大于等于断言below小于断言most小于等于断言within范围断言it(‘within’, function () { expect(2).to.within(1, 4)})property断言目标是否包含指定的属性it(‘property’, function () { expect({a: 1}).to.have.property(‘a’) // property可以同时对key, value断言 expect({a: { b: 1 }}).to.deep.have.property(‘a’, {b: 1}) expect({a: 1}).to.have.property(‘a’).to.an(’number’)})lengthOf断言数组, 字符串的长度it(’lengthOf’, function () { expect([1, 2, 3]).to.lengthOf(3) expect(’test’).to.lengthOf(4)})keys断言目标是否具有指定的keyit(‘keys’, function () { expect({a: 1}).to.have.any.keys(‘a’, ‘b’) expect({a: 1, b: 2}).to.have.any.keys({a: 1})})respondTo断言目标是否具有指定的方法it(‘respondTo’, function () { class Foo { getName () { return ‘fangfang’ } } expect(new Foo()).to.have.respondsTo(‘getName’) expect({a: 1, b: function () {}}).to.have.respondsTo(‘b’)})satisfy断言函数的返回值为true, expect的参数是函数的参数it(‘satisfy’, function () { function bar (n) { return n > 0 } expect(1).to.satisfy(bar)})oneOf断言目标是否为数组的成员it(‘oneOf’, function () { expect(1).to.oneOf([2, 3, 1])})change断言函数执行完成后。函数的返回值发生变化it(‘change’, function () { let a = 0 function add () { a += 3 } function getA () { return a } // 断言getA的返回值发生变化 expect(add).to.change(getA) // 断言变化的大小 expect(add).to.change(getA).by(3)})Karma什么是Karma?Karma是一个测试工具,能让你的代码在浏览器环境下测试。代码可能是设计在浏览器端执行的,在node环境下测试可能有些bug暴露不出来;另外,浏览器有兼容问题,karma提供了手段让你的代码自动在多个浏览器(chrome,firefox,ie等)环境下运行。如果你的代码只会运行在node端,那么你不需要用karma。安装, 运行Karma// 安装karmanpm install karma –save-dev// 在package.json文件中添加测试命令scripts: { test:unit: “karma start”}通过Karma测试项目, 需要在项目中添加配置karam.conf.js的文件。推荐使用karam init命令生成初始化的配置文件。下面是, karam init 命令的配置项。生成配置文件之后, 就可以通过"npm run test:unit"命令进行单元测试了。1. Which testing framework do you want to use(使用的测试框架)? mocha2. Do you want to use Require.js(是否使用Require)? no3. Do you want to capture any browsers automatically(需要测试的浏览器)? Chrome, IE,4. What is the location of your source and test files(测试文件的位置)? test/.test.js5. Should any of the files included by the previous patterns be excluded(需要排除的文件) ? node_modules6. Do you want Karma to watch all the files and run the tests on change(什么时候开始测试) ? change添加断言库// 安装npm install –save-dev chai karma-chai 配置karma.conf.js与webpack如果测试发送在浏览器环境, Karma会将测试文件, 模拟运行在浏览器环境中。所以推荐使用webpack, babel, 对测试文件进行编译操作。Karma中提供了处理文件中间件的配置。ps: 之前由于浏览器环境不支持require, 而我在test文件中使用了require, 并且我没有将测试文件进行编译, 耽误了我半天的时间:(karma.conf.js配置的更多的细节,可以查看karma文档// 安装babelnpm install –save-dev karma-webpack webpack babel-core babel-loader babel-preset-env// 对文件添加webpack的配置, 对配置文件使用babel进行处理module.exports = function(config) { config.set({ basePath: ‘’, frameworks: [‘mocha’, ‘chai’], files: [ ’test/.test.js’ ], exclude: [ ’node_modules’ ], preprocessors: { ’test/.test.js’: [‘webpack’] }, webpack: { // webpack4中新增的mode模式 mode: “development”, module: { rules: [ { test: /.js?$/, loader: “babel-loader”, options: { presets: [“env”] }, }, ] } }, reporters: [‘progress’], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: [‘Chrome’], singleRun: false, concurrency: Infinity })}编写测试文件// 通过babel, 浏览器可以正常的解析测试文件中的requireconst modeA = require(’../lib/a’)const expect = require(‘chai’).expectdescribe(’test’, function () { it(’example’, function () { expect(modeA.a).to.equals(1) })})Vue与Karma集成与处理浏览器中的require同理, 如果我们需要对.vue文件进行测试, 则需要通过vue-loader的对.vue文件进行处理。我们首先通过vue-cli初始化我们的项目, 这里我使用的是vue-cli2.x的版本, 3.x的版本vue-cli对webpack的配置作出了抽象, 没有将webpack的配置暴露出来, 我们会很难理解配置。如果需要使用vue-cli3.x集成karma, 则需要另外的操作。// 安装karma以及karam的相关插件npm install karma mocha karma-mocha chai karma-chai karma-webpack –save-dev// 配置karma.conf.js// webpack的配置直接使用webpack暴露的配置const webpackConfig = require(’./build/webpack.test.conf’)module.exports = function(config) { config.set({ basePath: ‘’, frameworks: [‘mocha’], files: [ ’test/.test.js’ ], exclude: [ ], // 测试文件添加中间件处理 preprocessors: { ’test/*.test.js’: [‘webpack’] }, webpack: webpackConfig, reporters: [‘progress’], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: [‘Chrome’], singleRun: false, concurrency: Infinity })}⚠️ 注意: 这里依然存在一个问题Can’t find variable: webpackJsonp, 我们可以将webpackConfig文件中的CommonsChunkPlugin插件注释后, karma将会正常的工作。编写测试import { expect } from ‘chai’import { mount } from ‘@vue/test-utils’import HelloWorld from ‘../src/components/HelloWorld.vue’describe(‘HelloWorld.vue’, function () { const wrapper = mount(Counter) it(‘Welcome to Your Vue.js App’, function () { console.log(‘Welcome to Your Vue.js App’) expect(wrapper.vm.msg).to.equals(‘Welcome to Your Vue.js App’) })})Vue-cli3与Karma集成对于vue-cli3我尝试自己添加karma.conf.js的配置, 虽然可以运行,但是存在问题。issue中, 官方建议我在vue-cli3版本中使用vue-cli的karma的插件解决。对于vue-cli3,可以使用vue-cli-plugin-unit-karma插件, 集成vue-cli3与karmaVueTestUtils对于VueTestUtils, 我这里并不想做过多的介绍。因为它拥有详尽和完善的中文文档, 在这里我只会做大致的概述。文档地址, 值得注意的一点文档中部分内容已经过时, 以及不适用与vue-cli3什么是VueTestUtils?VueTestUtils是Vue.js官方的单元测试实用工具库, 提供很多便捷的接口, 比如挂载组件, 设置Props, 发送emit事件等操作。我们首先使用vue-cli3创建项目, 并添加vue-cli-plugin-unit-karma的插件。而vue-cli-plugin-unit-karma插件已经集成了VueTestUtils工具, 无需重复的安装。VueRouterVueRouter, 是Vue的全局插件, 而我们测试的都是单文件组件, 我们该如何测试VueRouter的呢?, VueTestUtils为我们提供了localVue的API, 可以让我们在测试单文件组件的时候, 使用VueRouter。(更多内容请参考文档)import { mount, createLocalVue } from ‘@vue/test-utils’import Test from ‘../../src/views/Test.vue’import VueRouter from ‘vue-router’it(’localVue Router’, function () { const localVue = createLocalVue() localVue.use(VueRouter) const router = new VueRouter() // 挂载组件的同时, 同时挂载VueRouter const wrapper = mount(Test, { localVue, router }) // 我们可以在组件的实例中访问$router以及$route console.log(wrapper.vm.$route) })对于$router以及$route, 我们也可以通过mocks进行伪造, 并注入到组件的实例中it(‘mocks’, function () { // 伪造的$route的对象 const $route = { path: ‘/’, hash: ‘’, params: { id: ‘123’ }, query: { q: ‘hello’ } } const wrapper = mount(Test, { mocks: { $route } }) // 在组件的实例中访问伪造的$route对象 console.log(wrapper.vm.$route) })Vuex对于Vuex的测试, 我们需要明确一点我们不关心这个action或者mutation做了什么或者这个store是什么样子的, 我们只需要测试action将会在适当的时机触发。对于getters我们也不关心它返回的是什么, 我们只需要测试这些getters是否被正确的渲染。更多细节请查看文档。describe(‘Vuex’, function () { // 应用全局的插件Vuex const localVue = createLocalVue() localVue.use(Vuex) let actions let store let getters let isAction = false // 在每个测试用例执行前, 伪造action以及getters // 每个测试用例执行前, 都会重置这些数据 beforeEach(function () { // 是否执行了action isAction = false actions = { actionClick: function () { isAction = true }, actionInput: function () { isAction = true } } getters = { name: () => ‘方方’ } // 生成伪造的store store = new Vuex.Store({ state: {}, actions, getters }) }) // 测试是否触发了actions it(‘如果text等于actionClick, 触发了actionClick action’, function () { const wrapper = mount(TestVuex, { store, localVue }) wrapper.vm.text = ‘actionClick’ // 如果成功触发actionClick action, isAction将为true expect(isAction).to.true }) it(‘如果text等于actionInput,触发actionInput action’, function () { const wrapper = mount(TestVuex, { store, localVue }) wrapper.vm.text = ‘actionInput’ // 如果成功触发actionInput action, isAction将为true expect(isAction).to.true }) // 对于getters, 同理action // 我们只关注是否正确渲染了getters,并不关心渲染了什么 it(‘测试getters’, function () { const wrapper = mount(TestVuex, { store, localVue }) // 测试组件中使用了getters的dom, 是否被正确的渲染 expect(wrapper.find(‘p’).text()).to.equals(getters.name()) })}) ...

March 18, 2019 · 6 min · jiezi

大话Vuex

一、Vuex是什么官方定义:Vuex 是一个专为 Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化通俗理解:主要应用于Vue.js中管理数据状态的一个库通过创建一个集中的数据存储库(store),供程序中所有组件访问总结:Vuex就是在一个项目中,提供唯一的管理数据源的仓库。二、为什么要用Vuex单一使用Vue.js的场景在单一使用vue.js的场景中,我们难免要在不同的组件中互相传值。在该场景中,由一个根组件,两个父组件再各自拥有一个子组件,我们如果使用prop的属性传值,在这个详情组件需要使用添加组件中的某个值时,我们需要不停的触发某个事件将这个值一层一层一层一层地沿着这个路径传过去,这样能实现将值传递给详情组件,但这是相当的麻烦。现在让我们看下vuex.js场景下的效果使用Vuex.js的场景我们将使用专门的store存储所有的数据,如果我们需要取到组件二或更深一级的子组件的某个数据我们可以直接使用getter方法直接拿到其中的数据。如果我们需要向store中添加或更改某个数据,我们可以用mutation或直接$store.state的形式直接跨过父组件向store中直接添加或更改数据。就好比一个仓库,所有人能直接跨过上级拿到仓库中的某个你所需要的东西,这无疑是在我们使用vue开发项目时,相当省时省力的办法。三、Vuex的使用场景当一个组件需要多次派发事件跨组件/ 跨页面共享数据在页面数据结构比较复杂,数据和组件分离,分别处理,组件量较大的情况下,那么使用 Vuex 是非常合适的。四、Vuex的核心概念1.store:Vuex创建的一个集中数据存储库,供程序中所有组件访问2.state:页面数据状态管理容器对象,页面所需要的数据对象从该对象进行读取;3.getter:state的计算属性,可以从state中派生出新状态4.mutation:state的状态数据对象改变的操作方法,比如将页面的数据存储在state对象中,或者改变state对象中的数据,都需要通过mutation进行操作。该方法只能进行同步操作(提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。)5.action:操作行为处理模块,通过提交mutation来变更状态,可以包含异步操作。(异步逻辑都应该封装到 action 里面)6.module:store可以切分成模块,每个模块都可以拥有自己的state、getter、mutation、action7.dispatch:操作行为触发方法,是唯一能执行action的方法;8.commit:state状态改变进行提交的操作方法,对mutation进行提交,是唯一能执行mutation的方法五、Vuex过程解析过程:Vue组件接收交互行为,调用dispatch方法触发action相关操作处理,若页面的数据状态需要改变,则调用commint方法提交mutation修改state,通过getter可以获取到state对象的新值,重新渲染vue组件,界面随之更新。

March 18, 2019 · 1 min · jiezi

vue学习之vuex基础详解及源码解读(一)

概念Vuex是一个专为Vue.js应用程序开发的状态管理模式。当项目比较庞大的时候,每个组件的状态很多,为了方便管理,需要把组件中的状态抽取出来,放入Vuex中进行统一管理。每一个 Vuex 应用的核心就是store(仓库)。“store"基本上就是一个容器,它包含着你的应用中大部分的状态(state)。Vuex 和单纯的全局对象有以下两点不同:Vuex的状态存储是响应式的。当 Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会相应地得到高效更新。你不能直接改变store中的状态。改变store中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。简单应用构建vue工程vue init webpack vuexStudy构建后目录结构其中:index.jsimport Vue from ‘vue’import Vuex from ‘vuex’//如果在模块化构建系统中,请确保在开头调用了 Vue.use(Vuex)Vue.use(Vuex);export default new Vuex.Store({ state: { //存放组件之间共享的数据 //在组件通过this.$store.state.count获取 count: 0 }, mutations: { //显式的更改state里的数据,不能用于处理异步事件 //组件中通过this.$store.commit(‘incrementByStep’);调用 incrementByStep(state) { state.count++; } }, getters:{ //如vue中的计算属性一样,基于state数据的二次包装,常用于数据的筛选和多个数据的相关性计算 }, actions:{ //类似于mutation,用于提交mutation来改变状态,而不直接变更状态,可以包含任意异步操作 }});new Vuex.Store({}) 表示创建一个Vuex实例,通常情况下,他需要注入到Vue实例里。 Store是Vuex的一个核心方法,字面上理解为“仓库”的意思。Vuex Store是响应式的,当Vue组件从store中读取状态(state选项)时,若store中的状态发生更新时,他会及时的响应给其他的组件而且不能直接改变store的状态,改变状态的唯一方法是显式地提交更改。main.js引入vueximport Vue from ‘vue’import App from ‘./App’//vuex文件import store from ‘./store’Vue.config.productionTip = false;/* eslint-disable no-new */new Vue({ el: ‘#app’, //引入 store, components: { App }, template: ‘<App/>’})APP.vue引用了counter这个组件 <div id=“app”> <!–<img src=”./assets/logo.png">–> <!–<HelloWorld/>–> <counter/> </div></template><script>import HelloWorld from ‘./components/HelloWorld’import counter from ‘./components/counter’export default { name: ‘App’, components: { //HelloWorld counter }}</script><style>#app { font-family: ‘Avenir’, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px;}</style>counter.vue定义counter组件<template> <div id=“counterContent”> <div>{{ count }}</div> <button v-on:click=“addCount”>请点击</button> </div></template><script> export default { name: “counter”, computed: { count () { return this.$store.state.count } }, methods:{ addCount(){ debugger; this.$store.commit(‘incrementByStep’); } } }</script><style scoped> #counterContent{ background-color: blue; width:200px; height:50px; }</style>通过npm run dev启动项目,最终的结果如图:源码解读node添加Vuex依赖下载的vuex文件(node_modules目录下)如下:其中vuex.common.js在预编译调试时,CommonJS规范的格式,可以使用require("")引用的NODEJS格式。vuex.esm.js在预编译调试时, EcmaScript Module(ES MODULE),支持import from 最新标准的。vuex.js直接用在<script>标签中的,完整版本,直接就可以通过script引用。而vuex的源码托管在https://github.com/vuejs/vuex,这里研究git上的源码。入口Vuex 源码的入口是 src/index.js。import { Store, install } from ‘./store’import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from ‘./helpers’export default { Store, install, version: ‘VERSION’, mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers}这是Vuex对外暴露的API。其中, Store是Vuex提供的状态存储类,通常我们使用Vuex就是通过创建 Store的实例。接着是install方法,这个方法通常是我们编写第三方Vue插件的时候使用。installinstall是在store.js内暴露的方法当Vue通过npm安装到项目中的时候,我们在代码中引入第三方Vue插件需要添加下列代码import Vue from ‘vue’import Vuex from ‘vuex’//如果在模块化构建系统中,请确保在开头调用了 Vue.use(Vuex)Vue.use(Vuex);执行Vue.use(Vuex)的时候,其实就是调用了install的方法并传入Vue的引用。Vue.use Vue.use = function (plugin) { var installedPlugins = (this._installedPlugins || (this._installedPlugins = [])); if (installedPlugins.indexOf(plugin) > -1) { return this } // additional parameters var args = toArray(arguments, 1); args.unshift(this); if (typeof plugin.install === ‘function’) { //调用vuex的install plugin.install.apply(plugin, args); } else if (typeof plugin === ‘function’) { plugin.apply(null, args); } installedPlugins.push(plugin); return this };}install //判断Vue是否已存在,保证install方法只执行一次 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变量,index.js文件的其它地方使用Vue这个变量 Vue = _Vue //调用了 applyMixin 方法,给 Vue 的实例注入一个 $store 的属性 applyMixin(Vue)}plugin参数:args参数:var applyMixin = function (Vue) { //获取版本信息,这里是2 var version = Number(Vue.version.split(’.’)[0]); if (version >= 2) { //调用vuexInit Vue.mixin({ beforeCreate: vuexInit }); } else { var _init = Vue.prototype._init; Vue.prototype._init = function (options) { if ( options === void 0 ) options = {}; options.init = options.init ? [vuexInit].concat(options.init) : vuexInit; _init.call(this, options); }; } //给Vue实例注入$store 的属性,可以通过this.$store.xxx访问 function vuexInit () { var options = this.$options; // store injection if (options.store) { this.$store = typeof options.store === ‘function’ ? options.store() : options.store; } else if (options.parent && options.parent.$store) { this.$store = options.parent.$store; } }}; ...

March 18, 2019 · 2 min · jiezi

Vue笔记

知识点创建Vue项目vue init webpack vuetest(项目名称)project name 项目名称(不能有大写字母,如果有会报错Sorry, name can no longer contain capital letters)Project des:项目描述,可以直接点击回程,使用默认名字Author:作者Runtime+ Compiler:recommended for most users运行加编译Runtime-only:仅运行时installl vue-router?(Y/n)是否安装vue-router,这是官方的路由,大多数情况下都使用,这里就输入“y”后回车即可。Use ESLint to lint your code? (Y/n) 是否使用ESLint管理代码,ESLint是个代码风格管理工具,是用来统一代码风格的,并不会影响整体的运行,这也是为了多人协作,新手就不用了,一般项目中都会使用。Pick an ESLint preset (Use arrow keys) 选择一个ESLint预设,编写vue项目时的代码风格none (configure it yourself) 自己定义风格,具体选择哪个因人而异,选择标准风格Setup unit tests with Karma + Mocha? (Y/n) 是否安装单元测试,选择安装Setup e2e tests with Nightwatch(Y/n)? 是否安装e2e测试 ,选择安装this.$emit$on()——监听事件。$emit()——把事件沿着作用域链向上派送。(触发事件)!!$dispatch()——派发事件,事件沿着父链冒泡。$broadcast()——广播事件,事件向下传导给所有的后代。filters组件中status | statusFilter | statusFilterBfilters: { statusFilter(status) { const statusMap = { published: ‘success’, draft: ‘info’, deleted: ‘danger’ } return statusMap[status] }, statusFilterB(status) { const statusMap = { success: ‘danger’, danger: ‘info’, info: ‘success’ } return statusMap[status] } }Bugs ...

March 11, 2019 · 1 min · jiezi

VUE全家桶+ElementUi 项目踩坑总结

项目简介vue + axios + vue-router + vuex + ElementUI架构vuevue数据更新,视图不更新只有当实例被创建时 data 中存在的属性才是响应式的,Vue 不能检测对象属性的添加或删除; 可以使用 Vue.set(object, key, value) 方法向嵌套对象添加响应式属性Vue.set(vm.userProfile, ‘age’, 27)this.$set(this.transportAddData.serviceOrderList[a].serviceOrderItems[i], ‘deletePoundIds’, [])vue 数据与方法 vue 对象更改检测注意事项Vue 不能检测以下变动的数组:当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue当你修改数组的长度时,例如:vm.items.length = newLengthvue 数组更新检测持续触发事件的优化持续触发某个事件可以用函数的节流和防抖来做性能优化//防抖function(){ … clearTimeout(timeoutId); timeoutId = setTimeout(function () { console.log(‘content’, content); player(content.msgTypeId, comId) }, 500); … }// 节流var canRun = true;document.getElementById(“throttle”).onscroll = function(){ if(!canRun){ // 判断是否已空闲,如果在执行中,则直接return return; } canRun = false; setTimeout(function(){ console.log(“函数节流”); canRun = true; }, 300);};javaScript的Throttling(节流)和Debouncing(防抖)nextTick在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。get() { this.$http.get(’/api/article’).then(function (res) { this.list = res.data.data.list // ref list 引用了ul元素,我想把第一个li颜色变为红色 document.querySelectorAll(’li’)[0].style.color = ‘red’ //这里会报错-,因为还没有 li this.$nextTick( ()=> { document.querySelectorAll(’li’)[0].style.color = ‘red’ }) })},Vue.nextTick 的原理和用途音频文件自动播放报错谷歌浏览器(Chrome 66)音频自动播放报错DOMException: play() failed because the user didn’t interact with the document first.解决方案:AudioContext// Chromerequest.get(’/baseConfig/messageAudio/play’, { params: { “comId”: Cookies.get(‘comId’), “msgTypeId”: id }, responseType: ‘arraybuffer’ // 这里需要设置xhr response的格式为arraybuffer }) .then(req => { … let context = new (window.AudioContext || window.webkitAudioContext)(); context.decodeAudioData(req.data, decode => play(context, decode)); function play (context, decodeBuffer) { sourceadio = context.createBufferSource(); sourceadio.buffer = decodeBuffer; sourceadio.connect(context.destination); // 从0s开始播放 sourceadio.start(0); } … })Chrome 66禁止声音自动播放之后 [AudioContext](https://developer.mozilla.org… [AudioContext.decodeAudioData()](https://developer.mozilla.org...vuex使用vuex修改state的方法和区别可以直接使用 this.$store.state.变量 = xxx;this.$store.dispatch(actionType, payload) 或者 this.$store.commit(commitType, payload)相同点:能够修改state里的变量,并且是响应式的(能触发视图更新) 不同点: 若将vue创建 store 的时候传入 strict: true, 开启严格模式,那么任何修改state的操作,只要不经过 mutation的函数,vue就会报如下错throw error : [vuex] Do not mutate vuex store state outside mutation handlers。使用commit提交到mutation修改state的优点:vuex能够记录每一次state的变化记录,保存状态快照,实现时间漫游/回滚之类的操作。dispatch 和 commit的区别在于: 使用dispatch是异步操作, commit是同步操作, 所以 一般情况下,推荐直接使用commit,即 this.$store.commit(commitType, payload),以防异步操作会带来的延迟问题。vuex strict vuex Mutation vuex actionsvuex到底是什么const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit(‘increment’) } }})==vuex中的state本质上是没有template的隐藏的vue组件。==vuex工作原理详解axios兼容问题Edge 41.16299.15.0 post请求会自动转为getmicrosoft-edge/platform/issues vue 使用axios 在EDGE浏览器上面post请求变成了get请求取消请求场景:每次请求在拦截器中添加token,后台判断token是否过期;当进入一个页面时触发多次请求,且正好token已经过期。这个时候需要第一次请求完成之后知道token已经过期则弹出提示、页面跳转到登录、停止之后的请求;否则会因为多次请求和axios响应拦截而多次弹框提示。解决方案 axios自带cancelToken 取消方法,然后在路由跳转后停止之前的请求// 请求拦截中 添加 cancelTokenaxios.interceptors.request.use(config => { config.cancelToken = store.source.token return config}, err => { return Promise.reject(err)}) // 路由跳转中进行判断router.beforeEach((to, from, next) => { const CancelToken = axios.CancelToken store.source.cancel && store.source.cancel() store.source = CancelToken.source() next()})//sort文件/state: { source: { token: null, cancel: null } }axios api 路由变化时使用axios取消所有请求 vue项目中 axios请求拦截器与取消pending请求功能存在问题: 如果 token过期提示弹框为二次确认弹框,再次确认之后才会进行页面跳转,那么在点击确认之前,页面中之前的请求还是会继续进行; 解决方案:给弹窗添加全局状态,根据状态判断是否需要弹出弹框预检请求CORS跨域 CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,==IE浏览器不能低于IE10。== 浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。简单请求请求方法是以下三种方法之一: HEAD GET POSTHTTP的头信息不超出以下几种字段:Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain简单请求不会触发 CORS 预检请求。非简单请求当请求满足下述任一条件时,即为非简单请求:使用了下面任一 HTTP 方法: PUT DELETE CONNECT OPTIONS TRACE PATCH人为设置了对 CORS 安全的首部字段集合之外的其他首部字段。该集合为:AcceptAccept-LanguageContent-LanguageContent-Type (but note the additional requirements below)DPRDownlinkSave-DataViewport-WidthWidthContent-Type 的值不属于下列之一: application/x-www-form-urlencoded multipart/form-data text/plain请求中的XMLHttpRequestUpload 对象注册了任意多个事件监听器。请求中使用了ReadableStream对象。HTTP访问控制(CORS)预检请求非简单请求,会在正式通信之前,增加一次HTTP OPTIONS方法发起的查询请求,称为"预检"请求(preflight)。以获知服务器是否允许该实际请求。“预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。 ==该方法不会对服务器资源产生影响==优化方案Access-Control-Max-Age: <delta-seconds> //单位是秒。表示 preflight request (预检请求)的返回结果(即 Access-Control-Allow-Methods 和Access-Control-Allow-Headers 提供的信息) 可以被缓存多久Vue Routerpush、replace的区别push会向history添加新的记录,replace只是替换掉原来的记录,不会添加新的记录;这就导致了用replace方法跳转的页面是无法回到之前的页面的;(类似window.history)vue Router 编程式的导航路由懒加载为了提升页面加载速度,实现按需加载,也就是当路由访问时才加载对应组件,我们可以结合vue的异步组件和webpack的代码分割功能来实现路由的懒加载。{ path: ‘/iov/login’, name: ‘登录’, component: resolve => require([’@/views/login/login’], resolve),},{ path:’/iov/organization’, name:‘组织管理’, component:() => import(’@/views/enterprise/organization’)}vue Router 路由懒加载 vue 异步组件 vue + vue-router 懒加载 import / resolve+requireelementUI表单弹窗中 elementform 清除验证残留提示给表单添加不同的 ref (REFNAME),如果有相同的ref 会导致残留验证提示清除失败 this.dialogTranspor = true //弹窗打开后 dom 没有生成,所有要用 this.$nextTick this.$nextTick(function () { this.$refs.REFNAME.resetFields(); })页码数无法正常显示场景:列表页在跳到详情或其他页面后再返回列表页,无法正常显示对应页数(页码数放在state中),但请求的数据时正常的; 解决方案:页码数、总页数必须要在同一个对象中,否则当前页码数不能正常显示data() { return { //完成查询条件 searchComplate: { “comId”: Cookies.get(‘comId’), “transportOrderCode”: null, “orderCode”: null, “customerId”: null, “abnormal”: 2, “startTime”: null, “endTime”: null, “pageNum”: 1, “pageSize”: 20, total: 0, currentRow: ‘’, dataArea: [’’, ‘’], activeName: ‘’, expands: [] }, }}动态多级表单验证<li v-for="(item,index) in transportAddData.serviceOrderList”> <template v-for="(subItem,subIndex) in item.serviceOrderItems"> <tr > <td> <el-form-item :prop="‘serviceOrderList.’+index+’.serviceOrderItems.’ + subIndex + ‘.addressName’" :rules="{required: true, message: ‘卸货地不能为空’, trigger: ‘blur’}"> <el-input v-model=“subItem.addressName” placeholder=“地址”></el-input> </el-form-item> </td> <td> <el-form-item :prop="‘serviceOrderList.’+index+’.serviceOrderItems.’ + subIndex + ‘.planTonnage’" :rules="[{required: true, message: ‘必填项’, trigger: ‘blur’},{pattern: /^((([1-9]+(\d+)?)(.\d+)?)|(0.\d+))$/, message: ‘必须为正数且不为0’}]"> <el-input v-model=“subItem.planTonnage” placeholder=“预卸吨数”></el-input> </el-form-item> </td> … </tr> </template></li> ...

March 7, 2019 · 3 min · jiezi

vuex页面刷新数据丢失的解决办法

在vue项目中用vuex来做全局的状态管理, 发现当刷新网页后,保存在vuex实例store里的数据会丢失。原因:因为store里的数据是保存在运行内存中的,当页面刷新时,页面会重新加载vue实例,store里面的数据就会被重新赋值初始化解决思路:将state的数据保存在localstorage、sessionstorage或cookie中(三者的区别),这样即可保证页面刷新数据不丢失且易于读取。localStorage: localStorage的生命周期是永久的,关闭页面或浏览器之后localStorage中的数据也不会消失。localStorage除非主动删除数据,否则数据永远不会消失。sessionStorage:sessionStorage的生命周期是在仅在当前会话下有效。sessionStorage引入了一个“浏览器窗口”的概念,sessionStorage是在同源的窗口中始终存在的数据。只要这个浏览器窗口没有关闭,即使刷新页面或者进入同源另一个页面,数据依然存在。但是sessionStorage在关闭了浏览器窗口后就会被销毁。同时独立的打开同一个窗口同一个页面,sessionStorage也是不一样的。cookie:cookie生命期为只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。 存放数据大小为4K左右,有个数限制(各浏览器不同),一般不能超过20个。缺点是不能储存大数据且不易读取。由于vue是单页面应用,操作都是在一个页面跳转路由,因此sessionStorage较为合适,原因如下:sessionStorage可以保证打开页面时sessionStorage的数据为空;每次打开页面localStorage存储着上一次打开页面的数据,因此需要清空之前的数据。vuex中state数据的修改必须通过mutation方法进行修改,因此mutation修改state的同时需要修改sessionstorage,问题倒是可以解决但是感觉很麻烦,state中有很多数据,很多mutation修改state就要很多次sessionstorage进行修改,既然如此直接用sessionstorage解决不就行了,为何还要用vuex多此一举呢?vuex的数据在每次页面刷新时丢失,是否可以在页面刷新前再将数据存储到sessionstorage中呢,是可以的,beforeunload事件可以在页面刷新前触发,但是在每个页面中监听beforeunload事件感觉也不太合适,那么最好的监听该事件的地方就在app.vue中。在app.vue的created方法中读取sessionstorage中的数据存储在store中,此时用vuex.store的replaceState方法,替换store的根状态在beforeunload方法中将store.state存储到sessionstorage中。代码如下:export default { name: ‘App’, created () { //在页面加载时读取sessionStorage里的状态信息 if (sessionStorage.getItem(“store”) ) { this.$store.replaceState(Object.assign({}, this.$store.state,JSON.parse(sessionStorage.getItem(“store”)))) } //在页面刷新时将vuex里的信息保存到sessionStorage里 window.addEventListener(“beforeunload”,()=>{ sessionStorage.setItem(“store”,JSON.stringify(this.$store.state)) }) }}

March 7, 2019 · 1 min · jiezi

Vuex 的异步数据更新(小记)

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数mutation 是同步执行,不是异步执行。由于Mutations不接受异步函数,要通过Actions来实现异步请求。export const setAddPurchaseStyle = ({commit, state}, obj) => { url=‘http://xxx.com’ + ‘/json/’ + ‘/development’ + ‘/purchaserexp/create_company.js’; let _tempObj={}; // 着键是这个 return return new Promise(function(resolve, reject) { axios.get(url, {}).then((response) => { resolve(‘请求成功后,传递到 then’); }, (response) => { //失败 console.info(’error’, response); reject(‘请求失败后,传递到 catch’) }); }).then((res)=>{ // res 是 (请求成功后,传递到 then) commit(‘putSSS’, Math.random()); // 在Promise的成功中,调用 mutations 的方法 })};在Mutations中通过putSSS接收并更新style数据:export const putSSS=(state, val) => { state.style = val;};组件创建时更新数据,写在 created 中this.$store.dispatch(‘setStyle’,{ id:this.id, name: this.name, });使用 mapActions使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store)methods: { …mapActions([ ‘setStyle’ ]), test(){ // 映射后可直接使用方法,不需要写 this.$store.dispatch this.setStyle({ id:this.id, name: this.name, }); }} ...

March 6, 2019 · 1 min · jiezi

vueSSR: 从0到1构建vueSSR项目 --- vuex的配置(数据预取)

vuex的相关配置上一章做了node以及vue-cli3的配置,今天就来做vuex的部分。先打开官方文档-数据预取和状态看完之后,发现大致的逻辑就是利用mixin,拦截页面渲染完成之前,查看当前实例是否含有’asyncData’函数(由你创建以及任意名称),如果含有就进行调用,并且传入你需要的对象比如(store,route)例子// pages/vuex/index.jsexport default { name: “vuex”, asyncData({ store, route }) { // 触发 action 后,会返回 Promise return store.dispatch(‘fetchItem’, route.path) }, computed: { // 从 store 的 state 对象中的获取 item。 item() { return this.$store.state.items[this.$route.path] } }}//mixinimport Vue from ‘vue’;Vue.mixin({ beforeMount() { const { asyncData } = this.$options if (asyncData) { this.dataPromise = asyncData({ store: this.$store, route: this.$route }) } }})上面只是个大概的示例,下面开始正式来做吧。先创建一些文件 src/store.config.js 跟router.config.js 一样,在服务端运行避免状态单例 src/pages/store/all.js 全局公共模块// src/pages/store/all.js const all = { //开启命名空间 namespaced: true, //ssr注意事项 state 必须为函数 state: () => ({ count:0 }), mutations: { inc: state => state.count++ }, actions: { inc: ({ commit }) => commit(‘inc’) } } export default all;vuex 模块all 单独一个全局模块如果home有自己的数据,那么就在home下 惰性注册模块但是记得页面销毁时,也一定要销毁模块!!!因为当多次访问路由时,可以避免在客户端重复注册模块。如果想只有个别几个路由共用一个模块,可以在all里面进行模块嵌套,或者将这个几个页面归纳到一个父级路由下 ,在父级实例进行模块惰性注册。// home/index.jsimport all from ‘../store/all.js’;export default { name: ‘home’, computed:{ count(){ return this.$store.state.all.count } }, asyncData({ store}){ store.registerModule(‘all’,all); return store.dispatch(‘all/inc’) }, data() { return { activeIndex2: ‘1’, show:false, nav:[ { path:’/home/vue’, name:‘vue’ }, { path:’/home/vuex’, name:‘vue-vuex’ }, { path:’/home/vueCli3’, name:‘vue-cli3’ }, { path:’/home/vueSSR’, name:‘vue ssr’ } ] }; }, watch:{ $route:function(){ if(this.show){ this.show = false; } //切换路由时,进行自增 this.$store.dispatch(‘all/inc’); } }, mounted() { //做额外请求时,在mounted下进行 }, methods: { user_info(){ this.http.post(’/cms/i1/user_info’).then(res=> { console.log(res.data); }).catch( error => { console.log(error) }) } }, destroyed(){ this.$store.unregisterModule(‘all’) }}数据预取// store.config.js //store总配置 import Vue from ‘vue’; import Vuex from ‘vuex’; Vue.use(Vuex); //预请求数据 function fetchApi(id){ //该函数是运行在node环境 所以需要加上域名 return axios.post(‘http://localhost:8080/cms/i1/user_info’); } //返回 Vuex实例 避免在服务端运行时的单利状态 export function createStore() { return new Vuex.Store({ state:{ items:{} }, actions: { fetchItem ({commit}, id) { return fetchApi(id).then(item => { commit(‘setItem’,{id, item}) }) } }, mutations: { setItem(state, {id, item}){ Vue.set(state.items, id, item.data) } } }) }mixin相关在src下新建个methods 文件夹,这里存放写vue的全局代码以及配置 获取当前实例// src/methods/index.jsimport ‘./mixin’;import Vue from ‘vue’;import axios from ‘axios’;Vue.prototype.http = axios;// src/methods/mixin/index.js import Vue from ‘vue’; Vue.mixin({ beforeMount() { const { asyncData } = this.$options;//这里 自己打印下就知道了。就不过多解释了 //当前实例是否有该函数 if (asyncData) { // 有就执行,并传入相应的参数。 asyncData({ store: this.$store, route: this.$route }) } } })main.js 新增代码 import Vue from ‘vue’; Vue.config.productionTip = false; import VueRouter from ‘vue-router’; import App from ‘./App.vue’;+ import ‘./methods’; //同步路由状态+ import { sync } from ‘vuex-router-sync’; import { createRouter } from ‘./router.config.js’;+ import { createStore } from ‘./store.config.js’; export function createApp() { const router = createRouter() const store = createStore() //同步路由状态(route state)到 store sync(store, router) const app = new Vue({ router,+ store, render: h => h(App) }) return { app, router,+ store }; }下面更新,开发环境部署相关 ...

March 6, 2019 · 2 min · jiezi

vueX10分钟入门

通过本文你将:1.知道什么是vueX.2.知道为什么要用VueX.3.能跑一个VueX的例子。4.了解相关概念,面试的时候能说出一个所以然5.项目中用Vuex知道该学什么东西。好,走起。1.什么是vueX?Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。翻译成人话,Vuex是vuejs的官方管理数据状态的库。官网:https://vuex.vuejs.org/zh/2.为什么用它?举个例子,你用vue开发一个app,不同的组件,你都需要用户信息,还有一些公用的数据,你每一个组件请求一遍浪费性能,你不请求组件间属性和参数传来传去,你自己维护很墨迹,麻烦也容易出错。好吧,你觉得干不好或者麻烦,那么vueX帮你解决这个事儿。这个没什么复杂的,大学图书馆,自助借还书,每次都把书乱放,维护很麻烦,怎么办,都还给图书馆管理员,图书馆管理员统一管理调配。ok,图书管理员就是VueX.3.怎么用?1.安装npm install vuex –save2.初始化store.js,(vue-cli安装项目目录不墨迹),一般放到src/store/store.js下面,初始化代码,相当于搞了一个图书管理员。import Vue from ‘vue’import Vuex from ‘vuex’Vue.use(Vuex)export const store = new Vuex.Store({})3.写需要的组件创建一个Form.vue组件,怼下面的内容,<template> <div> <label for=“dabinge666”>你喜欢彬哥哪一点</label> <input name=“dabinge666”> </div></template>创建一个展示组件Display.vue<template> <div> <p>我喜欢彬哥:</p> </div></template>打开App.vue,删掉没用的东西,直接怼下面的代码,<template> <div id=“app”> <Form/> <Display/> </div></template><script>import Form from ‘./components/Form’import Display from ‘./components/Display’export default { name: ‘App’, components: { Form, Display }}</script>到这里,架子就搭好了。4.增加各种需要的东西,import Vue from ‘vue’import Vuex from ‘vuex’Vue.use(Vuex)export const store = new Vuex.Store({ state: { love: ’’ }, mutations: { change(state, love) { state.love = love } }, getters: { love: state => state.love }})这里注意,你不用去管这些破概念,你就照猫画虎,我写啥你抄啥,抄几遍,你就知道数据流向了。你不知道鼠标叫mouse,也不影响你玩电脑。love就是你喜欢我的东西,相当于一个变量,被传来传去的一会。好了,就这么简单可以用了。4.使用VueX打开main.js,导入,然后用。import { store } from ‘./store/store’new Vue({ el: ‘#app’, store, components: { App }, template: ‘<App/>’})到这里就相当于图书管理员上岗等着学生来还书了,来啊,互相伤害啊!5.我来了……既然搞数据,躲不开刚才我们的搞的表单组件,打开Form.vue<template> <div> <label for=“dabinge666”>你喜欢彬哥哪一点?</label> //输入:离我远一点 <input @input=“changed” name=“dabinge666”> </div></template><script>export default { methods: { changed: function(event) { //大声喊出你的对彬哥的爱,让整个图书馆都听到 this.$store.commit(‘change’, event.target.value) } }}</script>打开,Display.vue<template> <div> <p>我喜欢彬哥: {{ $store.getters.love }}</p> </div></template>漂亮,如果你运行成功,你就会发现,页面里面出现,我喜欢彬哥:离我远一点。告辞! ...

March 4, 2019 · 1 min · jiezi

Vuex mutitons 和 actions 初使用

Vuex 状态管理Vuex 依赖于 Vue 用来管理 Vue 项目状态状态的修改依赖于 commit 和 dispatchimport Vue from ‘Vue’;import Vuex from ‘Vuex’;export default new Vuex.Store({ state:{ count:100 }, mutations:{ change(state,payload){ state.count += payload; } }, actions:{ change(context,palyload){ context.commit(‘change’,palyload);// 异步触发 mutaiton } }, getters:{ getCount(){ return state.count; } }}){{$store.state.count}}<button @click=“commitChange”>更改count</button><button @click=“dispatchChange”>更改count</button>…methods:{ commitChange(){ this.$store.commit(‘change’,1); }, dispatchChange(){ this.$sotre.dispatch(‘change’,10); }}

March 4, 2019 · 1 min · jiezi

vuex实现及简略解析

大家都知道vuex是vue的一个状态管理器,它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。先看看vuex下面的工作流程图通过官方文档提供的流程图我们知道,vuex的工作流程,1、数据从state中渲染到页面;2、在页面通过dispatch来触发action;3、action通过调用commit,来触发mutation;4、mutation来更改数据,数据变更之后会触发dep对象的notify,通知所有Watcher对象去修改对应视图(vue的双向数据绑定原理)。使用vuex理解vuex的工作流程我们就看看vuex在vue中是怎么使用的。首先用vue-cli创建一个项目工程,如下图,选择vuex,然后就是一路的回车键安装好之后,就有一个带有vuex的vue项目了。进入目录然后看到,src/store.js,在里面加了一个状态{count: 100},如下import Vue from ‘vue’import Vuex from ‘vuex’ // 引入vuexVue.use(Vuex) // 使用插件export default new Vuex.Store({ state: { count: 100 // 加一个状态 }, getter: { }, mutations: { }, actions: { }})最后在App.vue文件里面使用上这个状态,如下<template> <div id=“app”> 这里是stort——->{{this.$store.state.count}} </div></template><script>export default { name: ‘app’}</script><style></style>项目跑起来就会看到页面上看到,页面上会有100了,如下图到这里我们使用vuex创建了一个store,并且在我们的App组件视图中使用,但是我们会有一些列的疑问。store是如何被使用到各个组件上的??为什么state的数据是双向绑定的??在组件中为什么用this.$store.dispch可以触发store的actions??在组件中为什么用this.$store.commit可以触发store的mutations??….等等等等带着一堆问题,我们来自己实现一个vuex,来理解vuex的工作原理。安装并使用store在src下新建一个vuex.js文件,然后代码如下’use strict’let Vue = nullclass Store { constructor (options) { let { state, getters, actions, mutations } = options }}// Vue.use(Vuex)const install = _Vue => { // 避免vuex重复安装 if (Vue === _Vue) return Vue = _Vue Vue.mixin({ // 通过mixins让每个组件实例化的时候都会执行下面的beforeCreate beforeCreate () { // 只有跟节点才有store配置,所以这里只走一次 if (this.$options && this.$options.store) { this.$store = this.$options.store } else if (this.$parent && this.$parent.$store) { // 子组件深度优先 父 –> 子—> 孙子 this.$store = this.$parent.$store } } })}export default { install, Store }然后修改store.js中的引入vuex模块改成自己的vuex.jsimport Vuex from ‘./vuex’ // 自己创建的vuex文件在我们的代码中export default { install, Store }导出了一个对象,分别是install和Storeinstall的作用是,当Vue.use(Vuex)就会自动调用install方法,在install方法里面,我们用mixin混入了一个beforeCreate的生命周期的钩子函数,使得当每个组件实例化的时候都会调用这个函数。在beforeCreate中,第一次根组件通过store属性挂载$store,后面子组件调用beforeCreate挂载的$store都会向上找到父级的$store,这样子通过层层向上寻找,让每个组件都挂上了一个$store属性,而这个属性的值就是我们的new Store({…})的实例。如下图通过层层向上寻找,让每个组件都挂上了一个$store属性设置state响应数据通过上面,我们已经从每个组件都通过this.$store来访问到我们的store的实例,下面我们就编写state数据,让其变成双向绑定的数据。下面我们改写store类class Store { constructor (options) { let { state, getters, actions, mutations } = options // 拿到传进来的参数 this.getters = {} this.mutations = {} this.actions = {} // vuex的核心就是借用vue的实例,因为vuex的数据更改回更新视图 this._vm = new Vue({ data: { state } }) } // 访问state对象时候,就直接返回响应式的数据 get state() { // Object.defineProperty get 同理 return this._vm.state }}传进来的state对象,通过new Vue({data: {state}})的方式,让数据变成响应式的。当访问state对象时候,就直接返回响应式的数据,这样子在App.vue中就可以通过this.$store.state.count拿到state的数据啦,并且是响应式的呢。编写mutations、actions、getters上面我们已经设置好state为响应式的数据,这里我们在store.js里面写上mutations、actions、getters,如下import Vue from ‘vue’import Vuex from ‘./vuex’ // 引入我们的自己编写的文件Vue.use(Vuex) // 安装store// 实例化store,参数数对象export default new Vuex.Store({ state: { count : 1000 }, getters : { newCount (state) { return state.count + 100 } }, mutations: { change (state) { console.log(state.count) state.count += 10 } }, actions: { change ({commit}) { // 模拟异步 setTimeout(() => { commit(‘change’) }, 1000) } }})配置选项都写好之后,就看到getters对象里面有个newCount函数,mutations和actions对象里面都有个change函数,配置好store之后我们在App.vue就可以写上,dispatch和commit,分别可以触发actions和mutations,代码如下<template> <div id=“app”> 这里是store的state——->{{this.$store.state.count}} <br/> 这里是store的getter——->{{this.$store.getters.newCount}} <br/> <button @click=“change”>点击触发dispach–> actions</button> <button @click=“change1”>点击触发commit—> mutations</button> </div></template><script>export default { name: ‘app’, methods: { change () { this.$store.dispatch(‘change’) // 触发actions对应的change }, change1 () { this.$store.commit(‘change’) // 触发mutations对应的change } }, mounted () { console.log(this.$store) }}</script>数据都配置好之后,我们开始编写store类,在此之前我们先编写一个循环对象工具函数。const myforEach = (obj, callback) => Object.keys(obj).forEach(key => callback(key, obj[key]))// 作用:// 例如{a: ‘123’}, 把对象的key和value作为参数// 然后就是函数运行callback(a, ‘123’)工具函数都准备好了,之后,下面直接县编写getters、mutations和actions的实现class Store { constructor (options) { let { state = {}, getters = {}, actions = {}, mutations = {} } = options this.getters = {} this.mutations = {} this.actions = {} // vuex的核心就是借用vue的实例,因为vuex的数据更改回更新视图 this._vm = new Vue({ data: { state } }) // 循环getters的对象 myforEach(getters, (getterName, getterFn) => { // 对this.getters对象进行包装,和vue的computed是差不多的 // 例如 this.getters[’newCount’] = fn(state) // 执行 this.getters’newCount’就会返回计算的数据啦 Object.defineProperty(this.getters, getterName, { get: () => getterFn(state) }) }) // 这里是mutations各个key和值都写到,this.mutations对象上面 // 执行的时候就是例如:this.mutations‘change’ myforEach(mutations, (mutationName, mutationsFn) => { // this.mutations.change = () => { change(state) } this.mutations[mutationName] = () => { mutationsFn.call(this, state) } }) // 原理同上 myforEach(actions, (actionName, actionFn) => { // this.mutations.change = () => { change(state) } this.actions[actionName] = () => { actionFn.call(this, this) } }) const {commit , dispatch} = this // 先存一份,避免this.commit会覆盖原型上的this.commit // 解构 把this绑定好 // 通过结构的方式也要先调用这类,然后在下面在调用原型的对应函数 this.commit = type => { commit.call(this, type) } this.dispatch = type => { dispatch.call(this, type) } } get state() { // Object.defineProperty 同理 return this._vm.state } // commi调用 commit (type) { this.mutationstype } // dispatch调用 dispatch (type) { this.actionstype }}通过上面的,我们可以看出,其实mutations和actions都是把传入的参数,赋值到store实例上的this.mutations和this.actions对象里面。当组件中this.$store.commit(‘change’)的时候 其实是调用this.mutations.change(state),就达到了改变数据的效果,actions同理。getters是通过对Object.defineProperty(this.getters, getterName, {})对this.getters进行包装当组件中this.$store.getters.newCount其实是调用getters对象里面的newCount(state),然后返回计算结果。就可以显示到界面上了。大家看看完成后的效果图。到这里大家应该懂了vuex的内部代码的工作流程了,vuex的一半核心应该在这里了。为什么说一半,因为还有一个核心概念module,也就是vuex的数据的模块化。vuex数据模块化由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割例如下面的store.js// 实例化store,参数数对象export default new Vuex.Store({ modules: { // 模块a a: { state: { count: 4000 }, actions: { change ({state}) { state.count += 21 } }, modules: { // 模块b b: { state: { count: 5000 } } } } }, state: { count : 1000 }, getters : { newCount (state) { return state.count + 100 } }, mutations: { change (state) { console.log(state.count) state.count += 10 } }, actions: { change ({commit}) { // 模拟异步 setTimeout(() => { commit(‘change’) }, 1000) } }})然后就可以在界面上就可以写上this.$store.state.a.count(显示a模块count),this.$store.state.a.b.count(显示a模块下,b模块的count),这里还有一个要注意的,其实在组件中调用this.$store.dispatch(‘change’)会同时触发,根的actions和a模块的actions里面的change函数。下面我们就直接去实现models的代码,也就是整个vuex的实现代码,‘use strict’let Vue = nullconst myforEach = (obj, callback) => Object.keys(obj).forEach(key => callback(key, obj[key]))class Store { constructor (options) { let state = options.state this.getters = {} this.mutations = {} this.actions = {} // vuex的核心就是借用vue的实例,因为vuex的数据更改回更新视图 this._vm = new Vue({ data: { state } }) // 把模块之间的关系进行整理, 自己根据用户参数维护了一个对象 // root._children => a._children => b this.modules = new ModulesCollections(options) // 无论子模块还是 孙子模块 ,所有的mutations 都是根上的 // 安装模块 installModules(this, state, [], this.modules.root) // 解构 把this绑定好 const {commit , dispatch} = this // 通过结构的方式也要先调用这类,然后在下面在调用原型的对应函数 this.commit = type => { commit.call(this, type) } this.dispatch = type => { dispatch.call(this, type) } } get state() { // Object.defineProperty 同理 return this._vm.state } commit (type) { // 因为是数组,所以要遍历执行 this.mutations[type].forEach(fn => fn()) } dispatch (type) { // 因为是数组,所以要遍历执行 this.actions[type].forEach(fn => fn()) }}class ModulesCollections { constructor (options) { // vuex [] // 注册模块 this.register([], options) } register (path, rawModule) { // path 是空数组, rawModule 就是个对象 let newModule = { _raw: rawModule, // 对象 _children: {}, // 把子模块挂载到这里 state: rawModule.state } if (path.length === 0) { // 第一次 this.root = newModule } else { // [a, b] ==> [a] let parent = path.slice(0, -1).reduce((root, current) => { return root._children[current] }, this.root) parent._children[path[path.length - 1]] = newModule } if (rawModule.modules) { // 遍历注册子模块 myforEach(rawModule.modules, (childName, module) => { this.register(path.concat(childName), module) }) } }}// rootModule {_raw, _children, state }function installModules (store, rootState, path, rootModule) { // rootState.a = {count:200} // rootState.a.b = {count: 3000} if (path.length > 0) { // 根据path找到对应的父级模块 // 例如 [a] –> path.slice(0, -1) –> [] 此时a模块的父级模块是跟模块 // 例如 [a,b] –> path.slice(0, -1) –> [a] 此时b模块的父级模块是a模块 let parent = path.slice(0, -1).reduce((root, current) => { return root[current] }, rootState) // 通过Vue.set设置数据双向绑定 Vue.set(parent, path[path.length - 1], rootModule.state) } // 设置getter if (rootModule._raw.getters) { myforEach(rootModule._raw.getters, (getterName, getterFn) => { Object.defineProperty(store.getters, getterName, { get: () => { return getterFn(rootModule.state) } }) }) } // 在跟模块设置actions if (rootModule._raw.actions) { myforEach(rootModule._raw.actions, (actionName, actionsFn) => { // 因为同是在根模块设置,子模块也有能相同的key // 所有把所有的都放到一个数组里面 // 就变成了例如 [change, change] , 第一个是跟模块的actions的change,第二个是a模块的actions的change let entry = store.actions[actionName] || (store.actions[actionName] = []) entry.push(() => { const commit = store.commit const state = rootModule.state actionsFn.call(store, {state, commit}) }) }) } // 在跟模块设置mutations, 同理上actions if (rootModule._raw.mutations) { myforEach(rootModule._raw.mutations, (mutationName, mutationFn) => { let entry = store.mutations[mutationName] || (store.mutations[mutationName] = []) entry.push(() => { mutationFn.call(store, rootModule.state) }) }) } // 递归遍历子节点的设置 myforEach(rootModule._children, (childName, module) => { installModules(store, rootState, path.concat(childName), module) })}const install = _Vue => { // 避免vuex重复安装 if (Vue === _Vue) return Vue = _Vue Vue.mixin({ // 通过mixins让每个组件实例化的时候都会执行下面的beforeCreate beforeCreate () { // 只有跟节点才有store配置 if (this.$options && this.$options.store) { this.$store = this.$options.store } else if (this.$parent && this.$parent.$store) { // 子组件深度优先 父 –> 子—> 孙子 this.$store = this.$parent.$store } } })}export default { install, Store }看到代码以及注释,主要流程就是根据递归的方式,处理数据,然后根据传进来的配置,进行操作数据。至此,我们把vuex的代码实现了一遍,在我们App.vue的代码里添加<template> <div id=“app”> 这里是store的state——->{{this.$store.state.count}} <br/> 这里是store的getter——->{{this.$store.getters.newCount}} <br/> 这里是store的state.a——->{{this.$store.state.a.count}} <br/> <button @click=“change”>点击触发dispach–> actions</button> <button @click=“change1”>点击触发commit—> mutations</button> </div></template>最后查看结果。完结撒花~~~博客文章地址:https://blog.naice.me/article…源码地址:https://github.com/naihe138/w… ...

February 28, 2019 · 5 min · jiezi

Vue笔记(六)——Vue组件通信&Vuex

组件通信vue本身的组件通信父==>子:父组件向子组件传参或者调用子组件的方法子==>父:子组件向父组件传参或者调用父组件的方法兄弟之间:兄弟组件之间传参或者调用方法父子通信传参 - props思路:定义子组件的属性(自定义),父组件赋值给子组件调用方法(1) - $refs思路:定义子组件的属性-ref=“a”,父组件通过:this.$refs.a.子组件方法();调用方法(2) - children思路:父组件通过:this.$children[0].子组件方法();子父通信调用方法(1) - $parent.$emit(“父组件自定义事件”);思路:父组件在组件的生命周期(mounted)用$on定义事件,子组件用this.$parent.$emit(“父组件自定义事件”);调用方法(2) - $emit(“父组件自定义事件”);思路:父组件在调用子组件时用v-on把事件传给子组件,子组件用this.$emit(“父组件自定义事件”)调用父组件传过来的事件<div id=“app”> <h1>父组件-{{this.text}}</h1> <!– 子组件 –> <child v-on:parentEvent=“changeText()"></child></div><script type=“text/javascript”> var vm = new Vue({ el:"#app”, data:{ text:’’ }, methods:{ changeText(_text){ this.text = _text; } }, components:{ ‘child’:{ template:’<p><label>子组件</label><input type=“text” v-on:input=“change” /></p>’, methods:{ change(event){ this.$emit(‘parentEvent’,event.target.value); } } } } });</script>调用方法(3) - parent思路:父组件在调用子组件时用v-on把事件传给子组件,子组件用this.$parent.父组件事件<div id=“app”> <h1>父组件-{{this.text}}</h1> <child></child></div><script type=“text/javascript”> var vm = new Vue({ el:"#app", data:{ text:’’ }, methods:{ changeText(_text){ this.text = _text; } }, components:{ ‘child’:{ template:’<p><label>子组件</label><input type=“text” v-on:input=“change” /></p>’, methods:{ change(event){ this.$parent.changeText(event.target.value); } } } } });</script>兄弟之间的通信和上面介绍的父子之间通信是一样的,因为任何连个子组件之间都会拥有一个共同的父级组件,所以兄弟组件之间的通信就是子1向父,然后父向子2,这样来达到兄弟之间组件的通信Vuex - 状态管理通信跨组件通信的一种实现方式用到就封装一个组件.js组件.js// 设置属性:stateconst state = { count = 0;}// 设置方法:mutaionsconst mutaions = { increment(_state){ _state.count += 1; }}// 执行方法const actions = { increment(_content){ _content.commit(‘increment’); }}// 暴露export default{ state, mutaions, actions}集合实例化 store.jsimport Vue from ‘Vue’;import Vuex from ‘vuex’;// 引入组件.jsimport transition from ‘./transion.js’;// 实例化对象const store = new Vue.Store({ modules:{ transition }});// 暴露对象export default store;主入口app.js实例化vuex对象// 引入vuex对象import store from ‘./vuex/store.js’;// 实例化vuex对象new Vue({ el:"#app", router, store, render:h=>h(app)});各组件之间调用1.调用参数$store.state.组建名.属性2.调用方法$store.dispatch(‘事件名’); ...

February 28, 2019 · 1 min · jiezi

关于Vuex的action传入多个参数的问题

问题:已知Vuex中通过actions提交mutations要通过context.commit(mutations,object)的方式来完成然而commit中只能传入两个参数,第一个就是mutations,第二个就是要传入的参数一开始遇到的问题是加入购物车方法中要传入一个字典对象里面保存产品信息item,还要传入一个产品数量的参数num然而如果这么写的话就会报错:context.commit(mutations,item,num)解决办法:将第二个参数以对象的放式提交就像这样mutations = { PRODUCT_ADDTO_CART:(state,product) => { //code }}actions = { productaddtocart:(context,value) => { context.commit(‘PRODUCT_ADDTO_CART’, value); },}在页面调用方法的时候是这样的:productAddToCart(item,productNum){ this.productaddtocart({“item”:item,“num”:this.productNum})}这里的关键信息是将参数打包成一个字典对象传入,在mutations里面调用的话就product.item,product.num就解决了

February 22, 2019 · 1 min · jiezi

vuejs组件通信精髓归纳

组件的分类常规页面组件,由 vue-router 产生的每个页面,它本质上也是一个组件(.vue),主要承载当前页面的 HTML 结构,会包含数据获取、数据整理、数据可视化等常规业务。功能性抽象组件,不包含业务,独立、具体功能的基础组件,比如日期选择器、弹窗警告等。这类组件作为项目的基础控件,会被大量使用,因此组件的 API 进行过高强度的抽象,可以通过不同配置实现不同的功能。业务组件,它不像第二类独立组件只包含某个功能,而是在业务中被多个页面复用的,它与独立组件的区别是,业务组件只在当前项目中会用到,不具有通用性,而且会包含一些业务,比如数据请求;而独立组件不含业务,在任何项目中都可以使用,功能单一,比如一个具有数据校验功能的输入框。组件的关系父子组件父子关系即是组件 A 在它的模板中使用了组件 B,那么组件 A 就是父组件,组件 B 就是子组件。// 注册一个子组件Vue.component(‘child’, { data: function(){ return { text: ‘我是father的子组件!’ } }, template: ‘<span>{{ text }}</span>’})// 注册一个父组件Vue.component(‘father’, { template: ‘<div><child></child></div>’ // 在模板中使用了child组件})兄弟组件两个组件互不引用,则为兄弟组件。Vue.component(‘brother1’, { template: ‘<div>我是大哥</div>’})Vue.component(‘brother2’, { template: ‘<div>我是小弟</div>’})使用组件的时候:<div id=“app”> <brother1></brother1> <brother2></brother2></div>跨级组件就是在父子关系中,中间跨了很多个层级组件的构成一个再复杂的组件,都是由三部分组成的:prop、event、slot,它们构成了 Vue.js 组件的 API。属性 propprop 定义了这个组件有哪些可配置的属性,组件的核心功能也都是它来确定的。写通用组件时,props 最好用对象的写法,这样可以针对每个属性设置类型、默认值或自定义校验属性的值,这点在组件开发中很重要,然而很多人却忽视,直接使用 props 的数组用法,这样的组件往往是不严谨的。插槽 slot插槽 slot,它可以分发组件的内容。和 HTML 元素一样,我们经常需要向一个组件传递内容,像这样:<alert-box> Something bad happened.</alert-box>可能会渲染出这样的东西:Error!Something bad happended.幸好,Vue 自定义的 <slot> 元素让这变得非常简单:Vue.component(‘alert-box’, { template: &lt;div class="demo-alert-box"&gt; &lt;strong&gt;Error!&lt;/strong&gt; &lt;slot&gt;&lt;/slot&gt; &lt;/div&gt; })如你所见,我们只要在需要的地方加入插槽就行了——就这么简单!自定义事件 event两种写法:在组件内部自定义事件event<template> <button @click=“handleClick”> <slot></slot> </button></template><script> export default { methods: { handleClick (event) { this.$emit(‘on-click’, event); } } }</script>通过 $emit,就可以触发自定义的事件 on-click ,在父级通过 @on-click 来监听:<i-button @on-click=“handleClick”></i-button>用事件修饰符 .native直接在父级声明所以上面的示例也可以这样写:<i-button @click.native=“handleClick”></i-button>如果不写 .native 修饰符,那上面的 @click 就是自定义事件 click,而非原生事件 click,但我们在组件内只触发了 on-click 事件,而不是 click,所以直接写 @click 会监听不到。组件的通信ref和$parent和$childrenVue.js 内置的通信手段一般有两种:ref:给元素或组件注册引用信息;$parent / $children:访问父 / 子实例。用 ref 来访问组件(部分代码省略):// component-aexport default { data () { return { title: ‘Vue.js’ } }, methods: { sayHello () { window.alert(‘Hello’); } }}<template> <component-a ref=“comA”></component-a></template><script> export default { mounted () { const comA = this.$refs.comA; console.log(comA.title); // Vue.js comA.sayHello(); // 弹窗 } }</script>$parent 和 $children 类似,也是基于当前上下文访问父组件或全部子组件的。这两种方法的弊端是,无法在跨级或兄弟间通信,比如下面的结构:// parent.vue<component-a></component-a><component-b></component-b><component-b></component-b>我们想在 component-a 中,访问到引用它的页面中(这里就是 parent.vue)的两个 component-b 组件,那这种情况下,是暂时无法实现的,后面会讲解到方法。provide / inject一种无依赖的组件通信方法:Vue.js 内置的 provide / inject 接口provide / inject 是 Vue.js 2.2.0 版本后新增的 API,在文档中这样介绍 :这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。假设有两个组件: A.vue 和 B.vue,B 是 A 的子组件:// A.vueexport default { provide: { name: ‘Aresn’ }}// B.vueexport default { inject: [’name’], mounted () { console.log(this.name); // Aresn }}需要注意的是:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。只要一个组件使用了 provide 向下提供数据,那其下所有的子组件都可以通过 inject 来注入,不管中间隔了多少代,而且可以注入多个来自不同父级提供的数据。需要注意的是,一旦注入了某个数据,那这个组件中就不能再声明 这个数据了,因为它已经被父级占有。provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。然后有两种场景它不能很好的解决:父组件向子组件(支持跨级)传递数据;子组件向父组件(支持跨级)传递数据。这种父子(含跨级)传递数据的通信方式,Vue.js 并没有提供原生的 API 来支持,下面介绍一种在父子组件间通信的方法 dispatch 和 broadcast。$attrs和$listeners如果父组件A下面有子组件B,组件B下面有组件C,这时如果组件A想传递数据给组件C怎么办呢? Vue 2.4开始提供了$attrs和$listeners来解决这个问题,能够让组件A之间传递消息给组件C。Vue.component(‘C’,{ template: &lt;div&gt; &lt;input type="text" v-model="$attrs.messagec" @input="passCData($attrs.messagec)"&gt; &lt;/div&gt; , methods:{ passCData(val){ //触发父组件A中的事件 this.$emit(‘getCData’,val) } } }) Vue.component(‘B’,{ data(){ return { mymessage:this.message } }, template: &lt;div&gt; &lt;input type="text" v-model="mymessage" @input="passData(mymessage)"&gt; &lt;!-- C组件中能直接触发getCData的原因在于 B组件调用C组件时 使用 v-on 绑定了$listeners 属性 --&gt; &lt;!-- 通过v-bind 绑定$attrs属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props声明的) --&gt; &lt;C v-bind="$attrs" v-on="$listeners"&gt;&lt;/C&gt; &lt;/div&gt; , props:[‘message’],//得到父组件传递过来的数据 methods:{ passData(val){ //触发父组件中的事件 this.$emit(‘getChildData’,val) } } }) Vue.component(‘A’,{ template: &lt;div&gt; &lt;p&gt;this is parent compoent!&lt;/p&gt; &lt;B :messagec="messagec" :message="message" v-on:getCData="getCData" v-on:getChildData="getChildData(message)"&gt;&lt;/B&gt; &lt;/div&gt; , data(){ return { message:‘hello’, messagec:‘hello c’ //传递给c组件的数据 } }, methods:{ getChildData(val){ console.log(‘这是来自B组件的数据’) }, //执行C子组件触发的事件 getCData(val){ console.log(“这是来自C组件的数据:"+val) } } }) var app=new Vue({ el:’#app’, template: &lt;div&gt; &lt;A&gt;&lt;/A&gt; &lt;/div&gt; })派发与广播——自行实现 dispatch 和 broadcast 方法要实现的 dispatch 和 broadcast 方法,将具有以下功能:在子组件调用 dispatch 方法,向上级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该上级组件已预先通过 $on 监听了这个事件;相反,在父组件调用 broadcast 方法,向下级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该下级组件已预先通过 $on 监听了这个事件。// 部分代码省略import Emitter from ‘../mixins/emitter.js’export default { mixins: [ Emitter ], methods: { handleDispatch () { this.dispatch(); // ① }, handleBroadcast () { this.broadcast(); // ② } }} //emitter.js 的代码:function broadcast(componentName, eventName, params) { this.$children.forEach(child => { const name = child.$options.name; if (name === componentName) { child.$emit.apply(child, [eventName].concat(params)); } else { broadcast.apply(child, [componentName, eventName].concat([params])); } });}export default { methods: { dispatch(componentName, eventName, params) { let parent = this.$parent || this.$root; let name = parent.$options.name; while (parent && (!name || name !== componentName)) { parent = parent.$parent; if (parent) { name = parent.$options.name; } } if (parent) { parent.$emit.apply(parent, [eventName].concat(params)); } }, broadcast(componentName, eventName, params) { broadcast.call(this, componentName, eventName, params); } }};因为是用作 mixins 导入,所以在 methods 里定义的 dispatch 和 broadcast 方法会被混合到组件里,自然就可以用 this.dispatch 和 this.broadcast 来使用。这两个方法都接收了三个参数,第一个是组件的 name 值,用于向上或向下递归遍历来寻找对应的组件,第二个和第三个就是上文分析的自定义事件名称和要传递的数据。可以看到,在 dispatch 里,通过 while 语句,不断向上遍历更新当前组件(即上下文为当前调用该方法的组件)的父组件实例(变量 parent 即为父组件实例),直到匹配到定义的 componentName 与某个上级组件的 name 选项一致时,结束循环,并在找到的组件实例上,调用 $emit 方法来触发自定义事件 eventName。broadcast 方法与之类似,只不过是向下遍历寻找。来看一下具体的使用方法。有 A.vue 和 B.vue 两个组件,其中 B 是 A 的子组件,中间可能跨多级,在 A 中向 B 通信:<!– A.vue –><template> <button @click=“handleClick”>触发事件</button></template><script> import Emitter from ‘../mixins/emitter.js’; export default { name: ‘componentA’, mixins: [ Emitter ], methods: { handleClick () { this.broadcast(‘componentB’, ‘on-message’, ‘Hello Vue.js’); } } }</script>// B.vueexport default { name: ‘componentB’, created () { this.$on(‘on-message’, this.showMessage); }, methods: { showMessage (text) { window.alert(text); } }}同理,如果是 B 向 A 通信,在 B 中调用 dispatch 方法,在 A 中使用 $on 监听事件即可。以上就是自行实现的 dispatch 和 broadcast 方法。找到任意组件实例——findComponents 系列方法它适用于以下场景:由一个组件,向上找到最近的指定组件;由一个组件,向上找到所有的指定组件;由一个组件,向下找到最近的指定组件;由一个组件,向下找到所有指定的组件;由一个组件,找到指定组件的兄弟组件。5 个不同的场景,对应 5 个不同的函数,实现原理也大同小异。向上找到最近的指定组件——findComponentUpward// 由一个组件,向上找到最近的指定组件function findComponentUpward (context, componentName) { let parent = context.$parent; let name = parent.$options.name; while (parent && (!name || [componentName].indexOf(name) < 0)) { parent = parent.$parent; if (parent) name = parent.$options.name; } return parent;}export { findComponentUpward };比如下面的示例,有组件 A 和组件 B,A 是 B 的父组件,在 B 中获取和调用 A 中的数据和方法:<!– component-a.vue –><template> <div> 组件 A <component-b></component-b> </div></template><script> import componentB from ‘./component-b.vue’; export default { name: ‘componentA’, components: { componentB }, data () { return { name: ‘Aresn’ } }, methods: { sayHello () { console.log(‘Hello, Vue.js’); } } }</script><!– component-b.vue –><template> <div> 组件 B </div></template><script> import { findComponentUpward } from ‘../utils/assist.js’; export default { name: ‘componentB’, mounted () { const comA = findComponentUpward(this, ‘componentA’); if (comA) { console.log(comA.name); // Aresn comA.sayHello(); // Hello, Vue.js } } }</script>向上找到所有的指定组件——findComponentsUpward// 由一个组件,向上找到所有的指定组件function findComponentsUpward (context, componentName) { let parents = []; const parent = context.$parent; if (parent) { if (parent.$options.name === componentName) parents.push(parent); return parents.concat(findComponentsUpward(parent, componentName)); } else { return []; }}export { findComponentsUpward };向下找到最近的指定组件——findComponentDownward// 由一个组件,向下找到最近的指定组件function findComponentDownward (context, componentName) { const childrens = context.$children; let children = null; if (childrens.length) { for (const child of childrens) { const name = child.$options.name; if (name === componentName) { children = child; break; } else { children = findComponentDownward(child, componentName); if (children) break; } } } return children;}export { findComponentDownward };向下找到所有指定的组件——findComponentsDownward// 由一个组件,向下找到所有指定的组件function findComponentsDownward (context, componentName) { return context.$children.reduce((components, child) => { if (child.$options.name === componentName) components.push(child); const foundChilds = findComponentsDownward(child, componentName); return components.concat(foundChilds); }, []);}export { findComponentsDownward };找到指定组件的兄弟组件——findBrothersComponents// 由一个组件,找到指定组件的兄弟组件function findBrothersComponents (context, componentName, exceptMe = true) { let res = context.$parent.$children.filter(item => { return item.$options.name === componentName; }); let index = res.findIndex(item => item._uid === context._uid); if (exceptMe) res.splice(index, 1); return res;}export { findBrothersComponents };相比其它 4 个函数,findBrothersComponents 多了一个参数 exceptMe,是否把本身除外,默认是 true。寻找兄弟组件的方法,是先获取 context.$parent.$children,也就是父组件的全部子组件,这里面当前包含了本身,所有也会有第三个参数 exceptMe。Vue.js 在渲染组件时,都会给每个组件加一个内置的属性 _uid,这个 _uid 是不会重复的,借此我们可以从一系列兄弟组件中把自己排除掉。Event Bus有时候两个组件之间需要进行通信,但是它们彼此不是父子组件的关系。在一些简单场景,你可以使用一个空的 Vue 实例作为一个事件总线中心(central event bus): //中央事件总线 var bus=new Vue(); var app=new Vue({ el:’#app’, template: &lt;div&gt; &lt;brother1&gt;&lt;/brother1&gt; &lt;brother2&gt;&lt;/brother2&gt; &lt;/div&gt; })// 在组件 brother1 的 methods 方法中触发事件bus.$emit(‘say-hello’, ‘world’)// 在组件 brother2 的 created 钩子函数中监听事件bus.$on(‘say-hello’, function (arg) { console.log(‘hello ’ + arg); // hello world})vuex处理组件之间的数据交互如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候才有上面这一些方法可能不利于项目的维护,vuex的做法就是将这一些公共的数据抽离出来,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。 详情可参考:https://vuex.vuejs.org/zh-cn/参考 vue组件之间8种组件通信方式总结参考 https://github.com/iview/ivie…参考 https://github.com/iview/ivie… ...

February 22, 2019 · 5 min · jiezi

vuejs组件通信精髓归纳

组件的分类常规页面组件,由 vue-router 产生的每个页面,它本质上也是一个组件(.vue),主要承载当前页面的 HTML 结构,会包含数据获取、数据整理、数据可视化等常规业务。功能性抽象组件,不包含业务,独立、具体功能的基础组件,比如日期选择器、模态框等。这类组件作为项目的基础控件,会被大量使用,因此组件的 API 进行过高强度的抽象,可以通过不同配置实现不同的功能。业务组件,它不像第二类独立组件只包含某个功能,而是在业务中被多个页面复用的,它与独立组件的区别是,业务组件只在当前项目中会用到,不具有通用性,而且会包含一些业务,比如数据请求;而独立组件不含业务,在任何项目中都可以使用,功能单一,比如一个具有数据校验功能的输入框。组件的构成一个再复杂的组件,都是由三部分组成的:prop、event、slot,它们构成了 Vue.js 组件的 API。属性 propprop 定义了这个组件有哪些可配置的属性,组件的核心功能也都是它来确定的。写通用组件时,props 最好用对象的写法,这样可以针对每个属性设置类型、默认值或自定义校验属性的值,这点在组件开发中很重要,然而很多人却忽视,直接使用 props 的数组用法,这样的组件往往是不严谨的。插槽 slot插槽 slot,它可以分发组件的内容。和 HTML 元素一样,我们经常需要向一个组件传递内容,像这样:<alert-box> Something bad happened.</alert-box>可能会渲染出这样的东西:Error!Something bad happended.幸好,Vue 自定义的 <slot> 元素让这变得非常简单:Vue.component(‘alert-box’, { template: &lt;div class="demo-alert-box"&gt; &lt;strong&gt;Error!&lt;/strong&gt; &lt;slot&gt;&lt;/slot&gt; &lt;/div&gt; })如你所见,我们只要在需要的地方加入插槽就行了——就这么简单!自定义事件 event两种写法:在组件内部自定义事件event<template> <button @click=“handleClick”> <slot></slot> </button></template><script> export default { methods: { handleClick (event) { this.$emit(‘on-click’, event); } } }</script>通过 $emit,就可以触发自定义的事件 on-click ,在父级通过 @on-click 来监听:<i-button @on-click=“handleClick”></i-button>用事件修饰符 .native直接在父级声明所以上面的示例也可以这样写:<i-button @click.native=“handleClick”></i-button>如果不写 .native 修饰符,那上面的 @click 就是自定义事件 click,而非原生事件 click,但我们在组件内只触发了 on-click 事件,而不是 click,所以直接写 @click 会监听不到。组件的通信ref和$parent和$childrenVue.js 内置的通信手段一般有两种:ref:给元素或组件注册引用信息;$parent / $children:访问父 / 子实例。用 ref 来访问组件(部分代码省略):// component-aexport default { data () { return { title: ‘Vue.js’ } }, methods: { sayHello () { window.alert(‘Hello’); } }}<template> <component-a ref=“comA”></component-a></template><script> export default { mounted () { const comA = this.$refs.comA; console.log(comA.title); // Vue.js comA.sayHello(); // 弹窗 } }</script>$parent 和 $children 类似,也是基于当前上下文访问父组件或全部子组件的。这两种方法的弊端是,无法在跨级或兄弟间通信,比如下面的结构:// parent.vue<component-a></component-a><component-b></component-b><component-b></component-b>我们想在 component-a 中,访问到引用它的页面中(这里就是 parent.vue)的两个 component-b 组件,那这种情况下,是暂时无法实现的,后面会讲解到方法。provide / inject一种无依赖的组件通信方法:Vue.js 内置的 provide / inject 接口provide / inject 是 Vue.js 2.2.0 版本后新增的 API,在文档中这样介绍 :这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。假设有两个组件: A.vue 和 B.vue,B 是 A 的子组件:// A.vueexport default { provide: { name: ‘Aresn’ }}// B.vueexport default { inject: [’name’], mounted () { console.log(this.name); // Aresn }}需要注意的是:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。只要一个组件使用了 provide 向下提供数据,那其下所有的子组件都可以通过 inject 来注入,不管中间隔了多少代,而且可以注入多个来自不同父级提供的数据。需要注意的是,一旦注入了某个数据,那这个组件中就不能再声明 这个数据了,因为它已经被父级占有。provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。然后有两种场景它不能很好的解决:父组件向子组件(支持跨级)传递数据;子组件向父组件(支持跨级)传递数据。这种父子(含跨级)传递数据的通信方式,Vue.js 并没有提供原生的 API 来支持,下面介绍一种在父子组件间通信的方法 dispatch 和 broadcast。派发与广播——自行实现 dispatch 和 broadcast 方法要实现的 dispatch 和 broadcast 方法,将具有以下功能:在子组件调用 dispatch 方法,向上级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该上级组件已预先通过 $on 监听了这个事件;相反,在父组件调用 broadcast 方法,向下级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该下级组件已预先通过 $on 监听了这个事件。// 部分代码省略import Emitter from ‘../mixins/emitter.js’export default { mixins: [ Emitter ], methods: { handleDispatch () { this.dispatch(); // ① }, handleBroadcast () { this.broadcast(); // ② } }} //emitter.js 的代码:function broadcast(componentName, eventName, params) { this.$children.forEach(child => { const name = child.$options.name; if (name === componentName) { child.$emit.apply(child, [eventName].concat(params)); } else { broadcast.apply(child, [componentName, eventName].concat([params])); } });}export default { methods: { dispatch(componentName, eventName, params) { let parent = this.$parent || this.$root; let name = parent.$options.name; while (parent && (!name || name !== componentName)) { parent = parent.$parent; if (parent) { name = parent.$options.name; } } if (parent) { parent.$emit.apply(parent, [eventName].concat(params)); } }, broadcast(componentName, eventName, params) { broadcast.call(this, componentName, eventName, params); } }};因为是用作 mixins 导入,所以在 methods 里定义的 dispatch 和 broadcast 方法会被混合到组件里,自然就可以用 this.dispatch 和 this.broadcast 来使用。这两个方法都接收了三个参数,第一个是组件的 name 值,用于向上或向下递归遍历来寻找对应的组件,第二个和第三个就是上文分析的自定义事件名称和要传递的数据。可以看到,在 dispatch 里,通过 while 语句,不断向上遍历更新当前组件(即上下文为当前调用该方法的组件)的父组件实例(变量 parent 即为父组件实例),直到匹配到定义的 componentName 与某个上级组件的 name 选项一致时,结束循环,并在找到的组件实例上,调用 $emit 方法来触发自定义事件 eventName。broadcast 方法与之类似,只不过是向下遍历寻找。来看一下具体的使用方法。有 A.vue 和 B.vue 两个组件,其中 B 是 A 的子组件,中间可能跨多级,在 A 中向 B 通信:<!– A.vue –><template> <button @click=“handleClick”>触发事件</button></template><script> import Emitter from ‘../mixins/emitter.js’; export default { name: ‘componentA’, mixins: [ Emitter ], methods: { handleClick () { this.broadcast(‘componentB’, ‘on-message’, ‘Hello Vue.js’); } } }</script>// B.vueexport default { name: ‘componentB’, created () { this.$on(‘on-message’, this.showMessage); }, methods: { showMessage (text) { window.alert(text); } }}同理,如果是 B 向 A 通信,在 B 中调用 dispatch 方法,在 A 中使用 $on 监听事件即可。以上就是自行实现的 dispatch 和 broadcast 方法。找到任意组件实例——findComponents 系列方法它适用于以下场景:由一个组件,向上找到最近的指定组件;由一个组件,向上找到所有的指定组件;由一个组件,向下找到最近的指定组件;由一个组件,向下找到所有指定的组件;由一个组件,找到指定组件的兄弟组件。5 个不同的场景,对应 5 个不同的函数,实现原理也大同小异。向上找到最近的指定组件——findComponentUpward// 由一个组件,向上找到最近的指定组件function findComponentUpward (context, componentName) { let parent = context.$parent; let name = parent.$options.name; while (parent && (!name || [componentName].indexOf(name) < 0)) { parent = parent.$parent; if (parent) name = parent.$options.name; } return parent;}export { findComponentUpward };比如下面的示例,有组件 A 和组件 B,A 是 B 的父组件,在 B 中获取和调用 A 中的数据和方法:<!– component-a.vue –><template> <div> 组件 A <component-b></component-b> </div></template><script> import componentB from ‘./component-b.vue’; export default { name: ‘componentA’, components: { componentB }, data () { return { name: ‘Aresn’ } }, methods: { sayHello () { console.log(‘Hello, Vue.js’); } } }</script><!– component-b.vue –><template> <div> 组件 B </div></template><script> import { findComponentUpward } from ‘../utils/assist.js’; export default { name: ‘componentB’, mounted () { const comA = findComponentUpward(this, ‘componentA’); if (comA) { console.log(comA.name); // Aresn comA.sayHello(); // Hello, Vue.js } } }</script>向上找到所有的指定组件——findComponentsUpward// 由一个组件,向上找到所有的指定组件function findComponentsUpward (context, componentName) { let parents = []; const parent = context.$parent; if (parent) { if (parent.$options.name === componentName) parents.push(parent); return parents.concat(findComponentsUpward(parent, componentName)); } else { return []; }}export { findComponentsUpward };向下找到最近的指定组件——findComponentDownward// 由一个组件,向下找到最近的指定组件function findComponentDownward (context, componentName) { const childrens = context.$children; let children = null; if (childrens.length) { for (const child of childrens) { const name = child.$options.name; if (name === componentName) { children = child; break; } else { children = findComponentDownward(child, componentName); if (children) break; } } } return children;}export { findComponentDownward };向下找到所有指定的组件——findComponentsDownward// 由一个组件,向下找到所有指定的组件function findComponentsDownward (context, componentName) { return context.$children.reduce((components, child) => { if (child.$options.name === componentName) components.push(child); const foundChilds = findComponentsDownward(child, componentName); return components.concat(foundChilds); }, []);}export { findComponentsDownward };找到指定组件的兄弟组件——findBrothersComponents// 由一个组件,找到指定组件的兄弟组件function findBrothersComponents (context, componentName, exceptMe = true) { let res = context.$parent.$children.filter(item => { return item.$options.name === componentName; }); let index = res.findIndex(item => item._uid === context._uid); if (exceptMe) res.splice(index, 1); return res;}export { findBrothersComponents };相比其它 4 个函数,findBrothersComponents 多了一个参数 exceptMe,是否把本身除外,默认是 true。寻找兄弟组件的方法,是先获取 context.$parent.$children,也就是父组件的全部子组件,这里面当前包含了本身,所有也会有第三个参数 exceptMe。Vue.js 在渲染组件时,都会给每个组件加一个内置的属性 _uid,这个 _uid 是不会重复的,借此我们可以从一系列兄弟组件中把自己排除掉。参考Vuejs组件精讲 ...

February 17, 2019 · 4 min · jiezi

Vuex 的状态页面加载后调用

由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态computed: { isBtnEditModel() { return this.$store.state.pageLayout.loginUserInfo.dataUser || this.ifIncharge } }直接引用会undifine

February 15, 2019 · 1 min · jiezi

Computed property XXX was assigned to but it has no setter

报错视图:原因:组件中v-model=“XXX”,而XXX是vuex state中的某个变量vuex中是单项流,v-model是vue中的双向绑定,但是在computed中只通过get获取参数值,没有set无法改变参数值解决方法: 1.在computed中添加get和set2.将v-model改成:value

February 12, 2019 · 1 min · jiezi

使用Fabric.js玩转H5 Canvas

前言之前使用这个框架写过一个卡片DIY的项目,中间遇到很多问题都只能通过google或github issues才能解决,国内资料较少,所以才想写这篇文章来简单的做下总结,希望可以帮到其他人哈。附上个人项目地址:vue-card-diy 欢迎star~ ✨什么是Fabric.js?Fabric.js 是一个强大的H5 canvas框架,在原生canvas之上提供了交互式对象模型,通过简洁的api就可以在画布上进行丰富的操作。该框架是个开源项目,项目地址: githubFabric.js有什么功能?使用Fabric.js,你可以在画布上创建和填充对象; 比如简单的几何形状 - 矩形,圆形,椭圆形,多边形,自定义图片或由数百或数千个简单路径组成的更复杂的形状。 另外,还可以使用鼠标缩放,移动和旋转这些对象; 修改它们的属性 - 颜色,透明度,z-index等。也可以将画布上的对象进行组合。下面我将会介绍我常用的功能以及场景,更多功能可以参考 官方文档安装npm安装npm install fabric –save通过cdn引用<script src=“http://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.6/fabric.min.js"></script>初始化首先在html页面中写一个350 x 200的canvas标签, 这里不写宽高也行,后面可以通过js来设置宽高<canvas id=“canvas” width=“350” height=“200”></canvas>初始化fabric的canvas对象,创建一个卡片(后面都用card表示画布对象)const card = new fabric.Canvas(‘canvas’) // …这里可以写canvas对象的一些配置,后面将会介绍// 如果<canvas>标签没设置宽高,可以通过js动态设置card.setWidth(350)card.setHeight(200)就是这么简单,这样就创建了一个基本的画布。开始花样操作监听画布上的事件官方提供了很多事件,以下为常用的事件:object:added 添加图层object:modified 编辑图层object:removed 移除图层selection:created 初次选中图层selection:updated 图层选择变化selection:cleared 清空图层选中// 在canvas对象初始化后,通过以下方式监听// 比如监听画布的图层编辑事件card.on(‘object:modified’, (e) => { console.log(e.target) // e.target为当前编辑的Object // …旋转,缩放,移动等编辑图层的操作都监听到 // 所以如果有撤销/恢复的场景,这里可以保存编辑状态});设置画布背景// 读取图片地址,设置画布背景fabric.Image.fromURL(‘xx/xx/bg.jpg’, (img) => { img.set({ // 通过scale来设置图片大小,这里设置和画布一样大 scaleX: card.width / img.width, scaleY: card.height / img.height, }); // 设置背景 card.setBackgroundImage(img, card.renderAll.bind(card)); card.renderAll();});如果要设置画布的背景颜色,可以在canvas初始化时设置const card = new fabric.Canvas(‘canvas’, { backgroundColor: ‘blue’ // 画布背景色为蓝色});// 或者card.backgroundColor = ‘blue’;// 或者card.setBackgroundColor(‘blue’);向画布添加图层对象fabric.js提供了很多对象,除了基本的 Rect,Circle,Line,Ellipse,Polygon,Polyline,Triangle对象外,还有如 Image,Textbox,Group等更高级的对象,这些都是继承自Fabric的Object对象。下面我就介绍如何添加图片和文字,其他对象大同小异/*** 如何向画布添加一个Image对象?/// 方式一(通过img元素添加)const imgElement = document.getElementById(‘my-image’);const imgInstance = new fabric.Image(imgElement, { left: 100, // 图片相对画布的左侧距离 top: 100, // 图片相对画布的顶部距离 angle: 30, // 图片旋转角度 opacity: 0.85, // 图片透明度 // 这里可以通过scaleX和scaleY来设置图片绘制后的大小,这里为原来大小的一半 scaleX: 0.5, scaleY: 0.5});// 添加对象后, 如下图card.add(imgInstance);// 方式二(通过图片路径添加)fabric.Image.fromURL(‘xx/xx/vue-logo.png’, (img) => { img.set({ hasControls: false, // 是否开启图层的控件 borderColor: ‘orange’, // 图层控件边框的颜色 }); // 添加对象后, 如下图 canvas.add(img);});/** 如何向画布添加一个Textbox对象?*/const textbox = new fabric.Textbox(‘这是一段文字’, { left: 50, top: 50, width: 150, fontSize: 20, // 字体大小 fontWeight: 800, // 字体粗细 // fill: ‘red’, // 字体颜色 // fontStyle: ‘italic’, // 斜体 // fontFamily: ‘Delicious’, // 设置字体 // stroke: ‘green’, // 描边颜色 // strokeWidth: 3, // 描边宽度 hasControls: false, borderColor: ‘orange’, editingBorderColor: ‘blue’ // 点击文字进入编辑状态时的边框颜色});// 添加文字后,如下图card.add(textbox);获取当前选中的图层对象// 方式一this.selectedObj = card.getActiveObject(); // 返回当前画布中被选中的图层 // 方式二card.on(‘selection:created’, (e) => { // 选中图层事件触发时,动态更新赋值 this.selectedObj = e.target})旋转图层// 顺时针90°旋转const currAngle = this.selectedObj.angle; // 当前图层的角度const angle = currAngle === 360 ? 90 :currAngle + 90;this.selectedObj.rotate(angle);// 如果是通过滑块的方式控制旋转// this.selectedObj.rotate(slideValue);// 所有图层的操作之后,都需要调用这个方法card.renderAll()翻转图层// 水平翻转,同理垂直翻转改为scaleY属性this.selectedObj.set({ scaleX: -this.selectedObj.scaleX,})card.renderAll()移除图层card.remove(this.selectedObj) // 传入需要移除的objectcard.renderAll()控制画布上的图层层级向画布添加图层,默认是依次往上叠加,但是当你选中一个图层进入active状态时,该图层会默认置于顶层,如果像禁止选中图层时指定,可以:// 在画布初始化后设置card.preserveObjectStacking = true // 禁止选中图层时自定置于顶部设置之后,我选中vue logo就是这个样子,不会置顶。如何上移和下移图层?// 上移图层this.selectedObj.bringForward();// 下移图层this.selectedObj.sendBackwards();// 也可以使用canvas对象的moveTo方法,移至图层到指定位置card.moveTo(object, index);画布状态记录框架提供了如 toJSON 和 loadFromJSON 方法,作用分别为导出当前画布的json信息,加载json画布信息来还原画布状态。// 导出当前画布信息const currState = card.toJSON(); // 导出的Json如下图// 加载画布信息card.loadFromJSON(lastState, () => { card.renderAll();});将画布导出成图片const dataURL = card.toDataURL({ format: ‘jpeg’, // jpeg或png quality: 0.8 // 图片质量,仅jpeg时可用 // 截取指定位置和大小 //left: 100, //top: 100, //width: 200, //height: 200});Fabric.js的基本介绍就到这里,这个框架很强大,还有很多功能可以去试试,欢迎大家评论交流哈!如转载本文请注明文章作者及出处! ...

February 3, 2019 · 2 min · jiezi

【已解决】使用vue-electron脚手架进行vuex赋值时,失败的解决办法。

1、初步尝试我首先尝试用mutation(commit)传参。结果控制台报错:[Vuex Electron] Please, don’t use direct commit’s, use dispatch instead of this.好好好。那我再用action传参试试。虽然控制台没报错,但却一直无法赋值!2、查找资料我找到一个解决方法:注释掉store目录下index.js的createSharedMutations插件。经测试确实可以!但不知道为什么。3、深入研讨经过进一步的查阅。我了解到,刚才传值失败,是因为electron-vue脚手架引入了vuex-electron介个插件。点击查看vuex-electron的文档文档中明确注明了:In case if you enabled createSharedMutations() plugin you need to create an instance of store in the main process. To do it just add this line into your main process (for example src/main.js):import ‘./path/to/your/store’意思是:如果你启用了这个插件,需要在主进程导出(export )store的实例。于是我在主进程中加上了这一句:import ‘../renderer/store’再次运行,赋值成功!4、反思vuex-electron介个插件,用于多进程间共享Vuex Store的状态。如果没有多进程交互的需求,完全可以不引入这个插件。再进一步思考。之前我都是图方便,直接用脚手架。但它们有可能加载不必要的插件。(甚至会导致兼容问题)需要注意~

January 28, 2019 · 1 min · jiezi

从0到1使用VUE-CLI3开发实战(五):模块化VUEX及使用vuetify

小肆前几天发了一篇2019年Vue精品开源项目库的汇总,今天小肆要使用的是在UI组件中排行第三的Vuetify。vuetify介绍Vuetify是一个渐进式的框架,完全根据Material Design规范开发,一共拥有80多个组件,对移动端支持非常好。支持SSR(服务端渲染),SPA(单页应用程序),PWA(渐进式Web应用程序)和标准HTML页面。vuetify官方文档给出了它具备的几点优势:安装安装算是比较简单了,在项目目录输入以下命令就OK:vue add vuetify但这时有一个问题,如果我们使用默认的icon,index.html里面引入的是google的链接 <link href=‘https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons’ rel=“stylesheet”>我们需要替换成国内的https://fonts.cat.net/底部导航组件今天我们先用vuetify的语法写一个底部导航的组件,先放代码:<template> <v-card flat> <v-bottom-nav :value=“true” fixed color=“transparent”> <v-btn color=“teal” :to="{path:’/’}" flat> <span>首页</span> <v-icon>home</v-icon> </v-btn> <v-btn color=“teal” :to="{path:’/lottery’}" flat> <span>足彩</span> <v-icon>favorite</v-icon> </v-btn> <v-btn color=“teal” :to="{path:’/competition’}" flat> <span>赛事</span> <v-icon>place</v-icon> </v-btn> <v-btn color=“teal” :to="{path:’/course’}" flat> <span>课程</span> <v-icon>music_video</v-icon> </v-btn> </v-bottom-nav> </v-card></template>这里主要用到的是v-bottom-nav这个API,下面这张图显示了它可用的全部属性:上述代码的实际显示效果:模块化vuex为了使用方便,我们改造一下vuex,新建store目录,目录结构如下:更改store.jsimport Vue from ‘vue’import Vuex from ‘vuex’import app from ‘./store/modules/app’import user from ‘./store/modules/user’import getters from ‘./store/getters’Vue.use(Vuex)const store = new Vuex.Store({ modules: { app, user }, getters})export default store全局loading昨天我们配置了axios,今天我们来配置一下全局loading。先写一个组件RequestLoading.vue<template> <transition name=“fade-transform” mode=“out-in”> <div class=“request-loading-component” v-if=“requestLoading”> <v-progress-circular :size=“50” color=“primary” indeterminate></v-progress-circular> </div> </transition></template><script>import { mapGetters } from ‘vuex’export default { name: ‘RequestLoading’, computed: { …mapGetters([‘requestLoading’]) }}</script><style lang=“stylus” scoped>.request-loading-component { position: fixed; left: 0; right: 0; top: 0; bottom: 0; background-color: rgba(48, 65, 86, 0.5); font-size: 150px; display: flex; flex-direction: row; justify-content: center; align-items: center; z-index: 999999;}</style>这里我们用到了,vuetify中的v-progress-circular接下来我们配置一下vuexapp.jsconst app = { state: { requestLoading: 0 }, mutations: { SET_LOADING: (state, status) => { // error 的时候直接重置 if (status === 0) { state.requestLoading = 0 return } if (status) { ++state.requestLoading } else { –state.requestLoading } } }, actions: { SetLoading({ commit }, status) { commit(‘SET_LOADING’, status) } }}export default appgetter.jsconst getters = { requestLoading: (state) => state.app.requestLoading, token: (state) => state.user.token, avatar: (state) => state.user.avatar, name: (state) => state.user.name}export default getters最后我们修改一下axios.js// 添加请求拦截器service.interceptors.request.use( (config) => { if (config.method === ‘post’ || config.method === ‘put’) { // post、put 提交时,将对象转换为string, 为处理Java后台解析问题 config.data = JSON.stringify(config.data) } // loading + 1 store.dispatch(‘SetLoading’, true) // 请求发送前进行处理 return config }, (error) => { // 请求错误处理 // loading 清 0 setTimeout(function() { store.dispatch(‘SetLoading’, 0) }, 300) return Promise.reject(error) })// 添加响应拦截器service.interceptors.response.use( (response) => { let { data, headers } = response if (headers[‘x-auth-token’]) { data.token = headers[‘x-auth-token’] } // loading - 1 store.dispatch(‘SetLoading’, false) return data }, (error) => { let info = {}, { status, statusText, data } = error.response if (!error.response) { info = { code: 5000, msg: ‘Network Error’ } } else { // 此处整理错误信息格式 info = { code: status, data: data, msg: statusText } } // loading - 1 store.dispatch(‘SetLoading’, false) return Promise.reject(info) })这样我们在等待接口返回数据是就会看到下面这样子:小结好啦 ,今天就到这里吧,如果有什么疑问,可以下面留言,小肆会及时回复的。记得点好看呦!前置阅读:用vue-cli3从0到1做一个完整功能手机站(一)从0到1开发实战手机站(二):Git提交规范配置从0到1使用VUE-CLI3开发实战(三): ES6知识储备从0到1使用VUE-CLI3开发实战(四):Axios封装 ...

January 26, 2019 · 2 min · jiezi

基础知识:vue-cli项目搭建及配置

vue-cli脚手架搭建// 准备 npm install vue-cli -g确保已全局安装node、npm // 下载 vue init webpack my-project下载脚手架项目// 进入 cd my-project // 运行 npm start把服务跑起来 // 访问 localhost:8080// 了解 以下为vue-cli的结构图config/index.js端口// 更改端口port: 3000,打开浏览器// 是否在编译完成后,// 自动打开浏览器访问http://localhost:3000autoOpenBrowser: false, 代理proxyTable: { // 接口跨域 // 解决跨域的问题 ‘/lots-web/**’: { target: ‘http://localhost:8080’ // 接口的域名 }, // 开发环境本地数据挂载 // json数据文件路径:static/mock/index.json ‘/api’: { target: ‘http://localhost:7000’, pathRewrite: { ‘^/api’: ‘/static/mock’ } }}build/webpack.base.conf.js路径配置alias: { ‘vue$’: ‘vue/dist/vue.esm.js’, ‘@’: resolve(‘src’), // styles ‘styles’: resolve(‘src/assets/styles’)} scss预编译// 安装模块:// npm install –save-dev sass-loader// npm install –save-dev node-sass{ test: /.sass$/, loaders: [‘style’, ‘css’, ‘sass’]}修改文件:xxx.vue<style lang=‘scss’ scoped><style>build/utils.jsvariables.scss预编译// 全局引用variables.scss变量文件 // 安装模块:// npm install –save-dev sass-resources-loaderscss: generateLoaders(‘sass’).concat( { loader: ‘sass-resources-loader’, options: { resources: path.resolve(__dirname, ‘../src/assets/styles/variables.scss’) } })variables.styl预编译// 全局引用variables.styl变量文件 // 安装模块:// npm install –save-dev stylus// npm install –save-dev stylus-loaderconst stylusOptions = { import: [ path.join(__dirname, “../src/assets/styles/variables.styl”) ]}return { stylus: generateLoaders(‘stylus’, stylusOptions), styl: generateLoaders(‘stylus’, stylusOptions)}src/main.js初始 主文件import Vue from ‘vue’import App from ‘./App’import router from ‘./router’ // 路由// 设置为 false 以阻止 vue 在启动时生成生产提示Vue.config.productionTip = false new Vue({ el: ‘#app’, router, components: {App}, template: ‘’}) 详细 主文件// 入口 import router from ‘./router’ // 路由import store from ‘./store’ // 状态import api from ‘./utility/api’ // 接口 // 模块 import axios from ‘axios’ // 接口调用import ‘babel-polyfill’ // ie9和一些低版本的浏览器对es6新语法的不支持问题import fastClick from ‘fastclick’ // 延迟300毫秒import VueAwesomeSwiper from ‘vue-awesome-swiper’ // 图片轮播import BScroll from ‘better-scroll’ // 拖动插件 // 公用样式 import ‘styles/reset.css’ // 重置import ‘styles/common.css’ // 公共import ‘styles/border.css’ // 1像素边import ‘styles/iconfont.css’ // 文字图标import ‘swiper/dist/css/swiper.css’ // 图片轮播// 公用组件 import Fade from ‘@/pages/common/fade’ // 显隐过渡import Gallery from ‘@/pages/common/gallery’ // 画廊Vue.component(‘c-fade’, Fade)Vue.component(‘c-gallery’, Gallery) // 全局调用 Vue.use(VueAwesomeSwiper) // 图片轮播Vue.prototype.$scroll = BScrollVue.prototype.$http = axiosVue.prototype.$api = api // 其他 fastClick.attach(document.body) // 为消除移动端浏览器,从物理触摸到触发点击事件之间的300ms延时的问题 // 创建Vue new Vue({ el: ‘#app’, router, store, components: {App}, template: ‘<App/>’})src/App.vuevue入口文件// keep-alive数据缓存 <keep-alive :exclude=“exclude”> <router-view/></keep-alive>data(){ return { exclude: [ ‘Detail’ ] }}// 用法 // 1、如不填,则缓存全部组件// 2、keep-alive include=“City”,缓存name=‘City’的组件// 3、keep-alive exclude=“Detail”,不缓存name=‘Detail’的组件// 生命周期 // 当时用keep-alive的时候,会触发activated和deactivated生命周期// activated 当组件被激活的时候调用// deactivated 当组件被移除的时候调用src/router/index.js路由文件人口// 初始 import Vue from ‘vue’import Router from ‘vue-router’import Home from ‘@/pages/home/home’export default new Router({ routes: [ { path: ‘/home’, component: Home } ])} // 组件和路由路径 import Home from ‘@/pages/home/home’const path = { home: ‘/’} // 路由和组件渲染 routes: [ { path: path.home, component: Home }] // 路由"#“号去除 mode: ‘history’, // 当前路由添加.activelinkActiveClass: ‘active’,linkExactActiveClass: ‘active’, // 切换路由时界面始终显示顶部 scrollBehavior(to, from, savePosition){ return { x: 0, y: 0 }} // vue用$route获取router // $router// 路由:’/detail/:name’ // ‘/detail/lzm’// this.$route.params.name// 路由:’/detail’ // ‘detail?name=lzm’// this.$route.query.name src/store/index.js状态管理文件入口// 状态管理 import Vue from ‘vue’import Vuex from ‘vuex’import state from ‘./state’import mutations from ‘./mutations’import actions from ‘./actions’Vue.use(Vuex)export default new Vuex.Store({ state, mutations, actions}) // 第四步state.js let defaultCity = { id: 1, spell: ‘beijing’, name: ‘北京’}if(localStorage.city){ defaultCity = JSON.parse(localStorage.city) // 字符串转对象}const state = { city: defaultCity}export default state// 第三步mutations.js const mutations = { changeCity(state, payload){ state.city = payload localStorage.city = JSON.stringify(payload) // 当为对象是,要转字符串 }}export default mutations// 第二步actions.js const actions = { changeCity(context, payload){ context.commit(‘changeCity’, payload) }}export default actions// 第一步xxx.vue // template部分:<div @click=“handleClick(item)">{{city}}</div>// script部分:import { mapState, mapActions} from ‘vuex’export default { computed: { …mapState([‘city’]) }, methods: { …mapActions([‘changeCity’]) handleClick(city){} this.changeCity(city) } }}axios$http调用接口// 全局调用 // 修改main.js文件Vue.prototype.$http = axios// xxx.vue文件:this.$http .get(’/api/index.json’, { params: {…} }) .then( fn(data) ).catch( fn(data )); // 局部调用 // xxx.vue文件:import axios from ‘axios’axios.get(’/api/index.json’) .then( fn(data) ) .catch( fn(data) ); // 案例 getHomeData() { this.$http .get(this.$api.home, { params: { name: this.city.name } }) .then(res => { let resData = res.data; if (resData.ret && resData.data) { let data = resData.data this.bannerList = data.bannerList this.iconList = data.iconList this.likeList = data.recommendList this.weekendList = data.weekendList } }) .catch((err) => { console.log(err) })}移动端移动端初始配置// 移动端访问 // 修改package.json文件:–host 0.0.0.0// mac查看ip: ifconfig// windows查看ip: ipconfig// 手机访问地址: ip:7000 // 缩放比例 // 修改文件:index.html<meta minimum-scale=1.0, maximum-scale=1.0, user-scalable=no><meta width=“device-width, height=device-height, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no”> // 初始化样式 reset.css // 1px像素设备不一致 borer.css // 延迟300ms // 模块 fastclick 解决移动端事件延迟300ms// main.js文件加入:fastClick.attach(document.body) // stylus运用 // 模块 stylus stylus-loader 类似scss预编译// xxx.vue文件:style lang=“stylus"iconfont文字图标// 网址 [http://www.iconfont.cn][2] // 步骤 // 选择图标加入购物车 →// 添加到项目 →// 下载到本地 →// 字体和css复制到项目 →// 更改iconfont目录和删除代码 →// 引入iconfont.css →<span class=“iconfont user-icon”>&#x e624;><span>better-scroll拖动使界面滚动// 全局 // 修改main.js文件:import BScroll from ‘better-scroll’Vue.prototype.$scroll= BScrollxxx.vue文件:mounted() { this.scroll = new this.$scroll(elevent); // element为dom或$refs}, // 局部 // xxx.vue文件:import BScroll from ‘better-scroll’this.scrolll = new BScroll(this.$refs.wrapper)// 定位到某个元素 this.scroll.scrollToElement(element) vue-awesome-swiper图片轮播// 网址 [https://www.swiper.com.cn/][3] // 步骤 // 安装模块 vue-awesome-swiper 轮播插件(2.6.7)// 修改main.js:import “swiper/dist/css/swiper.css"Vue.use(VueAwesomeSwiper)xxx.vue文件:<swiper :options=“swiperOption” v-if=“hasBanner”> <swiper-slide v-for=“item in list”> <img :src=“item.imgUrl”> </swiper-slide> <div class=“swiper-pagination” slot=“pagination”></div></swiper>swiperOption: { pagination: ‘.swiper-pagination’, autoplay: 3000, paginationType: ‘fraction’, loop: true}router-link路由链接// router-link组件的active设置 // 全局设置:export default new VueRouter({ linkActiveClass: ‘active’, linkExactActiveClass: ‘active’, routes : [ … 省略 ]});// 局部设置:<router-link :to="‘home’” tag=“li” active-class=“active” exact-active-class=“active” exact><标题router-link>// 加上exact 则遵循路由完全一致模式,该路由下的路径不匹配 // 标准 <router-link :to=”’/detail?name=’ + item.name” tag=“div”></router-link> // 路由跳转 this.$router.push(’/’)、this.$router.replace(’/’) fade过渡动画// template <transition> <slot></slot></transition> // style <style lang=“stylus” scoped>.v-enter, .v-leave-to opacity 0.v-enter-active, .v-leave-active transition opacity .5s</style>// name name=“fade”.fade-entertouch拖动事件// touchstart/touchmove <div class=“letter” @click=“handleClickLetter” @touchstart.prevent=“handleTouchStart” @touchmove=“handleTouchMove” ref=“letter”> <div class=“letter-cont” ref=“letterCont”> <span class=“letter-item” v-for=“item in list” :key=“item”>{{item}}</span> </div></div>methods: { handleClickLetter(e) { const letter = e.target.innerHTML this.$emit(‘change’, letter) }, handleTouchStart() { this.letterHeight = this.$refs.letterCont.clientHeight / this.letterLen this.letterOffsetTop = this.$refs.letter.offsetTop + this.$refs.letterCont.offsetTop }, handleTouchMove(e) { let touchY = e.touches[0].clientY let letterScope = (touchY - this.letterOffsetTop) / this.letterHeight if (letterScope > 0 && letterScope < this.letterLen) { if (this.timer) clearTimeout(this.timer) this.timer = setTimeout(() => { this.letterIndex = Math.floor(letterScope) }, 16) } }}其他组件里name的作用// 1、递归组件会用到// 2、取消缓存的时候会用到// 3、浏览器vue插件显示组件的时候用到子父组件传值// 子组件:handleClick(){ this.$emit(‘change’, value)}// 父组件:<component @change=“handleClick”></component>handleClick(){} $refs// this.$refs.msg 普通元素,引用指向dom:<div ref=‘msg’>Hello, world<div>// this.$refs.child 组件,引用指向组件实例:<c-child ref=‘child’>Hello, world<c-child>$route获取url数据// 1. /detail/:id// /detail/1389435894this.$route.params.id// 2. /detail// /detail?id=1389435894this.route.query.id利用setTimeout节流if(this.timer) clearTimeout(timer);this.timer = setTimeout(()=>{ console.log(‘‘xxx’)}, 16);滚动监听// 1.window.onscroll = this.handleScroll// 2.window.addEventListener(‘scroll’, this.handleScroll)浏览器cookielocalStorage.city = citylocalStorage.clear() ...

January 25, 2019 · 4 min · jiezi

酷狗音乐- Vue / React 全家桶的两种实现

引言两个月前用 Vue 全家桶实现过一次 酷狗音乐,最近又用 React 全家桶重构了下,最终成果和 Vue的实现基本一致,放个图:手机预览戳 Vue 版本, React 版本。demo 选择本来想用 React 全家桶重新选个项目,但是没有找到合适的,最终就重构了下,因为这个项目难度适中,非常适合练手。接近 10 个单页,内容不多不少,需要 router音乐播放作为全局组件,数据全局共享增删改,需要 redux, vuex好几个公共组件,可以封装复用项目源码在 这里,欢迎大家 star、fork项目对比我从根目录开始分析,左边 vue 右边 react根目录src 目录这里有几个区别:React 版本并没有 router 文件,因为它支持 path 和 component 属性,来定位要渲染的组件,就像这样:而 Vue router 似乎并没有提供 path 和 component API ,所以必须要到 Router 配置里去读取 path 和 component 属性。React 也没有 mixins, 因为用 HOC 取代了 mixins。以我放在 components/HOC/index.js 里的代码为例:而且,你也可以在里面加上生命周期钩子等等,实际上,React 之前也是采用 mixins 实现的,不过后来改了。一个 .vue 组件对应 React 中三个文件?在很多情况下,是这样子。Vue 的行为结构表现分离,很明显,而 React 的分离虽然不是很明显,但实际上也是有的。以 App.vue 为例App.vue 里的 style 对应 React 里的 App.less ,毫无疑问App.vue 里的 template 和 props 对应 React 里的 App.js ,React 称为 Presentational Components,一般只有一个 render 方法 return html, 譬如:App.vue 里剩余的部分,包括 ajax, mapState, 状态的变更,以及生命周期钩子等等,都是对应 React 里的 AppContainer.js ,React 称为 Container Components. 如图:实际上, AppContainer.js 负责行为逻辑,而 App.js 负责结构展示, App.less 负责样式表现,依旧是 行为/结构/表现 的分离。只不过与 Vue 稍有不同而已。这一点上,React 多费些脑力和胶水代码。Vuex 和 redux 目录这里跟我的实现有关系,redux 可能是比 Vuex 麻烦些,但不至于图示如此夸张。因为我重构的时候改了逻辑。selectorsselectors 和 Vue 中的 getters 有相似,但底层原理不同。举个例子,我们如果要从一个巨量的 array 里找到某个数据,比较耗性能怎么办?很明显可以对参数做个缓存,如果查询 id 和上一次一样,就返回上次的结果,不查询了。selectors 做的就是这个事。actionsReact 的 actions 和 Vuex 中的 actions 类似,都是发送指令,但不操作数据。reducersactions 发送指令,最终会到 reducers 里合并数据,与 Vue 中的 mutations 类似。如果你注意的话,就会发现,reducers 里合并数据总是返回一个新对象。而 Vuex 中,我们是直接修改 state 的数据的。这里其实牵涉到了 Vue 和 React 中的一个大不同。总结总体的目录和架构是类似的,不过具体用起来差别还不小。技术栈的广度Vue 全家桶只要加上 Vuex 和 Vue-router 就可以了,而 React 在读完 redux, react-redux, react-router 文档之后,会发现他们还拆分、引出了不少东西,譬如 reselect, redux-thunk 等等,并且 redux, reselect还不是局限于 React 的。API实践过程中,发现 Vue 中的一些类似的 API 在 React 中被进行了重构,比如 React 用 createRef 取代了 ref=“string”,用 HOC 取代了 mixins 等等,虽然有些不习惯,但是感觉还好。求职本人最近正在找工作,有兴趣的欢迎私信哦,坐标上海,半年经验,比较了解 Vue+es6,了解一点 React,具体简历 戳这里 ...

January 25, 2019 · 1 min · jiezi

收集vue2精佳文章,一月下半月 - 岂敢定居,一月三捷

一月: 一年之计在于春,一月是一年的开始一月下半月-岂敢定居,一月三捷。(01.16~01.31):寄徐丞 [宋] 赵师秀 病不窥园经一月,更无人迹损青苔。 池禽引子衡鱼去,野蔓开花上竹来。 亦欲鬓毛休似雪,争如丹汞只为灰。 秋风昨夜吹寒雨,有梦南游到海回。文章列表【收藏】2019年最新Vue相关精品开源项目库汇总创建vue-cli框架项目VuePress博客搭建笔记(二)个性化配置【前端笔记】Vuex快速使用vue -on如何绑定多个事件基于vue的验证码组件Vue自定义Toast插件Vue添加数据视图不更新问题聊一聊Vue组件模版,你知道它有几种定义方式吗?深入学习Vue SSR服务端渲染 用Nuxt.js打造CNode社区vue 源码学习(二) 实例初始化和挂载过程使用NodeJS 生成Vue中文版 docSet 离线文档手牵手教你写 Vue 插件Vue项目部署遇到的问题及解决方案预计今年发布的 Vue 3.0 到底有什么不一样的地方?记一次 Vue 单页面上线方案的优化vue-cli3使用svg问题的简单解决办法从react转职到vue开发的项目准备基于 Vue-Cli3 构建的脚手架模版新手福音用vue-cli3从0到1做一个完整功能手机站(一)vue-cli3 从搭建到优化Spring Security (三):与Vue.js整合电商网站项目总结:Vuex 带来全新的编程体验结合vue-cli来谈webpack打包优化vue开发环境配置跨域,一步到位Vue新手向:14篇教程带你从零撸一个Todo应用Vue2.0 核心之响应式流程基于Vue的任务节点图绘制插件(vue-task-node)Vue 实践小结vue项目接口管理【vue-cli3升级】老项目提速50%(二)Vuex 是什么,为什么需要【收藏】2019年最新Vue相关精品开源项目库汇总创建vue-cli框架项目VuePress博客搭建笔记(二)个性化配置RFCs for substantial changes / feature additions to Vue core 3.0February 14-15 - Vue.js AmsterdamMarch 25-27 - VueConf US in Tampa, FLApril 12th, VueDay 2019, Verona, Italy – Call for papers until 1st FebruaryIncoming workshop: Proven patterns for building Vue apps by Chris Fritz10 Best Tips for Learning Vue from Vue MastersImprove performance on large lists in VueBuild a Beautiful Website with VuePress and Tailwind.cssSelectively enable SSR or SPA mode in a Nuxt.js appGive Users Control Over App Updates in Vue CLI 3 PWAsWhen to “componentize” from the point of VueVue Route Component HooksWatch for Vuex State changes!GitHub - egoist/styled-vueGitHub - Justineo/vue-clampGitHub - edisdev/vue-datepicker-uiVue2+周报不积跬步,无以至千里;不积小流,无以成江海丁酉年【鸡年】/戊戌年【狗年】/己亥年【猪年】 对【Vue相关开源项目库汇总】的Star更新排名几个值得收藏的国外有关Vue.js网站(https://segmentfault.com/a/11… :conf.vuejs.org国外一个Vue.js视频教程scotch网站的技术视频教程vue-hackernews-2.0Weekly dose of handpicked Vue.js news!vuejsdevelopers-Vue开发者网站还是个人的?vuejsfeed-最新的Vue.js新闻、教程、插件等等vuecomponents-Vue.js组件集合社区madewithvuejs-收藏了用Vue.js实现的网站vuejsexamples-Vue.js的Demo满满的最新vueNYC资讯:VueNYCVueNYC - Vue.js: the Progressive Framework - Evan Youtwitter-search-VueNYC尤大大的PPT我已经上传了../PPT/Vue.js the Progressive Framework.pdf最新2018 VueConf资讯: (第二届VueConf将于2018年11月24日在杭州举行) ::资料:: ::PPT::更多资讯集web前端最近很火的vue2框架资源;定时更新,欢迎 Star 一下。:::【点击我】::: ...

January 25, 2019 · 1 min · jiezi