Vue+Vue-Router+Vuex+SSR项目
Vue-Vue-Router-Vuex-SSR
Vue+Webpack工程流搭建
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 -Dnpm 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 || productiontarget: '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.textapp.$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')}
}// 子组件propsprops: ['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) // Rootthis.$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
判断是否有el // el: '#root'
YES Has 'template' option ?
判断是否有 // template: '
{{text}}'
有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 invoked0 "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(实例创建完成)
when data changes (beforeUpdate) Virtual DOM re-render and patch (updated)
走 when wm.$destroy() is called
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.vueprops: ['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: truestate: {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) // 调用哪个mutationconsole.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端口
纯前端渲染:webpack dev server 8000 端口
访问服务端渲染页面:webpack server compiler server 创建 server bundle -> nodejs server 3333端
npm i vue -D // devDependenciesnpm i vue -S // dependenciesnpm i vue-server-renderernpm i koa-router -Snpm 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 通知 {{content}}{{btn}}
// 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) => {sif (!instance) returnconst len = instances.lengthconst index = instances.findIndex(inst => instance.id === inst.id)instance.splice(index, 1)if (len <= 1) returnconst removeHeight = instance.vm.heightfor (let i = index; i < len - 1; i++) {instances[i].verticalOffset = parseInt(instances[i].verticalOffset) - removeHeight - 16}
}const notify = (options) => {if (Vue.prototype.$isServer) returnconst {autoClose,...rest} = optionsconst instance = new NotificationConstructor({// propsData: optionspropsData: {...rest},data: {autoClose: autoClose === undefined ? 3000 : autoClose}})const id = `notification_${seed++}`instance.id = idinstance.vm = instance.$mount() // 节点有了,div😊有了document.body.appendChild(instance.vm.$el)instance.vm.visible = truelet verticalOffset = 0instances.forEach(item => {verticalOffset += item.$el.offsetHeight + 16})verticalOffset += 16instance.verticalOffset = verticalOffsetinstances.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!
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
