react.js源碼二

三、調度Scheduler
scheduling(調度)是fiber reconciliation的一個過程,主要決定應該在何時做什么?在stack reconciler中,reconciliation是“一氣呵成”,對于函數來說,這沒什么問題,因為我們只想要函數的運行結果,但對于UI來說還需要考慮以下問題:
并不是所有的state更新都需要立即顯示出來,比如屏幕之外的部分的更新;
并不是所有的更新優先級都是一樣的,比如用戶輸入的響應優先級要比通過請求填充內容的響應優先級更高;
理想情況下,對于某些高優先級的操作,應該是可以打斷低優先級的操作執行的,比如用戶輸入時,頁面的某個評論還在reconciliation,應該優先響應用戶輸入。比如18版本里提示一些不安全的生命周期主要時它被打斷了可能會被執行多次。
所以理想狀況下reconciliation的過程應該是每次只做一個很小的任務,做完后能夠“喘口氣兒”,回到主線程看下有沒有什么更高優先級的任務需要處理,如果有則先處理更高優先級的任務,沒有則繼續執行(cooperative scheduling 合作式調度)。
當用戶操作時,調用setState,react會把當前的更新送入對應組件對應的update queue中。但是react并不會立即執行對比并修改DOM的操作。而是交給scheduler去處理。
scheduler會根據當前主線程的使用情況去處理這次update。為了實現這種特性,最開始考慮使用了requestIdelCallback API

總的來講,通常,客戶端線程執行任務時會以幀的形式劃分,大部分設備控制在30-60幀是不會影響用戶體驗;在兩個執行幀之間,主線程通常會有一小段空閑時間,requestIdleCallback可以在這個空閑期(Idle Period)調用空閑期回調(Idle Callback),執行一些任務。
在這里插入圖片描述
低優先級任務由requestIdleCallback處理;
高優先級任務,如動畫相關的由requestAnimationFrame處理;
requestIdleCallback可以在多個空閑期調用空閑期回調,執行任務;
requestIdleCallback方法提供deadline,即任務執行限制時間,以切分任務,避免長時間執行,阻塞UI渲染而導致掉幀;
但是由于requestIdleCallback有以下兩個問題就采用了messageChannel模擬實現了requestIdleCallback。
1)兼容性;
2)50ms 渲染問題;(可能在一些任務很長時這個回調不會執行)
|— task queue —|— micro task —|— raf —|— render —|— requestIdleCallback – -|
requestIdleCallback是宏任務,messageChannel也宏任務。
為什么沒有? generator ?因為它是有狀態的,無法從中間中斷。
為什么沒有? setTimeout ?因為setTimeout有4-5ms的延時。
模擬了requestIdleCallback行為:

/*** schedule —> 把我的任務放進一個隊列里,然后以某一種節奏進行執行;* */// task 的任務隊列
const queue = [];
const threshold = 1000 / 60;const transtions = [];
let deadline = 0;// 獲取當前時間, bi  date-now 精確
const now = () => performance.now(); // 時間 ,精確
// 從任務queue中,選擇第一個 任務 
const peek = arr => arr.length === 0 ? null : arr[0];// schedule —> 把我的任務放進一個隊列里,然后以某一種節奏進行執行;
export function schedule (cb) {queue.push(cb);startTranstion(flush);
}// 此時,是否應該交出執行權
function shouldYield() {return navigator.scheduling.isInputPending() || now() >= deadline;
}// 執行權的切換
function startTranstion(cb) {transtions.push(cb) && postMessage();
}// 執行權的切換
const postMessage = (() => {const cb = () => transtions.splice(0, 1).forEach(c => c());const { port1, port2 } = new MessageChannel();port1.onmessage = cb;return () => port2.postMessage(null);
})()// 模擬實現 requestIdleCallback 方法
function flush() {// 生成時間,用于判斷deadline = now() + threshold;let task = peek(queue);// 我還沒有超出 16.666ms 同時,也沒有更高的優先級打斷我while(task && !shouldYield()) {const { cb } = task;const next = cb();// 相當于有一個約定,如果,你這個task 返回的是一個函數,那下一次,就從你這里接著跑// 那如果 task 返回的不是函數,說明已經跑完了。不需要再從你這里跑了if(next && typeof next === "function") {task.cb = next;} else {queue.shift()}task = peek(queue);}// 如果我的這一個時間片,執行完了,到了這里。task && startTranstion(flush)
}

