vuex、vue router 及 SSR

156 阅读5分钟

设计模式

MVC

image.png

开发体系分为 View Controller Model

Model:data 核心数据结构

View:HTML + CSS 完成视图展示、样式区分

Controller:JS 负责页面逻辑,数据再处理,动态响应、交互

这个体系中,user 操作view 和 controller,下沉到model层,model 层再去反应到view层

特点:架构为一个闭环

MVVM

image.png

VM: ViewModel 数据逻辑层

view 层和 viewModel层双向绑定

这个体系中,user 只操作view 层

面试题:MVVM的理解? 首先就是双向绑定。

  1. 区别上理解: 编程思想上抛弃了对DOM的严格感知,不再需要严格知道具体操作哪一个DOM,只需要知道模块 无需对流程的过程化操作。只需要改变data, 写法上,代码可维护性上聊聊
  2. 实现上

vuex

面试:vue组件传值? 兄弟节点传值:

  1. 通过父节点
  2. eventBus
  3. provide inject 通过注入,跨模块,跨父子层级
  4. vuex

SonSon2 给 son1 传值:

image.png

状态机中的数据都是响应式

vuex 总线机制,单例实现 -> 全局只能有一个实例 否则不知道son2弄在哪个状态机

vuex 组成和过程

component 提交 dispatch,触发actions 内部的方法,actions 调用 mutations 方法,mutations 再去修改states 中的值。 states 值可以被外部监听,响应到 component

image.png

面试题:为何要兜一圈?

为了异步操作。所有的异步操作都在 actions 中,保证到mutations 中都保持同步,states 渲染时候需要稳定。

面试题:异步如何转成同步?

例子: 如何将promise 转成同步?

.then esNext async await

场景:

method: {
  asyncFunc() {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve(true)
        console.log('async');
      }, 4000)
    })
  },
  asyncFunc2() {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve(true)
        console.log('async');
      }, 5000)
    })
  }
},
mounted () {
  // promise 解决
  this.asyncFunc.then(res => {
    console.log('mounted')
  })
}

缺点:写在回调里,多了回调地狱 async

mounted () {
  // promise 解决
  this.asyncFunc.then(res => {
    console.log('mounted')
  })

  // es语法
  await this.asyncFunc()
  console.log('mounted')
}

追问:多个异步函数?

// mounted里
// 全部 & 竞争
Promise.all(this.asyncFunc(), this.asyncFunc1().then(_=> {
  console.log('all done')
}))
Promise.race(this.asyncFunc(), this.asyncFunc1().then(_=> {
  console.log('all done')
}))

// 多个异步依赖执行取巧
await this.asyncFunc()
await this.asyncFunc2()

// 多个异步依赖执行co库 - 简单实现 - 迭代器模式
// koa
const pipeLine = [this.asyncFunc, this.asyncFunc2]

// 需要迭代器
setIterator(pipeLine)

// 通过下一个指针,做流水线 
// next() function* + yield - 生成器
function* setGenerator(pipeLine) {
  for (const fn of pipeLine) {
    yield fn(); // 归化城迭代器的每一个步骤
  }
}

// 配置迭代器
function setIterator(pipeLine) {
  const generator = setGenerator(pipeLine)

  GFC(generator)
}

// 流水线 - 区分异步同步,依次执行
function GFC(gen) {
  const item = gen.next();

  // 最后一个值为done
  if (item.done) {
    return item.values
  }

  const { value, done } = item
  // value 内容,done 状态
  if (value instanceof Promise) {
    value.then(e => GFC(gen))
  } else {
    // 同步的直接执行
    GFC(gen)
  }
}

vuex 使用

引入

// main.js
import store from './store'

src 下面建立 store 文件夹 index.js 为入口

import Vue from 'vue'
import Vuex from 'vuex'

// 1. use 上
Vue.use(Vuex)

// 2. 创建store 实例
const store = new Vuex Store({
  actions: {
    async setNodeInfo({ commit }, info) {
      // 这里可以异步转同步
      // await xxx()
      commit('SET_NODE_INFO'), {
        info
      }
    }
  }.
  mutations: {
    SET_NODE_INFO(state, { info }) {
      state.nodeInfo = info
    }
  },
  state: {
    nodeInfo: {
      name: '-',
      age: 0,
      words: 'hello world!'
    }
  }
})

export default store

App 为 hello world 父组件

// App.js
data () {
  return {
    nodeInfo: {
      name: '-',
      age: 0,
      words: 'hello world!'
    }
  }
}

