對於常用的庫和框架,如果僅限於會用,我覺得還是遠遠不夠,至少要理解它的思想,這樣才知道怎麼可以發揮最大威力,這篇文章是看了react-lite原始碼後寫的。

createElement和component

在react裡面,經過babel的解析後,jsx會變成createElement執行後的結果。

const Test = (props) =>

hello, {props。name}

經過babel解析後會變為createElement(Test, {name: “world}),這裡的Test就是上面的Test方法,name就是Test方法裡面接受的props中的name。

實際上當我們從開始載入到渲染的時候做了下面幾步:

// 1。 babel解析jsx

-> createElement(Test, {name: ”world“})

// 2。 對元件進行處理

const props = {name: world};

const newTest = new Test(props);

// 3。 執行render方法

newTest。render(); // 類元件

Test(props); // 函式元件

這樣也很容易理解,

const Test =

hello, world

和const Test = () =>

hello, world

的區別了。

key

react中的diff會根據子元件的key來對比前後兩次virtual dom(即使前後兩次子元件順序打亂),所以這裡的key最好使用不會變化的值,比如id之類的,最好別用index,如果有兩個子元件互換了位置,那麼index改變就會導致diff失效。

cloneElement

原來對cloneElement的理解就是類似cloneElement(App, {})這種寫法,現在看了實現之後才理解。原來第一個引數應該是一個reactElement,而不是一個reactComponent,應該是

,而不是App,這個也確實是我沒有好好看文件。

短路運算子判斷

為什麼布林型別和null型別的值可以這麼寫,而數字型別卻不行?

showLoading &&

如果showLoading是個數字0,那麼最後渲染出來的居然是個0,但是showLoading是個false或者null,最後就什麼都不渲染,這個是為什麼? 首先上述程式碼會被babel編譯為如下格式:

showLoading && React。createElement(Loading, null)

而如果showLoading是false或者0的時候,就會短路掉後面的元件,最後渲染出來的應該是個showLoading。 但是react-lite在渲染子元件的時候(遞迴渲染虛擬dom),會判斷當前是否為布林型別和null,如果是布林型別或者null,則會被直接過濾掉。

function collectChild(child, children) {

if (child != null && typeof child !== ‘boolean’) {

if (!child。vtype) {

// convert immutablejs data

if (child。toJS) {

child = child。toJS()

if (_。isArr(child)) {

_。flatEach(child, collectChild, children)

} else {

collectChild(child, children)

}

return

}

child = ‘’ + child

}

children[children。length] = child

}

}

shouldComponentUpdate

當shouldComponentUpdate返回false的時候,元件沒有重新渲染,但是更新後的state和props已經掛載到了元件上面,這個時候如果列印state和props,會發現拿到的已經是更新後的了。

setState

react裡面setState後不會立即更新,但在某些場景下也會立即更新,下面這幾種情況列印的值你都能回答的上來嗎?

class App extends React。Component {

state = {

count: 0;

}

test() {

this。setState({

count: this。state。count + 1

});

console。log(this。state。count); // 此時為0

this。setState({

count: this。state。count + 1

});

console。log(this。state。count); // 此時為0

}

test2() {

setTimeout(() => {

this。setState({

count: this。state。count + 1

});

console。log(this。state。count); // 此時為1

this。setState({

count: this。state。count + 1

});

console。log(this。state。count); // 此時為2

})

}

test3() {

Promise。resolve()。then(() => {

this。setState({

count: this。state。count + 1

});

console。log(this。state。count); // 此時為1

this。setState({

count: this。state。count + 1

});

console。log(this。state。count); // 此時為2

})

}

test4() {

this。setState(prevState => {

console。log(prevState。count); // 0

return {

count: prevState。count + 1

};

});

this。setState(prevState => {

console。log(prevState。count); // 1

return {

count: prevState。count + 1

};

});

}

async test4() {

await 0;

this。setState({

count: this。state。count + 1

});

console。log(this。state。count); // 此時為1

this。setState({

count: this。state。count + 1

});

console。log(this。state。count); // 此時為2

}

}

在react中為了防止多次setState導致多次渲染帶來不必要的效能開銷,會將待更新的state放到佇列中,等到合適的時機(生命週期鉤子和事件)後進行batchUpdate,所以在setState後無法立即拿到更新後的state。

因此很多人說setState是非同步的,setState表現確實是非同步,但是裡面沒有用非同步程式碼實現。update不是等主執行緒程式碼執行結束後才執行的,而是需要手動觸發。

如果是給setState傳入一個函式,這個函式是執行前一個setState後才被呼叫的,所以函式返回的引數可以拿到更新後的state。

但是如果將setState在非同步方法中(setTimeout、Promise等等)呼叫,由於這些方法是非同步的,會導致生命週期鉤子或者事件方法先執行,執行完這些後會將更新佇列的pending狀態置為false,這個時候在執行setState後會導致元件立即更新。從這裡也能說明setState本質並不是非同步的,只是模擬了非同步的表現。

ref

ref用到原生的標籤上,可以直接在元件內部用http://this。refs。xxx的方法獲取到真實DOM。

ref用到元件上,需要用ReactDOM。findDOMNode(http://this。refs。xxx)的方式來獲取到這個元件對應的DOM節點,http://this。refs。xxx獲取到的是虛擬DOM。

合成事件

react裡面將可以冒泡的事件委託到了document上,透過向上遍歷父節點模擬了冒泡的機制。

比如當觸發onClick事件時,會先執行target元素的onClick事件回撥函式,如果回撥函數里面阻止了冒泡,就不會繼續向上查詢父元素。否則,就會繼續向上查詢父元素,並執行其onClick的回撥函式。

當跳出迴圈的時候,就會開始進行元件的批次更新(如果沒有收到新的props或者state佇列為空就不會進行更新)。

本文如果有錯誤之處,希望能夠指出,歡迎大家一起探討。

參考:

react-lite

從零開始實現一個react