一旦reconciliation過程得到時間片,就開始進入work loop。work loop機制可以讓react在計算狀態和等待狀態之間進行切換。為了達到這個目的,對于每個loop而言,需要追蹤兩個東西:下一個工作單元(下一個待處理的fiber);當前還能占用主線程的時間。第一個loop,下一個待處理單元為根節點。
每個工作單元(fiber)執行完成后,都會查看是否還繼續擁有主線程時間片,如果有繼續下一個,如果沒有則先處理其他高優先級事務,等主線程空閑下來繼續執行
react17版本有時間切片ric,但是沒有使用。18版本里才使用了。
宏任務微任務執行示例

四、diff算法
react diff算法最好時是O(n), 最差的話,是 O(mn),而傳統的diff算法是O(n^3)。
react 是如何將 diff 算法的復雜度降下來的?
其實就是在算法復雜度、虛擬 dom 渲染機制、性能中找了?個平衡,react 采?了啟發式的算法,做了如下最優假設:
a. 如果節點類型相同,那么以該節點為根節點的 tree 結構,?概率是相同的,所以如果類型不同,可以直接「刪除」原節點,「插?」新節點;
b. 跨層級移動? tree 結構的情況?較少?,或者可以培養?戶使?習慣來規避這種情況,遇到這種情況同樣是采?先「刪除」再「插?」的?式,這樣就避免了跨層級移動
c. 同?層級的?元素,可以通過 key 來緩存實例,然后根據算法采取「插?」「刪除」「移動」的操作,盡量復?,減少性能開銷
d. 完全相同的節點,其虛擬 dom 也是完全?致的;

react為什么不去優化diff算法?
因為新版本下,diff算法不是約束性能瓶頸的問題了。

為什么要有key?
在?較時,會以 key 和 type 是否相同進??較,如果相同,則直接復制

vue diff算法和react diff算法相同/不同點:
共同點:
vue和diff算法,都是不進行跨層級比較,只做同級比較
不同點:
1.vue進行diff時,調用patch打補丁函數,一邊比較一邊給真實的dom打補丁,vue對比節點時,當節點元素類型相同,類名不同時,認為是不同的元素,刪除重新創建,而react認為是同類型的節點,進行修改操作
2.vue列表對比的時候,采用從兩端到中間的方式,舊集合和新集合兩端各存在兩個指針,兩兩進行比較,每次對比結束后,指針向隊列中間移動;react則是從左往右一次對比,利用元素的index和lastindex進行比較
3.當一個集合把最后一個節點移動到最前面,react會把前面的節點依次向后移動,而Vue只會把最后一個節點放在最前面,這樣的操作來看,Vue的diff性能是高于react的。

四、模擬實現react流程
react.js


const normalize = (children = []) => children.map(child => typeof child === 'string' ? createVText(child): child)export const NODE_FLAG = {EL: 1, // 元素 elementTEXT: 1 << 1
};
// El & TEXT  = 0const createVText = (text) => {return {type: "",props: {nodeValue: text + ""},$$: { flag: NODE_FLAG.TEXT }}
}const createVNode = (type, props, key, $$) => {return {type, props,key,$$,}
}export const createElement = (type, props, ...kids) => {props = props || {};let key = props.key || void 0;kids = normalize(props.children || kids);if(kids.length) props.children = kids.length === 1? kids[0] : kids;// 定義一下內部的屬性const $$ = {};$$.staticNode = null;$$.flag = type === "" ? NODE_FLAG.TEXT: NODE_FLAG.EL;return createVNode(type, props, key, $$)
}

path.js

