Vue+Vue-Router+Vuex+SSR项目

Vue-Vue-Router-Vuex-SSR

  1. Vue+Webpack工程流搭建

  2. Vue+Vue-Router+Vuex项目架构

服务端渲染

现在的前端框架是纯客户端渲染的,(请求网站的时候,返回的html是没有什么内容的),存在问题是没有办法seo, 白屏时间较长。需要等待js加载完成,执行完成之后才会显示内容。

服务端渲染解决这些问题。

webpack升级注意 ⚠️ :1. 版本变化 2. 配置变化 3. 插件变化

vue-loader配置

const isDev = process.env.NODE_ENV === 'development'
// vue-loader.config.js
const docsLoader = require.resolve('./doc-loader')
module.exports = (isDev) => {
 return {
  preserveWhitespace: true, // 去掉后面的空格
  extractCSS: !isDev, // 单独打包到css某文件
  cssModules: {},
  // hotReload: false, // 根据环境变量生成
  loaders: {
   'docs': docsLoader,
  },
  preLoader: {
  },
 }
}
module.exports = (isDev) => {
 return {
  preserveWhitespace: true,
  extractCSS: !isDev,
  cssModules: {},
 }
}

css module配置

module.exports = (isDev) => {
 return {
  preserveWhitespace: true,
  extractCSS: !isDev,
  cssModules: {
   localIdentName: isDev ? '[path]-[name]-[hash:base64:5]' : '[hash:base64:5]',
   // localIdentName: '[path]-[name]-[hash:base64:5]',
   camelCase: true
  }
 }
}

安装使用eslint和editorconfig以及precommit

npm i eslint eslint-config-standard eslint-plugin-standard eslint-plugin-promise eslint-plugin-import eslint-plugin-node

创建.eslintrc

{
 "extends": "standard"
}
npm i eslint-plugin-html
// package.json

"script": {
 "clean": "rimraf dist",
 "lint": "eslint --ext .js --ext .jsx --ext .vue client/",
 "build": "npm run clean && npm run build:client",
 //自动修复
 "lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue client/"
 "precommit": "npm run lint-fix",
}
npm i webpack -D
// webpack 4
npm uninstall webpack webpack-dev-server webpack-merge -D
// webpack 升级
npm i webpack webpack-dev-server webpack-merge webpack-cli -D

npm uninstall babel-loader extract-text-webpack-plugin file-loader html-webpack-plugin -D

webpack.config.base.js

const config = {
 mode: process.env.NODE_ENV, // development || production
 target: 'web',
}
npm i eslint-loader babel-eslint

Vue的一些点

import Vue from 'vue'

const div = document.createElement('div')
document.body.appendChild(div)

