从理解路由到实现一套Router(路由)

前言

  • 学完本篇文章你将对React不同优先级任务调度有一个初步的认识。

前置知识

学习本片文章前需要知道浏览器一帧中会做哪些事情

  • 三部分:执行js、渲染页面、空闲时间。* 在我们调用setState的时候会创建一个更新任务,当然react内部也会有其他等级的任务。为了避免任务执行时间超过当前帧空闲时间,造成页面卡顿的现象。React利用调度器实现了了在空闲时间内按优先级执行任务。几个概念

  • work:一个任务,其count属性代表该任务包含count个子组件需要更新。* workList:任务队列,包含着React需要执行的所有任务。* perform:执行更新流程,当任务执行完毕后,执行schedule,开始下一轮调度。* schedule:调度器,从workList中获取一个高优先级任务,交给perform执行。* perform与schedule循环往复,实现整个任务调度。初识任务调度


下面例子中,创建了三个不同等级的任务,每个任务里面都有100个组件需要更新。当然,更新组件的等级是相同的,这里只是作为区分。

html

<style> #app {word-break: break-all;} </style>

<body><div id="app"></div><script src="./react优先级调度算法-基础版.js"></script>
</body> 

javascript

const workList = [];
const contetnBox = document.querySelector("#app");

let list = [{value: "低",},{value: "中",},{value: "高",},
]

