Vue进阶之Vue RouterSSR

Vue Router&SSR

  • VueRouter
    • 前端路由
      • 模式
      • 路由的简单使用
      • 动态参数路由
      • 编程式导航
    • 手写一个vueRouter
      • router/core.js
      • router/index.js
      • main.js
      • App.vue
      • Home.vue
      • About.vue
    • vue-router原理
      • 总的package.json&packages
      • scripts
      • 重点:packages/router包
        • package.json
        • packages/router/src/index.ts
        • packages/router/src/router.ts
        • 不同模式之前的区别
          • hash - router/packages/router/src/history/hash.ts
          • history - router/packages/router/src/history/html5.ts
          • memory- router/packages/router/src/history/memory.ts
  • SSR
    • 浏览器渲染的过程
      • web 1.0时代
    • 手写一个小型SSR
      • package.json
      • server.js
      • app.js
      • client.js

VueRouter

现在都是 前后端分离的框架,由此有了SPA工程,从而有了路由,让前端去管理页面跳转

起初都是,后端路由=> url不同,都会向服务器发送请求,服务器返回一个html
现在:访问一个url,也都会向服务器拉一个页面回来,但是由于spa工程,服务器返回的都会在nginx层进行一个转发,都转发到根目录了

后端路由
优点:减轻前端压力,html由服务器拼接
缺点:用户体验差 php,jsp

前端路由

模式

  • hash模式
    localhost:8080/#home
    怎么监听呢?
    比如从#about到#home的过程
    路由的原理:首先监听到它的变化,然后进行组件切换
