深入理解React19中任務(wù)調(diào)度器Scheduler的實(shí)現(xiàn)
Scheduler = 最小堆 + MessageChannel + 時(shí)間片檢查
它的目標(biāo)只有一個(gè):推進(jìn)任務(wù),但永遠(yuǎn)別餓死瀏覽器。
調(diào)度任務(wù)
React 19 最新 Scheduler 源碼**(packages/scheduler)** 中,普通任務(wù)(非 Immediate,同步優(yōu)先級(jí)之外的任務(wù))的完整調(diào)度鏈:
unstable_scheduleCallback
↓
requestHostCallback
↓
schedulePerformWorkUntilDeadline
↓
performWorkUntilDeadline
↓
flushWork
↓
workLoop
↓
shouldYieldToHost
↓
advanceTimers
Scheduler 本質(zhì)是一個(gè)“帶時(shí)間片控制的最小堆 + 宏任務(wù)驅(qū)動(dòng)循環(huán)”調(diào)度器。
一、調(diào)度的起點(diǎn):unstable_scheduleCallback
源碼位置(React 19):
/packages/scheduler/src/forks/Scheduler.js
let getCurrentTime = () => performance.now();
// 為所有任務(wù)維護(hù)了連個(gè)最小堆(每次從隊(duì)列里面取出來(lái)的都是優(yōu)先級(jí)最高(時(shí)間即將過(guò)期))
// timerQueue // 延時(shí)任務(wù)隊(duì)列(task.startTime > now) —— 按 startTime 排序(延遲最小的先看)
// taskQueue // 立即可執(zhí)行的任務(wù)(task.startTime <= now)—— 按 expirationTime 排序(最緊急的先執(zhí)行)
// Timeout 對(duì)應(yīng)的值
var IMMEDIATE_PRIORITY_TIMEOUT = -1;
var USER_BLOCKING_PRIORITY_TIMEOUT = 250;
var NORMAL_PRIORITY_TIMEOUT = 5000;
var LOW_PRIORITY_TIMEOUT = 10000;
var IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt; // 1073741823
/**
? * 調(diào)度任務(wù)的入口
? * ?@param? {*} priorityLevel 優(yōu)先級(jí)等級(jí)
? * ?@param? {*} callback 任務(wù)回調(diào)函數(shù)
? * ?@param? {*} options { delay: number } 該對(duì)象有 delay 屬性,表示要延遲的時(shí)間(決定 expirationTime)
? * ?@returns
? */
function unstable_scheduleCallback(priorityLevel, callback, options) {
// 獲取當(dāng)前的時(shí)間
var currentTime = getCurrentTime();
var startTime;
// 設(shè)置起始時(shí)間 startTime:如果有延時(shí) delay,起始時(shí)間需要添加上這個(gè)延時(shí),否則起始時(shí)間就是當(dāng)前時(shí)間
if (typeof options === "object" && options !== null) {
var delay = options.delay;
if (typeof delay === "number" && delay > 0) {
startTime = currentTime + delay;
} else {
startTime = currentTime;
}
} else {
startTime = currentTime;
}
var timeout;
// 根據(jù)傳入的優(yōu)先級(jí)等級(jí)來(lái)設(shè)置不同的 timeout
switch (priorityLevel) {
case ImmediatePriority:
timeout = IMMEDIATE_PRIORITY_TIMEOUT; // -1
break;
case UserBlockingPriority:
timeout = USER_BLOCKING_PRIORITY_TIMEOUT; // 250
break;
case IdlePriority:
timeout = IDLE_PRIORITY_TIMEOUT; // 1073741823
break;
case LowPriority:
timeout = LOW_PRIORITY_TIMEOUT; // 10000
break;
case NormalPriority:
default:
timeout = NORMAL_PRIORITY_TIMEOUT; // 5000
break;
}
// 接下來(lái)就計(jì)算出過(guò)期時(shí)間
// 只有 ImmediatePriority 任務(wù) 比當(dāng)前時(shí)間要早,其他任務(wù)都會(huì)不同程度的延遲
var expirationTime = startTime + timeout;
// 創(chuàng)建一個(gè)新的任務(wù)
var newTask = {
id: taskIdCounter++, // 任務(wù) id
callback, // 執(zhí)行任務(wù)回調(diào)函數(shù) export type Callback = boolean => ?Callback;
priorityLevel, // 任務(wù)的優(yōu)先級(jí)
startTime, // 任務(wù)開(kāi)始時(shí)間
expirationTime, // 任務(wù)的過(guò)期時(shí)間
sortIndex: -1, // 用于小頂堆優(yōu)先級(jí)排序,始終從任務(wù)隊(duì)列中拿出最優(yōu)先的任務(wù)
};
if (enableProfiling) {
newTask.isQueued = false;
}
if (startTime > currentTime) {
// 說(shuō)明這是一個(gè)延時(shí)任務(wù)
newTask.sortIndex = startTime;
// 將該任務(wù)推入到 timerQueue 的任務(wù)隊(duì)列中
push(timerQueue, newTask);
if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
// 說(shuō)明 taskQueue 里面的任務(wù)已經(jīng)全部執(zhí)行完畢,然后從 timerQueue 里面取出一個(gè)優(yōu)先級(jí)最高的任務(wù)作為此時(shí)的 newTask
if (isHostTimeoutScheduled) {
cancelHostTimeout();
} else {
isHostTimeoutScheduled = true;
}
// 如果是延時(shí)任務(wù),調(diào)用 requestHostTimeout 進(jìn)行延時(shí)任務(wù)的調(diào)度
requestHostTimeout(handleTimeout, startTime - currentTime);
}
} else {
// 說(shuō)明不是延時(shí)任務(wù)
newTask.sortIndex = expirationTime; // 設(shè)置了 sortIndex 后,在任務(wù)隊(duì)列里面進(jìn)行一個(gè)排序
push(taskQueue, newTask);
if (enableProfiling) {
markTaskStart(newTask, currentTime);
newTask.isQueued = true;
}
// 最終調(diào)用 requestHostCallback 進(jìn)行普通任務(wù)的調(diào)度
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true;
requestHostCallback();
}
}
// 向外部返回任務(wù)
return newTask;
}
它的職責(zé)是:
- 根據(jù)優(yōu)先級(jí)計(jì)算 timeout
- 構(gòu)造一個(gè) Task 對(duì)象
- 放入任務(wù)到對(duì)應(yīng)的最小堆
- 請(qǐng)求宿主開(kāi)始調(diào)度(requestHostCallback 普通任務(wù) / requestHostTimeout 延時(shí)任務(wù))
任務(wù)執(zhí)行時(shí)刻:
IMMEDIATE_PRIORITY_TIMEOUT -> currentTime -> USER_BLOCKING_PRIORITY_TIMEOUT -> IDLE_PRIORITY_TIMEOUT
二、普通任務(wù)調(diào)用 requestHostCallback
當(dāng)普通任務(wù)進(jìn)入 taskQueue 后,調(diào)用 requestHostCallback,然后調(diào)用 schedulePerformWorkUntilDeadline:
/**
? *
? * ?@param? ?{*}? callback 是在調(diào)用的時(shí)候傳入的 flushWork
? * requestHostCallback 這個(gè)函數(shù)沒(méi)有做什么事情,主要就是調(diào)用 schedulePerformWorkUntilDeadline
? */
function requestHostCallback() {
if (!isMessageLoopRunning) {
isMessageLoopRunning = true;
schedulePerformWorkUntilDeadline();// 實(shí)例化 MessageChannel 進(jìn)行后面的調(diào)度
}
}
let schedulePerformWorkUntilDeadline; // undefined
if (typeof localSetImmediate === 'function') {
// Node.js and old IE.
schedulePerformWorkUntilDeadline = () => {
localSetImmediate(performWorkUntilDeadline);
};
} else if (typeof MessageChannel !== 'undefined') {
// 大多數(shù)情況下,使用的是 MessageChannel
const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;
schedulePerformWorkUntilDeadline = () => {
port.postMessage(null);
};
} else {
// setTimeout 進(jìn)行兜底
schedulePerformWorkUntilDeadline = () => {
localSetTimeout(performWorkUntilDeadline, 0);
};
}
schedulePerformWorkUntilDeadline 根據(jù)不同的環(huán)境選擇不同的生成宏任務(wù)的方式。大多數(shù)都是 MessageChannel :
- 每一次調(diào)度推進(jìn)
- 都是一個(gè)新的宏任務(wù)
- 瀏覽器中間有機(jī)會(huì) paint
三、延時(shí)任務(wù)調(diào)用 requestHostTimeout & handleTimeout
React Scheduler 不直接使用 setTimeout,而是抽象成 HostConfig,可在不同環(huán)境實(shí)現(xiàn)。
requestHostTimeout(callback, ms) 這個(gè)函數(shù)會(huì)安排一個(gè)底層超時(shí)回調(diào)。
在瀏覽器環(huán)境下它一般等價(jià)于:
const timeoutID = setTimeout(callback, ms);
注意:這是 ?嚴(yán)格意義上的延時(shí)調(diào)度?,用于把 timerQueue 的任務(wù)喚醒進(jìn) taskQueue。
requestHostTimeout 實(shí)際上就是調(diào)用 setTimoutout,然后在 setTimeout 中,調(diào)用傳入的 handleTimeout。
注意:延時(shí)任務(wù)只需要“不會(huì)提前執(zhí)行”,而不需要“精準(zhǔn)執(zhí)行”,所以這里使用了 setTimeout,setTimeout 只是負(fù)責(zé)“喚醒” Scheduler,不負(fù)責(zé)精度和優(yōu)先級(jí)(expirationTime 控制)控制,鑒于負(fù)責(zé)延時(shí)和必須是宏任務(wù)的特性,這里使用 setTimeout 最合適。
React 的調(diào)度精度來(lái)自:MessageChannel + 時(shí)間片檢查 + expirationTime 。
handleTimeout 是真正被 setTimeout 調(diào)用的函數(shù):
function handleTimeout(currentTime) {
// 遍歷 timerQueue,將時(shí)間已經(jīng)到了的延時(shí)任務(wù)放入到 taskQueue
advanceTimers(currentTime);
if (!isHostCallbackScheduled) {
if (taskQueue.length > 0) {
// 采用調(diào)度普通任務(wù)的方式進(jìn)行調(diào)度
requestHostCallback(flushWork);
} else {
// 如果任務(wù)仍然是延時(shí),繼續(xù)設(shè)置 HostTimeout
const firstTimer = peek(timerQueue);
if (firstTimer !== null) {
const nextDelay = firstTimer.startTime - currentTime;
requestHostTimeout(handleTimeout, nextDelay);
}
}
}
}
四、performWorkUntilDeadline
這是 Scheduler 的“驅(qū)動(dòng)心跳”。
let startTime = -1;
const performWorkUntilDeadline = () => {
if (enableRequestPaint) {
needsPaint = false;
}
if (isMessageLoopRunning) {
const currentTime = getCurrentTime();
// 這里的 startTime 并非 unstable_scheduleCallback 方法里面的 startTime
// 而是一個(gè)全局變量,默認(rèn)值為 -1
// 用來(lái)測(cè)量任務(wù)的執(zhí)行時(shí)間,從而能夠知道主線程被阻塞了多久
startTime = currentTime;
let hasMoreWork = true;
try {
// flushWork 為任務(wù)中轉(zhuǎn),本質(zhì)上內(nèi)部繼續(xù)調(diào)用 workLoop 判斷任務(wù)執(zhí)行情況
hasMoreWork = flushWork(currentTime);
} finally {
if (hasMoreWork) {
// 上面剛剛講過(guò)的方法,根據(jù)不同的環(huán)境選擇不同的生成宏任務(wù)的方式(MessageChannel)
schedulePerformWorkUntilDeadline();
} else {
isMessageLoopRunning = false;
}
}
}
};
它做三件事:
- 設(shè)置本次調(diào)度的時(shí)間起點(diǎn)
- 調(diào)用 flushWork
- 如果還有任務(wù) → 再發(fā)一個(gè) MessageChannel
五、flushWork 和 workLoop 執(zhí)行任務(wù)循環(huán)
// flushWork 是個(gè)中轉(zhuǎn),它真正執(zhí)行的是 workLoop
function flushWork(initialTime) {
// ...
// 各種開(kāi)關(guān)、報(bào)錯(cuò)捕獲...
// 其實(shí)核心就是 workLoop
return workLoop(initialTime);
}
// 不斷從任務(wù)隊(duì)列中取出任務(wù)執(zhí)行
function workLoop(initialTime: number) {
// initialTime 開(kāi)始執(zhí)行任務(wù)的時(shí)間
let currentTime = initialTime;
// advanceTimers 是用來(lái)遍歷 timerQueue,判斷是否有已經(jīng)到期的任務(wù)
// 如果有,將這個(gè)任務(wù)放入到 taskQueue
advanceTimers(currentTime);
currentTask = peek(taskQueue);
while (currentTask !== null) {
if (!enableAlwaysYieldScheduler) {
if (currentTask.expirationTime > currentTime && shouldYieldToHost()) {
// 任務(wù)沒(méi)有過(guò)期,并且需要中斷任務(wù),歸還主線程
break;
}
}
// 返回任務(wù)本身,致使任務(wù)之后可以接著繼續(xù)執(zhí)行
const callback = currentTask.callback;
if (typeof callback === 'function') {
currentTask.callback = null;
currentPriorityLevel = currentTask.priorityLevel;
const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
if (enableProfiling) {
markTaskRun(currentTask, currentTime);
}
// 執(zhí)行任務(wù),其他判斷都是做“階段過(guò)程資料收集”,無(wú)需關(guān)注
const continuationCallback = callback(didUserCallbackTimeout);
currentTime = getCurrentTime();
if (typeof continuationCallback === 'function') {
currentTask.callback = continuationCallback;
if (enableProfiling) {
markTaskYield(currentTask, currentTime);
}
advanceTimers(currentTime);
return true;
} else {
if (enableProfiling) {
markTaskCompleted(currentTask, currentTime);
currentTask.isQueued = false;
}
if (currentTask === peek(taskQueue)) {
pop(taskQueue);
}
advanceTimers(currentTime);
}
} else {
pop(taskQueue);
}
// 執(zhí)行完,再?gòu)?taskQueue 中取出一個(gè)任務(wù)
currentTask = peek(taskQueue);
if (enableAlwaysYieldScheduler) {
if (currentTask === null || currentTask.expirationTime > currentTime) {
break;
}
}
}
// 如果任務(wù)不為空,那么還有更多的任務(wù),hasMoreTask 為 true
if (currentTask !== null) {
return true;
} else {
// taskQueue 這個(gè)隊(duì)列空了,那么我們就從 timerQueue 里面去看延時(shí)任務(wù)
const firstTimer = peek(timerQueue);
if (firstTimer !== null) {
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
}
// 沒(méi)有進(jìn)入上面的 if,說(shuō)明 timerQueue 里面的任務(wù)也完了,返回 false,外部的 hasMoreWork 拿到的就也是 false
return false;
}
}
任務(wù)是否可以被打斷?
shouldYieldToHost()
如果返回 true:
- 當(dāng)前宏任務(wù)結(jié)束
- 重新發(fā) MessageChannel
- 瀏覽器可以渲染
六、shouldYieldToHost —— 時(shí)間片控制核心
源碼核心:
function shouldYieldToHost() {
const timeElapsed = getCurrentTime() - startTime;
if (timeElapsed < frameInterval) {
return false;
}
return true;
}
其中:
frameInterval = 5ms
注意:
React 不等 16ms 一整幀
而是:
每 5ms 檢查一次
為什么?
- 預(yù)留給瀏覽器 layout + paint
- 預(yù)留給輸入事件
七、advanceTimers —— 延遲任務(wù)晉升機(jī)制
每次 workLoop 結(jié)束后,會(huì)調(diào)用 advanceTimers(currentTime); 。
它會(huì):
- 檢查 timerQueue
- 把到時(shí)間的任務(wù)移動(dòng)到 taskQueue
function advanceTimers(currentTime) {
// 取出 startTime <= currentTime 的 timer
let timer = peek(timerQueue);
while (timer !== null && timer.startTime <= currentTime) {
pop(timerQueue);
timer.sortIndex = timer.expirationTime;
push(taskQueue, timer);
timer = peek(timerQueue);
}
}
這保證:延遲任務(wù)不會(huì)餓死
八、Scheduler 與 Fiber 的關(guān)系
Scheduler 不知道 Fiber。
它只負(fù)責(zé):“什么時(shí)候調(diào)用 callback”
而 callback 通常是:
performConcurrentWorkOnRoot
那里面才是:
- beginWork
- completeWork
- commitRoot
九、任務(wù)拆分的思路來(lái)源
假設(shè)有這樣一段源碼:
unstable_scheduleCallback(NormalPriority, () => {
heavyWork(); // 10ms
});
如果 heavyWork() 是同步執(zhí)行 10ms:,Scheduler ?無(wú)法中斷它,?因?yàn)?JS 是單線程的,函數(shù)執(zhí)行過(guò)程中無(wú)法被搶占。
來(lái)看源碼核心邏輯:
const continuation = callback();
if (typeof continuation === 'function') {
currentTask.callback = continuation;
} else {
pop(taskQueue);
}
如果 callback 返回一個(gè)函數(shù),Scheduler 會(huì)把它當(dāng)成“任務(wù)的下一段”。
舉一個(gè)最小可理解示例:
// 假設(shè)這是一個(gè)“大”任務(wù)
function createBigTask(total) {
let i = 0;
function work() {
while (i < total) {
console.log(i++);
if (shouldStopManually()) {
return work; // ?? 返回自身,表示還有后續(xù)
}
}
return null; // 完成
}
return work;
}
調(diào)度:
unstable_scheduleCallback( NormalPriority, createBigTask(1000) );
執(zhí)行:
workLoop()
↓
執(zhí)行 work()
↓
執(zhí)行到一部分
↓
返回 work (continuation)
↓
Scheduler 保存 callback = work
↓
shouldYieldToHost() 為 true
↓
break
↓
下一幀繼續(xù)執(zhí)行
在 React 里,被拆分的任務(wù)不是“普通函數(shù)”,而是:performConcurrentWorkOnRoot。
它內(nèi)部會(huì)調(diào)用:workLoopConcurrent() 。
核心邏輯(簡(jiǎn)化版):
while (
workInProgress !== null &&
!shouldYield()
) {
performUnitOfWork(workInProgress);
}
performUnitOfWork 只做一個(gè) Fiber 節(jié)點(diǎn):
function performUnitOfWork(unit) {
const next = beginWork(unit);
if (next === null) {
completeUnitOfWork(unit);
}
return next;
}
也就是說(shuō):React 把整棵 Fiber 樹(shù)拆成一個(gè)個(gè)“節(jié)點(diǎn)單位”/“任務(wù)切片”。
更形象的可以表示為:
一個(gè)組件樹(shù):
App
├─ Header
├─ Content
│ ├─ List
│ └─ Sidebar
└─ Footer
會(huì)被拆成:
performUnitOfWork(App) performUnitOfWork(Header) performUnitOfWork(Content) performUnitOfWork(List) performUnitOfWork(Sidebar) performUnitOfWork(Footer)
每個(gè) Fiber 節(jié)點(diǎn)就是一個(gè)小任務(wù)。
回到 Scheduler 這段判斷:
if (
currentTask.expirationTime > currentTime &&
shouldYieldToHost()
) {
break;
}
當(dāng)時(shí)間片用完時(shí):break 。
此時(shí):
- workLoop 停止
- 任務(wù)還沒(méi)完成
- currentTask.callback 仍然是 performConcurrentWorkOnRoot
下一幀:
MessageChannel
↓
performWorkUntilDeadline
↓
flushWork
↓
workLoop
↓
繼續(xù)執(zhí)行 performConcurrentWorkOnRoot
延時(shí)任務(wù)并不會(huì)立刻進(jìn)入 taskQueue,而是先進(jìn)入 timerQueue,等待被“喚醒”然后再調(diào)度。
十、完整調(diào)度流程
下面是一個(gè)完整的流程圖:
unstable_scheduleCallback()
↓
是否延時(shí)? (startTime > now)
/ \
是 否
↓ ↓
push(timerQueue) push(taskQueue)
↓ ↓
requestHostTimeout(requestHostCallback)
↓ ↓
等待 Timer requestHostCallback
↓ ↓
timeout 到時(shí) MessageChannel
↓ ↓
handleTimeout performWorkUntilDeadline
↓ ↓
advanceTimers flushWork
↓ ↓
timerQueue → taskQueue
↓
workLoop
↓
shouldYieldToHost?
↓
是 否
notifyBrowserPaint 執(zhí)行 Callback
↓
callback 返回 continuation?
↓
是 否
更新 Task.callback pop(taskQueue)
↓
可能再次調(diào)度
示例理解
示例 1:普通任務(wù)(正常進(jìn)入隊(duì)列)
unstable_scheduleCallback(
NormalPriority,
() => console.log("normal")
);
步驟:
- now <= startTime (0 delay)
- 放入 taskQueue
- requestHostCallback(flushWork)
- MessageChannel 觸發(fā) performWorkUntilDeadline
- flushWork → workLoop
- 執(zhí)行 task
- taskQueue 空 → 調(diào)度結(jié)束
示例 2:延時(shí)任務(wù)(未來(lái)才執(zhí)行)
unstable_scheduleCallback(
NormalPriority,
() => console.log("delayed"),
{ delay: 1000 }
);
步驟:
t=0 ↓ startTime = now + 1000 push(timerQueue) requestHostTimeout(handleTimeout, 1000)
1s 后:
handleTimeout 被 setTimeout 調(diào)用
↓
advanceTimers
→ 取出 timerQueue 里所有 startTime <= 1s 的任務(wù)
→ 推到 taskQueue
taskQueue 變非空
↓
requestHostCallback(flushWork)
↓
MessageChannel 回調(diào)
↓
flushWork → workLoop → task 執(zhí)行
示例 3:延時(shí)任務(wù)到了中間又被打斷
unstable_scheduleCallback(
NormalPriority,
step1,
{ delay: 1000 }
);
function step1() {
console.log("step1");
return step2;
}
function step2() {
console.log("step2");
}
執(zhí)行過(guò)程:
t=0 → timerQueue 入隊(duì)
t=1000 → handleTimeout
→ advanceTimers → taskQueue
↓
MessageChannel
↓
workLoop 執(zhí)行 step1 延時(shí)任務(wù)
↓
step1 返回 continuation step2 延時(shí)任務(wù)
↓
此時(shí)
workLoop 檢查 shouldYieldToHost
如果 time片到 → 中斷
→ callback (continuation) 留在 taskQueue
→ requestHostCallback 執(zhí)行普通任務(wù)
下次繼續(xù)執(zhí)行 step2 延時(shí)任務(wù)
設(shè)計(jì)哲學(xué)
① 延時(shí)任務(wù)不能搶占瀏覽器渲染
如果立即調(diào)度:
setTimeout(() => {}), Promise.then(...)
React 可能會(huì)阻塞頁(yè)面渲染
所以必須分開(kāi):
先 timerQueue 等待
再 taskQueue 才跑
② 延時(shí)任務(wù)按照優(yōu)先級(jí)
即使 delay 到了:
expirationTime = startTime + timeout
如果更高優(yōu)先級(jí)任務(wù)進(jìn)入 taskQueue:
React 會(huì)先執(zhí)行高優(yōu)先級(jí)的。
delay 控制什么時(shí)候進(jìn)入 taskQueue,而 expirationTime 控制進(jìn)入 taskQueue 之后的優(yōu)先級(jí)排序。
③ extendable 繼續(xù)推進(jìn)任務(wù)
一個(gè)任務(wù)返回 continuation 時(shí):
currentTask.callback = continuation;
并且還可能繼續(xù) schedule。
到此這篇關(guān)于深入理解React19中任務(wù)調(diào)度器Scheduler的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)React任務(wù)調(diào)度器Scheduler內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React Hook用法示例詳解(6個(gè)常見(jiàn)hook)
這篇文章主要介紹了React Hook用法詳解(6個(gè)常見(jiàn)hook),本文通過(guò)實(shí)例代碼給大家介紹了6個(gè)常見(jiàn)hook,需要的朋友可以參考下2021-04-04
詳解使用React進(jìn)行組件庫(kù)開(kāi)發(fā)
本篇文章主要介紹了詳解使用React進(jìn)行組件庫(kù)開(kāi)發(fā),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-02-02
React Hooks獲取數(shù)據(jù)實(shí)現(xiàn)方法介紹
這篇文章主要介紹了react hooks獲取數(shù)據(jù),文中給大家介紹了useState dispatch函數(shù)如何與其使用的Function Component進(jìn)行綁定,實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-10-10
探討JWT身份校驗(yàn)與React-router無(wú)縫集成
這篇文章主要為大家介紹了JWT身份校驗(yàn)與React-router無(wú)縫集成的探討解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
淺析JS中什么是自定義react數(shù)據(jù)驗(yàn)證組件
我們?cè)谧銮岸吮韱翁峤粫r(shí),經(jīng)常會(huì)遇到要對(duì)表單中的數(shù)據(jù)進(jìn)行校驗(yàn)的問(wèn)題。這篇文章主要介紹了js中什么是自定義react數(shù)據(jù)驗(yàn)證組件,需要的朋友可以參考下2018-10-10
詳解React項(xiàng)目中eslint使用百度風(fēng)格
這篇文章主要介紹了React項(xiàng)目中eslint使用百度風(fēng)格,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09

