摘要:異步渲染利用事件循環,延遲渲染函數的調用調用回調函數處理后跟函數的情況淺合并邏輯事件循環,關于的事件循環和的事件循環后續會多帶帶寫篇文章。
看源碼一個痛處是會陷進理不順主干的困局中,本系列文章在實現一個 (x)react 的同時理順 React 框架的主干內容(JSX/虛擬DOM/組件/生命周期/diff算法/setState/ref/...)
從 0 到 1 實現 React 系列 —— JSX 和 Virtual DOM
從 0 到 1 實現 React 系列 —— 組件和 state|props
從 0 到 1 實現 React 系列 —— 生命周期和 diff 算法
從 0 到 1 實現 React 系列 —— 優化 setState 和 ref 的實現
同步 setState 的問題而在現有 setState 邏輯實現中,每調用一次 setState 就會執行 render 一次。因此在如下代碼中,每次點擊增加按鈕,因為 click 方法里調用了 10 次 setState 函數,頁面也會被渲染 10 次。而我們希望的是每點擊一次增加按鈕只執行 render 函數一次。
export default class B extends Component { constructor(props) { super(props) this.state = { count: 0 } this.click = this.click.bind(this) } click() { for (let i = 0; i < 10; i++) { this.setState({ // 在先前的邏輯中,沒調用一次 setState 就會 render 一次 count: ++this.state.count }) } } render() { console.log(this.state.count) return (異步調用 setState) } }{this.state.count}
查閱 setState 的 api,其形式如下:
setState(updater, [callback])
它能接收兩個參數,其中第一個參數 updater 可以為對象或者為函數 ((prevState, props) => stateChange),第二個參數為回調函數;
確定優化思路為:將多次 setState 后跟著的值進行淺合并,并借助事件循環等所有值合并好之后再進行渲染界面。
let componentArr = [] // 異步渲染 function asyncRender(updater, component, cb) { if (componentArr.length === 0) { defer(() => render()) // 利用事件循環,延遲渲染函數的調用 } if (cb) defer(cb) // 調用回調函數 if (_.isFunction(updater)) { // 處理 setState 后跟函數的情況 updater = updater(component.state, component.props) } // 淺合并邏輯 component.state = Object.assign({}, component.state, updater) if (componentArr.includes(component)) { component.state = Object.assign({}, component.state, updater) } else { componentArr.push(component) } } function render() { let component while (component = componentArr.shift()) { renderComponent(component) // rerender } } // 事件循環,關于 promise 的事件循環和 setTimeout 的事件循環后續會多帶帶寫篇文章。 const defer = function(fn) { return Promise.resolve().then(() => fn()) }
此時,每點擊一次增加按鈕 render 函數只執行一次了。
ref 的實現在 react 中并不建議使用 ref 屬性,而應該盡量使用狀態提升,但是 react 還是提供了 ref 屬性賦予了開發者操作 dom 的能力,react 的 ref 有 string、callback、createRef 三種形式,分別如下:
// string 這種寫法未來會被拋棄 class MyComponent extends Component { componentDidMount() { this.refs.myRef.focus() } render() { return } } // callback(比較通用) class MyComponent extends Component { componentDidMount() { this.myRef.focus() } render() { return { this.myRef = ele }} /> } } // react 16.3 增加,其它 react-like 框架還沒有同步 class MyComponent extends Component { constructor() { super() { this.myRef = React.createRef() } } componentDidMount() { this.myRef.current.focus() } render() { return } }
React ref 的前世今生 羅列了三種寫法的差異,下面對上述例子中的第二種寫法(比較通用)進行實現。
首先在 setAttribute 方法內補充上對 ref 的屬性進行特殊處理,
function setAttribute(dom, attr, value) { ... else if (attr === "ref") { // 處理 ref 屬性 if (_.isFunction(value)) { value(dom) } } ... }
針對這個例子中 this.myRef.focus() 的 focus 屬性需要異步處理,因為調用 componentDidMount 的時候,界面上還未添加 dom 元素。處理 renderComponent 函數:
function renderComponent(component) { ... else if (component && component.componentDidMount) { defer(component.componentDidMount.bind(component)) } ... }
刷新頁面,可以發現 input 框已為選中狀態。
處理完普通元素的 ref 后,再來處理下自定義組件的 ref 的情況。之前默認自定義組件上是沒屬性的,現在只要針對自定義組件的 ref 屬性做相應處理即可。稍微修改 vdomToDom 函數如下:
function vdomToDom(vdom) { if (_.isFunction(vdom.nodeName)) { // 此時是自定義組件 ... for (const attr in vdom.attributes) { // 處理自定義組件的 ref 屬性 if (attr === "ref" && _.isFunction(vdom.attributes[attr])) { vdom.attributes[attr](component) } } ... } ... }
跑如下測試用例:
class A extends Component { constructor() { super() this.state = { count: 0 } this.click = this.click.bind(this) } click() { this.setState({ count: ++this.state.count }) } render() { return{this.state.count}} } class B extends Component { constructor() { super() this.click = this.click.bind(this) } click() { this.A.click() } render() { return ( ) } }
效果如下:
項目地址,關于如何 pr
本系列文章拜讀和借鑒了 simple-react,在此特別感謝 Jiulong Hu 的分享。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/96626.html
摘要:系列系列簡單模擬語法一系列合成事件與二系列算法實現分析三系列從到再到四系列與部分源碼解析五系列從使用了解的各種使用方案六的誕生他是的一種擴展語法。這個函數接受組件的實例或元素作為參數,以存儲它們并使它們能被其他地方訪問。 React系列 React系列 --- 簡單模擬語法(一)React系列 --- Jsx, 合成事件與Refs(二)React系列 --- virtualdom di...
摘要:返回元素的是將新的與原始元素的淺層合并后的結果。生命周期方法要如何對應到函數組件不需要構造函數。除此之外,可以認為的設計在某些方面更加高效避免了需要的額外開支,像是創建類實例和在構造函數中綁定事件處理器的成本。 React系列 React系列 --- 簡單模擬語法(一)React系列 --- Jsx, 合成事件與Refs(二)React系列 --- virtualdom diff算法實...
摘要:可以看到,這樣不僅沒有占用組件自己的,也不需要手寫回調函數進行處理,這些處理都壓縮成了一行。效果通過拿到周期才執行的回調函數。實現等價于的回調僅執行一次時,因此直接把回調函數拋出來即可。 1 引言 上周的 精讀《React Hooks》 已經實現了對 React Hooks 的基本認知,也許你也看了 React Hooks 基本實現剖析(就是數組),但理解實現原理就可以用好了嗎?學的是...
摘要:原文地址是一個庫,主要是通過操作數據的方式去操縱,為什么要重造輪子呢,因為覺的目前市面上的框架對于創建大型應用程序不夠直觀,不能滿足需求,所以誕生了。其實說它性能高,只不過是用的方式計算出最小的操作,所以性能就上來了。 原文地址:https://gmiam.com/post/react-... React 是一個 JS 庫,主要是通過操作數據的方式去操縱 DOM,為什么要重造輪子呢,因...
摘要:我們可以為元素添加屬性然后在回調函數中接受該元素在樹中的句柄,該值會作為回調函數的第一個參數返回。使用最常見的用法就是傳入一個對象。單向數據流,比較有序,有便于管理,它隨著視圖庫的開發而被概念化。 面試中問框架,經常會問到一些原理性的東西,明明一直在用,也知道怎么用, 但面試時卻答不上來,也是挺尷尬的,就干脆把react相關的問題查了下資料,再按自己的理解整理了下這些答案。 reac...
閱讀 3223·2021-11-23 09:51
閱讀 1030·2021-08-05 09:58
閱讀 663·2019-08-29 16:05
閱讀 971·2019-08-28 18:17
閱讀 3028·2019-08-26 14:06
閱讀 2721·2019-08-26 12:20
閱讀 2154·2019-08-26 12:18
閱讀 3064·2019-08-26 11:56