new Vue({
 el: div,
 template: '
this is content
' }) // webpack.config.practice.js resolve: {  alias: {   'vue': path.join(__dirname, '../node_modules/vue/dist/vue.esm.js')  } }

index.js

import Vue from 'vue'
import App from './app.vue'

import './assets/styles/global.style'

const root = document.createElement('div')
document.body.appendChild(root)

new Vue({
 rendeer: (h) => h(App)
}).$mount(root)

VUE实例

  • Vue实例的创建和作用

  • Vue实例的属性

  • Vue实例的方法

import Vue from 'vue'
const app = new Vue({
 // el: '#root',
 template: '{{text}}
',  data: {   text: 0  } }) app.$mount('#root') setInterval(()=>{  // app.text += 1  // app.$options.data += 1 不可以使用  app.$data.text += 1 // 可以使用 },1000) console.log(app.$data) console.log(app.$props) console.log(app.$el) console.log(app.$options) console.log(app.$slots) console.log(app.$scopedSlots) console.log(app.$ref) // div节点/组件实例 console.log(app.$isServer) // 服务端渲染 app.$options.render = (h) => {  return h('div', {}, 'new render function') }
props: {
 filter: {
  type: String,
  required: true
 },
 todos: {
  type: Array,
  required: true
 }
}

watch

const app = new Vue({
 // el: '#root',
 template: '{{text}}
',  data: {   text: 0  },  watch: {   text(newText, oldText) {    console.log('${newText}:${oldText}')   }  } }) app.$mount('#root')
const unWatch = app.$watch('text', (newText, oldText){
 console.log(`${nextText}:${oldText}`)
})
setTimeout(()=>{
 unWatch() // 取消 2秒后
}, 2000)

事件监听:

app.$on('test', () => {
 console.log('test emited')
})

//触发事件
app.$emit('test')

app.$on('test', (a, b) => {
 console.log('test emited ${a} ${b}')
})

//触发事件
app.$emit('test', 1, 2)
$once只监听一次
app.$once('test', (a, b) => {
 console.log('test emited ${a} ${b}')
})

forceUpdate强制组件渲染一次

非响应式的:

// 值在变,但不会导致重新渲染
data: {
 text: 0,
 obj: {}
}

setInterval(() => {
 app.obj.a = app.text
 app.$forceUpdate()
},1000)

一直在渲染会让你的性能降低

某个对象上的,某个属性名,给他定义一个值:

let i = 0
setInterval(()=>{
 i++
 app.$set(app.obj, 'a', i) // 变化,某个对象上的某个属性值给它一个值
},1000)

vm.$nextTick([callback])

等DOM节点渲染完成。Vue在下一次更新的时候调用callback

将回调延迟到下次DOM更新循环之后执行,在修改数据之后立即使用它,然后等待DOM更新,它跟全局方法Vue.nextTick一样,不同的是回调的this自动绑定到调用它的实例上。

2.1.0新增,如果没有提供回调且支持Promise的环境中,则返回一个Promise.注意polyfill.

vue生命周期

new Vue({
 el: '#root',
 template: '
{{text}}
',  data: {   text: 'jeskson'  },  beforeCreate() {   console.log(this, 'beforeCreate')  },  created() {   console.log(this, 'created')  },  beforeMount() {   console.log(this, 'beforeMount')  },  mounted() {   console.log(this, 'beforeCreate')  },  beforeUpdate() {   console.log(this, 'beforeUpdate')  },  updated() {   console.log(this, 'updated')  },  activated() {   console.log(this, 'activated')  },  deactivated() {   console.log(this, 'deactivated')  },  beforeDestroy() {   console.log(this, 'beforeDestroy')  },  destroyed() {   console.log(this, 'destroyed')  },  render(h) {   console.log('render function invoked')   return h('div', {}, this.text)  },  renderError(h, err) {   return h('div', {}, err.stack)  },  errorCaptured() {   // 向上冒泡,并且正式环境可以使用  } })
undefined 'beforeCreate'
undefined 'created'
 "beforeMount"
0
 "mounted" template: `
 name: {{name}} 
` computed: {  name() {   return `${this.firstName} ${this.lastName}`  }  } 遍历数组 v-model.number 数字 v-model.trim 去掉空格
   

定义组件

import Vue from 'vue'

const compoent = {
 template: '
This is compoent
' } // Vue.component('CompOne', compoent) new Vue({  components: {   CompOne: compoent  },  el: '#root',  template: '' }) // 报错 const compoent = {  template: '
{{text}}
',  data: {   text: 123  } } [vue warn] the "data" option should be a function that returns a per-instance value in component definitions.

props

// 子组件
const compoent = {
 props: {
  active: Boolean,
  propOne: String
 },
 template: `
  
       {{propOne}}    see me if active   
 `,  data() {   return {    text: 0   }  } } // 父组件传递

不允许在组件内部修改this.propOne='inner content',

vue warn: avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "propOne"
// 子组件触发props修改

// 子组件
methods: {
 handleChange() {
  this.$emit('change')
 }
}

// 子组件props

props: ['active', 'propOne'],

props: {
 active: {
  type: Boolean,
  required: true,
  default: true
 }
}

props: {
 active: {
  // type: Boolean,
  // required: true,
  validator(value) {
   return typeof value === 'boolean'
  }
 }
}
const CompVue = Vue.extend(compoent)

new CompVue({
 el: '#root',
 propsData: {
  propOne: 'xxx'
 }
})

const parent = new Vue({
 name: 'parent'
})

const componet2 = {
 extends: compoent,
 data () {
  return {
   text: 1
  }
 },
 mounted () {
  console.log(this.$parent.$options.name) // Root
  this.$parent.text = '12345'
 }
}

new Vue({
 parent: parent,
 name: 'Root',
 el: '#root',
 mounted () {
  console.log(this.$parent.$options.name) // parent
 },
 components: {
  Comp: component2
 },
 data: {
  text: 2333
 },
 template: `
  
{{text}}
    ` })

栗子 input

// 子组件
handleInput (e) {
 this.$emit('input', e.target.value)
}

// 父组件
... :value="value" @input="value = arguments[0]"

// 
const component = {
 props: ['value'],
 template: `
  
      
 `,  methods: {   handleInput (e) {    this.$emit('input', e.target.value)   }  } } const component = {  model:; {   prop: 'value1',   event: 'change'  },  props: ['value1'],  template: `   
      
 `,  methods: {   handleInput (e) {    this.$emit('change', e.target.value)   }  } }

属性✍️

slot

const component = {
 template: `
  
   
    
   
            
  
 `,  data () {   return {    style: {     width: '200px';     height: '200px';     border: '1px solid #aaa'    }   }  } }   this is header    this is body 

slot-scope

特殊

template: `
 
  
 
`, // 父组件 - 组件内部使用的变量   {{props.value}} {{props.aaa}} 

provide inject

provide : {
 yeye: this,
 value: this.value, // 错误
},

provide() {
 const data = {}

 Object.defineProperty(data, 'value', {
  get: () => this.value,
  enumerable: true
 })

 return {
  yeye: this,
  data
  // value: this.value
 }
}

// 子组件
inject: ['yeye', 'data']
template: '
child component: {{data.value}}
'

new Vue, beforeCreate, created, beforeMount, mounted

没有el,就没有挂载beforeMount, mounted

若使用const app , app.$mount('#root') 就会执行

setInterval(() => {
 app.text = app.text += 1
},1000)

// 主动销毁
app.$destroy()

组件:

activated() {
 // 在组件
 console.log(this, 'activated')
}
deactivated() {
 // 在组件
 console.log(this, 'deactivated')
}

变化:

el:

undefined 'beforeCreate'
undefined 'created'
 "beforeMount"
0
 "mounted"

dom有关的放在mounted里面,数据可created,mounted

服务端渲染不会执行(因为服务端没有Dom环境,所有更本就没有这些内容),beforeMount,mounted, 会执行create,beforeCreate

生命周期

new Vue()

Init Events(事件已经ok了) & Lifecycle

beforeCreate(不要修改data里的数据)

Init injections & reactivity

created(数据操作)

Has 'el' option ? No when vm.$mount(el) is called

YES Has 'template' option ?

有template属性:

解析render

render(h) {
 console.log('render function invoked')
 return h('div', {}, this.text)
}

Waiting for update signal form WDS...

undefined "beforeCreate"
undefined "created"
 
 "beforeMount" render function invoked  
0
 "mounted"

Yes:有 Compile template into render function

No:没有 Compile el's  outerHTML as template

beforeMount

Create vm.$el and replace 'el' with it

mounted

Mounted(实例创建完成)

beforeDestroy

Teardown watchers, child components and event listeners

destroyed

renderError

打包正式上线不会调用的,开发的时候会使用

renderError (h, err) {
 return h('div', {}, err.stack)
}

正式开发环境,收集线上的错误

errorCaptured() {
 
}

vue里面的data绑定到template

watch监听到某个一数据的变化,指定某个操作,(服务器使用)

Vue的原生指令

Vue的组件 render function

render (createElement) {
 return createElement('comp-one', {
  ref: 'comp'
 }, [
  createElement('span', {
   ref: 'span'
  }, this.value)
 ])
}

render (createElement) {
 return createElement('div', {
  style: this.style,
  on: {
   click: () => { this.$emit('click') }
  }
 }, this.$slots.default)
}

Vue-Router && Vuex

import Router from 'vue-router'

import routers from './routes'

exports default () => {
 return new Router({
  routers,
  mode: 'history',
  // base: '/base/',
  linkActiveClass: 'active-link', // 子集
  linkExactActiveClass: 'exact-active-link', // 准确目标
  scrollBehavior (to, from, savedPosition) {
   if (savedPosition) {
    return savedPosition
   } else {
    return { x: 0, y: 0 }
   }
  },
  // parseQuery (query) {
  // },
  // stringifyQuery (obj) {
  // }
 })
}

transition


 
this.$route
// path: '/app/:id',  to="/app/123"
fullPath: '/app/123'
hash: ""
matched: [{}]
meta: {title:''}
name: 'app'
params: {id: '123'}
path: '/app/123'
query: {}
// routes.js
{
 path: '/app/:id',
 props: true,
 component: Todo,
 name: 'app',
 meta: {
  title: 'this is app',
  description: 'asdasd'
 }
}

// todo.vue

props: ['id'],
mounted () {
 console.log(this.id)
}
{
 path: '/login',
 components: {
  default: Login,
  a: Todo
 }
}

Vue-router 导航守卫

import createRouter from './config/router'

Vue.use(VueRouter)

const router = createRouter()

router.beforeEach((to, from, next) => {
 // 做登录验证操作
 console.log('before each invoked')
 // next()
 if (to.fullPath === '/app') {
  next('/login') // 如果没有登录的话跳转到登录页面 next({ path: '/login' })
 } else {
  next() // 符合条件
 }
})

router.beforeResolve((to, from, next) => {
 console.log('before resolve invoked')
 next()
})

router.afterEach((to, from) => {
 console.log('after each invoked')
})
// routes.js

{
 path: '/app',
 beforeEnter (to, from, next) {
  console.log('app route before enter')
  next() // 只有当点击进入,才会调用
 }
}

// before each invoked
// app route before enter
// before resolve invoked
// after each invoked
// todo.vue
export default {
 beforeRouteEnter (to, from, next) {
  console.log('todo before enter')
  next()
 },
 
 beforeRouteUpdate (to, from, next) {
  console.log('todo update enter')
  next()
 }, 
 
 beforeRouteLeave (to, from, next) {
  console.log('todo leave enter')
  next()
 },

}

离开:

todo leave enter
before each invoked
before resolve invoked
after each invoked

进入:

before each invoked
app route before enter
todo before enter
before resolve invoked
after each invoked

Vuex集成

import Vuex from 'vuex'

const store = new Vuex.Store({
 state: {
  count: 0
 },
 mutations: {
  updateCount (state, num) {
   state.count = num
  }
 }
})

export default store

服务端渲染

import createRouter from './config/router'
import createStore from './store/store'

Vue.use(VueRouter)
Vue.use(Vuex)

const router = createRouter()
const store = createStore()

Vuex 中 state 和 getters

// store.js
import Vuex from 'vuex'
import defaultState from './state/state'
import mutations from './mutations/mutations'
import getters from './getters/getters'

export default () => {
 return new Vuex.Store({
  state: defaultState,
  mutations,
  getters
 })
}
// state/state.js
export default {
 count: 0,
 firstName: 'dada',
 lastName: 'dada'
}
// mutations/mutations.js
export default {
 updateCount (state, num) {
  state.count = num
 }
}

getters

// getters/getters.js =========== computed
export default {
 fullName(state) {
  return `${state.firstName} ${state.lastName}`
 }
}
// app.vue
computed: {
 count () {
  return this.$store.state.count
 },
 
 fullName () {
  return this.$store.getters.fullName
 }
}

快速使用

import {
 mapState,
 mapGetters
} from 'vuex'

computed: {
 // ...mapState(['count']),
 // ...mapState({
 //  counter: 'count'
 // }),
 ...mapState({
  counter: (state) => state.count
 }),
 ...mapGetters(['fullName'])
}

Vuex 中 mutation 和 action

// 开发环境 store.js
const isDev = process.env.NODE_ENV === 'development'

export default () => {
 return new Vuex.Store({
  strict: isDev,
  state: defaultState,
  mutations,
  getters
 })
}
// actions/actions.js
// dispatch 触发 actions 的
// 异步
export default {
 updateCountAsync (store, data) {
  setTimeout(() => {
   store.commit('updateCount', data.num)
  }, data.time)
 } 
}
// store.js
import Vuex from 'vuex'
import defaultState from './state/state'
import mutations from './mutations/mutations'
import getters from './getters/getters'
import actions from './actions/actions'

export default () => {
 return new Vuex.Store({
  state: defaultState,
  mutations,
  getters,
  actions
 })
}
import {
 mapState,
 mapGetters,
 mapActions,
 mapMutations
} from 'vuex'

mounted () {
 this.updateCountAsync({
  num: 5,
  time: 2000
 })
}

// mapActions mapMutations 操作
methods: {
 ...mapActions(['updateCountAsync']),
 ...mapMutations(['updateCount'])
}

Vuex 中的模块

// app.vue
mounted () {
 this['a/updateText']('123')
}

methods: {
 ...mapActions(['updateCountAsync']),
 ...mapMutations(['updateCount', 'a/updateText'])
}

computed: {
 ...mapState({
  counter: (state) => state.count,
  textA: state => state.a.text
 }),
 ...mapGetters(['fullName', 'a/textPlus'])
 textA () {
  return this.$store.state.a.text
 }
}

// store.js
modules {
 a: {
  namespaced: true
  state: {
   text: 1
  },
  mutations: {
   updateText (state, text) {
    console.log('a.state', state)
    state.text = text
   }
  },
  getters: {
   textPlus (state, getters, rootState) {
    return state.text + rootState.count + rootState.b.text
   }
  },
  actions: {
   add ({ state, commit, rootState }) {
    commit('updateText', rootState.count) // 全局找{ root: true }
    // commit('updateCount', rootState.count, { root: true }) // 全局找{ root: true }
   }
  }
 },
 
 b: {
  state: {
   text: 2
  },
  actions: {
   testAction ({ commit }) {
    commit('a/updateText', 'test text', { root: true })
   }
  }
 }
}

store.hotUpdate({})

Vuex 中的 API

// index.js
const router = createRouter()
const store = createStore()

store.registerModule('c', {
 state: {
  text: 3
 }
})

// 监听这个值的变化
store.watch((state) => state.count + 1, (newCount) => {
 console.log(newCount)
})

// 订阅
store.subscribe((mutation, state) => {
 console.log(mutation.type) // 调用哪个mutation
 console.log(mutation.payload) // mutation 接收的参数 传入的值
})

store.subscribeAction((action, state) => {
 console.log(action.type)
 console.log(action.payload)
})

store.unregisterModule('c')
// store.js
export default () => {
 const store = new Vuex.Store({
  strict: isDev,
  state: defaultState,
  mutations,
  getters,
  actions,
  plugins: [
   (store) => {
    console.log('my plugin invoked')
   }
  ]
 })
}

// my plugin invoked
// before each invoked
// before resolve invoked
// after each invoked

服务端渲染构建流程

访问服务端渲染页面:webpack server compiler -> nodejs server 3333端口

  1. 纯前端渲染:webpack dev server 8000 端口

  2. 访问服务端渲染页面:webpack server compiler server 创建 server bundle -> nodejs server 3333端

npm i vue -D // devDependencies

npm i vue -S // dependencies

npm i vue-server-renderer

npm i koa-router -S

npm i axios -S

server 服务端渲染

const koa = require('koa')
const app = new Koa()
const isDev = process.env.NODE_ENV = 'development'

dev-ssr.js

const Router = require('koa-router')
const axios = require('axios')
const MemoryFS = require('memory-fs')
const webpack = require('webpack')
const VueServerRenderer = require('vue-server-render')

组件开发

notification 通知


// index.js
// 全局
import Notification from './notification.vue'
import notify from './function'

export default (Vue) => {
 Vue.component(Notification.name, Notification)
 Vue.prototype.$notify = notify
}
// func-notification.js
import Notification from './notification.vue'

export default {
 extends: Notification,
 computed: {
  style() {
   return {
    position:; 'fixed',
    right: '20px',
    bottom: `${this.verticalOffset}px`
   }
  }
 },
 mounted () {
  this.createTimer()
 },
 methods: {
  createTimer() {
   if (this.autoClose) {
    this.timer = setTimeout(() => {
     this.visible = false
    }, this.autoClose)
   }
  },
  clearTimer () {
   if (this.timer) {
    clearTImeout(this.timer)
   }
  },
  afterEnter() {
   this.height = this.$el.offsetHeight
  }
 },
beforeDestory () {
 this.clearTimer()
},
 data() {
  return {
   verticalOffset: 0,
   autoClose:  3000,
   height: 0,
   visible: false
  }
 }
}
// function.js
import Vue from 'vue'
import Component from './func-notification'

const NotificationConstructor = Vue.extend(Component)

const instances = []
let seed = 1 // 组件id的

const removeInstance = (instance) => {s
 if (!instance) return
 const len = instances.length
 const index = instances.findIndex(inst => instance.id === inst.id)

 instance.splice(index, 1)

 if (len <= 1) return
 const removeHeight = instance.vm.height
 for (let i = index; i < len - 1; i++) {
  instances[i].verticalOffset = parseInt(instances[i].verticalOffset) - removeHeight - 16
 }
}

const notify = (options) => {
 if (Vue.prototype.$isServer) return
 
 const {
  autoClose,
  ...rest
 } = options
 const instance = new NotificationConstructor({
  // propsData: options
  propsData: {
   ...rest
  },
  data: {
   autoClose: autoClose === undefined ? 3000 : autoClose
  }
 })

 const id = `notification_${seed++}`
 instance.id = id
 instance.vm = instance.$mount() // 节点有了,div有了

 document.body.appendChild(instance.vm.$el)
 instance.vm.visible = true

 let verticalOffset = 0
 instances.forEach(item => {
  verticalOffset += item.$el.offsetHeight + 16
 })
 verticalOffset += 16
 instance.verticalOffset = verticalOffset
 instances.push(instance)
 
 instance.vm.$on('close', () => {
  removeInstance(instance)
  document.body.removeChild(instance.vm.$el)
  instance.vm.$destroy()
 })
 instance.vm.$on('close', () => {
  instance.vm.visible = false
 })
 return instance.vm
}
// "precommit": "npm run lint-fix",

部署

https://github.com/webVueBlog/Vue-Vue-Router-Vuex-SSR

ok!

你可能感兴趣的:(vue,gwt,dom,react,comet)