1:04

HelloWorld.vue
异步改变 store 的值

可以做一个computed 上

一些辅助函数:mapState

computed: {
  // 辅助函数
  ...mapState(['nodeInfo'])
  // 第二种写法
  ...mapState
}

vuex 源码

首先看3.x

git checkout 3.x
git pull

Vue.use() 默认调用 install

store.js

// use 的开始
function install (_Vue)
// 校验vue已经被挂载,区别传入和Vue 相同 => 已经use过了

Vuex 3 理论基础为mixin Vuex 4 provide inject

minxin.js

// 兼容下老版本
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什么时候初始化? - beforeCreate

vuex 初始化

// mixin.js
function vuexInit () {
  // 这里this 指向实例
    const options = this.$options
    // store injection
    // store 注入到每个实例中
    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
    }
  }

store 实例对象的描述

构造函数

// store.js
// cdn 直接引用方式,可以自动 install

if (!Vue && typeof window !== 'undefined' && window.Vue) {
      install(window.Vue)
    }

 if (__DEV__) {
  assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
  assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
  // 必须要用new操作符 调用 store
  assert(this instanceof Store, `store must be called with the new operator.`)
}

面试题:vuex 自己定义了告警,为什么不用console.assert?

主要的目的为了 throw Error ,对框架进行核心把控,提高稳定性

// 用来定义用户定义的getters - 响应式
    this._wrappedGetters = Object.create(null)

vuex 实现响应式的方式:根据vue 为响应式

// 响应式$watch
  this._watcherVM = new Vue()

面试题:Object.create(null) 和 {} 创建对象的区别?

原型链的区别
Object.create(null).__proto__ 为undefined

{}.__proto__ 是Object.prototype

Object.create(null)可以只保证自身的属性

// 写单例core,可以参考 确认this是当前store 的实例
const store = this
const { dispatch, commit } = this
this.dispatch = function boundDispatch (type, payload) {
  return dispatch.call(store, type, payload)
}
this.commit = function boundCommit (type, payload, options) {
  return commit.call(store, type, payload, options)
}

装载模块

installModule 遍历注册Mutation,Action,Getter,Children

核心:vue如何做响应式 wrapperGetters -> 拿到用户的值

Object.defineProperty 做数据劫持

// 遍历地将所有getters侨接上store,并配置成computed 属性
Object.defineProperty(store.getters, key, {
    get: () => store._vm[key],
    enumerable: true // for local getters
  })
// 利用vue 做响应式
store._vm = new Vue({
  data: {
    $$state: state
  },
  computed
})

异步处理看看

SSR

传统CSR 静态文件拿到data -> 全部给到client (浏览器)样式和结构引擎解析,渲染

image.png

SSR 服务侧生成好一个配置页面,直接给到client

区别:浏览器端渲染和服务端渲染

优点:首屏快,SEO友好 -> 后台直接会有配置 缺点:所有东西都是后台传输,server压力很大

image.png

配置

建立serve服务

Mongo Angular Express Node

npm vue-server-render express -D

vue-server-render 前端渲染器

最外层建立server文件夹

index.js node 服务 0. 加载依赖

const express = require('express')
const Vue = require('vue')

const app = express()
const renderer =require('vue-server-render').createRenderer()

渲染器渲染page得到html内容

  1. page
const page = new Vue({
  template: '<div>hello world</div>'
})
  1. 传递接口
app.get('/', async(req, res) => {
  try {
    // 转成字符串
    const html = await renderer.renderToString(page)
    res.send(html)
  } catch {
    res.status(500).send('serve inner error')
  }
})
  1. 启动服务器监听
app.listen(3000, () => {

})
  1. 执行

ssr 路由 普通router:

import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      component: HelloWorld
    }
  ]
})

SSR router

不可以返回路由实例,需要返回工厂函数

import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'

Vue.use(Router)

export default function createRouter() {
  return new Router({
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      component: HelloWorld
    }
  ]
})
}

面试题:为什么不导出一个router实例 而是工厂函数?

因为用户每次操作都在服务端做的,连接服务端有无数个客户 -> 每一个请求都要创建一个实例,保证用户每次进入系统应用重新切换

例子:

import Vue from 'vue';
import App from './router'
import createRouter from './router'

export default function createApp () {
  const router = createRouter()
  const app = new Vue({
    router, 
    render: h => h(App)
  })

  return { app, router }
} 

金融板块中直接塞值不安全