import { mount } from "./mount";
import { diff } from './diff';function patchChildren(prev, next, parent) {// diff 整個的邏輯還是耗性能的,所以,我們可以先提前做一些處理。if(!prev) {if(!next) {// nothing} else {next = Array.isArray(next) ? next : [next];for(const c of next) {mount(c, parent);}}} else if (prev && !Array.isArray(prev)) {// 只有一個 childrenif(!next) parent.removeChild(prev.staticNode);else if(next && !Array.isArray(next)) {patch(prev, next, parent)} else {// 如果prev 只有一個節點,next 有多個節點parent.removeChild(prev.staticNode);for(const c of next) {mount(c, parent);}}} else diff(prev, next, parent);
}export function patch (prev, next, parent) {// type: 'div' -> 'ul'if(prev.type !== next.type) {parent.removeChild(prev.staticNode);mount(next, parent);return;}// type 一樣,diff props // 先不看 children const { props: { children: prevChildren, ...prevProps}} = prev;const { props: { children: nextChildren, ...nextProps}} = next;// patch Porpsconst staticNode = (next.staticNode = prev.staticNode);for(let key of Object.keys(nextProps)) {let prev = prevProps[key],next = nextProps[key]patchProps(key, prev, next, staticNode)}for(let key of Object.keys(prevProps)) {if(!nextProps.hasOwnProperty(key)) patchProps(key, prevProps[key], null, staticNode);}// patch Children !!!patchChildren(prevChildren,nextChildren,staticNode)}export function patchProps(key, prev, next, staticNode) {// style if(key === "style") {// margin: 0 padding: 10if(next) {for(let k in next) {staticNode.style[k] = next[k];}}if(prev) {// margin: 10; color: redfor(let k in prev) {if(!next.hasOwnProperty(k)) {// style 的屬性,如果新的沒有,老的有,那么老的要刪掉。staticNode.style[k] = "";}}}}else if(key === "className") {if(!staticNode.classList.contains(next)) {staticNode.classList.add(next);}}// eventselse if(key[0] === "o" && key[1] === 'n') {prev && staticNode.removeEventListener(key.slice(2).toLowerCase(), prev);next && staticNode.addEventListener(key.slice(2).toLowerCase(), next);} else if (/\[A-Z]|^(?:value|checked|selected|muted)$/.test(key)) {staticNode[key] = next} else {staticNode.setAttribute && staticNode.setAttribute(key, next);}
}

mount.js

import { patchProps } from "./patch";
import { NODE_FLAG } from "./react";export function mount(vnode, parent, refNode) {// 為什么會有一個 refNode?/**                   |* 假如: ul ->  li  li  li(refNode) */if(!parent) throw new Error('no container');const $$ = vnode.$$;if($$.flag & NODE_FLAG.TEXT) {// 如果是一個文本節點const el = document.createTextNode(vnode.props.nodeValue);vnode.staticNode = el;parent.appendChild(el);} else if($$.flag & NODE_FLAG.EL) {// 如果是一個元素節點的情況,先不考慮是一個組件的情況;const { type, props } = vnode;const staticNode = document.createElement(type);vnode.staticNode = staticNode;// 我們再來處理,children 和后面的內容const { children, ...rest} = props;if(Object.keys(rest).length) {for(let key of Object.keys(rest)) {// 屬性對比的函數patchProps(key, null, rest[key], staticNode);}}if(children) {// 遞歸處理子節點const __children = Array.isArray(children) ? children : [children];for(let child of __children) {mount(child, staticNode);}}refNode ? parent.insertBefore(staticNode, refNode) : parent.appendChild(staticNode);}}

diff.js

import { mount } from './mount.js'
import { patch } from './patch.js'export const diff = (prev, next, parent) => {let prevMap = {}let nextMap = {}// 遍歷我的老的 childrenfor (let i = 0; i < prev.length; i++) {let { key = i + '' } = prev[i]prevMap[key] = i}let lastIndex = 0// 遍歷我的新的 childrenfor (let n = 0; n < next.length; n++) {let { key = n + '' } = next[n]// 老的節點let j = prevMap[key]// 新的 childlet nextChild = next[n]nextMap[key] = n// 老的children      新的children// [b, a]           [c, d, a]  =>  [c, b, a]  --> c// [b, a]           [c, d, a]  =>  [c, d, b, a]  --> dif (j == null) {// 從老的里面,沒有找到。新插入let refNode = n === 0 ? prev[0].staticNode : next[n - 1].staticNode.nextSiblingmount(nextChild, parent, refNode)}else {// [b, a]           [c, d, a]  =>  [c, d, a, b]  --> a// 如果找到了,我 patch patch(prev[j], nextChild, parent)if (j < lastIndex) {// 上一個節點的下一個節點的前面,執行插入let refNode = next[n - 1].staticNode.nextSibling;parent.insertBefore(nextChild.staticNode, refNode)}else {lastIndex = j}}}// [b, a]           [c, d, a]  =>  [c, d, a]  --> bfor (let i = 0; i < prev.length; i++) {let { key = '' + i } = prev[i]if (!nextMap.hasOwnProperty(key)) parent.removeChild(prev[i].staticNode)}
}

render.js

import { mount } from "./mount";
import { patch } from "./patch";// step 1
// setTimeout(() => render(vnode, document.getElementById("app")))// step 2
// setTimeout(() => render(null, document.getElementById("app")),5000)export function render(vnode, parent) {let prev = parent.__vnode;if(!prev) {mount(vnode, parent);parent.__vnode = vnode;} else {if(vnode) {// 新舊兩個patch(prev, vnode, parent);parent.__vnode = vnode;} else {parent.removeChild(prev.staticNode)}} 
}

