深入理解React
對於常用的框架,如果僅限於會用,我覺得還是遠遠不夠,至少要理解它的思想,這樣才不會掉入各種坑裡面,這篇文章是基於react-lite原始碼來寫的。
createElement和component
在react裡面,經過babel的解析後,jsx會變成createElement執行後的結果。
const Test = (props) => <h1>hello, {props.name}</h1>; <Test name="world" /> 複製程式碼
<Test name="world" />
經過babel解析後會變為createElement(Test, {name: "world}),這裡的Test就是上面的Test方法,name就是Test方法裡面接受的props中的name。
實際上當我們從開始載入到渲染的時候做了下面幾步:
// 1. babel解析jsx <Test name="world"> -> createElement(Test, {name: "world"}) // 2. 對函式元件和class元件進行處理 // 如果是類元件,不做處理,如果是函式元件,增加render方法 const props = {name: world}; const newTest = new Component(props); newTest.render = function() { return Test(props); } // 3. 執行render方法 newTest.render(); 複製程式碼
這樣也很容易理解,const Test = <div>hello, world</div>
和const Test = () =><div>hello, world</div>
的區別了。
key
react中的diff會根據子元件的key來對比前後兩次virtual dom(即使前後兩次子元件順序打亂),所以這裡的key最好使用不會變化的值,比如id之類的,最好別用index,如果有兩個子元件互換了位置,那麼index改變就會導致diff失效。
cloneElement
原來對cloneElement的理解就是類似cloneElement(App, {})這種寫法,現在看了實現之後才理解。原來第一個引數應該是一個reactElement,而不是一個reactComponent,應該是<App />
,而不是App,這個也確實是我沒有好好看文件。
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表現確實是非同步,但是裡面沒有用非同步程式碼實現。而且不是等主執行緒程式碼執行結束後才執行的,而是需要手動觸發。 如果是給setState傳入一個函式,這個函式是執行前一個setState後才被呼叫的,所以函式返回的引數可以拿到更新後的state。 但是如果將setState在非同步方法中(setTimeout、Promise等等)呼叫,由於這些方法是非同步的,會導致生命週期鉤子或者事件方法先執行,執行完這些後會將更新佇列的pending狀態置為false,這個時候在執行setState後會導致元件立即更新。從這裡也能說明setState本質並不是非同步的,只是模擬了非同步的表現。
ref
ref用到原生的標籤上,可以直接在元件內部用this.refs.xxx的方法獲取到真實DOM。 ref用到元件上,需要用ReactDOM.findDOMNode(this.refs.xxx)的方式來獲取到這個元件對應的DOM節點,this.refs.xxx獲取到的是虛擬DOM。
合成事件
react裡面將可以冒泡的事件委託到了document上,通過向上遍歷父節點模擬了冒泡的機制。 比如當觸發onClick事件時,會先執行target元素的onClick事件回撥函式,如果回撥函式裡面阻止了冒泡,就不會繼續向上查詢父元素。否則,就會繼續向上查詢父元素,並執行其onClick的回撥函式。 當跳出迴圈的時候,就會開始進行元件的批量更新(如果沒有收到新的props或者state佇列為空就不會進行更新)。
參考:
- ofollow,noindex">react-lite
- 從零寫一個react