list.forEach(item => {const btn = document.createElement("button");btn.innerText = item.value;contetnBox?.appendChild(btn);btn.onclick = () => {// 添加任务workList.unshift({ ...item, count: 100 })schedule()}
})

// 执行任务
const renderComponent = (content) => {// 更好的观察任务调度,做的延迟效果let i = 10000000;while (i) {i--;}const ele = document.createElement("span");ele.innerText = `${content}`;contetnBox?.appendChild(ele)
}

// 调度器
function schedule() {// 获取一个任务,并弹出任务队列const curWork = workList.pop();if (curWork) {// 执行更新流程perform(curWork)}
}

// 更新流程
function perform(work) {while (work.count) {// count代表这个任务中有count个组件需要更新work.count -= 1;renderComponent(work.value)}// 当前任务中的组件全部更新完后,继续执行调度schedule()
} 

效果图

在点击按钮后,会向任务队列workList中添加work。调度器开始工作,调度器从workList中获取一个任务,并弹出任务队列中。任务执行完后,调度器继续调度。schedule与perform循环调用,直至任务队列清空。

按优先级顺序执行任务

引入React调度器

引入该包的目的是使用一些方法辅助完成调度工作

项目结构

learn-schedule
├─ package-lock.json
├─ package.json
├─ public
│└─ index.html
└─ src └─ index.js 

导包

import {//空闲优先级unstable_IdlePriority as IdlePriority,//低优先级unstable_LowPriority as LowPriority,//用户阻塞优先级unstable_UserBlockingPriority as UserBlockingPriority,//普通优先级unstable_NormalPriority as NormalPriority,//立刻执行的优先级unstable_ImmediatePriority as ImmediatePrity,// 当某一个preform正在被调度,但是还没被执行时,可以使用该函数进行取消unstable_cancelCallback as cancelCallback,// 用于调度preform方法unstable_scheduleCallback as scheduleCallback,// 当前帧是否用尽了, 用尽了为true,此时需要中断任务unstable_shouldYield as shouldYield,// 返回当前正在调度的任务unstable_getFirstCallbackNode as getFirstCallbackNode,// unstable_scheduleCallback的返回值CallbackNode
} from "scheduler" 

添加变量

// 本次schedule进行时,正在调度的任务的优先级
// 设置初始值为undefined
let prevPriority = undefined; 

修改schedule方法

function schedule() {// 当前正在执行的调度任务const cbNode = getFirstCallbackNode();// 获取优先级最高的任务const curWork = workList.sort((node1, node2) => {return node1.priority - node2.priority;})[0]// 如果任务不存在,即任务队列为空if (!curWork) {return;}const { priority } = curWork;// 只有本次任务优先级 > 已经正在在执行的任务的优先级,才会中断正在执行的任务if (priority === prevPriority) {return;}// 此时本次的任务优先级 > 正在执行的任务优先级// 需要中断正在执行的任务if (cbNode) {cancelCallback(cbNode);}// 执行任务,以某个优先级来调度某个任务// 为什么要使用bind,因为scheduleCallback第二个参数是一个回调函数scheduleCallback(priority, perform.bind(null, curWork))
} 

修改work

// 本次schedule进行时,正在调度的任务的优先级
let prevPriority = undefined;

const workList = [];
const contetnBox = document.querySelector("#app");

let list = [{priority: IdlePriority,value: "低",},{priority: LowPriority,value: "中",},{priority: NormalPriority,value: "高",},
]

list.forEach(item => {const btn = document.createElement("button");btn.innerText = item.value;contetnBox?.appendChild(btn);btn.onclick = () => {// 添加任务workList.unshift({ ...item, count: 100 })schedule()}
}) 

修改perform方法

任务执行完的时候,将prevPriority制空

// 更新流程
function perform(work) {if (work.count === 0) {const workIndex = workList.indexOf(work)workList.splice(workIndex, 1)// 任务执行完的时候,将prevPriority制空prevPriority = undefined;}while (work.count) {work.count -= 1;renderComponent(work.value)}schedule()
} 

效果图

这个时候已经创建了三个不同优先级的任务,当点击多次低优先级任务后,再点击中优先级任务,会发现中优先级任务先被执行。此时已经完成了不同优先级的任务调度。

超出空闲时间时,任务可中断

上一个版本我们可以发现一个问题,当低优先级任务执行过程中,点击中优先级任务的时候,并没有马上中断低优先级的任务,而是等当前正在执行的低优先级任务执行完毕后,才执行中优先级任务。解决该问题的方法就是使perform可中断

修改perform

function perform(work) {// 当前任务是否是同步执行// ImmediatePrity是立即执行优先级,所以需要同步执行const isSync = work.priority === ImmediatePrity;// shouldYield判断浏览器当前帧是否剩余空闲时间while ((isSync || !shouldYield()) && work.count) {work.count -= 1;renderComponent(work.value)}if (work.count === 0) {const workIndex = workList.indexOf(work)workList.splice(workIndex, 1)// 任务执行完的时候,将prevPriority制空prevPriority = undefined;} else {prevPriority = work.priority;}//继续调度schedule()
} 

效果图

当多次点击低优先级任务的时候,再点击中优先级任务,会发现调度器立刻切换到中优先级任务执行,当中优先级任务执行完毕后,会接着执行低优先级任务,并且页面流畅渲染。

优化调度

到这里的时候,其实已经实现了不同优先级的任务调度,但是还有可优化的余地。

观察发现,当任务正在执行时碰到没有空闲时间用完,需要中断执行,这时候会再次执行schedule方法重新进行一系列的调度工作。这个时候会浪费一些性能,其实当没有更高优先级的任务时,我们可以不进行调度,直接再下一个空闲时间内继续执行当前的work。

添加全局变量

// 当前被调度的回调函数
let curCallback = null; 

修改schedule

在函数最后一行为curCallback赋值。curCallback是一个包裹了当前work的数据结构,后面会讲到。

// 调度器
function schedule() {// 当前正在执行的调度任务const cbNode = getFirstCallbackNode();// 获取优先级最高的任务const curWork = workList.sort((node1, node2) => {return node1.priority - node2.priority;})[0]// 如果任务不存在,即任务队列为空if (!curWork) {curCallback = null;return;}const { priority } = curWork;// 只有本次任务优先级 > 已经正在在执行的任务的优先级,才会中断正在执行的任务if (priority <= prevPriority) {return;}// 此时本次的任务优先级 > 正在执行的任务优先级// 需要中断正在执行的任务if (cbNode) {cancelCallback(cbNode);}// 执行任务,以某个优先级来调度某个任务// 为什么要使用bind,因为scheduleCallback第二个参数是一个回调函数curCallback = scheduleCallback(priority, perform.bind(null, curWork))
} 

修改perform

在perform执行的最后,需要重新获取一遍curCallback,此时会触发schedule的priority === prevPriority判断,当没有更高优先级时,前后两个的curCallback值相等时,此时就可以直接循环perform函数即可,由因为perform是当作回调函数传递给scheduleCallback的,而且当perform返回一个函数时,scheduleCallback会直接执行这个函数,而不会去执行其他调度相关的工作,减少了部分性能开支。

// 更新流程
function perform(work) {// 当前任务是否是同步执行// ImmediatePrity是立即执行优先级,所以需要同步执行const isSync = work.priority === ImmediatePrity;// shouldYield判断浏览器当前帧是否剩余空闲时间while ((isSync || !shouldYield()) && work.count) {work.count -= 1;renderComponent(work.value)}if (work.count === 0) {const workIndex = workList.indexOf(work)workList.splice(workIndex, 1)prevPriority = undefined;} else {prevPriority = work.priority;}//存储当前回调const prevCallback = curCallback//继续调度schedule()//获取新的回调const newCallback = curCallback// 当没有更高优先级的时候,直接走performif (prevCallback === newCallback) {return perform.bind(null, work)}
} 

补充:curCallback是什么

其实就是对当前work信息的一个封装

在React源码源码中长这样

type Task = {id: number,callback: Callback | null,priorityLevel: PriorityLevel,startTime: number,expirationTime: number,sortIndex: number,isQueued?: boolean,
}; 

在项目中打印

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue Router 提供了一套强大的导航守卫(Guard)系统,这些守卫可以在路由变化之前或之后执行自定义操作,允许开发者拦截、验证、修改或重定向路由过程。路由守卫主要分为以下几种类型: 1. **全局守卫** (Global Guards): - **beforeEach**: 在每次导航尝试之前被调用,可以阻止导航、改变目标URL或者提供一些默认参数。 - ** afterEach**: 在每次导航完成后被调用,无论是否发生导航失败。 2. **路由级守卫** (Route Guards): - **beforeEnter**: 只在进入特定路由时触发,用于验证或处理进入该路由的权限或状态。 - **enter**: 类似于beforeEnter,但它是异步的,因此可以用它来做一些需要时间的操作,如数据请求。 - **afterEnter**: 在进入路由并完成渲染后执行。 - **beforeLeave**: 当离开当前路由时触发,可以用来确认离开操作。 - **leave**: 异步的离开守卫,类似`afterEnter`。 3. **导航失败守卫** (Navigation Failure Guards): - **catch**: 当导航失败(例如,未找到匹配的路由)时触发。 你可以使用这些守卫来实现各种功能,比如身份验证、数据加载、错误处理等。为了更好地利用它们,你需要在Vue实例的`router.options`中配置守卫,或者在每个具体的路由对象上设置。例如: ```javascript // 全局守卫示例 router.beforeEach((to, from, next) => { // 检查用户是否已登录 if (!isAuthenticated()) { next('/login'); } else { next(); } }); // 路由级守卫示例 const router = new VueRouter({ routes: [ { path: '/private', component: PrivateComponent, beforeEnter: authenticateUser } ] }); ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值