摘要:本文翻譯自原作者如果有任何版權(quán)問題,請聯(lián)系當你在組件中調(diào)用時,你覺得會發(fā)生什么當然,會用這條狀態(tài)重新渲染組件并且更新匹配到的,然后返回元素。如果你之前使用過一些渲染器比如說,你可能知道在頁面中使用超過一個渲染器是沒什么問題的。
本文翻譯自:How Does setState Know What to Do?原作者:Dan Abramov
如果有任何版權(quán)問題,請聯(lián)系shuirong1997@icloud.com
當你在組件中調(diào)用setState時,你覺得會發(fā)生什么?
import React from "react"; import ReactDOM from "react-dom"; class Button extends React.Component { constructor(props) { super(props); this.state = { clicked: false }; this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState({ clicked: true }); } render() { if (this.state.clicked) { returnThanks
; } return ( ); } } ReactDOM.render(, document.getElementById("container"));
當然,React會用{ clicked: true} 這條狀態(tài)重新渲染組件并且更新匹配到的DOM,然后返回Thanks
元素。
聽起來似乎簡潔明了。但別急,React(或者說React DOM)是怎么做的?
更新DOM聽起來像是React DOM的事兒,但別忘了我們調(diào)用的可是this.setState(),它是React的東西,可不是React DOM的。另外,我們的基類React.Component是被定義在React內(nèi)部。
所以問題來了:React.Component內(nèi)部的setState怎么能去更新DOM呢?
事先聲明:就像我的其他博客,你不需要熟練掌握React。這篇博客是為那些想要看看面紗之后是什么東西的人準備的。完全可選!
我們或許會認為React.Component類已經(jīng)包含了DOM更新邏輯。
但如果這是事實,那this.setState是如何工作在其他環(huán)境中呢?比如:在React Native App中的組件也能繼承React.Component,他們也能像上面一樣調(diào)用this.setState(),并且React Native工作在Android和iOS的原生視圖而不是DOM中。
你可能也對React Test Renderer 或 Shallow Renderer比較熟悉。這兩個測試渲染器讓你可以渲染一般的組件并且也能在他們中調(diào)用this.setState,但他們可都不使用DOM。
如果你之前使用過一些渲染器比如說React ART,你可能知道在頁面中使用超過一個渲染器是沒什么問題的。(比如:ART組件工作在React DOM 樹的內(nèi)部。)這會產(chǎn)生一個不可維持的全局標志或變量。
所以React.Component以某種方式將state的更新委托為具體的平臺(譯者注:比如Android, iOS),在我們理解這是如何發(fā)生之前,讓我們對包是如何被分離和其原因挖得更深一點吧!
這有一個常見的錯誤理解:React "引擎"在react包的內(nèi)部。這不是事實。
事實上,從 React 0.14開始對包進行分割時,React包就有意地僅導出關(guān)于如何定義組件的API了。React的大部分實現(xiàn)其實在“渲染器”中。
渲染器的其中一些例子包括:react-dom,react-dom/server,react-native,react-test-renderer,react-art(另外,你也可以構(gòu)建自己的)。
這就是為什么react包幫助很大而不管作用在什么平臺上。所有它導出的模塊,比如React.Component,React.createElement,React.Children和[Hooks](https://reactjs.org/docs/hooks-intro.html),都是平臺無關(guān)的。無論你的代碼運行在React DOM、React DOM Server、還是React Native,你的組件都可以以一種相同的方式導入并且使用它們。
與之相對的是,渲染器會暴露出平臺相關(guān)的接口,比如ReactDOM.render(),它會讓你可以把React掛載在DOM節(jié)點中。每個渲染器都提供像這樣的接口,但理想情況是:大多數(shù)組件都不需要從渲染器中導入任何東西。這能使它們更精簡。
大多數(shù)人都認為React“引擎”是位于每個獨立的渲染器中的。許多渲染器都包含一份相同的代碼—我們叫它“調(diào)節(jié)器”,為了表現(xiàn)的更好,遵循這個步驟 可以讓調(diào)節(jié)器的代碼和渲染器的代碼在打包時歸到一處。(拷貝代碼通常不是優(yōu)化“打包后文件”(bundle)體積的好辦法,但大多數(shù)React的使用者一次只需要一個渲染器,比如:react-dom(譯者注:因此可以忽略調(diào)節(jié)器的存在))
The takeaway here 是react包僅僅讓你知道如何使用React的特性而無需了解他們是如何被實現(xiàn)的。渲染器(react-dom,react-native等等)會提供React特性的實現(xiàn)和平臺相關(guān)的邏輯;一些關(guān)于調(diào)節(jié)器的代碼被分享出來了,但那只是多帶帶渲染器的實現(xiàn)細節(jié)而已。
現(xiàn)在我們知道了為什么react和react-dom包需要為新特定更新代碼了。比如:當React16.3新增了Context接口時,React.createContext()方法會在React包中被暴露出來。
但是React.createContext()實際上不會實現(xiàn)具體的邏輯(譯者注:只定義接口,由其他渲染器來實現(xiàn)邏輯)。并且,在React DOM和React DOM Server上實現(xiàn)的邏輯也會有區(qū)別。所以createContext()會返回一些純粹的對象(定義如何實現(xiàn)):
// 一個簡單例子 function createContext(defaultValue) { let context = { _currentValue: defaultValue, Provider: null, Consumer: null }; context.Provider = { $$typeof: Symbol.for("react.provider"), _context: context }; context.Consumer = { $$typeof: Symbol.for("react.context"), _context: context, }; return context; }
你會在某處代碼中使用
所以如果你將react升級到16.3+,但沒有升級react-dom,你將使用一個還不知道Provider和Consumer類型的渲染器,這也就舊版的react-dom可能會報錯:fail saying these types are invalid的原因。
同樣的警告也會出現(xiàn)在React Native中,但是不同于React DOM,一個新的React版本不會立即產(chǎn)生一個對應的React Native版本。他們(React Native)有自己的發(fā)布時間表。大概幾周后,渲染器代碼才會多帶帶更新到React Native庫中。這就是為什么新特性在React Native生效的時間會和React DOM不同。
Okay,那么現(xiàn)在我們知道了react包不包含任何好玩的東西,并且具體的實現(xiàn)都在像react-dom,react-native這樣的渲染器中。但這并不能回答我們開頭提出的問題。React.Component里的setState()是如何和對應的渲染器通信的呢?
答案是每個渲染器都會在創(chuàng)建的類中添加一個特殊的東西,這個東西叫updater。它不是你添加的東西—恰恰相反,它是React DOM,React DOM Server 或者React Native在創(chuàng)建了一個類的實例后添加的:
// React DOM 中是這樣 const inst = new YourComponent(); inst.props = props; inst.updater = ReactDOMUpdater; // React DOM Server 中是這樣 const inst = new YourComponent(); inst.props = props; inst.updater = ReactDOMServerUpdater; // React Native 中是這樣 const inst = new YourComponent(); inst.props = props; inst.updater = ReactNativeUpdater;
從 setState的實現(xiàn)就可以看出,它做的所有的工作就是把任務委托給在這個組件實例中創(chuàng)建的渲染器:
// 簡單例子 setState(partialState, callback) { // 使用`updater`去和渲染器通信 this.updater.enqueueSetState(this, partialState, callback); }
React DOM Server 可能想忽略狀態(tài)更新并且警告你,然而React DOM和React Native將會讓調(diào)節(jié)器的拷貝部分去 處理它。
這就是盡管this.setState()被定義在React包中也可以更新DOM的原因。它調(diào)用被React DOM添加的this.updater并且讓React DOM來處理更新。
現(xiàn)在我們都比較了解“類”了,但“鉤子”(Hooks)呢?
當人們第一次看到 鉤子接口的提案時,他們常回想:useState是怎么知道該做什么呢?這一假設簡直比對this.setState()的疑問還要迷人。
但就像我們?nèi)缃窨吹降哪菢樱?b>setState()的實現(xiàn)一直以來都是模糊不清的。它除了傳遞調(diào)用給當前的渲染器外什么都不做。所以,useState鉤子做的事也是如此。
這次不是updater,鉤子(Hooks)使用一個叫做“分配器”(dispatcher)的對象,當你調(diào)用React.useState()、React.useEffect()或者其他自帶的鉤子時,這些調(diào)用會被推送給當前的分配器。
// In React (simplified a bit) const React = { // Real property is hidden a bit deeper, see if you can find it! __currentDispatcher: null, useState(initialState) { return React.__currentDispatcher.useState(initialState); }, useEffect(initialState) { return React.__currentDispatcher.useEffect(initialState); }, // ... };
多帶帶的渲染器會在渲染你的組件之前設置分配器(dispatcher)。
// In React DOM const prevDispatcher = React.__currentDispatcher; React.__currentDispatcher = ReactDOMDispatcher;let result; try { result = YourComponent(props); } finally { // Restore it back React.__currentDispatcher = prevDispatcher;}
React DOM Server的實現(xiàn)在這里。由React DOM和React Native共享的調(diào)節(jié)器實現(xiàn)在這里。
這就是為什么像react-dom這樣的渲染器需要訪問和你調(diào)用的鉤子所使用的react一樣的包。否則你的組件將找不到分配器!如果你有多個React的拷貝在相同的組件樹中,代碼可能不會正常工作。然而,這總是造成復雜的Bug,因此鉤子會在它耗光你的精力前強制你去解決包的副本問題。
如果你不覺得這有什么,你可以在工具使用它們前精巧地覆蓋掉原先的分配器(__currentDispatcher的名字其實我自己編的但你可以在React倉庫中找到它真正的名字)。比如:React DevTools會使用一個特殊的內(nèi)建分配器來通過捕獲JavaScript調(diào)用棧來反映(introspect)鉤子。不要在家里重復這個(Don’t repeat this at home.)(譯者注:可能是“不要在家里模仿某項實驗”的衍生體。可能是個笑話,但我get到)
這也意味著鉤子不是React固有的東西。如果在將來有很多類庫想要重用相同的基礎鉤子,理論上來說分配器可能會被移到分離的包中并且被塑造成優(yōu)秀的接口—會有更少讓人望而生畏的名稱—暴露出來。在實際中,我們更偏向去避免過于倉促地將某物抽象,直到我們的確需要這么做。
updater和__currentDispatcher都是泛型程序設計(依賴注入/dependency injection)的絕佳實例。渲染器“注入”特性的實現(xiàn)。就像setState可以讓你的組件看起來簡單明了。
當你使用React時,你不需要考慮它是如何工作的。我們期望React用戶去花費更多的時間去考慮它們的應用代碼而不是一些抽象的概念比如:依賴注入。但如果你曾好奇this.setState()或useState()是怎么知道它們該做什么的,那我希望這篇文章將幫助到你。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/100865.html
摘要:譯的生命周期的使用場景原文鏈接作者翻譯上名這個圖片,就是組件的生命周期,從形成到銷毀的過程。這并不意味著沒有用。最常見的用例更新以響應或更改。是否可以調(diào)用總結(jié)在理想的世界中,我們不會使用生命周期方法。 [譯]React 的生命周期的使用場景 showImg(https://segmentfault.com/img/bVLTCt?w=2000&h=800); 原文鏈接:React Lif...
摘要:為了使用它們,您可以向組件添加一個屬性,該屬性的值是一個回調(diào)函數(shù),它將接收底層的元素或組件的已掛接實例,作為其第一個參數(shù)。通常最好使用另一個生命周期方法,而不是依賴這個回調(diào)函數(shù),但是很高興知道它存在。 React 常見的面試題 (在 React 里面,你可以知道也可以不知道的事, 但是你會發(fā)現(xiàn)他們確實很有用) 根據(jù)記錄,問這些問題可能不是深入了解他們在使用 React 方面的經(jīng)驗的最...
摘要:此篇文章我們將會繼續(xù)探索組件的特性,特別是生命周期。這些方法叫做組件的生命周期方法且會根據(jù)特定并可預測的順序被調(diào)用。基本上所有的組件的生命周期方法都可以被分割成四個階段初始化掛載階段更新階段卸載階段。 原文:https://medium.com/react-ecosystem/react-components-lifecycle-ce09239010df#.j7h6w8ccc 譯者序...
摘要:我現(xiàn)在寫的這些是為了解決和這兩個狀態(tài)管理庫之間的困惑。這甚至是危險的,因為這部分人將無法體驗和這些庫所要解決的問題。這肯定是要第一時間解決的問題。函數(shù)式編程是不斷上升的范式,但對于大部分開發(fā)者來說是新奇的。規(guī)模持續(xù)增長的應 原文地址:Redux or MobX: An attempt to dissolve the Confusion 原文作者:rwieruch 我在去年大量的使用...
摘要:基礎的理論概念這篇文章是我的一次嘗試,希望能夠形式化的介紹關(guān)于本身的一些理念模型。我對于此實際的理念模型是在每次的更新過程中返回下一個階段的狀態(tài)。的目標是提升對在動畫,布局以及手勢方面的友好度。我已經(jīng)邀請了團隊的成員來對本文檔的準確性進行。 前言 本文主要是對收集到的一些官方或者其他平臺的文章進行翻譯,中間可能穿插一些個人的理解,如有錯誤疏漏之處,還望批評指正。筆者并未研究過源碼,只是...
閱讀 2409·2021-11-19 09:40
閱讀 3575·2021-10-12 10:12
閱讀 1883·2021-09-22 15:04
閱讀 2897·2021-09-02 09:53
閱讀 761·2019-08-29 11:03
閱讀 1122·2019-08-28 18:11
閱讀 1724·2019-08-23 15:28
閱讀 3579·2019-08-23 15:05