Vue-Vue-Router-Vuex-SSR
- Vue+Webpack project flow setup
- Vue+ VUE-Router +Vuex project architecture
Server side rendering
The current front-end framework is pure client rendering, (when requesting 🤴 website, the returned HTML is no content), there is no way to SEO, white screen time is long. The content will not be displayed until the JS load is complete and the execution is complete.
Server-side rendering solves these problems.
Webpack upgrade note ⚠️ : 1. Version change 2. Configuration change 3. Plug-in changes
Vue – loader configuration
const isDev = process.env.NODE_ENV === 'development'
Copy the code
// vue-loader.config.js
const docsLoader = require.resolve('./doc-loader')
module.exports = (isDev) = > {
return {
preserveWhitespace: true.// Remove the space after it
extractCSS: !isDev, // Package to a CSS file separately
cssModules: {},
// hotReload: false, // generated based on environment variables
loaders: {
'docs': docsLoader,
},
preLoader: {},}}Copy the code
module.exports = (isDev) = > {
return {
preserveWhitespace: true.extractCSS: !isDev,
cssModules: {},}}Copy the code
CSS module configuration
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}}}Copy the code
The installation uses ESLint and EditorConfig along with preCommit
npm i eslint eslint-config-standard eslint-plugin-standard eslint-plugin-promise eslint-plugin-import eslint-plugin-node
Copy the code
Create eslintrc.
{
"extends": "standard"
}
Copy the code
npm i eslint-plugin-html
Copy the code
// package.json
"script": {
"clean": "rimraf dist"."lint": "eslint --ext .js --ext .jsx --ext .vue client/"."build": "npm run clean && npm run build:client".// Automatic repair
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue client/"
"precommit": "npm run lint-fix",}Copy the code
npm i webpack -D
// webpack 4
npm uninstall webpack webpack-dev-server webpack-merge -D
/ / webpack upgrade
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
Copy the code
webpack.config.base.js
const config = {
mode: process.env.NODE_ENV, // development || production
target: 'web',}Copy the code
npm i eslint-loader babel-eslint
Copy the code
Some points of Vue
import Vue from 'vue'
const div = document.createElement('div')
document.body.appendChild(div)
new Vue({
el: div,
template: '<div>this is content</div>'
})
// webpack.config.practice.js
resolve: {
alias: {
'vue': path.join(__dirname, '.. /node_modules/vue/dist/vue.esm.js')}}Copy the code
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)
Copy the code
VUE instance
- Vue instance creation and functions
- Properties of the Vue instance
- Vue instance method
import Vue from 'vue'
const app = new Vue({
// el: '#root',
template: '<div ref="div">{{text}}</div>'.data: {
text: 0
}
})
app.$mount('#root')
setInterval(() = >{
// app.text += 1
// app.$options.data += 1 cannot be used
app.$data.text += 1 // It can be used
},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 node/component instance
console.log(app.$isServer) // Server render
app.$options.render = (h) = > {
return h('div', {}, 'new render function')}Copy the code
props: {
filter: {
type: String.required: true
},
todos: {
type: Array.required: true}}Copy the code
watch
const app = new Vue({
// el: '#root',
template: '<div ref="div">{{text}}</div>'.data: {
text: 0
},
watch: {
text(newText, oldText) {
console.log('${newText}:${oldText}')
}
}
})
app.$mount('#root')
Copy the code
const unWatch = app.$watch('text', (newText, oldText){
console.log(`${nextText}:${oldText}`)})setTimeout(() = >{
unWatch() // Cancel after 2 seconds
}, 2000)
Copy the code
Event listening:
app.$on('test'.() = > {
console.log('test emited')})// Trigger the event
app.$emit('test')
app.$on('test'.(a, b) = > {
console.log('test emited ${a} ${b}')})// Trigger the event
app.$emit('test'.1.2)
Copy the code
$once only listens to app once.$once('test'.(a, b) = > {
console.log('test emited ${a} ${b}')})Copy the code
ForceUpdate forces the component to render once
Nonresponsive:
// The value is changing, but does not result in re-rendering
data: {
text: 0.obj: {}}setInterval(() = > {
app.obj.a = app.text
app.$forceUpdate()
},1000)
Copy the code
Rendering all the time will slow down your performance
Name of some property on some object, and give it a value:
let i = 0
setInterval(() = >{
i++
app.$set(app.obj, 'a', i) // Change an attribute value on an object to give it a value
},1000)
Copy the code
vm.$nextTick([callback])
Wait until the DOM node is rendered. Vue calls callback on the next update
Postponing the callback until after the next DOM update loop, using it immediately after modifying the data, and then waiting for DOM updates is the same as the global method Vue. NextTick, except that the callback’s this is automatically bound to the instance calling it.
New in 2.1.0, return a Promise in an environment that does not provide a callback and supports Promise. Pay attention to the polyfill.
Vue life cycle
new Vue({
el: '#root'.template: '<div>{{text}}</div>'.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() {
// Bubbles up, and the formal environment can be used}})Copy the code
undefined 'beforeCreate'
undefined 'created'
<div id="root"></div> "beforeMount"
<div>0</div> "mounted"
template: `<div> name: {{name}} </div>`
computed: {
name() {
return `The ${this.firstName} The ${this.lastName}`
}
}
<li v-for="(item, index) in arr"> </li><li v-for="(val, key) in obj"></li>V-model. number v-model.trim trim <div><input type="radio" value="one" v-model="pick"/>
<input type="radio" value="two" v-model="pick"/>
</div>
Copy the code
Define the components
import Vue from 'vue'
const compoent = {
template: '<div>This is compoent</div>'
}
// Vue.component('CompOne', compoent)
new Vue({
components: {
CompOne: compoent
},
el: '#root'.template: '<comp-one></comp-one>'
})
/ / an error
const compoent = {
template: '<div>{{text}}</div>'.data: {
text: 123
}
}
[vue warn] the "data" option should be a function that returns a per-instance value in component definitions.
Copy the code
props
/ / child component
const compoent = {
props: {
active: Boolean.propOne: String
},
template: `
{{propOne}}
see me if active
`.data() {
return {
text: 0}}}// Parent component pass
<comp-one :active="true" prop-one="text1"></comp-one>
Copy the code
It is not allowed to modify this.propOne=’inner content’ inside a component,
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"
Copy the code
// The child component triggers props modification
/ / child component
methods: {
handleChange() {
this.$emit('change')}}// Subcomponent props
props: ['active'.'propOne'].props: {
active: {
type: Boolean.required: true.default: true}}props: {
active: {
// type: Boolean,
// required: true,
validator(value) {
return typeof value === 'boolean'}}}Copy the code
const CompVue = Vue.extend(compoent)
new CompVue({
el: '#root'.propsData: {
propOne: 'xxx'}})Copy the code
🌰
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}}
`
})
Copy the code
🌰 chestnuts input
/ / child component
handleInput (e) {
this.$emit('input', e.target.value)
}
/ / the parent component. :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)
}
}
}
Copy the code
Attribute ✍ ️
slot
const component = {
template: `
`,
data () {
return {
style: {
width: '200px';
height: '200px';
border: '1px solid #aaa'
}
}
}
}
<comp-one>
<span slot="header"> this is header </span>
<span slot="body"> this is body </span>
</comp-one>
Copy the code
slot-scope
Special 🌰
template: `
`.// Parent component - a variable used inside the component
<comp-one>
<span slot-scope="props"> {{props.value}} {{props.aaa}} </span>
</comp-one>
Copy the code
provide inject
provide : {
yeye: this.value: this.value, / / error
},
provide() {
const data = {}
Object.defineProperty(data, 'value', {
get: () = > this.value,
enumerable: true
})
return {
yeye: this,
data
// value: this.value}}/ / child component
inject: ['yeye'.'data']
template: '<div>child component: {{data.value}}</div>'
Copy the code
new Vue, beforeCreate, created, beforeMount, mounted
Without EL, no beforeMount, mounted is mounted
$mount(‘#root’) is executed if a const app is used
setInterval(() = > {
app.text = app.text += 1
},1000)
// Active destruction
app.$destroy()
Copy the code
Components:
activated() {
/ / in the component
console.log(this.'activated')}deactivated() {
/ / in the component
console.log(this.'deactivated')}Copy the code
Changes:
el:
undefined 'beforeCreate'
undefined 'created'
<div id="root"></div> "beforeMount"
<div>0</div> "mounted"
Copy the code
Dom objects are stored in mounted. Data can be created in mounted
BeforeMount, Mounted, create, beforeCreate will not perform server rendering (because the server does not have a Dom environment, all databases do not have this content)
The life cycle
new Vue()
Init Events(Events are ok) & Lifecycle
BeforeCreate (do not modify the data in the data)
Init injections & reactivity
Created (Data manipulation)
Has ‘el’ option ? No when vm.$mount(el) is called
- // el: ‘#root’
YES Has ‘template’ option ?
- // template: ‘
<div>
{{text}}</div>
‘
Has the template attribute:
Parsing the render
render(h) {
console.log('render function invoked')
return h('div', {}, this.text)
}
Waiting for update signal form WDS...
undefined "beforeCreate"
undefined "created"
<div id="root"></div> "beforeMount"
render function invoked
<div< / a > 0div>"mounted"
Copy the code
Yes: Compile template into render function is available
No: No Compile el’s outerHTML as template
beforeMount
Create vm.$el and replace ‘el’ with it
mounted
Mounted(The instance is created successfully)
- when data changes (beforeUpdate) Virtual DOM re-render and patch (updated)
- Walk when wm.$destroy() is called
beforeDestroy
Teardown watchers, child components and event listeners
destroyed
renderError
Package officially online will not call, development will use
renderError (h, err) {
return h('div', {}, err.stack)
}
Copy the code
Formally develop the environment to collect errors on the line
errorCaptured(){<! -- bubbles up -->}Copy the code
Data in vue is bound to template
Watch listens for a change in data, specifies an operation, (server use)
Vue’s native instruction
Vue component 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)
}
Copy the code
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'./ / the subset
linkExactActiveClass: 'exact-active-link'.// Accurate target
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { x: 0.y: 0}}},// parseQuery (query) {
// },
// stringifyQuery (obj) {
// }})}Copy the code
transition
<transition name="fade">
<router-view />
</transition>
Copy the code
this.$route
// path: '/app/:id', to="/app/123"
fullPath: '/app/123'
hash: ""
matched: [{}]
meta: {title:' '}
name: 'app'
params: {id: '123'}
path: '/app/123'
query: {}
Copy the code
// 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)
}
Copy the code
{
path: '/login'.components: {
default: Login,
a: Todo
}
}
Copy the code
Vue-router Navigation guard
import createRouter from './config/router'
Vue.use(VueRouter)
const router = createRouter()
router.beforeEach((to, from, next) = > {
// Perform login authentication
console.log('before each invoked')
// next()
if (to.fullPath === '/app') {
next('/login') Next ({path: '/login'})
} else {
next() // Meet the criteria
}
})
router.beforeResolve((to, from, next) = > {
console.log('before resolve invoked')
next()
})
router.afterEach((to, from) = > {
console.log('after each invoked')})Copy the code
// routes.js
{
path: '/app',
beforeEnter (to, from, next) {
console.log('app route before enter')
next() // It will only be called if you click enter}}// before each invoked
// app route before enter
// before resolve invoked
// after each invoked
Copy the code
// 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()
},
}
Copy the code
Leave:
todo leave enter
before each invoked
before resolve invoked
after each invoked
Copy the code
To:
before each invoked
app route before enter
todo before enter
before resolve invoked
after each invoked
Copy the code
Vuex integration
import Vuex from 'vuex'
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
updateCount (state, num) {
state.count = num
}
}
})
export default store
Copy the code
Server side rendering
import createRouter from './config/router'
import createStore from './store/store'
Vue.use(VueRouter)
Vue.use(Vuex)
const router = createRouter()
const store = createStore()
Copy the code
State and getters in Vuex
// 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
})
}
Copy the code
// state/state.js
export default {
count: 0.firstName: 'dada'.lastName: 'dada'
}
Copy the code
// mutations/mutations.js
export default {
updateCount (state, num) {
state.count = num
}
}
Copy the code
getters
// getters/getters.js =========== computed
export default {
fullName(state) {
return `${state.firstName} ${state.lastName}`}}Copy the code
// app.vue
computed: {
count () {
return this.$store.state.count
},
fullName () {
return this.$store.getters.fullName
}
}
Copy the code
Quick to use
import {
mapState,
mapGetters
} from 'vuex'
computed: {
/ /... mapState(['count']),
/ /... mapState({
// counter: 'count'
// }),. mapState({counter: (state) = >state.count }), ... mapGetters(['fullName'])}Copy the code
Mutation and Action in Vuex
// Development environment store.js
const isDev = process.env.NODE_ENV === 'development'
export default() = > {return new Vuex.Store({
strict: isDev,
state: defaultState,
mutations,
getters
})
}
Copy the code
// actions/actions.js
// dispatch triggers actions
/ / asynchronous
export default {
updateCountAsync (store, data) {
setTimeout(() = > {
store.commit('updateCount', data.num)
}, data.time)
}
}
Copy the code
// 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
})
}
Copy the code
import {
mapState,
mapGetters,
mapActions,
mapMutations
} from 'vuex'
mounted () {
this.updateCountAsync({
num: 5.time: 2000})}// open mutations
methods: {
...mapActions(['updateCountAsync']),
...mapMutations(['updateCount'])}Copy the code
Modules in 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})}}},b: {
state: {
text: 2
},
actions: {
testAction ({ commit }) {
commit('a/updateText'.'test text', { root: true })
}
}
}
}
store.hotUpdate({})
Copy the code
The API Vuex
// index.js
const router = createRouter()
const store = createStore()
store.registerModule('c', {
state: {
text: 3}})// Listen for changes in this value
store.watch((state) = > state.count + 1.(newCount) = > {
console.log(newCount)
})
/ / subscribe
store.subscribe((mutation, state) = > {
console.log(mutation.type) // which mutation to call
console.log(mutation.payload) // mutation Incoming value of the received parameter
})
store.subscribeAction((action, state) = > {
console.log(action.type)
console.log(action.payload)
})
store.unregisterModule('c')
Copy the code
// 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
Copy the code
Server-side render build process
Access the server rendering page: Webpack Server Compiler -> NodeJS Server 3333 port
- Pure front-end rendering: WebPack Dev Server 8000 port
- 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
Copy the code
Server Server rendering
const koa = require('koa')
const app = new Koa()
const isDev = process.env.NODE_ENV = 'development'
Copy the code
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')
Copy the code
Component development
Notification noticeCopy the code
<template>
<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>
<script>
export default {
name: 'Notification'.props: {
content: {
type: String.require: true
},
btn: {
type: String.default: 'off'}},data() {
return {
visible: true}},computed: {
style () {
return{}}},methods: {
handleClose (e) {
e.preventDefault() // The default event is blocked
this.$emit("close")},afterLeave() {
this.$emit("closed")},afterEnter(){},clearTimer(){},
createTimer(){},}}</script>
Copy the code
// index.js
/ / global
import Notification from './notification.vue'
import notify from './function'
export default (Vue) => {
Vue.component(Notification.name, Notification)
Vue.prototype.$notify = notify
}
Copy the code
<notification content="test notify">
Copy the code
// func-notification.js
import Notification from './notification.vue'
export default {
extends: Notification,
computed: {
style() {
return {
position:; 'fixed'.right: '20px'.bottom: `The ${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}}}Copy the code
// function.js
import Vue from 'vue'
import Component from './func-notification'
const NotificationConstructor = Vue.extend(Component)
const instances = []
let seed = 1 // Of the component 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 } = optionsconst instance = new NotificationConstructor({
// propsData: options
propsData: {
...rest
},
data: {
autoClose: autoClose === undefined ? 3000 : autoClose
}
})
const id = `notification_${seed++}`
instance.id = id
instance.vm = instance.$mount() // the node is available, div😊 is available
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
}
Copy the code
// "precommit": "npm run lint-fix",
Copy the code
The deployment of
Github.com/webVueBlog/…
ok!