import { createRouter, createWebHashHistory } from 'vue-router'const router = createRouter({history: createWebHashHistory(),routes: [//...],
})
  • history模式
    localhost:8080/home
    localhost:8080/about
    通过history模式将应用发布上线后,将dist部署到服务器进行路由的切换发生404的问题
    原因:每个路径都会向服务器发送请求,服务器收到请求后发现没有路径,则会返回404,这就需要nginx来处理
location /{root /user/local/nginx/html/dist;index index.html index.htmtry_files $uri $uri/ /index.html
}
import { createRouter, createWebHistory } from 'vue-router'const router = createRouter({history: createWebHistory(),routes: [//...],
})

路由的简单使用

官网

// 1. 定义路由组件.
// 也可以从其他文件导入
const Home = { template: '<div>Home</div>' }
const About = { template: '<div>About</div>' }// 2. 定义一些路由
// 每个路由都需要映射到一个组件。
// 我们后面再讨论嵌套路由。
const routes = [{ path: '/', component: Home },{ path: '/about', component: About },
]// 3. 创建路由实例并传递 `routes` 配置
// 你可以在这里输入更多的配置,但我们在这里
// 暂时保持简单
const router = VueRouter.createRouter({// 4. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。history: VueRouter.createWebHashHistory(),routes, // `routes: routes` 的缩写
})// 5. 创建并挂载根实例
const app = Vue.createApp({})
//确保 _use_ 路由实例使
//整个应用支持路由。
app.use(router)   //use原理 this.$route this.$router vuex pinia插件都是作为vue的插件app.mount('#app')

组件内使用:
使用hooks的方式使用路由
原因:
现在在组合式api的setup中是不能使用this的方式,那么要是想使用router就得使用hooks的方式

//使用hooks的方式使用路由
import {useRouter} from 'vue-router'const router=useRouter()
//路由的跳转 
router.back()

动态参数路由

const User = {template: '<div>User</div>',
}// 这些都会传递给 `createRouter`
const routes = [// 动态字段以冒号开始{ path: '/users/:id', component: User },
]
// /users/johnny 和 /users/jolyne 这样的 URL 都会映射到同一个路由。
// 用冒号 : 表示。当一个路由被匹配时,它的 params 的值将在每个组件中以 this.$route.params 的形式暴露出来。
// 因此,我们可以通过更新 User 的模板来呈现当前的用户 IDconst User = {template: '<div>User {{ $route.params.id }}</div>',
}

有这么一种情况,针对不同id的用户,路由的前面路径是同样的,但是后面会针对不同人的id从而路径是不同的,这里会有一个问题,
当路径从 /users/1变到 /users/2的时候,组件还会重新执行之前的生命周期吗?
答案是 不会的
那么从1变成2的话,我们怎么拿到最新的id呢?
方式一:
在created中使用watch监听路由的变化

const User = {template: '<div>User {{ $route.params.id }}</div>',created(){this.$watch(()=>this.$route.params,()=>{console.log()})}
}

方式二:
通过导航守卫的方式

async beforeRouteUpdate(to,from){//to from
}

编程式导航

const username = 'eduardo'
// 我们可以手动建立 url,但我们必须自己处理编码
router.push(`/user/${username}`) // -> /user/eduardo
// 同样
router.push({ path: `/user/${username}` }) // -> /user/eduardo
// 如果可能的话,使用 `name` 和 `params` 从自动 URL 编码中获益
router.push({ name: 'user', params: { username } }) // -> /user/eduardo
// `params` 不能与 `path` 一起使用
router.push({ path: '/user', params: { username } }) // -> /user

手写一个vueRouter

  1. 如何监测 url 发生变化
  2. 如何改变 url 不引起页面刷新
  3. 要支持hash和history这两种模式
  4. router-link和router-view底层的实现

hash:
当hash值变化的时候,会触发hashchange发生变化,除过hashchange,popstate也能监听到hash值发生变化
history:
通过popstate的变化,监听到值的变化

但是通过push和replace是不会出发popstate的事件的

在这里插入图片描述

  • src
    • components
      • Home.vue
      • About.vue
    • router
      • index.js
      • core.js
    • App.vue
    • main.js

router/core.js

let Vue =null
class HistoryRoute{constructor() {this.current=null}
}
class VueRouter{constructor(options) {this.mode = options.mode||'hash';  //默认是hash模式this.routes = options.routes || []this.routesMap = this.createMap(options.routes)// map// {path:Component,path2:Component2}this.history=new HistoryRoute()this.init()}init() {if (this.mode === "hash") {//url:http://localhost:8080/#/home location.hash="#/home"location.hash ? '' : (location.hash = "/")window.addEventListener('load', () => {this.history.current = location.hash.slice(1)  //#号不要了})window.addEventListener('hashchange', () => {this.history.current = location.hash.slice(1)})// 这两个都是可以的// window.addEventListener('popstate', () => {//     this.history.current=location.hash.slice(1)// })} else {// history// popstate load location.pathnamelocation.pathname ? "" : (location.pathname = "/")window.addEventListener('load', () => {this.history.current=location.pathname})window.addEventListener('popstate', () => {this.history.current=location.pathname})}}createMap(routes) {return routes.reduce((acc, cur) => {acc[cur.path] = cur.componentreturn acc}, {})}
}// Vue提供的install方法将插件接入到vue生态中
VueRouter.install = function (v) {// 这里需要处理 router-view router-link this.$router访问路由实例 this.$route访问访问路由对象Vue = vVue.mixin({beforeCreate() {console.log("this", this);if (this.$options && this.$options.router) {// 根节点// $router已经挂载到根实例上this._root = thisthis._router = this.$options.routerVue.util.defineReactive(this, 'xxx', this._router.history);} else {// 非根组件this._root=this.$parent&&this.$parent._root}Object.defineProperty(this, '$router', {get() {return this._root._router}})Object.defineProperty(this, '$route', {get() {return this._root._router.history.current }})}})Vue.component('router-link', {props: {to:String},render(h) {// 每个组件都有一个_self来获取当前组件实例let mode = this._self._root._router.modelet to = mode === 'hash' ? `#${this.to}` : this.toreturn h('a',{attrs:{href:to}},this.$slots.default)}})Vue.component('router-view', {render(h) {let current = this._self._root._router.history.currentlet routeMap = this._self._root._router.routesMapreturn h(routeMap[current])}})
}export default VueRouter

其他代码的补充:

router/index.js

// import VueRouter from 'vue-router'
import VueRouter from './core.js'
import Vue from 'vue'
Vue.use(VueRouter)const routes = [{name:"home",path: "/home",component:()=>import("../components/Home.vue")},{name:"about",path:"/about",component:()=>import('../components/About.vue')}
]export default  new VueRouter({mode: "hash",routes
})

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = falsenew Vue({router,  //这里会对router进行初始化,那么this.$options就会有routerrender: h => h(App),
}).$mount('#app')

App.vue

<template><div id="app"><router-link to="/home">Home</router-link> |<router-link to="/about">About</router-link><hr><router-view></router-view></div>
</template><script>export default {name: 'App',
}
</script><style>
</style>

Home.vue

<template><div><h1>home</h1></div>
</template><script>
export default {data() {return {};},methods: {},
}
</script>
<style scoped>
</style>

About.vue

<template><div><h1>about</h1></div>
</template><script>
export default {data() {return {};},methods: {},
}
</script>
<style scoped>
</style>

请添加图片描述

vue-router原理

vue-router源码
这里看的是 vue3的router原理,
vue3的router github
是pnpm+node_modules这样一个解决方案

总的package.json&packages

在这里插入图片描述
为了便于对packages文件夹下的包进行一个管理,因此在packages中包含了很多命令

  • release 发布
  • size 对当前体积大小进行检查
  • build 打包 :pnpm run -r build 这里 -r 的意思是 递归,递归去执行下面所有子包的build指令
    多包工程的好处是:在外层的脚本中执行里面所有的脚本,这样的话,就可以方便对多个包进行管理,将相似的包放到一个工程当中,对相似的包进行统一的一个管理
    在这里插入图片描述

scripts

  • check-size.mjs:对当前的大小进行一个check及压缩
  • release.mjs:发布
  • verifyCommit:git commit的规范校验
    在这里插入图片描述

重点:packages/router包

package.json

在这里插入图片描述

  1. version:版本
  2. main:index.js 主入口为index.js
  3. unpkg:最终包cnd资源的路径,访问的是dist/vue-router-global.js资源的内容
    vue3创建的项目安装vue-router后,在node_modules的vue-router中能根据路径找到需要的资源
    在这里插入图片描述
  4. module:需要的是mjs类型的资源文件
  5. types:需要的是ts类型的文件
  6. exports:导出的文件内容
  7. files:最终打包之后的产物有哪些
    在这里插入图片描述

vue3创建的项目安装vue-router后,在node_modules的vue-router中的资源内容有哪些都是files来限制的
在这里插入图片描述

  1. scripts:
    在这里插入图片描述
  • dev:jest的测试用例
  • build:执行当前的整体的构建
    基于rollup进行打包的,基本上很多基础库用的都是rollup进行打包的,他打包后的产物会更小
    rollup配置:
    在这里插入图片描述
    最终的vue-router会打包成这么几种产物,根据当前不同的规范,引用不同的文件内容
  • build:dts 执行当前所有ts文件的构建
  • build:playground 执行playground文件的构建
  • build:e2e:执行端到端的测试
  • build:size 执行大小的check
  • test:xxxx 测试的用例
packages/router/src/index.ts
  1. 几种模式分别在主入口进行导出,history,memory,hash模式
  2. 当前的公共方法,paseQuery
  3. 创建router实例的createRouter方法
  4. 全局组件RouterLink和RouterView
  5. 通过hooks的方式提供的useRouter和useRoute

createRouterMatcher中进行相关的路由的匹配,规则的定义这些事情
在这里插入图片描述
在这里插入图片描述

先从主入口找到 createRouter函数
在这里插入图片描述

packages/router/src/router.ts

createRouter方法
在这里插入图片描述
router的install方法:

  1. 组件的全局注入
    在这里插入图片描述
    RouterView:
    通过props传入name和route,vue3使用setup传入值
    在这里插入图片描述
  2. 组件通过router,route的访问
    接下来看 this.$router 和 this.$route 是怎么实现的:
    在这里插入图片描述
  3. 初始化的逻辑

在这里插入图片描述
在这里插入图片描述

  1. 通过provide进行全局存储的能力,可以使得再setup中获取到router,route 在这里插入图片描述
    在这里插入图片描述
    再回到之前的使用上面:
//使用hooks的方式使用路由
import {useRouter,useRoute} from 'vue-router'const router=useRouter()
const route=useRoute()
//路由的跳转 
router.back()
  1. 卸载
    在这里插入图片描述
不同模式之前的区别
hash - router/packages/router/src/history/hash.ts

hash和history的区别点并不多,开始是对base进行处理,后面都直接调用用webhistory,对事件的监听,事件方法的处理进行了统一,只是对路径做了区别
hash和history内部都是通过popstate来监听的
在这里插入图片描述

history - router/packages/router/src/history/html5.ts

基本结构

  1. 创建基础location

let createBaseLocation = () => location.protocol + ‘//’ + location.host

  1. 创建当前location

function createCurrentLocation

  1. 使用history的监听

function useHistoryListeners

  1. state的处理

function buildState

  1. 导出webHistory

export function createWebHistorynormalizeBase

webHistory:

export function createWebHistory(base?: string): RouterHistory {// base的规范化base = normalizeBase(base)// 1.创建 vue router 的history对象const historyNavigation = useHistoryStateNavigation(base)// 2.创建 vue router 监听器const historyListeners = useHistoryListeners(base,historyNavigation.state,historyNavigation.location,historyNavigation.replace)function go(delta: number, triggerListeners = true) {if (!triggerListeners) historyListeners.pauseListeners()history.go(delta)}// 组装routerHistory对象const routerHistory: RouterHistory = assign({// it's overridden right afterlocation: '',base,go,createHref: createHref.bind(null, base),},historyNavigation,historyListeners)// 3. 添加location的劫持Object.defineProperty(routerHistory, 'location', {enumerable: true,get: () => historyNavigation.location.value,})// 4. 添加state的劫持Object.defineProperty(routerHistory, 'state', {enumerable: true,get: () => historyNavigation.state.value,})// 返回整个router history对象return routerHistory
}
import {RouterHistory,NavigationCallback,NavigationType,NavigationDirection,HistoryState,ValueContainer,normalizeBase,createHref,HistoryLocation,
} from './common'
import {computeScrollPosition,_ScrollPositionNormalized,
} from '../scrollBehavior'
import { warn } from '../warning'
import { stripBase } from '../location'
import { assign } from '../utils'type PopStateListener = (this: Window, ev: PopStateEvent) => any// 创建基础location
let createBaseLocation = () => location.protocol + '//' + location.hostinterface StateEntry extends HistoryState {back: HistoryLocation | nullcurrent: HistoryLocationforward: HistoryLocation | nullposition: numberreplaced: booleanscroll: _ScrollPositionNormalized | null | false
}/*** Creates a normalized history location from a window.location object* @param base - The base path* @param location - The window.location object*/
// 创建当前location
function createCurrentLocation(base: string,location: Location
): HistoryLocation {const { pathname, search, hash } = location// allows hash bases like #, /#, #/, #!, #!/, /#!/, or even /folder#endconst hashPos = base.indexOf('#')if (hashPos > -1) {let slicePos = hash.includes(base.slice(hashPos))? base.slice(hashPos).length: 1let pathFromHash = hash.slice(slicePos)// prepend the starting slash to hash so the url starts with /#if (pathFromHash[0] !== '/') pathFromHash = '/' + pathFromHashreturn stripBase(pathFromHash, '')}const path = stripBase(pathname, base)return path + search + hash
}// 使用history的监听
function useHistoryListeners(base: string,historyState: ValueContainer<StateEntry>,currentLocation: ValueContainer<HistoryLocation>,replace: RouterHistory['replace']
) {let listeners: NavigationCallback[] = []let teardowns: Array<() => void> = []// TODO: should it be a stack? a Dict. Check if the popstate listener// can trigger twicelet pauseState: HistoryLocation | null = null// 接收最新的state数据const popStateHandler: PopStateListener = ({state,}: {state: StateEntry | null}) => {const to = createCurrentLocation(base, location)  //获取到新的location的信息const from: HistoryLocation = currentLocation.value //之前的const fromState: StateEntry = historyState.valuelet delta = 0if (state) {// 目标路由state不为空时,更新currentLocation和historyState缓存currentLocation.value = tohistoryState.value = state// ignore the popstate and reset the pauseState// 暂停监控时,中断跳转并重置pauseStateif (pauseState && pauseState === from) {pauseState = nullreturn}delta = fromState ? state.position - fromState.position : 0} else {replace(to)}// Here we could also revert the navigation by calling history.go(-delta)// this listener will have to be adapted to not trigger again and to wait for the url// to be updated before triggering the listeners. Some kind of validation function would also// need to be passed to the listeners so the navigation can be accepted// call all listeners// 发布跳转事件,将location,跳转类型,跳转距离等信息返回给所有注册的订阅者,// 通知监听发布路由变化的这些地方,谁进行了事件的监听,则在这里做派发listeners.forEach(listener => {listener(currentLocation.value, from, {delta,type: NavigationType.pop,direction: delta? delta > 0? NavigationDirection.forward: NavigationDirection.back: NavigationDirection.unknown,})})}// 暂停当前的监听function pauseListeners() {pauseState = currentLocation.value}// 监听function listen(callback: NavigationCallback) {// set up the listener and prepare teardown callbackslisteners.push(callback)const teardown = () => {const index = listeners.indexOf(callback)if (index > -1) listeners.splice(index, 1)}teardowns.push(teardown)return teardown}function beforeUnloadListener() {const { history } = windowif (!history.state) returnhistory.replaceState(assign({}, history.state, { scroll: computeScrollPosition() }),'')}// 销毁function destroy() {for (const teardown of teardowns) teardown()teardowns = []window.removeEventListener('popstate', popStateHandler)window.removeEventListener('beforeunload', beforeUnloadListener)}// 监听了popstate// set up the listeners and prepare teardown callbackswindow.addEventListener('popstate', popStateHandler)  //拿到最新的数据更新location的信息// TODO: could we use 'pagehide' or 'visibilitychange' instead?// https://developer.chrome.com/blog/page-lifecycle-api/// beforeunload 浏览器刷新,或者即将离开当前站点会触发 window.addEventListener('beforeunload', beforeUnloadListener, {passive: true,})return {pauseListeners,listen,destroy,}
}/*** Creates a state object*/
// state的处理
function buildState(back: HistoryLocation | null,current: HistoryLocation,forward: HistoryLocation | null,replaced: boolean = false,computeScroll: boolean = false
): StateEntry {return {back,current,forward,replaced,position: window.history.length,scroll: computeScroll ? computeScrollPosition() : null,}
}function useHistoryStateNavigation(base: string) {// 使用浏览器的history和locationconst { history, location } = window// 对其包装// private variablesconst currentLocation: ValueContainer<HistoryLocation> = {value: createCurrentLocation(base, location),    //通过createCurrentLocation方法获取当前location的信息}const historyState: ValueContainer<StateEntry> = { value: history.state }// build current history entry as this is a fresh navigationif (!historyState.value) {changeLocation(  currentLocation.value,{back: null,current: currentLocation.value,forward: null,// the length is off by one, we need to decrease itposition: history.length - 1,replaced: true,// don't add a scroll as the user may have an anchor, and we want// scrollBehavior to be triggered without a saved positionscroll: null,},true)}function changeLocation(to: HistoryLocation,state: StateEntry,replace: boolean): void {//接收到达的location和当前的数据/*** if a base tag is provided, and we are on a normal domain, we have to* respect the provided `base` attribute because pushState() will use it and* potentially erase anything before the `#` like at* https://github.com/vuejs/router/issues/685 where a base of* `/folder/#` but a base of `/` would erase the `/folder/` section. If* there is no host, the `<base>` tag makes no sense and if there isn't a* base tag we can just use everything after the `#`.*/const hashIndex = base.indexOf('#')// 进行url的拼接const url =hashIndex > -1? (location.host && document.querySelector('base')? base: base.slice(hashIndex)) + to: createBaseLocation() + base + totry {// BROWSER QUIRK// NOTE: Safari throws a SecurityError when calling this function 100 times in 30 seconds// 去调用浏览器本身的history能力,将url和state传入historyhistory[replace ? 'replaceState' : 'pushState'](state, '', url)historyState.value = state} catch (err) {if (__DEV__) {warn('Error with push/replace State', err)} else {console.error(err)}// Force the navigation, this also resets the call countlocation[replace ? 'replace' : 'assign'](url)}}// 对push和replace操作都不会触发popStatefunction replace(to: HistoryLocation, data?: HistoryState) {// 先进行数据的组装const state: StateEntry = assign({},history.state,buildState(historyState.value.back,// keep back and forward entries but override current positionto,historyState.value.forward,true),data,{ position: historyState.value.position })// 拿到最新的数据给到changeLocationchangeLocation(to, state, true)// 更新下当前location变量currentLocation.value = to}// push('/home') {path:'home',name:'home'}function push(to: HistoryLocation, data?: HistoryState) {// Add to current entry the information of where we are going// as well as saving the current position// 对要处理的信息进行合并const currentState = assign({},// use current history state to gracefully handle a wrong call to// history.replaceState// https://github.com/vuejs/router/issues/366historyState.value,history.state as Partial<StateEntry> | null,{forward: to,scroll: computeScrollPosition(),//记录当前的位置信息,便于返回的时候能重回到之前的位置})if (__DEV__ && !history.state) {warn(`history.state seems to have been manually replaced without preserving the necessary values. Make sure to preserve existing history state if you are manually calling history.replaceState:\n\n` +`history.replaceState(history.state, '', url)\n\n` +`You can find more information at https://router.vuejs.org/guide/migration/#Usage-of-history-state`)}// push和replace都会调用changeLocation来改变当前的位置信息,修改后的信息currentState.current和currentState传入changeLocation(currentState.current, currentState, true)const state: StateEntry = assign({},buildState(currentLocation.value, to, null),{ position: currentState.position + 1 },data)changeLocation(to, state, false)currentLocation.value = to}// 最终返回一个location,statereturn {location: currentLocation,state: historyState,push,replace,}
}/*** Creates an HTML5 history. Most common history for single page applications.** @param base -*/
export function createWebHistory(base?: string): RouterHistory {// base的规范化base = normalizeBase(base)// 1.创建 vue router 的history对象const historyNavigation = useHistoryStateNavigation(base)// 2.创建 vue router 监听器const historyListeners = useHistoryListeners(base,historyNavigation.state,historyNavigation.location,historyNavigation.replace)function go(delta: number, triggerListeners = true) {if (!triggerListeners) historyListeners.pauseListeners()history.go(delta)}// 组装routerHistory对象const routerHistory: RouterHistory = assign({// it's overridden right afterlocation: '',base,go,createHref: createHref.bind(null, base),},historyNavigation,historyListeners)// 3. 添加location的劫持Object.defineProperty(routerHistory, 'location', {enumerable: true,get: () => historyNavigation.location.value,})// 4. 添加state的劫持Object.defineProperty(routerHistory, 'state', {enumerable: true,get: () => historyNavigation.state.value,})// 返回整个router history对象return routerHistory
}
memory- router/packages/router/src/history/memory.ts

这个历史记录的主要目的是处理 SSR,由于SSR是不能访问 location,document
因此,history是在内存当中模拟当前location的行为,使用队列的形式去模拟浏览器针对当前路由的行为

export function createMemoryHistory(base: string = ''): RouterHistory {let listeners: NavigationCallback[] = []// queue来模拟栈let queue: HistoryLocation[] = [START]let position: number = 0base = normalizeBase(base)// 对行为入栈,出栈等等进行处理// setLocation 相当于push,更改当前location数据,set会加1,进行入栈的操作function setLocation(location: HistoryLocation) {position++if (position !== queue.length) {// we are in the middle, we remove everything from here in the queuequeue.splice(position)}queue.push(location)}function triggerListeners(to: HistoryLocation,from: HistoryLocation,{ direction, delta }: Pick<NavigationInformation, 'direction' | 'delta'>): void {const info: NavigationInformation = {direction,delta,type: NavigationType.pop,}for (const callback of listeners) {callback(to, from, info)}}const routerHistory: RouterHistory = {// rewritten by Object.definePropertylocation: START,// TODO: should be kept in queuestate: {},base,createHref: createHref.bind(null, base),// 针对replace对queue位置进行出栈处理replace(to) {// remove current entry and decrement positionqueue.splice(position--, 1)setLocation(to)},push(to, data?: HistoryState) {setLocation(to)},listen(callback) {listeners.push(callback)return () => {const index = listeners.indexOf(callback)if (index > -1) listeners.splice(index, 1)}},destroy() {listeners = []queue = [START]position = 0},go(delta, shouldTrigger = true) {const from = this.locationconst direction: NavigationDirection =// we are considering delta === 0 going forward, but in abstract mode// using 0 for the delta doesn't make sense like it does in html5 where// it reloads the pagedelta < 0 ? NavigationDirection.back : NavigationDirection.forwardposition = Math.max(0, Math.min(position + delta, queue.length - 1))if (shouldTrigger) {triggerListeners(this.location, from, {direction,delta,})}},}// 劫持了location,最终返回queue的position位置Object.defineProperty(routerHistory, 'location', {enumerable: true,get: () => queue[position],})if (__TEST__) {// @ts-expect-error: only for testsrouterHistory.changeURL = function (url: string) {const from = this.locationqueue.splice(position++ + 1, queue.length, url)triggerListeners(this.location, from, {direction: NavigationDirection.unknown,delta: 0,})}}return routerHistory
}

SSR

SSR server side render 服务端渲染
CSR client side render 客户端渲染

浏览器渲染的过程

请添加图片描述

web 1.0时代

服务端渲染 php jsp 这时候还没有前端开发
ajax的出现,然后是SPA,后面衍生 前后端分离,这时候就是客户端渲染CSR的阶段
交互的整体逻辑:后端返回一个功能html,前端通过js加载主机js请求完数据后再渲染页面内容,没有node层

SEO工程,涉及到爬虫,低级爬虫爬的只是html页面结构
SPA工程,div空的节点,没内容可以爬虫

因此,许多公司或团队要求使用SSR的原因是,
1.希望自己的官网能够被爬虫爬到,这样别人在百度搜索当中,就能提高优先级。
在一些官网项目中会用到SSR
2.解决首屏渲染的问题,SSR返回整个html,因此首屏渲染是比较快
让首屏加载的资源越少,节点更少,组件异步,则首屏渲染的更快,加载的更快
但是,中小厂 不具备这个能力,因为要搭建SSR的话,需要node层,这样要考虑比较多的事情。

使用SSR需要考虑的问题:

  • 代码复杂度增加
    • 多了一层node环境,访问不到locationdocument,很多基础包都需要兼容,比如自己的包,二方包,需要针对不兼容的地方做兼容,对第三方包就很难兼容了
  • 需要更多的服务器,需要更多的负载均衡,本来只需要将静态资源给客户端,但是现在需要返回一个完整的html给客户端,服务器增加了渲染html的需求,node层需要提前去获取数据,使得更高的io和更高的cpu的占用,在运维层面需要高要求
  • 工程化,需要多个端的构建,SSR,client

Vue官网对SSR的介绍

如果想针对Vue体系使用SSR,那么建议直接使用 Nuxt 方案,或者Vite SSR
在这里插入图片描述
在这里插入图片描述

大厂一般使用的是React的方案,并且都是自研的,小厂一般没有SSR的需求

手写一个小型SSR

package.json

{"name": "node-starter","version": "0.0.0","type": "module","dependencies": {"express": "^4.17.2","vue": "^3.2.26"}
}

server.js

import express from 'express';
import { renderToString } from 'vue/server-renderer';
import { createApp } from './app.js';const server = express();server.get('/', (req, res) => {const app = createApp();renderToString(app).then(html => {res.send(`<!DOCTYPE html><html><head><title>Vue SSR Example</title><script type="importmap">{"imports": {"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"}}</script><script type="module" src="/client.js"></script></head><body><div id="app">${html}</div></body></html>`);});
});server.use(express.static('.'));server.listen(3000, () => {console.log('ready');
});

app.js

import { createSSRApp } from 'vue';export function createApp() {return createSSRApp({data: () => ({ count: 1 }),template: `<button @click="count++">{{ count }}</button>`,});
}

client.js

import { createApp } from './app.js';createApp().mount('#app');

在这里插入图片描述

pnpm i

在这里插入图片描述

node server.js

请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/494304.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Springboot应用开发:配置类整理

目录 编写目的 一、线程池 1.1 setCorePoolSize 1.2 setMaxPoolSize 1.3 setQueueCapacity 1.4 setKeepAliveSeconds 1.5 setThreadNamePrefix 1.6 setRejectedExecutionHandler 1.7 示例代码 二、Durid数据库连接池 2.1 ServletRegistrationBean 2.2 FilterRegist…

JVM系列(十二) -常用调优命令汇总

最近对 JVM 技术知识进行了重新整理&#xff0c;再次献上 JVM系列文章合集索引&#xff0c;感兴趣的小伙伴可以直接点击如下地址快速阅读。 JVM系列(一) -什么是虚拟机JVM系列(二) -类的加载过程JVM系列(三) -内存布局详解JVM系列(四) -对象的创建过程JVM系列(五) -对象的内存分…

LightGBM分类算法在医疗数据挖掘中的深度探索与应用创新(上)

一、引言 1.1 医疗数据挖掘的重要性与挑战 在当今数字化医疗时代,医疗数据呈爆炸式增长,这些数据蕴含着丰富的信息,对医疗决策具有极为重要的意义。通过对医疗数据的深入挖掘,可以发现潜在的疾病模式、治疗效果关联以及患者的健康风险因素,从而为精准医疗、个性化治疗方…

STM32F407寄存器点灯

背景描述&#xff1a; 最近用32开发遇到问题不得不看寄存器了&#xff0c;就回顾了一下寄存器手册的查看方式和寄存器的使用方法&#xff1b; 上一次这么细致的记录还是在刚学习STM32的时候&#xff0c;之前觉得看寄存器手册以及配置寄存器是有点难度的事情&#xff0c;现在回头…

Cline 3.0发布:从AI编程助手到通用智能体平台的进化

引言 在人工智能快速发展的今天&#xff0c;开发者工具正在经历一场革命性的变革。作为VSCode生态中备受欢迎的AI编程助手&#xff0c;Cline迎来了具有里程碑意义的3.0版本更新。本次Cline 3.0更新不仅带来了用户呼声最高的自动审批功能&#xff0c;还通过一系列创新优化全面提…

【Jenkins】持久化

文章目录 持续集成CI持续部署CD部署部署到linux服务器 持续集成好处&#xff1a; 持续集成CI 持续集成&#xff08;Continuous integration&#xff0c;简称CI&#xff09;指的是频繁地&#xff08;一天多次&#xff09;将代码集成到主干。 持续集成的目的就是让产品可以快速…

Promise链式调用

Promise链式调用 上一篇我们实现了通过promise的方式实现获取国家基本信息&#xff0c;本次我们来使用promise链式调用来实现邻国的展现 首先&#xff0c;我们从第一个国家中获取到邻国的国家代码名称 const neighbour data[0].borders[0];然后我们通过fetch来获取邻国信息&a…

路由器的原理

✍作者&#xff1a;柒烨带你飞 &#x1f4aa;格言&#xff1a;生活的情况越艰难&#xff0c;我越感到自己更坚强&#xff1b;我这个人走得很慢&#xff0c;但我从不后退。 &#x1f4dc;系列专栏&#xff1a;网路安全入门系列 目录 路由器的原理一&#xff0c;路由器基础及相关…

2025系统架构师(一考就过):案例题之一:嵌入式架构、大数据架构、ISA

一、嵌入式系统架构 软件脆弱性是软件中存在的弱点(或缺陷)&#xff0c;利用它可以危害系统安全策略&#xff0c;导致信息丢失、系统价值和可用性降低。嵌入式系统软件架构通常采用分层架构&#xff0c;它可以将问题分解为一系列相对独立的子问题&#xff0c;局部化在每一层中…

重拾设计模式--状态模式

文章目录 状态模式&#xff08;State Pattern&#xff09;概述状态模式UML图作用&#xff1a;状态模式的结构环境&#xff08;Context&#xff09;类&#xff1a;抽象状态&#xff08;State&#xff09;类&#xff1a;具体状态&#xff08;Concrete State&#xff09;类&#x…

python使用pip进行库的下载

前言 现如今有太多的python编译软件&#xff0c;其库的下载也是五花八门&#xff0c;但在作者看来&#xff0c;无论是哪种方法都是万变不离其宗&#xff0c;即pip下载。 pip是python的包管理工具&#xff0c;无论你是用的什么python软件&#xff0c;都可以用pip进行库的下载。 …

【IMU:视觉惯性SLAM系统】

视觉惯性SLAM系统简介 相机&#xff08;单目/双目/RGBD)与IMU结合起来就是视觉惯性&#xff0c;通常以单目/双目IMU为主。 IMU里面有个小芯片可以测量角速度与加速度&#xff0c;可分为6轴(6个自由度)和9轴&#xff08;9个自由度&#xff09;IMU&#xff0c;具体的关于IMU的介…

Halcon例程代码解读:安全环检测(附源码|图像下载链接)

安全环检测核心思路与代码详解 项目目标 本项目的目标是检测图像中的安全环位置和方向。通过形状匹配技术&#xff0c;从一张模型图像中提取安全环的特征&#xff0c;并在后续图像中识别多个实例&#xff0c;完成检测和方向标定。 实现思路 安全环检测分为以下核心步骤&…

【蓝桥杯】43688-《Excel地址问题》

Excel地址问题 题目描述 Excel 单元格的地址表示很有趣&#xff0c;它可以使用字母来表示列号。比如&#xff0c; A 表示第 1 列&#xff0c; B 表示第 2 列&#xff0c; … Z 表示第 26 列&#xff0c; AA 表示第 27 列&#xff0c; AB 表示第 28 列&#xff0c; … BA 表示…

【C++读写.xlsx文件】OpenXLSX开源库在 Ubuntu 18.04 的编译、交叉编译与使用教程

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; ⏰发布时间⏰&#xff1a; 2024-12-17 …

大数据、人工智能、云计算、物联网、区块链序言【大数据导论】

这里是阿川的博客&#xff0c;祝您变得更强 ✨ 个人主页&#xff1a;在线OJ的阿川 &#x1f496;文章专栏&#xff1a;大数据入门到进阶 &#x1f30f;代码仓库&#xff1a; 写在开头 现在您看到的是我的结论或想法&#xff0c;但在这背后凝结了大量的思考、经验和讨论 这是目…

ffmpeg翻页转场动效的安装及使用

文章目录 前言一、背景二、选型分析2.1 ffmpeg自带的xfade滤镜2.2 ffmpeg使用GL Transition库2.3 xfade-easing项目三、安装3.1、安装依赖([参考](https://trac.ffmpeg.org/wiki/CompilationGuide/macOS#InstallingdependencieswithHomebrew))3.2、获取ffmpeg源码3.3、融合xf…

什么是3DEXPERIENCE SOLIDWORKS,它有哪些角色和功能?

将业界领先的 SOLIDWORKS 3D CAD 解决方案连接到基于单一云端产品开发环境 3DEXPERIENCE 平台。您的团队、数据和流程全部连接到一个平台进行高效的协作工作&#xff0c;从而能快速的做出更好的决策。 目 录&#xff1a; ★ 1 什么是3DEXPERIENCE SOLIDWORKS ★ 2 3DEXPERIE…

如何正确计算显示器带宽需求

1. 对显示器的基本认识 一个显示器的参数主要有这些&#xff1a; 分辨率&#xff1a;显示器屏幕上像素点的总数&#xff0c;通常用横向像素和纵向像素的数量来表示&#xff0c;比如19201080&#xff08;即1080p&#xff09;。 刷新率&#xff1a;显示器每秒钟画面更新的次数&…

leetcode212. 单词搜索 II

给定一个 m x n 二维字符网格 board 和一个单词&#xff08;字符串&#xff09;列表 words&#xff0c; 返回所有二维网格上的单词 。 单词必须按照字母顺序&#xff0c;通过 相邻的单元格 内的字母构成&#xff0c;其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一…