中文字幕 日本 在线 高清,久久精品国产99精品国,超碰人人香蕉,一区二区三区无码高清视频

React 18發(fā)布,僅用400行代碼就能實(shí)現(xiàn)一個(gè)Mini-React(react 17)

CSDN 編者按】400行代碼在React 18中實(shí)現(xiàn)可中斷的異步更新的最小模型!

原文鏈接:https://betterpr

ogramming.pub/react-18-has-been-released-implement-mini-react-in-400-lines-of-code-837559761758

聲明:本文為 CSDN 翻譯,轉(zhuǎn)載請(qǐng)注明來源。

作者 | Zachary Lee 譯者 | 彭慧中

責(zé)編 | 屠敏

出品 | CSDN(ID:CSDNnews)

以下為譯文:

React v18已經(jīng)發(fā)布,它給我們帶來了許多特性,但最重要的特性是可中斷的異步更新,許多新的上層API都是通過它創(chuàng)建的??梢哉f,它是React v18的底層引擎。

本文將使用大約400行代碼帶你實(shí)現(xiàn)一個(gè)可以異步更新和可中斷更新的Mini-React,如下圖所示:

React 18發(fā)布,僅用400行代碼就能實(shí)現(xiàn)一個(gè)Mini-React(react 17)

我使用了React官方網(wǎng)站提供的tic-tac-toe教程示例(以下是鏈接:https://reactjs.org/tutorial/tutorial.html#what-are-we-building),可以看到它非常有效。此外,它目前支持函數(shù)組件和類組件,可以滿足開發(fā)者80%的需求!我也把它放在GitHub上(以下是鏈接:https://github.com/islizeqiang/mini-react),你也可以在本地復(fù)制它,并按照我的文章一步一步地調(diào)試。

這是我在閱讀了大量React的源代碼后創(chuàng)建的,在整體邏輯和函數(shù)命名上基本上和React一樣,如果你對(duì)React的內(nèi)部原理感興趣,這篇文章就是為你準(zhǔn)備的!

React 18發(fā)布,僅用400行代碼就能實(shí)現(xiàn)一個(gè)Mini-React(react 17)

JSX和createEelement

我相信你對(duì) React 中的 JSX 并不陌生。我們使用 JSX 來描述 DOM,它們最終會(huì)被 babel 轉(zhuǎn)換成 React 提供的 API。例如下面的代碼:

React 18發(fā)布,僅用400行代碼就能實(shí)現(xiàn)一個(gè)Mini-React(react 17)

你也可以自己在StackBlitz上試試(在終端輸入node transform-JSX.js):

// run `node transform-JSX.js` in the terminal
const babel = require(\'@babel/core\');const optionsObject = { presets: [\'@babel/preset-env\'], plugins: [[\'@babel/plugin-transform-react-jsx\']],};
const { code } = babel.transformSync( \'const element = <div id=\"test\"><h1>Hello</h1></div>\', optionsObject);
console.log(code);

你還可以在編譯好的字符串中加入更多的元素,再看看最終的結(jié)果,我在這里直接給出React.createElement提供的選項(xiàng)。

1.type:表示當(dāng)前節(jié)點(diǎn)的類型,如上圖中的div。

2.config:表示當(dāng)前元素節(jié)點(diǎn)上的屬性,如上圖中的{id: \”test\”}。

3.children:子元素,可以是多個(gè)、簡(jiǎn)單的文本,也可以由React.createElement創(chuàng)建的子節(jié)點(diǎn)。

然后根據(jù)這個(gè)要求實(shí)現(xiàn)你自己的React.createElement,就像下面的代碼一樣,我們定義一個(gè)自定義的數(shù)據(jù)結(jié)構(gòu)。

React 18發(fā)布,僅用400行代碼就能實(shí)現(xiàn)一個(gè)Mini-React(react 17)

渲染

然后我們可以根據(jù)上面創(chuàng)建的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)一個(gè)簡(jiǎn)化版的渲染函數(shù),將JSX渲染到真實(shí)的DOM上。

下面的代碼演示將使用CodeSandbox,拖動(dòng)左欄查看代碼,點(diǎn)擊上方的菜單按鈕查看目錄結(jié)構(gòu)。你也可以直接編輯,查看顯示的結(jié)果。

import React from \"./mini-react\";
const App = ( <div id=\"test\"> <h1>Hello</h1> </div>);
// eslint-disable-next-line react/no-deprecatedReact.render(App, document.getElementById(\"root\"));

所以你可以看到它工作,但現(xiàn)在它只渲染一次,不能與我們互動(dòng)。

另外,請(qǐng)注意我們?cè)谶@里使用react-scripts@3.4.4來幫助編譯JSX,API在以后的版本中已經(jīng)發(fā)生了變化,但是React.createElement在結(jié)束時(shí)仍然被調(diào)用。我提供的GitHub庫(kù)使用了Vite而不是react-scripts。

接下來,是React的核心纖程架構(gòu)和并發(fā)模式,這是從React 17開始提出的,主要是為了解決一旦完整的元素樹被遞歸,就無(wú)法終止的問題,這可能導(dǎo)致主線程長(zhǎng)時(shí)間被阻塞,那些高優(yōu)先級(jí)的任務(wù)(比如那些用戶輸入或動(dòng)畫等)無(wú)法及時(shí)處理。

所以在React源代碼中,工作被分解成小單元。一旦瀏覽器處于空閑狀態(tài),它將處理這些小的工作單元,然后將結(jié)果映射到實(shí)際的DOM,直到所有結(jié)果都被處理完。

requestIdleCallback是一個(gè)實(shí)驗(yàn)性API,它在瀏覽器空閑時(shí)執(zhí)行回調(diào)。接下來,我們將使用這個(gè)API來簡(jiǎn)單地實(shí)現(xiàn)這個(gè)功能。我將在最后給出React目前使用的調(diào)度程序包的模擬實(shí)現(xiàn)。

在開始編寫下一個(gè)代碼之前,我想再次介紹一下工作單元之間的連接。

React 18發(fā)布,僅用400行代碼就能實(shí)現(xiàn)一個(gè)Mini-React(react 17)

就像上面的圖片一樣,我們將像鏈表一樣創(chuàng)建每個(gè)纖程節(jié)點(diǎn)之間的連接,它們是:

1.child:父節(jié)點(diǎn)指向第一個(gè)子元素的指針。

2.return/parent:所有子元素都有一個(gè)指向父元素的指針。

3.sibling:從第一個(gè)子元素指向下一個(gè)同級(jí)元素。

所以現(xiàn)在你可以愉快地編寫代碼:

import React from \"./mini-react\";
const App = ( <div id=\"test\"> <h1>Hello</h1> </div>);
// eslint-disable-next-line react/no-deprecatedReact.render(App, document.getElementById(\"root\"));

盡管添加了這么多代碼,我們只是重構(gòu)了渲染邏輯。重構(gòu)后的調(diào)用順序?yàn)閣orkLoop →performUnitOfWork→reconcileChildren。下面讓我來總結(jié)一下各個(gè)功能的作用:

1.workLoop:通過連續(xù)調(diào)用requestIdleCallback來獲得空閑時(shí)間。如果當(dāng)前空閑且有單元任務(wù)要執(zhí)行,則執(zhí)行每個(gè)單元任務(wù)。

2.performUnitOfWork:執(zhí)行的特定單元任務(wù)。這是鏈表思想的體現(xiàn)。即一次只處理一個(gè)纖程節(jié)點(diǎn),并返回下一個(gè)要處理的節(jié)點(diǎn)。

3.reconcileChildren:協(xié)調(diào)當(dāng)前纖程節(jié)點(diǎn),它實(shí)際上是虛擬DOM的比較,并記錄要進(jìn)行的更改。你可以看到,我們直接在每個(gè)纖程節(jié)點(diǎn)上修改和保存,因?yàn)楝F(xiàn)在它只是對(duì)JavaScript對(duì)象的修改,而不涉及真正的DOM。

4.最后一步是commitRoot。如果當(dāng)前需要更新(根據(jù)wipRoot),并且沒有下一個(gè)單元任務(wù)要處理(根據(jù)!nextUnitOfWork),這意味著需要將虛擬更改映射到實(shí)際的DOM。commitRoot負(fù)責(zé)根據(jù)纖程節(jié)點(diǎn)的變化修改真實(shí)的DOM。

到目前為止,我們已經(jīng)實(shí)現(xiàn)了纖程架構(gòu),是時(shí)候見證它的威力了。

我們想給組件添加狀態(tài),讓我們實(shí)現(xiàn)一個(gè)useState。

import React from \"./mini-react\";import \"./styles.css\";
function calculateWinner(squares) { const lines = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6] ]; for (let i = 0; i < lines.length; i = 1) { const [a, b, c] = lines[i]; if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { return squares[a]; } } return ;}
class Square extends React.Component { render { return ( <button onClick={this.props.onClick} className=\"square\"> {this.props.value} </button> ); }}
class Board extends React.Component { renderSquare(i) { return ( <Square value={this.props.squares[i]} onClick={ => { this.props.onClick(i); }} /> ); }
render { return ( <div> <div className=\"board-row\"> {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)} </div> <div className=\"board-row\"> {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)} </div> <div className=\"board-row\"> {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)} </div> </div> ); }}
class App extends React.Component { constructor(props) { super(props); this.State = { history: [ { squares: Array(9).fill() } ], stepNumber: 0, xIsNext: true }; }
handleClick(i) { const history = this.state.history.slice(0, this.state.stepNumber 1); const current = history[history.length - 1]; const squares = current.squares.slice; if (calculateWinner(squares) || squares[i]) { return; }
squares[i] = this.state.xIsNext ? \"X\" : \"O\"; this.setState({ history: history.concat([ { squares } ]), stepNumber: history.length, xIsNext: !this.state.xIsNext }); }
jumpTo(step) { this.setState({ stepNumber: step, xIsNext: step % 2 === 0 }); }
render { const { history } = this.state; const current = history[this.state.stepNumber]; const winner = calculateWinner(current.squares);
const moves = history.map((step, move) => { const desc = move ? `Go to move #${move}` : \"Go to game start\"; return ( <li key={move}> <button onClick={ => this.jumpTo(move)}>{desc}</button> </li> ); });
let status; if (winner) { status = `Winner: ${winner}`; } else { status = `Next player: ${this.state.xIsNext ? \"X\" : \"O\"}`; }
return ( <div className=\"game\"> <div className=\"game-board\"> <Board squares={current.squares} onClick={(i) => { this.handleClick(i); }} /> </div> <div className=\"game-info\"> <div>{status}</div> <ol>{moves}</ol> </div> </div> ); }}
// eslint-disable-next-line react/no-deprecatedReact.render(<App />, document.getElementById(\"root\"));

useState巧妙地將hook的狀態(tài)保留在纖程節(jié)點(diǎn)上,并通過隊(duì)列修改狀態(tài)。從這里,我們也可以知道為什么React-hooks要求每次調(diào)用的順序不能改變。

除此以外,我們還實(shí)現(xiàn)了一個(gè)Component ,這里只是簡(jiǎn)單地轉(zhuǎn)換為一個(gè)渲染的方法,并添加了一點(diǎn)它的獨(dú)特身份。

React 18發(fā)布,僅用400行代碼就能實(shí)現(xiàn)一個(gè)Mini-React(react 17)

模擬requestIdleCallback

現(xiàn)在我們幾乎實(shí)現(xiàn)了所有的功能,讓我解釋一下React目前采用的調(diào)度器包,它實(shí)際上是一個(gè)比requestIdleCallback更復(fù)雜的調(diào)度邏輯,包括更新任務(wù)的優(yōu)先級(jí)等等。

上面是我實(shí)現(xiàn)模擬requestIdleCallback的參考調(diào)度程序,它結(jié)合了requestAnimationFrame和MessageChannel。這里使用MessageChannel的目的是使用宏任務(wù)來處理每一輪的單元任務(wù)。

那么為什么要使用宏任務(wù)呢?

為了放棄主線程,瀏覽器可以在空閑期間更新DOM或接收事件。因?yàn)闉g覽器更新DOM是一個(gè)獨(dú)立的任務(wù),而JavaScript在這個(gè)時(shí)候不會(huì)被執(zhí)行,因?yàn)橹骶€程一次只能運(yùn)行一個(gè)功能,要么執(zhí)行JS,要么處理DOM計(jì)算樣式,要么接收輸入事件,等等。

為什么不使用微任務(wù)呢?

因?yàn)槲⑷蝿?wù)包含在每一輪宏任務(wù)中,所以在所有微任務(wù)執(zhí)行完畢之前,也就是當(dāng)前宏任務(wù)未完成時(shí),主線程不能放棄。

為什么不使用setTimeout呢?

因?yàn)槿绻鹲etTimeout被嵌套調(diào)用超過5次,該函數(shù)將被視為阻塞,瀏覽器將把最小時(shí)間設(shè)置為4ms,所以它不夠精確。

最終版本

下面是最終的版本,你可以看到,在去掉注釋后,不到400行代碼就實(shí)現(xiàn)了React的核心思想。

import React from \"./mini-react\";import \"./styles.css\";
function calculateWinner(squares) { const lines = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6] ]; for (let i = 0; i < lines.length; i = 1) { const [a, b, c] = lines[i]; if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { return squares[a]; } } return ;}
class Square extends React.Component { render { return ( <button onClick={this.props.onClick} className=\"square\"> {this.props.value} </button> ); }}
class Board extends React.Component { renderSquare(i) { return ( <Square value={this.props.squares[i]} onClick={ => { this.props.onClick(i); }} /> ); }
render { return ( <div> <div className=\"board-row\"> {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)} </div> <div className=\"board-row\"> {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)} </div> <div className=\"board-row\"> {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)} </div> </div> ); }}
class App extends React.Component { constructor(props) { super(props); this.state = { history: [ { squares: Array(9).fill() } ], stepNumber: 0, xIsNext: true }; }
handleClick(i) { const history = this.state.history.slice(0, this.state.stepNumber 1); const current = history[history.length - 1]; const squares = current.squares.slice; if (calculateWinner(squares) || squares[i]) { return; }
squares[i] = this.state.xIsNext ? \"X\" : \"O\"; this.setState({ history: history.concat([ { squares } ]), stepNumber: history.length, xIsNext: !this.state.xIsNext }); }
jumpTo(step) { this.setState({ stepNumber: step, xIsNext: step % 2 === 0 }); }
render { const { history } = this.state; const current = history[this.state.stepNumber]; const winner = calculateWinner(current.squares);
const moves = history.map((step, move) => { const desc = move ? `Go to move #${move}` : \"Go to game start\"; return ( <li key={move}> <button onClick={ => this.jumpTo(move)}>{desc}</button> </li> ); });
let status; if (winner) { status = `Winner: ${winner}`; } else { status = `Next player: ${this.state.xIsNext ? \"X\" : \"O\"}`; }
return ( <div className=\"game\"> <div className=\"game-board\"> <Board squares={current.squares} onClick={(i) => { this.handleClick(i); }} /> </div> <div className=\"game-info\"> <div>{status}</div> <ol>{moves}</ol> </div> </div> ); }}
// eslint-disable-next-line react/no-deprecatedReact.render(<App />, document.getElementById(\"root\"));

我還在GitHub中添加了一個(gè)TypeScript版本的Mini-React(https://github.com/islizeqiang/mini-react/blob/master/src/mini-react.ts),如果你有興趣,可以去看看。

React 18發(fā)布,僅用400行代碼就能實(shí)現(xiàn)一個(gè)Mini-React(react 17)

END

成就一億技術(shù)人

React 18發(fā)布,僅用400行代碼就能實(shí)現(xiàn)一個(gè)Mini-React(react 17)

版權(quán)聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻(xiàn),該文觀點(diǎn)僅代表作者本人。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如發(fā)現(xiàn)本站有涉嫌抄襲侵權(quán)/違法違規(guī)的內(nèi)容, 請(qǐng)發(fā)送郵件至 舉報(bào),一經(jīng)查實(shí),本站將立刻刪除。

(0)
上一篇 2024年5月16日 下午3:14
下一篇 2024年5月16日 下午3:26

相關(guān)推薦