框架学习！学习曲线平滑的框架！让前端工作越来越明确
一、前提思考我们如何给vue写插件，router其实也是一个插件，一个全局插件。相信大家对于vue.use(xxx)很眼熟了，这个时候让我们来看vue-router是如何使用的吧，最后自己写一个router替换掉vue-router。vue-router源码地址：https://github.com/vuejs/vue-router1.1）搭建项目我们使用vue-cli脚手架搭建项目，你可以在自定义中勾选vue-router，或者自己下载yarn add vue-routervue create my-vue-router yarn add vue-router / npm install vue-router1.2）vue-router使用方式如果是自定义勾选router后的项目，我们会发现vue-router已经引入项目中使用了，我们来看看它的使用方式。引入vue-router插件// 在main只是一个文件的应用，不是插件的引用 import router from './router' // 真正的引用在router/index.js 并思考use方法，做了什么事情 import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter)创建router插件const router = new VueRouter({ mode: 'xxx', base: process.env.BASE_URL, routes }) export default router挂载router插件import router from './router' new Vue({ router, render: h => h(App) }).$mount('#app')使用router插件跳转路由并渲染dom<router-link to='xiaojuzi'></router-link> <router-view></router-view> this.$router.push('xx')1.3）vue-router的功能分析实现spa单页面应用的路由更新且不刷新页面【我们要实现hash路由和history路由】一个路由地址对应一个页面的渲染地址【我们要实现监听和渲染页面，时时render页面】1.4）写vue插件的思想1. 实现一个router功能类【你需要完成的功能】处理路由的类型监听router的变化响应router的事件2. 实现一个初始化插件的方法【你需要完成的功能】$ruoter的组册完成两个全局组件，即router-view和router-link二、手写自己的router【实现最简单的router功能】2.1）创建my-vue-router.js// 我们在初始化的时候，给我们申明的Vue赋值，这个时候在vue.use使用的时候， // 我们就可以在自己的my-vue-router.js中拿到vue的构造函数 // 目前只是hash功能/且只支持一层router let Vue; class MyRouter { constructor(options) { this.$options = options // 把current作为响应式数据 // 将来发生变化，router-view的render函数能够再次执行 const initial = window.location.hash.slice(1) || "/" Vue.util.defineReactive(this, 'current', initial) // 监听hash变化 window.addEventListener("hashchange", () => { console.log(this.current) this.current = window.location.hash.slice(1) }) } } // 参数是Vue.use调用时传入的,这里就可以拿到vue咯，保存你的vue吧 MyRouter.install = function (_Vue) { Vue = _Vue // 1.挂载$router属性 // this.$router.push() // 我们在use的时候，实际上这个时候new vue实例还没有开始执行，我们无法拿到vue的run-time的结果 // 这个时候我就借用Vue混入的思想，并借用生命周期做到延时效果 // 最后我们在全局挂载$router Vue.mixin({ beforeCreate () { // 次钩子在每个组件创建实例时都会调用 // 根实例才有该选项 if (this.$options.router) { Vue.prototype.$router = this.$options.router } } }) // 2.注册实现两个组件router-view,router-link Vue.component('router-link', { // 必须传入to地址 props: { to: { type: String, required: true, }, }, // xxx // return {this.$slots.default} render (h) { return h( "a", { attrs: { href: "#" + this.to, }, }, this.$slots.default ) } }) Vue.component('router-view', { render (h) { // 获取当前路由对应的组件 let component = null // 查询routes映射表中，是否有这个route地址 const route = this.$router.$options.routes.find( (route) => route.path === this.$router.current ) // 有这个ruoter地址，则返回route对应的组件地址 if (route) { component = route.component } console.log(this.$router.current, component) return h(component) } }) } export default MyRouter2.1）使用自己的my-vue-routerimport Vue from 'vue' // 这里把 import VueRouter from 'vue-router'改成刚刚自己写的my-vue-router.js文件 import VueRouter from './my-vue-router' import Home from '../views/Home.vue' Vue.use(VueRouter) const routes = [ { path: '/', name: 'Home', component: Home }, { path: '/about', name: 'About', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') } ] const router = new VueRouter({ mode: 'hash', base: process.env.BASE_URL, routes }) export default router 三、撸人家源码去了3.1) 人家是怎么监听history路由hash路由大家好理解，可以兼容hashchange事件上面我们写了一个监听hash变化的简单版router，想想history也是原理一样，监听popstate事件// 在人家的vue-router的类中的constructor constructor (router: Router, base: ?string) { window.addEventListener('popstate', e => { const current = this.current this.transitionTo(getLocation(this.base), route => { if (expectScroll) { handleScroll(router, route, current, true) } }) }) }3.2) 源码vuerouter类看人家这个类，干了几件事mode是我们传入的路由类型，这个大家都好理解，在这个文件中，主要是区分三种路由模式第二个主要是给暴露init/push/replace方法，但其实这里只是一个代理，真正的实现根据模式来区分export default class VueRouter { mode: string; // 传入的字符串参数，指示history类别 history: HashHistory | HTML5History | AbstractHistory; // 实际起作用的对象属性，必须是以上三个类的枚举 fallback: boolean; // 如浏览器不支持，'history'模式需回滚为'hash'模式 constructor (options: RouterOptions = {}) { let mode = options.mode || 'hash' // 默认为'hash'模式 this.fallback = mode === 'history' && !supportsPushState // 通过supportsPushState判断浏览器是否支持'history'模式 if (this.fallback) { mode = 'hash' } if (!inBrowser) { mode = 'abstract' // 不在浏览器环境下运行需强制为'abstract'模式 } this.mode = mode // 根据mode确定history实际的类并实例化 switch (mode) { case 'history': this.history = new HTML5History(this, options.base) break case 'hash': this.history = new HashHistory(this, options.base, this.fallback) break case 'abstract': this.history = new AbstractHistory(this, options.base) break default: if (process.env.NODE_ENV !== 'production') { assert(false, `invalid mode: ${mode}`) } } } init (app: any /* Vue component instance */) { const history = this.history // 根据history的类别执行相应的初始化操作和监听 if (history instanceof HTML5History) { history.transitionTo(history.getCurrentLocation()) } else if (history instanceof HashHistory) { const setupHashListener = () => { history.setupListeners() } history.transitionTo( history.getCurrentLocation(), setupHashListener, setupHashListener ) } history.listen(route => { this.apps.forEach((app) => { app._route = route }) }) } // VueRouter类暴露的以下方法实际是调用具体history对象的方法 push (location: RawLocation, onComplete?: Function, onAbort?: Function) { this.history.push(location, onComplete, onAbort) } replace (location: RawLocation, onComplete?: Function, onAbort?: Function) { this.history.replace(location, onComplete, onAbort) } }3.4）源码install方法【实在是妙啊】建议先看我的手写板和注解熟悉的mixin和钩子beforeCreate还有借用vue的util中的defineReactive方法，是这个对象变成响应式对象最后挂载两个全局组件import View from './components/view' import Link from './components/link' export let _Vue export function install (Vue) { if (install.installed && _Vue === Vue) return install.installed = true _Vue = Vue const isDef = v => v !== undefined const registerInstance = (vm, callVal) => { let i = vm.$options._parentVnode if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) { i(vm, callVal) } } Vue.mixin({ beforeCreate () { if (isDef(this.$options.router)) { this._routerRoot = this this._router = this.$options.router this._router.init(this) Vue.util.defineReactive(this, '_route', this._router.history.current) } else { this._routerRoot = (this.$parent && this.$parent._routerRoot) || this } registerInstance(this, this) }, destroyed () { registerInstance(this) } }) Object.defineProperty(Vue.prototype, '$router', { get () { return this._routerRoot._router } }) Object.defineProperty(Vue.prototype, '$route', { get () { return this._routerRoot._route } }) Vue.component('RouterView', View) Vue.component('RouterLink', Link) const strats = Vue.config.optionMergeStrategies // use the same hook merging strategy for route hooks strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created }3.4）源码history/* @flow */ import type Router from '../index' import { History } from './base' import { cleanPath } from '../util/path' import { START } from '../util/route' import { setupScroll, handleScroll } from '../util/scroll' import { pushState, replaceState, supportsPushState } from '../util/push-state' export class HTML5History extends History { _startLocation: string constructor (router: Router, base: ?string) { super(router, base) this._startLocation = getLocation(this.base) } setupListeners () { if (this.listeners.length > 0) { return } const router = this.router const expectScroll = router.options.scrollBehavior const supportsScroll = supportsPushState && expectScroll if (supportsScroll) { this.listeners.push(setupScroll()) } const handleRoutingEvent = () => { const current = this.current // Avoiding first `popstate` event dispatched in some browsers but first // history route not updated since async guard at the same time. const location = getLocation(this.base) if (this.current === START && location === this._startLocation) { return } this.transitionTo(location, route => { if (supportsScroll) { handleScroll(router, route, current, true) } }) } window.addEventListener('popstate', handleRoutingEvent) this.listeners.push(() => { window.removeEventListener('popstate', handleRoutingEvent) }) } go (n: number) { window.history.go(n) } push (location: RawLocation, onComplete?: Function, onAbort?: Function) { const { current: fromRoute } = this this.transitionTo(location, route => { pushState(cleanPath(this.base + route.fullPath)) handleScroll(this.router, route, fromRoute, false) onComplete && onComplete(route) }, onAbort) } replace (location: RawLocation, onComplete?: Function, onAbort?: Function) { const { current: fromRoute } = this this.transitionTo(location, route => { replaceState(cleanPath(this.base + route.fullPath)) handleScroll(this.router, route, fromRoute, false) onComplete && onComplete(route) }, onAbort) } ensureURL (push?: boolean) { if (getLocation(this.base) !== this.current.fullPath) { const current = cleanPath(this.base + this.current.fullPath) push ? pushState(current) : replaceState(current) } } getCurrentLocation (): string { return getLocation(this.base) } } export function getLocation (base: string): string { let path = window.location.pathname if (base && path.toLowerCase().indexOf(base.toLowerCase()) === 0) { path = path.slice(base.length) } return (path || '/') + window.location.search + window.location.hash } 3.5）源码hash/* @flow */ import type Router from '../index' import { History } from './base' import { cleanPath } from '../util/path' import { getLocation } from './html5' import { setupScroll, handleScroll } from '../util/scroll' import { pushState, replaceState, supportsPushState } from '../util/push-state' export class HashHistory extends History { constructor (router: Router, base: ?string, fallback: boolean) { super(router, base) // check history fallback deeplinking if (fallback && checkFallback(this.base)) { return } ensureSlash() } // this is delayed until the app mounts // to avoid the hashchange listener being fired too early setupListeners () { if (this.listeners.length > 0) { return } const router = this.router const expectScroll = router.options.scrollBehavior const supportsScroll = supportsPushState && expectScroll if (supportsScroll) { this.listeners.push(setupScroll()) } const handleRoutingEvent = () => { const current = this.current if (!ensureSlash()) { return } this.transitionTo(getHash(), route => { if (supportsScroll) { handleScroll(this.router, route, current, true) } if (!supportsPushState) { replaceHash(route.fullPath) } }) } const eventType = supportsPushState ? 'popstate' : 'hashchange' window.addEventListener( eventType, handleRoutingEvent ) this.listeners.push(() => { window.removeEventListener(eventType, handleRoutingEvent) }) } push (location: RawLocation, onComplete?: Function, onAbort?: Function) { const { current: fromRoute } = this this.transitionTo( location, route => { pushHash(route.fullPath) handleScroll(this.router, route, fromRoute, false) onComplete && onComplete(route) }, onAbort ) } replace (location: RawLocation, onComplete?: Function, onAbort?: Function) { const { current: fromRoute } = this this.transitionTo( location, route => { replaceHash(route.fullPath) handleScroll(this.router, route, fromRoute, false) onComplete && onComplete(route) }, onAbort ) } go (n: number) { window.history.go(n) } ensureURL (push?: boolean) { const current = this.current.fullPath if (getHash() !== current) { push ? pushHash(current) : replaceHash(current) } } getCurrentLocation () { return getHash() } } function checkFallback (base) { const location = getLocation(base) if (!/^\/#/.test(location)) { window.location.replace(cleanPath(base + '/#' + location)) return true } } function ensureSlash (): boolean { const path = getHash() if (path.charAt(0) === '/') { return true } replaceHash('/' + path) return false } export function getHash (): string { // We can't use window.location.hash here because it's not // consistent across browsers - Firefox will pre-decode it! let href = window.location.href const index = href.indexOf('#') // empty path if (index < 0) return '' href = href.slice(index + 1) return href } function getUrl (path) { const href = window.location.href const i = href.indexOf('#') const base = i >= 0 ? href.slice(0, i) : href return `${base}#${path}` } function pushHash (path) { if (supportsPushState) { pushState(getUrl(path)) } else { window.location.hash = path } } function replaceHash (path) { if (supportsPushState) { replaceState(getUrl(path)) } else { window.location.replace(getUrl(path)) } }四、嗯，有些地方我也没看太懂，自己动手实践一下，写下你自己的router吧
一、Vuex状态管理的思想数据封装，集中式管理，可见的数据变更【单项数据流】解决跨组件之间的数据共享同步数据和异步数据更新试图还不会使用的同学可以看相关文档vuex官方文档：https://vuex.vuejs.org/zh/vuex源码地址：https://github.com/vuejs/vuex 【可以看src文件下内容/也可以看dist打包后的js文件】个人使用文档：http://gongpengji.com/index.php/archives/22/1.1）什么时候使用vuex借用redux作者的话：（你品这句话）Flux 架构就像眼镜：您自会知道什么时候需要它。想要了解状态管理必须看官方神图1.2）流程大体分为三步actions派发任务mutations执行任务state维护的数据状态1.3) 实现一个vuex插件需要拆解几部分实现store类完成一个响应式的state完成commit（）完成dispatch（）完成getter完成module【本次没有实现这一块】实现全局挂载$store二、实现自己的vuex并能支撑基本功能前三步都是使用，和原来的使用方式是一样的，主要看最后一个文件那就是一个小型的vuex2-1）main.js中使用import store from './juziStore' new Vue({ router, store, render: h => h(App) }).$mount('#app')2-2）App.vue中调用 mutations-count:{{ $store.state.count }} actions-count:{{ $store.state.count }} getters-count:{{ $store.getters.dobolde }} 2-3）创建juziStore文件夹里面 - index.js细心的朋友发现我这里有一行注释了的代码，其实这个注释的代码以前是这样的【import Store from 'vuex'】，这样store就是从我们下载的npm包中引用store类但是我这里是vuex.common.js，其实这就是vuex的源码，我会直接引用，并断点。学习源码使用的推荐代码地址：【里面有vuex.js/vuex.common.js等等，我们都可以直接拿来学习断点，不断查看运行步骤】https://github.com/vuejs/vuex/tree/9039c2169634925682ffcb21c57f5df355e16ad1/distimport Vue from 'vue' // import MyStore from './vuex.common' import MyStore from './myStore' Vue.use(MyStore) export default new MyStore.Store({ state: { count: 0 }, mutations: { add (state) { state.count++ } }, actions: { add ({ commit }) { setTimeout(() => { commit('add') }, 222); } }, getters: { dobolde: (state) => { return state.count * 2 } }, modules: { } }) 2-4）创建juziStore文件夹里面 - myStore.js注意看注解// 1、实现状态管理仓库 store // 2、实现install插件 // 3、实现响应式state // 4、实现commit方法 // 5、实现dispath方法 // 6、实现getter方法 // 7、暴露store模块 // 喜闻乐见的Vue借用又来了 let Vue; class Store { constructor(options) { // 保存配置 this.$options = options // 指定上下文执行环境 this.commit = this.commit.bind(this) this.dispatch = this.dispatch.bind(this) // 保存this this.getters = {} // 拿到参数中的getters对象 this._wrappendGetters = options.getters let computed = {}; const store = this // 遍历对象 Object.keys(this._wrappendGetters).forEach(key => { // 当前的getters中的函数 let fn = store._wrappendGetters[key] // 借用computed计算属性，并返回携带当前state的函数，记得在下方new vue的时候加上computed computed[key] = function() { return fn(store.state) }; // 对这个getter对象监听 Object.defineProperty(store.getters, key, { get: () => store._vm[key], enumerable: true // for local getters }) }) // 响应式state this._vm = new Vue({ data: { // 利用vue的机制，加上$$ state就不会被代理，这样用户就不能通过_vm直接访问到state $$state: options.state }, computed }) } // 有访问state到时候，返回内部的$$state // 存取器 get state () { // 使用vue实例下面的观察属性_data，这是一个响应式对象 return this._vm._data.$$state } set state (v) { console.error('不能直接修改state') } commit (type, payload) { // 我们去那options里面的mutations const _fn = this.$options.mutations[type] if (!_fn) { console.error(type + '的mutations不存在') return } _fn(this.state, payload) } dispatch (type, payload) { // 我们去那options里面的actions const _fn = this.$options.actions[type] if (!_fn) { console.error(type + '的actions不存在') return } _fn(this, payload) } } function install (_Vue) { // 看过我之前写的my-vue-router，就知道这里，在外部use这个插件的时候，我们就能拿到Vue实例 Vue = _Vue // 这里在讲一下吧 // 因为在执行use的时候，我们是拿不到new vue时候里面的参数的，vue对象还没执行。 // 借用混入，等待生命周期创建的时候，在挂载到Vue实例中 Vue.mixin({ beforeCreate () { if (this.$options.store) { Vue.prototype.$store = this.$options.store } } }) } export default { Store, install }最后，手写的vuex，就完成基本功能啦，还不快去玩耍玩耍三、手撕vuex源码想看全部代码，在一个js中的可以看推荐代码地址：【里面有vuex.js/vuex.common.js等等，我们都可以直接拿来学习断点，不断查看运行步骤】https://github.com/vuejs/vuex/tree/9039c2169634925682ffcb21c57f5df355e16ad1/dist想看作者如何设计编写整个vuex的过程的，通用方法和功能分离就看https://github.com/vuejs/vuex/tree/dev/src我们今天就主要看三个文件 - 有兴趣的可以查看其他文件和功能3-1）源码：utils.js里面大部分都是对对象处理的方法，有个印象就可以了，当读到人家实现store类的时候，知道这里有它直接调用的方法， 对对象进行封装/** * Get the first item that pass the test * by second argument function * * @param {Array} list * @param {Function} f * @return {*} */ export function find (list, f) { return list.filter(f)[0] } /** * Deep copy the given object considering circular structure. * This function caches all nested objects and its copies. * If it detects circular structure, use cached copy to avoid infinite loop. * * @param {*} obj * @param {Array<Object>} cache * @return {*} */ export function deepCopy (obj, cache = []) { // just return if obj is immutable value if (obj === null || typeof obj !== 'object') { return obj } // if obj is hit, it is in circular structure const hit = find(cache, c => c.original === obj) if (hit) { return hit.copy } const copy = Array.isArray(obj) ? [] : {} // put the copy into cache at first // because we want to refer it in recursive deepCopy cache.push({ original: obj, copy }) Object.keys(obj).forEach(key => { copy[key] = deepCopy(obj[key], cache) }) return copy } /** * forEach for object */ export function forEachValue (obj, fn) { Object.keys(obj).forEach(key => fn(obj[key], key)) } export function isObject (obj) { return obj !== null && typeof obj === 'object' } export function isPromise (val) { return val && typeof val.then === 'function' } export function assert (condition, msg) { if (!condition) throw new Error(`[vuex] ${msg}`) } export function partial (fn, arg) { return function () { return fn(arg) } }3-2）源码：mixin.js˙这里面的代码也不多，主要干了两件事对vue不同版本的兼容处理在vue实例上挂载$storeexport 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 } } }3-3）最后重头戏：源码store.js 【部分注解】我截取部分，4、5百行还是有点多的[主要思考上面我手写的，和人家源码实现的地方]我会以我自己理解的方式来给源码注解，如有错误，请指正包涵 // 调用mixin方法，给vue挂载store的 import applyMixin from './mixin' // 结合devtool，可以在mutations数据的时候，查看状态管理中的store仓库 import devtoolPlugin from './plugins/devtool' import ModuleCollection from './module/module-collection' // 解构出刚刚通用工具方法，大部分是处理obj import { forEachValue, isObject, isPromise, assert, partial } from './util' // 喜闻乐见，写vue插件都有的借用Vue let Vue // bind on install // 定义store类 export class Store { constructor (options = {}) { const { plugins = [], strict = false } = options // 给我们的store仓库初始化 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() this._makeLocalGettersCache = Object.create(null) // bind commit and dispatch to self 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) } // strict mode this.strict = strict const state = this._modules.root.state // init root module. // this also recursively registers all sub-modules // and collects all module getters inside this._wrappedGetters // 初始化所有模块信息 installModule(this, state, [], this._modules.root) // initialize the store vm, which is responsible for the reactivity // (also registers _wrappedGetters as computed properties) // 执行store的数据变化 resetStoreVM(this, state) } // 寄存器 get state () { return this._vm._data.$$state } set state (v) { if (__DEV__) { assert(false, `use store.replaceState() to explicit replace store state.`) } } // 这就是commit方法，去执行mutations中的方法 commit (_type, _payload, _options) { // check object-style commit const { type, payload, options } = unifyObjectStyle(_type, _payload, _options) const mutation = { type, payload } // 在这里拿到mutations里面的方法 const entry = this._mutations[type] if (!entry) { if (__DEV__) { console.error(`[vuex] unknown mutation type: ${type}`) } return } this._withCommit(() => { // 可能有多个mutations方法，通过遍历并执行 entry.forEach(function commitIterator (handler) { handler(payload) }) }) } // dispatch方法，执行actions中的方法，我们发现方法里面 // 是使用promise包裹的函数返回结果，这是一个异步方法 // 如何去那到actions中的方法，和上面mutataions一样 dispatch (_type, _payload) { // check object-style dispatch const { type, payload } = unifyObjectStyle(_type, _payload) const action = { type, payload } const entry = this._actions[type] if (!entry) { if (__DEV__) { console.error(`[vuex] unknown action type: ${type}`) } return } const result = entry.length > 1 ? Promise.all(entry.map(handler => handler(payload))) : entry[0](payload) return new Promise((resolve, reject) => { result.then(res => { try { this._actionSubscribers .filter(sub => sub.after) .forEach(sub => sub.after(action, this.state)) } catch (e) { if (__DEV__) { console.warn(`[vuex] error in after action subscribers: `) console.error(e) } } resolve(res) }, error => { try { this._actionSubscribers .filter(sub => sub.error) .forEach(sub => sub.error(action, this.state, error)) } catch (e) { if (__DEV__) { console.warn(`[vuex] error in error action subscribers: `) console.error(e) } } reject(error) }) }) } watch (getter, cb, options) { if (__DEV__) { assert(typeof getter === 'function', `store.watch only accepts a function.`) } return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options) } replaceState (state) { this._withCommit(() => { this._vm._data.$$state = state }) } } function resetStore (store, hot) { store._actions = Object.create(null) store._mutations = Object.create(null) store._wrappedGetters = Object.create(null) store._modulesNamespaceMap = Object.create(null) const state = store.state // init all modules installModule(store, state, [], store._modules.root, true) // reset vm resetStoreVM(store, state, hot) } // 这个方法里面就包裹着getters的执行方法 // 我自己写的getter，就是抄源码的，遍历对象、借用computed，最后兼容并返回 function resetStoreVM (store, state, hot) { const oldVm = store._vm // bind store public getters store.getters = {} // reset local getters cache store._makeLocalGettersCache = Object.create(null) const wrappedGetters = store._wrappedGetters const computed = {} forEachValue(wrappedGetters, (fn, key) => { // use computed to leverage its lazy-caching mechanism // direct inline function use will lead to closure preserving oldVm. // using partial to return function with only arguments preserved in closure environment. computed[key] = partial(fn, store) Object.defineProperty(store.getters, key, { get: () => store._vm[key], enumerable: true // for local getters }) }) // use a Vue instance to store the state tree // suppress warnings just in case the user has added // some funky global mixins const silent = Vue.config.silent Vue.config.silent = true store._vm = new Vue({ data: { $$state: state }, computed }) Vue.config.silent = silent // enable strict mode for new vm if (store.strict) { enableStrictMode(store) } if (oldVm) { if (hot) { // dispatch changes in all subscribed watchers // to force getter re-evaluation for hot reloading. store._withCommit(() => { oldVm._data.$$state = null }) } Vue.nextTick(() => oldVm.$destroy()) } } function installModule (store, rootState, path, module, hot) { // 里面我删除了，想看的可以去github上看，把其他环境的状态方法全部拿到并遍历 } /** * make localized dispatch, commit, getters and state * if there is no namespace, just use root ones */ function makeLocalContext (store, namespace, path) { const noNamespace = namespace === '' const local = { dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => { const args = unifyObjectStyle(_type, _payload, _options) const { payload, options } = args let { type } = args if (!options || !options.root) { type = namespace + type if (__DEV__ && !store._actions[type]) { console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`) return } } return store.dispatch(type, payload) }, commit: noNamespace ? store.commit : (_type, _payload, _options) => { const args = unifyObjectStyle(_type, _payload, _options) const { payload, options } = args let { type } = args if (!options || !options.root) { type = namespace + type if (__DEV__ && !store._mutations[type]) { console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`) return } } store.commit(type, payload, options) } } // getters and state object must be gotten lazily // because they will be changed by vm update Object.defineProperties(local, { getters: { get: noNamespace ? () => store.getters : () => makeLocalGetters(store, namespace) }, state: { get: () => getNestedState(store.state, path) } }) return local } function makeLocalGetters (store, namespace) { if (!store._makeLocalGettersCache[namespace]) { const gettersProxy = {} const splitPos = namespace.length Object.keys(store.getters).forEach(type => { // skip if the target getter is not match this namespace if (type.slice(0, splitPos) !== namespace) return // extract local getter type 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. Object.defineProperty(gettersProxy, localType, { get: () => store.getters[type], enumerable: true }) }) store._makeLocalGettersCache[namespace] = gettersProxy } return store._makeLocalGettersCache[namespace] } function registerMutation (store, type, handler, local) { const entry = store._mutations[type] || (store._mutations[type] = []) entry.push(function wrappedMutationHandler (payload) { handler.call(store, local.state, payload) }) } function registerGetter (store, type, rawGetter, local) { if (store._wrappedGetters[type]) { if (__DEV__) { console.error(`[vuex] duplicate getter key: ${type}`) } return } store._wrappedGetters[type] = function wrappedGetter (store) { return rawGetter( local.state, // local state local.getters, // local getters store.state, // root state store.getters // root getters ) } } function unifyObjectStyle (type, payload, options) { if (isObject(type) && type.type) { options = payload payload = type type = type.type } if (__DEV__) { assert(typeof type === 'string', `expects string as the type, but found ${typeof type}.`) } return { type, payload, options } } // 给vue写插件，还要写个install方法，接收Vue作为参数，在这里我们就能拿到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) }四、一遍遍的临摹，一遍遍的思考
SEO的出现1、搜索引擎-在信息大爆炸的时代，为广大用户提供最精确，最优质的搜索结果，搜索引擎，放出一个虚拟用户，收录各服务中html信息2、前端框架的兴起，v8运行js还是快的，主体html结构都压缩在js中，用户可看不懂js啊，拜拜，你这个服务器下啥子都没有，不来啦。seo（技术层面占3-4成）目前来说，前端能做什么先做本质的东西，什么其他优化啊，友好啊，先不提，基座都定不了，怎么开展其他工作呢1、基础目标：构建出静态页面前后端不分离开发（主流seo排行高页面大部分为前后端不分离项目）nuxt next 等node服务构建框架 同理的ssr服务端渲染预渲染 （如果真想做把seo做起来，不太建议该方式）2、基座遇到的问题前后端不分离 -- 渐渐不被提倡 （由繁到简，在简到繁）node服务都没有线上部署过，怎么配合运维线上部署，后期怎么维护、、、、ssr 没有去实践过，俺就不谈论 思想是一样的，搭建node中间服务去渲染页面，并返回给浏览器已经构建好的html文件优缺点对比1、前后端不分离优点：符合seo标准的静态页面页面结构清晰，页面内容可控制比例高，定制化方便开发风险性较低，代码可读性高缺点：开发与迭代所需工时长项目较重，DOM操作频繁各类插件维护版本2、nuxt / ssr-server优点：符合seo标准的静态页面项目打包轻，数据驱动比DOM轻，快缺点：前期开发成本高，node服务错误较难排查node服务，如何维护，高并发访问量如何处理（稳定性有待查看）3、预渲染优点：开发快，在原有项目改动方便缺点：动态数据维护较差，seo效果一般nuxt开发实践开发前请通读官方文档与API本文档只会聊使用到的技术点，其他还是多看看官方文档嘻嘻--俺也没有仔细阅读官方文档，这样不太好哦，写完文档，俺得回去在看看（第一遍看有什么，第二遍看为什么，第三遍看怎么实现）一、搭建项目官网脚手架 / 当然你也可以自己搭建，安装所需npm包即可npx create-nuxt-app <项目名> yarn create nuxt-app <项目名>这里我使用了nuxt模版提供选择的express，因为在后期我想通过express框架，对node服务进行个性化管理与配置目录结构├── .nuxt --------------------------- 打包后项目主体文件 ├── api ----------------------------- 说明文件 ├── asyncData ------------------- node服务端请求接口 ├── syncData -------------------- 浏览器请求接口 ├── assets -------------------------- 项目静态资源文件目录（图片、公共样式、字体等） ├── components ---------------------- 组件 ├── layouts ------------------------- 布局模式 ├── middleware ---------------------- 中间件 ├── pages --------------------------- 页面（该文件夹下也对应着项目路由路径） ├── plugins ------------------------- 插件 └── main.js --------------------- 等同于vue的main.js(vue对象挂载其他插件) └── index.js -------------------- 引用当前文件夹的总出口 ├── server └── index.js -------------------- node服务启动文件 ├── public -------------------------- 入口文件 ├── static -------------------------- 组件 ├── store --------------------------- 布局模式 ├── utils --------------------------- 中间件 ├── app.html ------------------------ html入口文件 ├── nuxt.config.js ------------------- webpack配置入口 ├── package.json -------------------- 项目配置使用脚手架新建项目，大部分目录都会自动生成现在项目有了，先不要急着写，整理出页面所需要的基础配置与依赖--感谢来自佳宁支援搭建webpacknode服务配置路由layout布局重新封装axios请求（异步数据处理）插件依赖vuex状态管理最后开始写页面或改造已有项目基本目标确定，开始工作workwork二、搭建webpacknuxt.config.jsmodemodule.exports = { mode: 'universal' }mode有两个模式，默认是universal，另一个是spaspa是单页，所以只有一个入口文件，sitemap也就只有一个url，这会导致网站辛辛苦苦搭建的服务端渲染最多只被搜索引擎收录一个页面。而universal则能实现所有网站路径完全被收录，这才是最初我们使用nuxt的初衷envmodule.exports = { env: { NODE_ENV: process.env.NODE_ENV // 申明执行环境 } }headmodule.exports = { head: { title: 'TeeMo', meta: [ ], link: [ ], script: [ ] } }全局页面的head头部信息，当然nuxt也支持你在vue中使用head对象，来定制不同页面的head信息完整版loadingmodule.exports = { loading: false, }在页面切换的时候，Nuxt.js 使用内置的加载组件显示加载进度条。你可以定制它的样式，禁用或者创建自己的加载组件类型： Boolean 或 Object 或 Stringcssmodule.exports = { css: [ 'swiper/css/swiper.css' ] }在 Nuxtjs 里配置全局的 CSS 文件、模块、库。如果你引入less，scss样式文件，你只需要安装对应的less-loader，sass-loader，Nuxt.js 会自动识别被导入文件的扩展名，之后，webpack 会使用相应的预处理器进行处理。前提是，你安装了对应预处理器。cssmodule.exports = { cache: false //默认false，后期时候可以做优化处理做标记 }对于部分网页进行服务端的缓存，可以获得更好的渲染性能，但是缓存又涉及到一个数据的及时性的问题，所以在及时性和性能之间要有平衡和取舍。完整版buildmodule.exports = { build: { extractCSS: { allChunk: true }, extend (config, { isDev, isClient }) { if (isDev && isClient) { config.module.rules.push({ enforce: 'pre', test: /\.(js|vue)$/, loader: 'eslint-loader', exclude: /(node_modules)/ }) } } } }build顾名思义，打包构建，其中api众多，细看链接extractCSS对所有样式提取压缩extend 对js，vue进行eslint语法检测，并压缩文件pluginsmodule.exports = { plugins: [ { src: '@/assets/styles/index.less', ssr: false }, { src: '@/plugins/index', ssr: false } ] }加载我们的插件完整版module.exports = { mode: 'universal', env: {å NODE_ENV: process.env.NODE_ENV }, head: { title: 'TeeMo', meta: [ ], link: [ ], script: [ ] }, loading: false, css: [ 'swiper/css/swiper.css' ], cache: false, build: { extractCSS: { allChunk: true }, extend (config, { isDev, isClient }) { if (isDev && isClient) { config.module.rules.push({ enforce: 'pre', test: /\.(js|vue)$/, loader: 'eslint-loader', exclude: /(node_modules)/ }) } } }, plugins: [ { src: '@/assets/styles/index.less', ssr: false }, { src: '@/plugins/index', ssr: false } ] } 三、node服务配置server/index.jsconst express = require('express') const consola = require('consola') const { Nuxt } = require('nuxt') const { Builder } = require('nuxt') const app = express() const port = process.env.PORT || 8080 app.set('port', port) // 读取nuxt配置文件 const config = require('../nuxt.config.js') config.dev = (process.env.NODE_ENV === 'dev') async function start () { // 初始化nuxt对象 const nuxt = new Nuxt(config) // 只在本地开发启动热更新 if (config.dev) { const builder = new Builder(nuxt) await builder.build() } // express api 使用渲染树构建服务 app.use(nuxt.render) // 监听端口 app.listen(port) consola.ready({ message: `Server listening on http://localhost:${port}`, badge: true }) } start()四、路由nuxt对vue-router进行封装，让我们不用关心与维护路由文件（router.js），那他的路由路径怎么对应，其实就是对应着pages下面的文件路径，即是页面路径。所以我们在写页面的时候，规范组件抽离至component文件夹，也不要有其他不是单独页面的文件copy官网介绍基础路由假设 pages 的目录结构如下：pages/ --| user/ -----| index.vue -----| one.vue --| index.vue那么，Nuxt.js 自动生成的路由配置如下：router: { routes: [ { name: 'index', path: '/', component: 'pages/index.vue' }, { name: 'user', path: '/user', component: 'pages/user/index.vue' }, { name: 'user-one', path: '/user/one', component: 'pages/user/one.vue' } ] } 动态路由与动态嵌套路由在 Nuxt.js 里面定义带参数的动态路由，需要创建对应的以下划线作为前缀的 Vue 文件 或 目录。目录结构：pages/ --| _category/ -----| _subCategory/ --------| _id.vue --------| index.vue -----| _subCategory.vue -----| index.vue --| _category.vue --| index.vueNuxt.js 自动生成的路由配置如下：router: { routes: [ { path: '/', component: 'pages/index.vue', name: 'index' }, { path: '/:category', component: 'pages/_category.vue', children: [ { path: '', component: 'pages/_category/index.vue', name: 'category' }, { path: ':subCategory', component: 'pages/_category/_subCategory.vue', children: [ { path: '', component: 'pages/_category/_subCategory/index.vue', name: 'category-subCategory' }, { path: ':id', component: 'pages/_category/_subCategory/_id.vue', name: 'category-subCategory-id' } ] } ] } ] }五、页面模版你可以定制化 Nuxt.js 默认的应用模板，定制化默认的 html 模板，只需要在src文件夹下（默认是应用根目录）创建一个 app.html 的文件。默认模板为： {{ HEAD }} {{ APP }} 布局Nuxt.js 允许你扩展默认的布局，或在 layout 目录下创建自定义的布局。可通过添加 layouts/default.vue 文件来扩展应用的默认布局。注意：必须在布局文件中添加 <nuxt/> 组件用于显示页面的主体内容错误页面：layouts/error.vue 文件来定制化默认错误页面页面 Hello {{ name }}! 提供的对象asyncData最重要的一个键, 支持 异步数据处理，另外该方法的第一个参数为当前页面组件的 上下文对象fetch与 asyncData 方法类似，用于在渲染页面之前获取数据填充应用的状态树（store）。不同的是 fetch 方法不会设置组件的数据head配置当前页面的 Meta 标签layout指定当前页面使用的布局（layouts 根目录下的布局文件）loading如果设置为false，则阻止页面自动调用this.$nuxt.$loading.finish()和this.$nuxt.$loading.start(),您可以手动控制它,请看例子,仅适用于在nuxt.config.js中设置loading的情况下transition指定页面切换的过渡动效scrollToTop布尔值，默认: false。 用于判定渲染页面前是否需要将当前页面滚动至顶部。这个配置用于 嵌套路由的应用场景validate校验方法用于校验 动态路由的参数middleware指定页面的中间件，中间件会在页面渲染之前被调用六、重新封装axios请求（服务端环境区分）requset.js按照需要封装axios，该文件的console会打印到pm2 logs日志文件/* eslint-disable eqeqeq */ import axios from 'axios' import config from '@/api/asyncData/asyncConfig' import qs from 'qs' const apiConfig = axios.create({ // 设置超时时间 timeout: 15000, // 请求的baseUrl baseURL: '', // 请求头部信息 headers: { }, withCredentials: true, // 请求参数转换 transformRequest: [function (data) { return qs.stringify(data) }] }) // 请求拦截器 apiConfig.interceptors.request.use(request => { if (config.IS_RELEASE) { console.log( `${new Date().toLocaleString()}【 Url=${request.url} 】Params=`, request.params || request.data ) } return request }, error => { return Promise.reject(error) }) export default async (options = { method: 'POST' }) => { let isdata = true if ( options.method.toUpperCase() !== 'POST' && options.method.toUpperCase() !== 'PUT' && options.method.toUpperCase() !== 'PATCH' ) { isdata = false } const res = await apiConfig({ method: options.method, url: options.url, data: isdata ? options.data : null, params: !isdata ? options.data : null }) if (config.IS_RELEASE) { console.log( `${new Date().toLocaleString()}【接口响应】:`, res.data.code + ' ------ ' + res.data.message ) } if (res.data.code !== '2000') { const errorData = { code: 500, data: { list: [] }, message: '服务器异常' } return errorData } else { return res.data } } asyncConfig.js环境区分，打包时候使用cross-env来改变NODE_ENV环境变量，或直接在服务端自己修改NODE_ENV// 不同环境的请求配置 const prodConfigs = { dev: { apiHost: 'http://dev', IS_RELEASE: true }, test: { apiHost: 'http://test', IS_RELEASE: true }, test2: { apiHost: 'http://test2', IS_RELEASE: true }, pre: { apiHost: 'https://pre', IS_RELEASE: true }, pro: { apiHost: 'https://pro', IS_RELEASE: false }, production: { apiHost: 'https://pro', IS_RELEASE: true } } // console.log(process.env.NODE_ENV) // 打包时候默认NODE_ENV环境变量为production const hostConfig = prodConfigs[process.env.NODE_ENV] export default hostConfig index.js暴露接口import request from './request' import hostConfig from './asyncConfig' // 轮播图 export const getBanners = (params) => { return request({ url: `${hostConfig.apiHost}/xxx`, method: 'GET', data: params }) }插件统一写在plugins文件夹下，可以使用index来管理插件总路口,这样你就不用在nuxt.config.js中plugins输入多个模块nuxt.config.jsplugins: [ { src: '@/plugins/index', ssr: false } ]index.jsimport './main.js' import 'xxxxx'main.js这就相当于vue中的全局挂载文件入口import Vue from 'vue' import apis from '@/api/syncData/index' import reg from '@/utils/validator.js' import { Button, Cell, Toast, ActionSheet, Lazyload, PullRefresh, List, Popup } from 'vant' // 常用组件 import 'vant/lib/index.css' import vueDirectiveImagePreviewer from 'vue-directive-image-previewer' import 'vue-directive-image-previewer/dist/assets/style.css' import VueAwesomeSwiper from 'vue-awesome-swiper' Vue.use(Button) Vue.use(Cell) Vue.use(Toast) Vue.use(List) Vue.use(ActionSheet) Vue.use(PullRefresh) Vue.use(Popup) Vue.use(Lazyload, { lazyComponent: true }) Vue.use(vueDirectiveImagePreviewer) Vue.use(VueAwesomeSwiper) // 全局配置Toast Toast.setDefaultOptions({ forbidClick: true, duration: 1500 }) // 挂载到Vue身上 Vue.prototype.$Toast = Toast Vue.prototype.$apis = apis Vue.prototype.$reg = reg vuex状态管理还没有使用过，但请不要使用客户端对象使用方式与在vue中大同小异，提供钩子fetchfetch 方法会在渲染页面前被调用，作用是填充状态树 (store) 数据，与 asyncData 方法类似，不同的是它不会设置组件的数据。nuxtServerInit如果在状态树中指定了 nuxtServerInit 方法，Nuxt.js 调用它的时候会将页面的上下文对象作为第2个参数传给它（服务端调用时才会执行）。当我们想将服务端的一些数据传到客户端时，这个方法是灰常好用的。最后开始写页面或改造已有项目1、初始化数据都在asyncData方法中完成，并return出来2、created生命周期及其之前不要使用客户端对象，实在要写执行函数，就加一个判断，用process.client判断一下环境再执行if (process.client) { window.xxxx )3、路由跳转，我使用的是<nuxt-link :to="{path:'xxx'}"></nuxt-link>,nuxtLink组件渲染完毕之后实际上就是a标签思考过程：客户端打开页面请求node服务，此时node服务执行nuxt，再请求java接口渲染页面，页面渲染完毕返回客户端一、每一个用户打开页面，node服务都会执行构建页面吗？二、都说node面对高并发有很好的效果，真的是这样吗？nodejs的两大牛逼依赖v8：是google开发的一套高效javascript运行时，nodejs能够高效执行 js 代码的很大原因主要在它。libuv：是用C语言实现的一套异步功能库，nodejs高效的异步编程模型很大程度上归功于libuv的实现。部署方案服务器需安装node环境与pm2第一步、服务器拉区git项目地址第二步、以此执行前端命令npm install // 下载前端依赖文件npm run build // 构建nuxt前后端启动文件npm start // 启动服务，服务器代理该服务因环境问题，每个环境构建命令与启动命令不一致，每个环境都需要单独打包构建开发环境 npm run build:dev测试一套环境 npm run build:test测试二套环境 npm run build:test2预发环境 npm run build:pre线上环境 npm run build:pro启动开发环境 npm run start:dev其他环境 npm startpackage.json"scripts": { "build:dev": "cross-env NODE_ENV=dev nuxt build ", "build:test": "cross-env NODE_ENV=test nuxt build ", "build:test2": "cross-env NODE_ENV=test2 nuxt build ", "build:pre": "cross-env NODE_ENV=pre nuxt build ", "build:pro": "cross-env NODE_ENV=production nuxt build ", "start:dev": "cross-env NODE_ENV=all pm2 start server/index.js --node-args='--harmony' --name 'nuxt'", "start": "cross-env NODE_ENV=all pm2-docker start server/index.js --node-args='--harmony' --name 'nuxt'", }
框架中的状态管理第一篇 vuex(请先看神图)单项数据流vuex管理流程状态管理应用包含三部分：state，驱动应用的数据源；view，以声明方式将 state 映射到视图；actions，响应在 view 上的用户输入导致的状态变化。进入实战在使用全局安装vue vue-cli使用vue全家桶指令vue create 项目名称安装vuex-- yarn add vuex1.打开main.js文件，此为vue项目的入口在这个文件中，创建vue实例，并挂在其他模块插件，在new构造函数中挂载store，store为整个vuex仓库，还可以挂载router路由最后记住挂载根节点import router from './router/' import store from './store/' new Vue({ store, router, render(h) { return h(App) } }).$mount('#app')2.创建与pages同级的文件夹store，创建index.js导入相应的包，并在Vue上使用Vuex创建Vuex实例 new Vuex.Store({})关键部分来了：在实例中定义Vuex灵魂部分state、getters、mutation、action、module，在后面会阐述，先看看我的简单布局 import Vue from 'vue' import Vuex from 'vuex' import http from '../utils/http' Vue.use(Vuex) const store = new Vuex.Store({ state: { data: {} }, getters: { }, mutations: { setFirstData(state, result) { state.data = result }, }, actions: { async loadFirstData({commit}, params) { let result = await http.get(params.firstReq) commit('setFirstData', result) }, }, module: { 状态树 } }) export default store3.看完大致结构，能帮助初学者快速搭建，当然每个单独的模块也可以单独定义在外面，再引入实例中。接下来我们来分别讲一讲五大核心的各自作用。stateVuex使用的单一状态树，在一个状态树中用一个对象，包裹着你定义的所有状态。当数据量大，且分为不同模块时，我们还可以使用module,后面会讲到。getter(getters)相信在学Vuex之前，大家已经了解Vue中的计算属性，用来监控data中的状态是否发生变化。其实getters类似于计算属性computed，我们可以在该对象中定义多个方法，当state中的数据发生变化，函数就会执行并返回新的状态。getter的返回值会根据他的依赖被缓存起来，且只有当它的依赖值发生变化才会被重新计算。注意：getter中定义函数，函数的一个参数为state，及上面定义的状态。Getter 也可以接受其他 getter 作为第二个参数。mutation(mutations)在Vuex中，想要更改store中的状态只有一个方法，那就是mutation。在mutation中你可以定义多个方法，这些方法的操作，最终是改变你在state中定义的属性，且这些方法接收state作为第一参数，当你想传入参数给该方法时，第二参数：载荷payload，该payload可以是对象或其他基本类型方法（state,payload） { } 注意：mutation中确实是定义了很多方法，但你只能通过store.commit('方法名')来调用。mutation必须必须是同步函数，不要在方法中进行异步操作支持的三种调用方式store.commit({ type: 'increment', amount: 10 }) store.commit('increment', { amount: 10 }) store.commit('increment', 10)action(actions)(详细再看vuex官网)看完到上面mutation时，发现mutation有一个尴尬的地方，它只能进行同步操作，但我们往往需要请求接口资源，进行异步操作。action就是来弥补mutation的不足的地方，但在使用action有几个注意的地方。不要尝试在action中直接改变state中的状态。你也改不了。(#^.^#)action提交的是mutation，而不是直接修改状态。通过context.commit(提交一个mutation方法)摘抄点官方文档：Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象，因此你可以调用 context.commit 提交一个 mutation，或者通过 context.state 和 context.getters 来获取 state 和 getters。当然你也可以使用ES6的解构赋值 { xx } 将commit从context解构出来。在所需组件中如何调用action方法:store.dispatch('increment') // 以载荷形式分发 store.dispatch('incrementAsync', { amount: 10 }) // 以对象形式分发 store.dispatch({ type: 'incrementAsync', amount: 10 })module（此处还有许多细节请查看官网）顾名思义：模块，解决最开始谈到的问题，Vuex维护的是一颗单一状态树，但当这颗状态树过于庞大时，就显得不好维护和提取所需属性。这个时候单一的状态树可以在分为多个模块，就像树的分支，在每一个单独的分支上，依旧有state，mutation，getter，action，module ，每个模块还可以在嵌套多个子模块。const moduleA = { state: { ... }, mutations: { ... }, actions: { ... }, getters: { ... }} const moduleB = { state: { ... }, mutations: { ... }, actions: { ... }} const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB }}) store.state.a // -> moduleA 的状态 store.state.b // -> moduleB 的状态对于模块内部的 getter，根节点状态会作为第三个参数暴露出来：const moduleA = { // ... getters: { sumWithRootCount (state, getters, rootState) { return state.count + rootState.count } }}如何在组件中使用以上讲完store内部的定义和部分规则，那么到了实践者最关心的环节，如何在组件中使用vuex管理的状态和方法呢！在Vuex中还有配对的4个方法，需要谁结构谁mapStateimport { mapState } from 'vuex' export default { // ... computed: mapState({ // 箭头函数可使代码更简练 count: state => state.count, // 传字符串参数 'count' 等同于 `state => state.count` countAlias: 'count', // 为了能够使用 `this` 获取局部状态，必须使用常规函数 countPlusLocalState (state) { return state.count + this.localCount } })}mapGetterimport { mapGetters } from 'vuex' export default { // ... computed: { // 使用对象展开运算符将 getter 混入 computed 对象中 ...mapGetters([ 'doneTodosCount', 'anotherGetter', // ... ]) }}mapMutationsimport { mapMutations } from 'vuex' export default { // ... methods: { ...mapMutations([ 'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')` // `mapMutations` 也支持载荷： 'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)` ]), ...mapMutations({ add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')` }) }}mapActionsimport { mapActions } from 'vuex' export default { // ... methods: { ...mapActions([ 'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')` // `mapActions` 也支持载荷： 'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)` ]), ...mapActions({ add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')` }) }}
vue 自定义指令：全局指令Vue.directive('focus',{inserted(el) { el.focus() }})调用 v-focus组件，实例上的指令directives: {demo(el,) { xxx } demo:{ bind(el,binding){ } }}过滤器filter Vue.filter('g',(value)=>{return xxx})也可以在局部上定义过滤器filters:{g(value){ }}还可以定义多个，并绑定多个过滤器绑定多个过滤器时，后一个过滤器是上一个过滤后的value还可以加参数插件使用插件：Vue.use('插件'，参数)开发咯插件.install = function(Vue,options){Vue.xxx = function(){} 全局资源 Vue.directives(){} Vue.mixins(){} 在实例上绑定方法 Vue.prototype.$xxx(){}}Vue-CLI:起步vue create xiaomi1,4,5,6stulessin packgjsonnoyarn
VUE （计算属性）计算属性：computed + watch + methodscomputed: {xxx : function(){ return 表达式 }}computed:基于缓存，检测data数据是否改变当数据没有改变时，多次调动该函数，但函数不会执行表达式中调用this.data中的属性，当data数据改变，(函数才会执行)表达式会重新计算，并返回。但是在methods中methods:{xxx : function () { return 表达式 }}多次调用，xxx会被执行多次，不管data数据是否改变，但是发现表达式的返回结果一样，DOM也不会重新渲染watch: {title() { }}零、computed watch methods的使用场景computed：--当依赖某些状态（state）产生新值的时候，优先选择使用computed：watch：--用来解决beforeUpdate,updated，两个钩子里修改状态导致死循环的问题，采用watch--监测可能异步改变值的时候methods：--定义事件响应函数--定义组件公共处理函数一、computed 和 methods 的区别1、相同点如果作为模板的数据显示，二者都能实现相应的功能，唯一不同的是methods定义的方法需要执行。2、不同点-- computed 会基于响应数据缓存，methods不会缓存（但是在绚烂DOM时会判断DOM是否改变，改变时才会重新渲染DOM）-- diff之前先看data里的数据是否发生变化，如果没有变化computed的方法不会执行，但methods里的方法会执行二、watch vs computed1、相同点如果作为信息展示，二者可以实现相同的功能。不同点是watch可能需要监听更过的属性。在这个时候我们优先使用computed2、不同点-- 当想在更新data数据后再次更新数据的时候，不能使用updated生命周期钩子。建议使用watch来监听data数据的变化-- 使用 watch 选项允许我们执行异步操作 (访问一个 API)，限制我们执行该操作的频率，并在我们得到最终结果前，设置中间状态。这些都是计算属性无法做到的。三、如何使模板重新渲染1、只要改变data里的数据就会可能触发模板渲染2、如果经过diff算法比较后，发现更新前后更新后的虚拟DOM相同，就不会渲染模板Class与Style绑定v-bind:xxx="表达式结果类型：可以是字符串，可以是对象，还可以是数组"v-bind:class="[]"v-bind:style="xxx"v-bind:class="{}" 对象且可以深拷贝兼容性1.自动添加前缀：当 v-bind:style 使用需要添加浏览器引擎前缀的 CSS 属性时，如 transform，Vue.js 会自动侦测并添加相应的前缀。2.多重值：从 2.3.0 起你可以为 style 绑定中的属性提供一个包含多个值的数组，常用于提供多个带前缀的值，条件渲染v-if 和 v-else 中间不能有元素（切记）v-else-if 包裹条件的模板标签，条件写在template上，但渲染后不会有该标签 :key 相同标签的独立性注意， 元素仍然会被高效地复用，因为它们没有添加 key 属性。v-show(boolean) -- 判断，显示隐藏v-for同时和v-if存在同标签判断中！v-for优先！当它们处于同一节点，v-for 的优先级比 v-if 更高，这意味着 v-if 将分别重复运行于每个 v-for 循环中。当你只想为部分项渲染节点时，这种优先级的机制会十分有用，v-for="(item,index） of/in 数组/对象"v-for可以接收三个参数，value，key，indexv-if 和 v-show 的区别DOM是真正否渲染！DOM是否隐藏，一定会渲染！数组变异方法:可以改变原数组的方法push()pop()shift()unshift()splice()sort()reverse()替换数组:不会改变原数组，而是返回一个新数组filter()concat()slice()注意：由于 JavaScript 的限制，Vue 不能检测以下数组的变动：1. 当你利用索引直接设置一个数组项时，例如：vm.items[indexOfItem] = newValue2. 当你修改数组的长度时，例如：vm.items.length = newLength目前2.0版本为了解决这个问题：使用setvm.$set(监听操作)Vue.set(监听操作)vue2.0使用definpropty监测对象，对数组下标可以操作，但不会响应vue3.0即将使用es6的监听方法，解决响应问题事件处理v-on:事件="xxx(yy)"注意可以传参表单v-model： 可以使用在 元素上创建双向数据绑定，在select中的option没有value时，默认为html中的内容多行文本：在文本区域插值 ( {{text}} ) 并不会生效，应用 v-model 来代替。如果 v-model 表达式的初始值未能匹配任何选项， 元素将被渲染为“未选中”状态。在 iOS 中，这会使用户无法选择第一个选项。因为这样的情况下，iOS 不会触发 change 事件。因此，更推荐像上面这样提供一个值为空的禁用选项。 .number.trim 去除首尾空白字符事件修饰符.stop -- 阻止单机事件继续传播.prevent -- 提交事件不在重载加载页面.capture -- 添加事件监听器时使用事件捕获模式，即元素自身触发的事件先在此处理，然后才交由内部元素进行处理.self -- 必须在绑定元素上.once -- 执行一次.passive -- 取消默认事件注意事件修饰符可以多个使用，但需要注意执行顺序.exact 精准控制组合触发事件Rxjs：定义对象是否可观察
vue (开篇)（数据驱动）（函数式编程）（数据双向绑定）(不操作DOM)引入vue.js文件vue框架的优点：1、易学，入门2、性能好（虚拟DOM）3、组件（高内聚，低耦合）vue提示插件：Vetur常用方法：注意函数中使用this指向实例var vm = new Vue ({el: '标签', ---标签内的元素全部被vue监听 data: { 数据驱动 }, mounted () { this指向实例 且该函数自动执行 对异步请求的数据实时更新 }, computed: { total() { return } }, methods: { handleInput(e) { this.list.push(e.target.value) } }, components: { 'gp-10' : Gp10 //vue中模板中的自定义组件会自动转为小写；注意多个使用自定义标签时，不要使用单标签 }}).$mount('#root')标签中的方法v-for="" 循环v-if="" 条件v-bind:xxx = "" vue接管标签中的属性v-bind: === :v-on: === @v-model.number= "" 数值的话转为number (语法糖)v-bind:value="" + v-on 的结合v-on:input="" 事件处理函数v-on:keyup.13= ""v-on:keyup.enter= ""v-once 布尔值，标签中的文本不在更新v-text 不会解析标签v-html 少用.prevent显示数据内容使用 : {{}}动态参数 : []组件自定义：全局定义组件Vue.component('gp10',{template: "浏览器认识的标签"})局部定义组件var Gp10 = { props: ['title'] data() { return { } } template: "浏览器认识的标签", methods: { 自定义事件函数 }}实例：$话题Object.freeze(data) --- 冻结当前属性不能修改vm.$el --- 返回当前节点在实例外面vm.$el = '#xxx' 无效生命周期函数$options ： 调用自定义在Vue上的方法！this.options.方法 或 v-on:click='$option.方法'vm.$destory() -- 销毁实例，解绑全部指令和事件监听器触发：beforedestory 和 destoryed 的钩子钩子起步 new Vue()1.beforeCreate(){}--可以访问this --不可以访问this.title..等其他数据属性--此时可访问DOM模板2.created(){} --- 数据初始化的机会，但还没渲染DOM--第一次初始化的机会--此时可以访问数据--此时可访问DOM模板【创建结束后，进入判断，判断有无$el，有无$mount,有无模板-template】隐式函数钩子：render(createElement)3.beforeMount(){}--第二次初始化的机会--此时可访问DOM模板，但并没有绚烂【真实的绚烂DOM，替换el节点！】4.mounted(){}--此时DOM模板渲染完成，可以拿到该元素节点5.beforeUpdate(){}--当上面的钩子中data被更新的时候，指向当前钩子--拿的DOM是原来没更新的DOM6.updated()--当上面的钩子中data被更新的时候，指向当前钩子--拿的DOM是更新后的DOM7.beforeDestory()--卸载响应式，及__od__8.destroyed(){}errorCaptured
VUE组件组件的命名，注意规范：标签一定用肉串，或全小写子组件注意需要包裹divprops:['使用小驼峰']全局组件--局部组件Vue.components('xxx',{template:'xxx'})const Blog = {template:'xxx' components:{ xxx //引用其他组件 }}注意组件之间是单项数据流--组件外部不能互相访问--子组件数据传给父组件--父组件自定义方法，子组件在事件函数中调用！父子组件间信息传递-- 父传子：prop = ['']-- 子传父:父组件methods : {父组件方法(参数) { console.log(参数) }}子组件：methods : {子组件方法() { this.$emit('父组件自定义方法',传参) }}--组件外部不能访问组件内部的数据--组件内部不能修改组件外部的数据--不允许子组件修改父组件的数据--也不允许父组件修改子组件的数据父子组件传参局部子组件：应该在父组件定义之前定义；通过父组件模板tem中传.prop: [ 'xxx' ],子组件接收父组件传来的参数在子组件中使用，this.xxx插槽 --- slot，想在组件中放入内容，内容可以是标签，或文本 template: ` <slot>可定义多个插槽</slot> <slot name=''>且可定义name属性，来渲染name对应的原HTML标签</slot> <slot></slot> `插槽prop--请看官网-还组件之间还可以访问参数的方法,父组件访问子组件的信息动态组件-会被销毁及会执行destroyed钩子异步组件，路由懒加载<component :is="xxx"></comonent> 必须使用浏览器认识的标签但是，li只是零时装填，还是看xxx组件中的模板tem中的内容标签。会替换掉li的！使用<keep-alive></keep-live>缓存失去活性的组件，八个常用钩子失效，可用钩子activated,deactivated专为keep-alive使用！一个组件的data必须是一个函数浏览器渲染机制：ul下有的必须是li,所有vue中li装载模板变量局部组件：注意全局注册props:注意事项props: ['必须使用小驼峰','小驼峰']约束参数类型props: {"xxx":类型（String）， "xxx": Number "xxx": [String,Number] "xxx":{ type:Number required:true } "xxx":{ type:Number default: 100或function(){ return 100xxx } } "xxx":{ 自定义 validator:function(value){ return value === ??? } }}巨有用：非props在父组件tem...中定义的属性,但是子组件中的props中没有定义，会在子组件的标签上显示，在prop中定义了，该属性就会消失在子组件标签上。在子组件中使用inheritAttrs: false,也可以使子组件不继承某些属性！自定义组件使用v-model**将原生事件绑定在组件上@.click.native=""事件在父组件域上，执行调用在子组件。父子组件数据通信！方法一：父组件作用域下：update:v-xxx:update:xxx子组件:$emit('update:xxx',xxx-参数)方法二：父组件作用域下:title.sync="title"子组件:$emit('update:xxx',xxx-参数)混入-就是很多混子混进来了-大家一起混--继承--公用！mixins:[xxx,xxx]原定义的option中的方法会顶掉混子，即mixins中的方法data 会 merge，相同则覆盖。其他options也是一样咯！边界处理：子组件 标签中定义<xxx ref=''>父访问子 $refs子访问父 $parent子访问更节点 $root依赖注入父组件 provide: function () {return { getMap: this.getMap }}子访问，类似props ：inject:['']vue-bus bus总线递归组件：自己调用自己，记住给一个出口及终止条件内联模板：inline-template,在模板标签中写DOM标签强制渲染页面$forceUpdate();