国产精品福利自产拍在线观看,国产原创中文无码精品视频,岛国av无码精品一区二区三区,人人妻妻人人

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

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

原文鏈接:https://betterpr

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

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

作者 | Zachary Lee 譯者 | 彭慧中

責編 | 屠敏

出品 | CSDN(ID:CSDNnews)

以下為譯文:

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

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

React 18發(fā)布,僅用400行代碼就能實現(xiàn)一個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),你也可以在本地復制它,并按照我的文章一步一步地調(diào)試。

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

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

JSX和createEelement

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

React 18發(fā)布,僅用400行代碼就能實現(xiàn)一個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提供的選項。

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

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

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

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

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

渲染

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

下面的代碼演示將使用CodeSandbox,拖動左欄查看代碼,點擊上方的菜單按鈕查看目錄結(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)在它只渲染一次,不能與我們互動。

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

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

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

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

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

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

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

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

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

3.sibling:從第一個子元素指向下一個同級元素。

所以現(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)用順序為workLoop →performUnitOfWork→reconcileChildren。下面讓我來總結(jié)一下各個功能的作用:

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

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

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

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

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

我們想給組件添加狀態(tài),讓我們實現(xiàn)一個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é)點上,并通過隊列修改狀態(tài)。從這里,我們也可以知道為什么React-hooks要求每次調(diào)用的順序不能改變。

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

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

模擬requestIdleCallback

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

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

那么為什么要使用宏任務呢?

為了放棄主線程,瀏覽器可以在空閑期間更新DOM或接收事件。因為瀏覽器更新DOM是一個獨立的任務,而JavaScript在這個時候不會被執(zhí)行,因為主線程一次只能運行一個功能,要么執(zhí)行JS,要么處理DOM計算樣式,要么接收輸入事件,等等。

為什么不使用微任務呢?

因為微任務包含在每一輪宏任務中,所以在所有微任務執(zhí)行完畢之前,也就是當前宏任務未完成時,主線程不能放棄。

為什么不使用setTimeout呢?

因為如果setTimeout被嵌套調(diào)用超過5次,該函數(shù)將被視為阻塞,瀏覽器將把最小時間設置為4ms,所以它不夠精確。

最終版本

下面是最終的版本,你可以看到,在去掉注釋后,不到400行代碼就實現(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中添加了一個TypeScript版本的Mini-React(https://github.com/islizeqiang/mini-react/blob/master/src/mini-react.ts),如果你有興趣,可以去看看。

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

END

成就一億技術人

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

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

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

相關推薦