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