對於經常使用的框架,若是僅限於會用,我以爲仍是遠遠不夠,至少要理解它的思想,這樣纔不會掉入各類坑裏面,這篇文章是基於react-lite源碼來寫的。javascript
在react裏面,通過babel的解析後,jsx會變成createElement執行後的結果。java
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。 實際上當咱們從開始加載到渲染的時候作了下面幾步:react
// 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>
的區別了。git
react中的diff會根據子組件的key來對比先後兩次virtual dom(即便先後兩次子組件順序打亂),因此這裏的key最好使用不會變化的值,好比id之類的,最好別用index,若是有兩個子組件互換了位置,那麼index改變就會致使diff失效。github
爲何布爾類型和null類型的值能夠這麼寫,而數字類型卻不行?數組
showLoading && <Loading />
複製代碼
若是showLoading是個數字0,那麼最後渲染出來的竟然是個0,可是showLoading是個false或者null,最後就什麼都不渲染,這個是爲何? 首先上述代碼會被babel編譯爲以下格式:bash
showLoading && React.createElement(Loading, null)
複製代碼
而若是showLoading是false或者0的時候,就會短路掉後面的組件,最後渲染出來的應該是個showLoading。 可是react-lite在渲染子組件的時候(遞歸渲染虛擬dom),會判斷當前是否爲布爾類型和null,若是是布爾類型或者null,則會被直接過濾掉。babel
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
}
}
複製代碼
原來對cloneElement的理解就是相似cloneElement(App, {})這種寫法,如今看了實現以後才理解。原來第一個參數應該是一個reactElement,而不是一個reactComponent,應該是<App />
,而不是App,這個也確實是我沒有好好看文檔。框架
當shouldComponentUpdate返回false的時候,組件沒有從新渲染,可是更新後的state和props已經掛載到了組件上面,這個時候若是打印state和props,會發現拿到的已是更新後的了。dom
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用到原生的標籤上,能夠直接在組件內部用this.refs.xxx的方法獲取到真實DOM。 ref用到組件上,須要用ReactDOM.findDOMNode(this.refs.xxx)的方式來獲取到這個組件對應的DOM節點,this.refs.xxx獲取到的是虛擬DOM。
react裏面將能夠冒泡的事件委託到了document上,經過向上遍歷父節點模擬了冒泡的機制。 好比當觸發onClick事件時,會先執行target元素的onClick事件回調函數,若是回調函數裏面阻止了冒泡,就不會繼續向上查找父元素。不然,就會繼續向上查找父元素,並執行其onClick的回調函數。 當跳出循環的時候,就會開始進行組件的批量更新(若是沒有收到新的props或者state隊列爲空就不會進行更新)。
參考: