一、前提思考我们如何给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) }四、一遍遍的临摹，一遍遍的思考
前言bb前段时间，一同事问我react相关知识，我的大脑突然空白，许多细节都已模糊，回去看看以前的项目，才回想起来点，温故知新（捡起来）vue,react都是很好的框架,两则相互的竞争，相互借鉴，突破对方的弱点，相信前端会越来越好（近年来相对稳定，有好有坏吧，不管怎么说，不管什么行业，尤其靠技术吃饭的，自己不提升将会被时代抛弃）同样服务端渲染的next框架nuxt搞过，好奇心：next也要学学，先学习该工具的使用vue与react框架思想一样，那么相对应他们与之配套的服务端渲染nuxt和next是不是大同小异，gogogo学习一个新语言，新框架，新工具，都请先阅读官方文档：Next英文文档，中文文档有点落后一、搭建项目官网脚手架 / 当然你也可以自己搭建，安装所需npm包即可新版本next需要注意node版本10以上npm init next-app # or yarn create next-app使用官方cli初始化项目，就只一个pages文件夹，其他都需要你自己丰富我的demo目录结构├── .next --------------------------- 打包后项目主文件 ├── api ----------------------------- 说明文件 ├── asyncData ------------------- node服务端请求接口 ├── syncData -------------------- 浏览器请求接口 ├── assets -------------------------- 项目静态资源文件目录（图片、公共样式、字体等） ├── components ---------------------- 组件 ├── layouts ------------------------- 布局模式 ├── pages --------------------------- 页面（该文件夹下也对应着项目路由路径） ├── server └── index.js -------------------- node服务启动文件 ├── static -------------------------- robot静态文件 ├── store --------------------------- redux状态管理 ├── utils --------------------------- 工具 ├── next.config.js ------------------- webpack配置入口 ├── package.json -------------------- 项目配置老规矩，整理出页面所需要的基础配置与依赖webpacknode服务配置路由获取数据和组件生命周期rudex状态管理最后开始写页面或改造已有项目基本目标确定，开始工作workwork一、webpacknext并没有对该配置文件做多余的处理，编程者还是自己选择所需的打包吧webpack使用zeit下提供的三连utils，简易的话就不用配置啦，相对nuxt，他就没有其他多余的配置，大多还是webpack的使用next.config.jsconst withSass = require('@zeit/next-sass') const withCSS = require('@zeit/next-css') const withSourceMaps = require('@zeit/next-source-maps') module.exports = withSass(withCSS(withSourceMaps()))二、node服务配置server/index.js这里可以使用express，也可以使用koa作为node框架服务const express = require('express') const next = require('next') const compression = require('compression') const dev = process.env.NODE_ENV !== 'production' const app = next({dev}) const handle = app.getRequestHandler() let port = dev ? 6666 : 8080 console.log('Waiting ready on http://localhost ' + port + ' ……') // Pass in the absolute path to your robots.txt file app.prepare() .then(() => { const server = express() if (!dev) { server.use(compression()) //gzip } // 动态请求处理，自定义路由映射 server.get('/a/:id', (req, res) => { const actualPage = '/detail' const queryParams = {id: req.params.id} app.render(req, res, actualPage, queryParams) }) server.get('/robots.txt', (req, res) => ( res.status(200).sendFile('robots.txt', optionsPlain) )); server.get('/sitemap.html', (req, res) => ( res.status(200).sendFile('sitemap.html', optionsHtml) )); server.get('/sitemap.xml', (req, res) => ( res.status(200).sendFile('sitemap.xml', optionsXml) )); server.get('*', (req, res) => { return handle(req, res) }) server.listen(port, (err) => { if (err) throw err console.log('> Ready on http://localhost ' + port) }) }) .catch((ex) => { console.error(ex.stack) process.exit(1) }) 其实对比nuxt，启动文件一样的，在该node入口服务文件中，使用express启动服务，对请求进行处理，再使用next模块render页面返回客户端。这里还添加seo两大文件，robots.txt，sitemap.html，站点链接地图与访问指定可访问页面三、路由还是与nuxt一样，让我们不用关心与维护路由文件（app.js），那他的路由路径怎么对应，其实就是对应着pages下面的文件路径，即是页面路径。所以我们在写页面的时候，规范组件抽离至component文件夹，也不要有其他不是单独页面的文件让我们看看他具体使用，两个页面如何跳转一、link下必须是一个元素节点二、想要页面跳转，link包裹的必须是a标签，其他标签href传递不下去，没用的哦，引用官方解释：The default behaviour for the <Link> component is to push a new url into the stack. You can use the replace prop to prevent adding a new entry.// pages/index.js import Link from 'next/link'; function Home() { return ( <> Home hello 小橘子 首页 </> ); } export default Home;// pages/xiaojuzi.js import Link from 'next/link'; function About() { return ( <> 首页 </> ); } export default xiaojuzi;如果很少访问某些页面，可以将其手动设置prefetch为false 666 我们关心的动态路由它又来了方法一（服务端方式）：动态路由映射，node服务配置文件中，我们对路由重新pushstate，当node服务接受到一个/a/1的请求，这个时候我们通过拦截，将路由映射到/detail的页面，并携带动态id参数过去，最后render页面 server.get('/a/:id', (req, res) => { const actualPage = '/detail' const queryParams = {id: req.params.id} app.render(req, res, actualPage, queryParams) })方法二：next提供了特殊的文件命名，[id].js,在中括号就是动态的路由路径页面文件，该动态参数你可以在query中取到，来看官方例子与解释Defining routes by using predefined paths is not always enough for complex applications, in Next.js you can add brackets to a page ([param]) to create a dynamic route (a.k.a. url slugs, pretty urls, et al).Consider the following page pages/post/[pid].js:import { useRouter } from 'next/router'; const Post = () => { const router = useRouter(); const { pid } = router.query; return Post: {pid} ; }; export default Post;同理，多动态参数，那文件夹就使用[]来命名包裹例：pages/post/[pid]/[comment].js将匹配/post/1/a-comment。它的query对象是：{ pid: '1', comment: 'a-comment' }。四、获取数据和组件生命周期与nuxt提供的asyncData一样，next也提供了钩子getInitialProps在函数式组件中import fetch from 'isomorphic-unfetch'; function Page({ stars }) { return Next stars: {stars} ; } Page.getInitialProps = async ({ req }) => { const res = await fetch('https://api.github.com/repos/zeit/next.js'); const json = await res.json(); return { stars: json.stargazers_count }; }; export default Page;在类组件中import React from 'react'; class HelloUA extends React.Component { static async getInitialProps({ req }) { const userAgent = req ? req.headers['user-agent'] : navigator.userAgent; return { userAgent }; } render() { return Hello World {this.props.userAgent} ; } } export default HelloUA;getInitialProps 接收具有以下属性的上下文对象：pathname -URL的路径部分query -URL的查询字符串部分被解析为对象asPath- String实际路径（包括查询）的-在浏览器中显示req -HTTP请求对象（仅服务器）res -HTTP响应对象（仅服务器）err -渲染期间遇到任何错误的错误对象五、rudex状态管理依旧是快乐正常使用，在你的app最外层组件挂载，全局嘻嘻看到一篇还可以的博客，还不会使用rudex的可以看看六、默认页面一、自定义app.js。Next.js使用该App组件来初始化页面。您可以覆盖它并控制页面初始化。这使您可以做一些令人惊奇的事情，例如：页面更改之间的持久布局导航页面时保持状态使用自定义错误处理 componentDidCatch将其他数据注入页面（例如，通过处理GraphQL查询）要覆盖，请创建./pages/_app.js文件并覆盖App类import React from 'react'; import App from 'next/app'; class MyApp extends App { render() { const { Component, pageProps } = this.props; return <Component {...pageProps} />; } } export default MyApp;六、最后开始写页面或改造已有项目enenen，细节部分还得多实践实践,最后奉上请求方案axios.jsimport axios from "axios"; import qs from "qs"; // 创建axios默认请求 axios.defaults.baseURL = "http://gongpengji.com"; // 配置超时时间 axios.defaults.timeout = 100000; // 配置请求拦截 axios.interceptors.request.use( // xxx ); // 添加响应拦截器 axios.interceptors.response.use( function(response) { console.log(response); return response; }, function(error) { // 对响应错误做点什么 return Promise.reject(error); } ); /** * get请求 * @method get * @param {url, params, loading} 请求地址，请求参数，是否需要加载层 */ var get = function(url, params, loading) { return new Promise((resolve, reject) => { // { // params: params // } axios .get(url, params) .then(res => { resolve(res); }) .catch(err => { reject(err); }); }); }; /** * post请求 * @method post * @param {url, params} 请求地址，请求参数，是否需要加载层 */ var post = function(url, data) { return new Promise((resolve, reject) => { // qs.stringify(data) axios .post(url, data) .then(res => { console.log(res); resolve(res); }) .catch(err => { reject(err); }); }); }; export default { get, post };思考在看一边服务端渲染实现思想都是一样，两则使用上的不同，给我又一次不同的体会（英文文档真的难读，但看的越多越容易找到表达的关键点）【谷歌翻译随时点开哈哈哈】，发现在服务端进行请求路径修改并reader指定页面，确实nice。现在清楚当客户端请求过来，nextjs才开始处理请求，并render，在这之间，我觉得还可以其他事件！
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'", }
Redux状态管理const { createStore } = require('redux')const defailState = {count: 0;}const reducer = function(state = defailState,action){xxx>>>自己调用的 接收一个状态，返回一个新的状态，原状态是对象，新状态也要是对象 } const store = createStore(reducer,action,xxx)--创建的时候会被执行一次reducerconst newState = store.getState()-获取最新状态-每次调用的时候也会执行reducerstore.dispatch({ ---派发的是action参数xxx: xxx})---每次调用时也会执行一次reducer订阅你的改变信息提醒---store.subsrcibe(()=>{xxxx})yarn add react-redux context--注入包import { Provider } from 'react-redux'ReactDom.render(<Provider store="store"> <Redux></Redux> </Provider>, document.xxxid('#root'))Redux.js中import { connect } from 'react-redux'iconst mapStateToProps = (state) => {return{ count:state.count }}const mapDispathToProps = (dispath) => {return{ incment(){ dispath({ type: xxx, payload:xxx, }) }, decment(){ dispath({ type: xxx, payload:xxx, }) } }}export defalut connect(mapStateToProps,mapDispathTopProps)(Redux)connect(fn1,fn2),接收两个函数fn1:绑定state中的值，返回对象fn2:绑定dispath函数，派发扁平对象，返回对象什么纯函数给定预期的输入，返回预期的输出什么不是纯函数给一个输入，不确定输出的结果！副作用想做异步请求，Action派发之前，需要中间件拦截：第三方工具--xxxxxx很多redux-thuckimport {applyMiddleware} from 'redux' ---中间件可以让dispatch接受函数import Thunk from 'redux-thuck'const store = createStore(reducer,applyMiddleware(Tunck))增强dispath，可以接收函数或对象或数组了在componentDidMount(){}中可以调用props中定义的方法UI组件思想ReduxUI 纯DOM模板:定义函数式组件，返回UI 注意接收传入的参数props,如果是传函数<ReduxUI {{ ..this.props,handleclick = {this.handleclick.bind(this)}, xxx}}></ReduxUI>ActionCreator !抽离函数ActionCreator.jsexport const loadData = ()=>{return (dispath) = > { dispath({扁平对象}) }}在Redux中结构的引入action-typesexport const INDEX = 'x/index'combineReducers() 合并reducer只接受同步函数
原生版 React-router-native浏览器版本 React-router-domyarn add react-router-domimport {BrowserRouter as Router,Route,Switch} from 'react-router-dom'<Router><Route path='/' exact component={Cookbooks}></Route> <Route path='/maps' component={xxx}></Route></Router>exact 精准匹配原本是包容性路由！派他性路由--->Switch<Switch><Route path='/' exact component={Cookbooks}></Route> <Route path='/maps' component={xxx}></Route> <Route path='*' component={page404}></Route></Switch>this:在被route包裹中定义的组件，子组件中的this.props中被定义上history,location,match对象，有很多方法和数据可以调用！Link:toNavLink:toactiveClassNameRoute:上的属性--这三个都有props属性1.component={组件} path匹配了，才会渲染，会卸载组件2.render={函数式组件} path匹配了，才会渲染 ，只是渲染组件3.(牛逼-可以自定义route事件，可以拿到props的三大无敌属性history,location,match)children={函数式组件} 不管匹不匹配都会渲染让任意一个组件拥有props--使用高阶组件挂在上去withRouterwithRouterReact-router-dom版本：react-router -3.0版本：react-router-dom -4.x/5.xreact-router-dom -4.x/5.x的区别：1、5.x性能提升2、解决了两个bug-- Router 不需要一个唯一的子组件 --之前需要一个标签包裹 -- Router 再和connect一起使用的时候，4.x要求withRouter--Router在外定义。5.x可以卸载connect里面 了
hook --- 状态 useStateconst [ xxx,setxxx ] = useState(默认值)允许你创建多个状态，多个状态不会互相影响也可以在一个状态上维护一个对象每次必须新的对象，来替换原先状态.hook --- useEffectuseEffect( ()=>{ xxx } ）类似mount钩子加update钩子加unmount也允许你写多个useEffect(),并且接受第二个参数，为[ xxx ]，及监听的状态不发生改变，该函数就不会执行每次它自己调用的时候，都是在DOM渲染完毕之后的时候第一次会自动调用状态改变时会自动调用组件卸载的时候也会自动调用，但必须return 一个函数,这个函数会在卸载后执行 ！hook --- useContexthook --- useReduerhook --- useCallbackhook --- useMemohook --- useRefhook --- useImperaiveHandlehook --- useLayoutEffecthook --- useDebugValue
Reacthtml属性和DOM属性的区别组件通信父子组件：props兄弟组件：（状态提升）A用父组件的方法，把值传给父组件，兄弟用父组件的值！跨组件通信：Context其实还有老版本context！Context,类似vue中的provide，inject一个树下的全局数据！xxx:let aaa = React.createContext('默认值')exprot xxx父组件：import aaa from 'xxx'<aaa.Provider value={'赵倩'}>子组件，子子子组件</xxx.Provider>或let {Provider} = aaa<Provide></Provide>子组件：static contextType = aaa<>{this.context.xxx}<>函数式组件：<Consumer>{ 数据 }</Consumer>HOC:高阶组件highordercomponent 增强式组件！本质是个函数，接收一个组件作为参数，对组件进行自定义加工后返回（组件）
React组件状态管理：statesuper()setState:是异步的this.setState({}) merge上面的state的操作，1.setState实质上是将传入的第一个参数对象的state对象做merge2.如果依据愿原有的state更新state，最好传入一个函数，setState((prevState)=>{return xxx},callback(回调，等待上一个函数的执行，等待DOM渲染，类似nexttick))setState会导致组件重新渲染只要父组件render被调用，子组件render也会被调用，不管父组件的状态更不更新状态提升：Lifting State Up兄弟组件传参A子组件调用父组件方法，A子组件的值在使用方法的时候把A组件的状态（数据），传给了父组件，父组件接收一下，可以在传给B子组件受控组件和非受控组件表单 新版：this.input = React.createRef() 事件处理this.handleClick.bind(this)使用bind，每一次都会创建一个新的事件函数定义:在constructorthis.xxx = this.xxx.bind(this)常用1、需要传参的()=> this.handleClick(x,x,x,e)2、只需要传ethis.handleClick()老（16-）生命周期钩子 没加react.fiber算法时初始化阶段constructor(){}装载阶段componentWillMount(){}render(){}componentDidMount(){}props改变的阶段componentWillReceiveProps(){}shouldComponentUpdate(){return boolean} 不能再此修改：setState; === PureComponentcomponentWillUpdate(){}render() 不能再此修改：setStatecomponentDidUpdate(){} 不能再此修改：setStatestates状态阶段shouldComponentUpdate(){return boolean} 不能再此修改：setState; === PureComponentcomponentWillUpdate(){}render() 不能再此修改：setStatecomponentDidUpdate(){} 不能再此修改：setState卸载阶段可以使用React.Dom.xxxxxnode方法卸载节点componentWillUnmount()新（16+）生命周期钩子16.4挂载时constructor(){}getDerivedStateFromProps(){}render(){}React(DOM,refs)componentDidMount(){}更新时此钩子，在16.3时只是监测props不会陷入死循环getDerivedStateFromProps(props,state){必须有返回值，没有写null props为父组件实实传入的值 state为子组件 return { title:props.title } }merage两次的值shouldComponentUpdate(){}render(){}getSnapshotBeforeUpdate(){必须有返回值，没有写null}配合使用componentDidUpdate(){}React(DOM,refs)componentDidMount(){}卸载时componentWillUnmount16.3 getDerivedStateFromProps此钩子，在16.3时只是监测props,但是在16.4的时候，此钩子会在props,setState,forceupdata的时候触发引用了不是state的数据，并更新数据,但不会跟新DOM使用this.forceUpdate(),强迫人家一下咯！（刷新页面）forceUpdate使用，不会调用shouldComponentUpdate钩子static defaultProps()static displayName()
React轻量级UI库React高性能的体现：虚拟DOM-在内存数据，性能极高。React Fiber 16+核心算法-分片哪里用了JSX，哪里就要引React函数式组件，一个JS就是一个组件，但是没有状态，也叫无状态组件、傻瓜组件，影子组件，可以接收父组件传递的信息包裹用的标签，Fragment,记住解构去引，或不引用可以用空标签包裹创建方式1.Class方式创建组件2.js无状态创建方式3.使用createClass创建组件时，无法暴露，但可以引入插件：create-react-classporps：传值静态属性：默认值Props.defaultProps={title:"xxx"}或者static defaultProps = {title: "xxx"}读取子元素节点：this.props.childrenprops类型检查：使用包：prop-types静态属性：static propTypes = {title = PropTypes.[xxx/string/array/node...]}node节点可以传模板下去比较element,React 元素React 标签