index.js

import { render } from "./render";
import { createElement } from "./react";// 用戶的開發:
// react / preact / vueconst vnode = createElement("ul",{id: "ul-test",className: "padding-20",style: {padding: "10px",},},createElement("li", { key: "li-0" }, "this is li 01")
);const nextVNode = createElement("ul",{style: {width: "100px",height: "100px",backgroundColor: "green",},},[createElement("li", { key: "li-a" }, "this is li a"),createElement("li", { key: "li-b" }, "this is li b"),createElement("li", { key: "li-c" }, "this is li c"),createElement("li", { key: "li-d" }, "this is li d"),]
);const lastVNode = createElement("ul",{style: {width: "100px",height: "200px",backgroundColor: "pink",},},[createElement("li", { key: "li-a" }, "this is li a"),createElement("li", { key: "li-c" }, "this is li c"),createElement("li", { key: "li-d" }, "this is li d"),createElement("li", { key: "li-f" }, "this is li f"),createElement("li", { key: "li-b" }, "this is li b"),]
);setTimeout(() => render(vnode, document.getElementById("app")))
setTimeout(() => render(nextVNode, document.getElementById("app")),6000)
setTimeout(() => render(lastVNode, document.getElementById("app")),8000)
console.log(nextVNode);

使用rollup進行編譯運行:
下載rollup插件,創建rollup.config.js文件

