Vue+Vue-Router+Vuex+SSR项目 关注 共
7128字,需浏览
15分钟
·
2022-02-22 01:22
Vue-Vue-Router-Vuex-SSR 服务端渲染 现在的前端框架是纯客户端渲染的,(请求🤴网站的时候,返回的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实例 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' "root"></div> "beforeMount"
0div> "mounted" template : ` name: {{name}}
` computed : { name() { return `${this .firstName} ${this .lastName} ` } }for="(item, index) in arr" >遍历数组</li>li> v-model.number 数字 v-model.trim 去掉空格
<input type ="radio" value ="one" v-model ="pick" /> <input type ="radio" value ="two" v-model ="pick" /> </div>
定义组件 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 } } }// 父组件传递 "true" prop-one="text1" ></comp-one>
不允许在组件内部修改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' } } } } <span slot ="header" > this is header span > <span slot ="body" > this is body span > </comp-one>
slot-scope 特殊🌰
template: ` `
,// 父组件 - 组件内部使用的变量 <span slot-scope ="props" > {{props.value}} {{props.aaa}} span > </comp-one>
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' "root"></div> "beforeMount"
0div> "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: '
{{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" "root"></div> "beforeMount" render function invoked
0div> "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
"fade"> <router-view /> </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端口
纯前端渲染:webpack dev server 8000 端口 访问服务端渲染页面: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 通知
<transition name ="fade" @after-leave ="afterLeave" @after-enter ="afterEnter" > <div class ="notification" :style ="style" v-show ="visible" @mouseenter ="clearTimer" @mouseleave ="createTimer" > <span class ="content" > {{content}}span > <a class ="btn" @click ="handleClose" > {{btn}}a > div > transition > </template>