react 学习(三) 组件更新
我們上一節了了解了函數式組件和類組件的處理方式,本質就是處理基于 babel 處理后的 type 類型,最后還是要處理虛擬 dom。本小節我們學習下組件的更新機制。
我們知道我們定義類組件的時候,只能通過 setState 方式修改狀態值,這樣頁面才會重新渲染。如果你直接修改 state,其實沒有什么作用的。
實現簡版更新機制
我們先寫下 Counter 的例子,點擊加一,如下:
// src/index.jsclass Counter extends React.Component {constructor(props) {super(props);// 類的構造函數中 唯一給 state 賦值的地方this.state = {number: 0,};}handleClick = () => {// 明顯 setState 是繼承來的this.setState({number: ++this.state.number,});};render() {return (<div><p>{this.props.title}</p><p>{this.state.number}</p><button onClick={this.handleClick}>plus</button></div>);} } ReactDOM.render(<Counter title="加法" />, document.getElementById("root"));實現 setState
// src/Component.js export class Component {constructor(props) {// react 的實例單獨注冊了一個更新器,回來統一處理 state,類似寫函數嵌套多了,把不同功能單獨提出去this.updater = new Updater(this) // 把組件實例傳入}setState(partialState) {// 我們可以寫多個 setState 方法,react 會統一處理,所以很明顯使用一個棧存儲的this.updater.addState(partialState)} }class Updater{constructor(classInstance) {this.classInstance = classInstancethis.pendingStates = []}addState(partialState) {// 狀態存儲this.pendingStates.push(partialState)// 觸發更新this.emitUpdate()}emitUpdate() {this.updaetComponent()}// 組件更新原理//1.計算新的 state//2.重新執行render//3.得到新的虛擬dom,真實dom//4.覆蓋重新掛載updaetComponent() {const {calssInstance, pendingStates} = thisif (pendingStates.length) {// 獲取新的狀態let newState = this.getState()// 查看是否更新showUpdate(classInstance, newState)}}getState() {const {classInstance, pendingStates} = this// 得到老狀態let {state} = classInstance// 合并狀態pendingStates.forEach(nextState => {state = {...state,...nextState}})// 清空pendingStates.length = []return state} }function showUpdate(classInstance, newState) {classInstace.state = newState // 用新的狀態 直接覆蓋組件實例的狀態classInstance.forceUpadte() // 強制更新, 此方法在父組件上 }強制更新
// Components.js Component 類 // 這里的邏輯是 獲取老的真實dom,獲取新的虛擬dom 生成的真實dom,使用 replaceChild 方法,用新的dom替換舊的真實dom forceUpdate() {let oldRenderVdom = this.oldRenderVdomlet oldDOM = finDOM(oldRenderVdom) // 根據虛擬 dom 獲取真實domlet newRenderVdom = this.render()compareTowVdom(oldDOM.parentNode, oldDOM, newRenderVdom)this.oldRenderVdom = newRenderVdom }這里可能有的朋友有疑問,舊的虛擬 dom,舊的真實 dom,那里獲取的呢,我們還的改動下上一小節的代碼:
// react-dom.jsfunction createDOM(vdom) {...vdom.dom = dom // 我們把得到的真實 dom,添加到虛擬 dom 對象上...}// 函數組件 babel 把屬性轉為 props 對像 function mountFunctionComponent(vdom) {const { type, props } = vdom;// 函數式組件 type 是個 函數 返回虛擬函數let renderVdom = type(props);// 把舊的 渲染的虛擬dom 存起來 / 看這里vdom.oldRenderVdom = renderVdom;return createDOM(renderVdom); }// 類組件 function mountClassComponent(vdom) {const { type, props } = vdom;const classInstance = new type(props);const renderVdom = classInstance.render();// 把舊的 渲染的虛擬dom 存儲 / 看這里classInstance.oldRenderVdom = vdom.oldRenderVdom = renderVdom;return createDOM(renderVdom); }通過上面添加的代碼,我們已經在虛擬 dom 上和類的實例上綁定了虛擬 dom,所以回過頭來我們可以在 forceUpdate 方法中獲取舊的虛擬 dom,那如何拿到舊的真實 dom 呢?
// react-dom.js function findDOM(vdom) {if (!vdom) return nullif (vdom.dom) return vdom.dom // 我們在上面 createDOM 中做了綁定// 如果沒有虛擬domlet renderVdom = vdom.oldRenderVdomreturn findDOM(renderVdom) }什么情況下會沒有虛擬 dom 呢?例如 function One() {return <h1>123<h1/>}, function Two() {return <One />},我們調用 Two 函數的時候,得到的就不是虛擬 dom,這個時候就需要繼續遞歸 findDOM。
實現 compareTwoVdom 進行 dom 替換
// react-dom.js function compareTwoVdom(parentDOM, oldDOM, newVdom) {let newDOM = createDOM(newVdom) // 我們執行 render 得到的新的虛擬 domparentDOM.replace(newDOM, oldDOM) // 完成替換 }這里遺漏了一個小問題,我們沒有處理點擊事件,我們需要在屬性中判斷是否是 on 開頭的變量:
// react-dom.js function updateProps() {....if (/^on[A-Z].*/.test(key)) {// 轉成 js 原生的小寫dom[key.toLowerCase()] = newProps[key]}... }我們在入口文件中改用我們自己寫的代碼,發現效果一樣。當讓這里這是簡單的實現完全的 dom 替換,沒有對 setState 做異步處理,但是我們已經能理解 react 類組件的更新原理。
我們下一小節實現批量更新和合成事件,如果有不對,歡迎指正!
總結
以上是生活随笔為你收集整理的react 学习(三) 组件更新的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux 学习“安装CentOS与多重
- 下一篇: Portraiture4免费磨皮插件支持