const livereload = require('rollup-plugin-livereload');
const serve = require('rollup-plugin-serve');module.exports = {input: './react/index.js',output: {file: './dist/bundle.js',format: "iife" // es, umd, amd, cjs,iife以script腳本加載執行},plugins: [livereload(),serve({openPage: "/public/index.html",port: 3020,contentBase:'./'})]
}// rollup -c // 我默認去找根目錄下的 rollup.config.js    -w 監聽文件變化,重新編譯。

執行 rollup -c // 我默認去找根目錄下的 rollup.config.js -w 監聽文件變化,重新編譯。
將輸入開始的文件,編譯打包到output目錄下。這樣就可以訪問對應端口查看頁面。

react 冒泡到fiberroot 而不是到root,是因為render函數可能調用多次,會導致錯亂。
react為什么實現合成事件,是因為如果寫很多監聽事件會導致性能下降,還有兼容性問題。

react18的新特性
React 18 中的重大更改僅限于幾個簡單的 API 更改,以及對 React 中多個行為的穩定性和一致性的一些改進,比較重要的一點是,不再支持 IE 瀏覽器。
1、客戶端渲染 API
帶有 createRoot() 的 root API,替換現有的 render() 函數,提供更好的人體工程學并啟用新的并發渲染特性。
2、自動批量處理
以下都是批量處理了,以優化性能并避免重渲染。但是之前的版本在settimeout里是會渲染兩次的。

const App = () => {const handleClick = () => {setA((a) => a + 1);setB((b) => b - 1);// Updates batched - single re-render};setTimeout(() => {setA((a) => a + 1);setB((b) => b - 1);// New (v18): Updates batched - single re-render}, 1000);// ...
};

3、并發渲染特性,比圖startansition等,它是基于任務優先級,時間分片實現的。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/212152.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/212152.shtml
英文地址,請注明出處:http://en.pswp.cn/news/212152.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

什么是CDN?用了CDN一定會更快嗎?

文章目錄 前言CDN是什么?CDN的工作原理為什么要加個CNAME那么麻煩&#xff1f;怎么知道哪個服務器IP里調用方最近&#xff1f; 回源是什么回源是什么&#xff1f;那還有哪些情況會發生回源呢&#xff1f; 怎么判斷是否發生回源用了CDN一定比不用的更快嗎&#xff1f;什么情況下…

光伏電站全貌

光伏電站 簡介 每一篇文章開篇我都會寫一個內容簡介&#xff0c;一來梳理自己的寫作思路&#xff0c;二來方便讀者整體了解文章寫作意圖和脈絡。本篇是新能源方面的開篇之作&#xff0c;我選取了介紹光伏電站基礎知識&#xff0c;首先我們要了解光伏電站基礎分類&#xff0c;然…

PHP基礎 - 運算符

算術運算符 運算符描述實例+加法$x = 2 + 2; echo $x;-減法$x = 5 - 3; echo $x;*乘法$x = 4 * 3; echo $x;/除法$x = 10 / 2; echo $x;%取余$x = 15 % 4; echo $x;++自增$x = 5; $x++; echo $x;--自減$x = 5; $x--; echo $x;算術運算符的使用場景: 1)加法運算符 +:用于將兩…

Copilot的11個新功能,你不能錯過!

我的新書《Android App開發入門與實戰》已于2020年8月由人民郵電出版社出版&#xff0c;歡迎購買。點擊進入詳情 文章目錄 1. PowerPoint2. Excel3. One Note4. Word5. 必應聊天現在變為Copilot6. GPT-4為Copilot聊天提供動力7. Microsoft Teams8. Outlook9. Copilot Studio10.…

磁盤存儲器

目錄 1.1 磁盤存儲器1.2 磁盤的性能指標1.3 磁盤存儲器(續)1.4 磁盤陣列 \quad \quad \quad 左南右北為0 左北右南為1 \quad \quad 1.1 磁盤存儲器 \quad 磁盤的驅動器 \quad 磁盤的控制器 \quad 主機每次對磁盤進行讀和寫操作都是以扇區為單位的 現在比較流行的是SATA標準 \…

【kafka實踐】12|如何實現exactly once

前面的章節中我們聊到如何避免保證消息丟失&#xff0c;沒有印象的同學可以再看看&#xff0c;本節我們將展開如何實現kafka的一次精確。 首先我們需要明白兩個概念“冪等”和“事物” 冪等 “冪等”這個詞原是數學領域中的概念&#xff0c;指的是某些操作或函數能夠被執行多…

基于SpringBoot 2+Layui實現的管理后臺系統源碼+數據庫+安裝使用說明

springboot-plus 一個基于SpringBoot 2 的管理后臺系統,包含了用戶管理&#xff0c;組織機構管理&#xff0c;角色管理&#xff0c;功能點管理&#xff0c;菜單管理&#xff0c;權限分配&#xff0c;數據權限分配&#xff0c;代碼生成等功能 相比其他開源的后臺系統&#xff0…

vue 實現返回頂部功能-指定盒子滾動區域

vue 實現返回頂部功能-指定盒子滾動區域 html代碼css代碼返回頂部顯示/隱藏返回標志 html代碼 <a-icontype"vertical-align-top"class"top"name"back-top"click"backTop"v-if"btnFlag"/>css代碼 .top {height: 35px;…

令牌桶算法理解學習(限流算法)

令牌桶算法是網絡流量整形&#xff08;Traffic Shaping&#xff09;和速率限制&#xff08;Rate Limiting&#xff09;中最常使用的一種算法。典型情況下&#xff0c;令牌桶算法用來控制發送到網絡上的數據的數目&#xff0c;并允許突發數據的發送。 用簡單的話語來說就是限制…

Vscode中配置SSH

方法&#xff1a; 本地生成秘鑰&#xff0c;并將生成的秘鑰保存在服務器上 步驟&#xff1a; 一、用戶端生成秘鑰 1、在cmd中輸入ssh-keygen -t rsa&#xff0c;一直點回車即可 2、打開生成的秘鑰文件&#xff08;位置&#xff1a;C:\Users\用戶名\.ssh\id_rsa.pub&#x…

【Java】BigInteger用法

前言 在Java中&#xff0c;由于沒有long long類型。如果需要使用比long類型更大的整數數據時&#xff0c;就可以使用BigInteger類&#xff0c;它支持任意精度的整數。 創建BigInteger類型數據 Test public void test1() {Scanner scan new Scanner(System.in);//1.控制臺讀…

leetcode做題筆記2048. 下一個更大的數值平衡數

如果整數 x 滿足&#xff1a;對于每個數位 d &#xff0c;這個數位 恰好 在 x 中出現 d 次。那么整數 x 就是一個 數值平衡數 。 給你一個整數 n &#xff0c;請你返回 嚴格大于 n 的 最小數值平衡數 。 示例 1&#xff1a; 輸入&#xff1a;n 1 輸出&#xff1a;22 解釋&a…

Linux中的SNAT與DNAT實踐

Linux中的SNAT與DNAT實踐 1、SNAT的介紹1.1&#xff0c;SNAT概述1.2&#xff0c;SNAT源地址轉換過程1.3&#xff0c;SNAT轉換 2、DNAT的介紹2.1&#xff0c;DNAT概述2.2&#xff0c;DNAT轉換前提條件2.3&#xff0c;DNAT的轉換 3、防火墻規則的備份和還原4、tcpdump抓包工具的運…

騰訊再推互動微短劇,游戲的風吹向了短劇

當你看劇時不再擁有上帝視角&#xff0c;處在女主的位置上&#xff0c;你又會做出什么樣的選擇&#xff1f; 騰訊最新上線的短劇《摩玉玄奇2》在原版之外還推出了互動版&#xff0c;就給出了這樣一個新玩法。 《摩玉玄奇2》原版是普通的后宮職場微短劇&#xff0c;互動版則是…

虛擬機VMware安裝centos以及配置網絡

目錄 1、CentOS7的下載2、CentOS7的配置3、CentOS7的安裝4、CentOS7的網絡配置 4.1、自動獲取IP4.2、固定獲取IP 5、XShell連接CentO 準備工作&#xff1a;提前下載和安裝好VMware。VMware的安裝可以參考這一篇文章&#xff1a;VMware15的下載及安裝教程。 1、CentOS7的下載 …

qt 字符串操作

在 QT 中&#xff0c;你可以使用QString類來操作字符串。QString是一個模板類&#xff0c;它可以存儲不同字符集的字符串&#xff0c;并且提供了許多用于操作字符串的方法。 以下是一些常見的操作字符串的方法&#xff1a; append()方法&#xff1a;將一個字符串附加到QString的…

從零開始搭建企業管理系統(五):統一響應結果和全局異常處理

統一響應結果和全局異常處理 前言統一響應結果定義響應結構定義響應對象定義響應狀態對象統一返回響應對象定義 Controller 攔截類 全局異常處理 前言 做個功能之前我們想一下為什么要做統一響應結果和全局異常處理呢&#xff1f; 這是因為我們的項目采用的是前后端分離開發&am…

【C++】小項目:C++實現通訊錄管理系統

文章目錄 1、系統需求完整代碼 1、系統需求 本文將利用C來實現一個通訊錄管理系統 系統中需要實現的功能如下&#xff1a; 添加聯系人&#xff1a;向通訊錄中添加新人&#xff0c;信息包括&#xff08;姓名、性別、年齡、聯系電話、家庭住址&#xff09;最多記錄1000人顯示聯…

LVGL | Demo實例使用說明

LVGL | Demo實例使用說明 時間&#xff1a;2023年12月10日21:51:17 文章目錄 LVGL | Demo實例使用說明Demos for LVGLAdd the examples to your projectsDemosWidgetsMusic playerKeypad and encoderBenchmarkStress Contributing Demos for LVGL Add the examples to your p…

基于SSM的酒店管理旅店系統(Java畢業設計)

大家好&#xff0c;我是DeBug&#xff0c;很高興你能來閱讀&#xff01;作為一名熱愛編程的程序員&#xff0c;我希望通過這些教學筆記與大家分享我的編程經驗和知識。在這里&#xff0c;我將會結合實際項目經驗&#xff0c;分享編程技巧、最佳實踐以及解決問題的方法。無論你是…