国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

從 setState promise 化的探討 體會 React 團隊設計思想

forrest23 / 3500人閱讀

摘要:我們來從設計思想上,和官方團隊的回應上,了解一下否決理由。此外,還有一個方法新的接口設計支持接收一個回調函數,當其子組件掛載時,這個回調函數就會相應觸發。

從 setState 那個眾所周知的小秘密說起...

在 React 組件中,調用 this.setState() 是最基本的場景。這個方法描述了 state 的變化、觸發了組件 re-rendering。但是,也許看似平常的 this.setState() 里面卻也許蘊含了很多鮮為人知的設計和討論。

相信很多開發者已經意識到,setState 方法“或許”是異步的。也許你覺得,看上去更新 state 是如此輕而易舉的操作,這并沒有什么可異步處理的。但是要意識到,因為 state 的更新會觸發 re-rendering,而 re-rendering 代價昂貴,短時間內反復進行渲染在性能上肯定是不可取的。所以,React 采用 batching 思想,它會 batches 一系列連續的 state 更新,而只觸發一次 re-render。

關于這些內容,如果你還不清楚,推薦參考@程墨的系列文章:setState:這個API設計到底怎么樣;英語好的話,可以直接關注長發飄飄的 Eric Elliott 著名的引起系列口水戰的吐槽文:setState() Gate。

或者,直接看下面的一個小例子。
比如,最簡單的一個場景是:

function incrementMultiple() {
  this.setState({count: this.state.count + 1});
  this.setState({count: this.state.count + 1});
  this.setState({count: this.state.count + 1});
}

直觀上來看,當上面的 incrementMultiple 函數被調用時,組件狀態的
count 值被增加了3次,每次增加1,那最后 count 被增加了3。但是,實際上的結果只給 state 增加了1。不信你自己試試~

讓 setState 連續更新的幾個 hack

如果想讓 count 一次性加3,應該如何優雅地處理潛在的異步操作,規避上述問題呢?

以下提供幾種解決方案:

方法一:常見的一種做法便是將一個回調函數傳入 setState 方法中。即 setState 著名的函數式用法。這樣能保證即便在更新被 batched 時,也能訪問到預期的 state 或 props。(后面會解釋這么做的原理)

方法二:另外一個常見的做法是需要在 setState 更新之后進行的邏輯(比如上述的連續第二次 count + 1),封裝到一個函數中,并作為第二個參數傳給 setState。這段函數邏輯將會在更新后由 React 代理執行。即:

setState(updater, [callback])

方法三:把需要在 setState 更新之后進行的邏輯放在一個合適的生命周期 hook 函數中,比如 componentDidMount 或者 componentDidUpdate 也當然可以解決問題。也就是說 count 第一次 +1 之后,出發 componentDidUpdate 生命周期 hook,第二次 count +1 操作直接放在 componentDidUpdate 函數里面就好啦。

一個引起廣泛討論的 Issue

這些內容貌似已經不再新鮮,很多 React 資深開發者其實都是了解的,或能很快理解。

可是,你想過這個問題嗎:
現代 javascript 處理異步流程,很流行的一個做法是使用 promises,那么我們能否應用這個思路解決呢?

說具體一些,就是調用 setState 方法之后,返回一個 promise,狀態更新完畢后我們在調用 promise.then 進行下一步處理。

答案是肯定的,但是卻被官方否決了。

我是如何得出“答案是肯定的,但是是不被官方建議的。”這個結論,喜歡刨根問底的讀者請繼續往下閱讀,相信你一定會有所啟發,也能更充分理解 React 團隊的設計思想。

第 2642 Issue 解讀和深入分析

我是一步一步在 Facebook 開源 React 的官方 Github倉庫上,找到了線索。

整個過程跟下來,相信在各路大神的 comments 之間,你會對 React 的設計理念以及 javascript 解決問題的思路有一個更清晰的認識。

一切的探究始于 React 第 #2642 號 issue: Make setState return a promise,上面關于 count 連續 +3 大家已經有所了解。接下來我舉一個真正在生產開發中的例子,方便大家理解討論。

我們現在開發一個可編輯的 table,需求是:當用戶敲下“回車”,光標將會進入下一行(調用 setState 進行光標移動);如果用戶當前已經在最后一行,那么敲下回車時,第一步將先創建一個新行(調用 setState 創建新的最后一行),在新行創建之后,再去新的最后一行進行光標聚焦(調用 setState 進行光標移動)。

常見且錯誤的處理在于:

this.setState({
  selected: input
  // 創建新行
}.bind(this));
this.props.didSelect(this.state.selected);

因為第一個 this.setState 是異步進行的話,下一處 didSelect 方法執行 this.setState 時,所處理的參數 this.state.selected 可能還不是預期的下一行。很明顯,這就是 this.setState 的異步性帶來的問題。

為了解決這個完成這樣的邏輯,想到了 setState 第二個參數解決方案,用代碼簡單表述就是:

this.setState({
  selected: input
  // 創建新行
}, function() {
    this.props.didSelect(this.state.selected);
}).bind(this));

這種解決方案是使用嵌套的 setState 方法。但這無疑潛在地會帶來嵌套地獄的問題。

Promise 化方案登場

這一切是不是像極了傳統 Javascript 處理異步老套路?解決回調地獄,你是不是應激性地想到了 promise?

如果 setState 方法返回的是一個 promises,自然會更加優雅:

setState() currently accepts an optional second argument for callback and returns undefined.

This results in a callback hell for a very stateful component. Having it return a promise would make it much more managable.

如果用 promise 風格解決問題的話,無非就是:

this.setState({
  selected: input
}).then(function() {
  this.props.didSelect(this.state.selected);
}.bind(this));

看上去沒什么問題,一個很時髦的設計。但是,我們進一步想:如果想讓 React 支持這樣的特性,采用提出 pull request 的方式,我們該如何去改源代碼呢?

探索 React 源碼,完成 setState promise 化的改造

首先找到源碼中關于 setState 定義的地方,它在 react/src/isomorphic/modern/class/ReactBaseClasses.js 這個目錄下:

ReactComponent.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === "object" ||
      typeof partialState === "function" ||
      partialState == null,
    "setState(...): takes an object of state variables to update or a " +
      "function which returns an object of state variables.",
  );
  this.updater.enqueueSetState(this, partialState, callback, "setState");
};

我們首先看到一句注釋:

You can provide an optional callback that will be executed when the call to setState is actually completed.

這是采用 setState 第二個參數傳入處理回調的基礎。

另外,從注釋中我們還找到:

When a function is provided to setState, it will be called at some point in the future (not synchronously). It will be called with the up to date component arguments (state, props, context).

這是給 setState 方法直接傳入一個函數的基礎。

言歸正傳,如何改動源碼,使得 setState promise 化呢?
其實很簡單,我直接上代碼:

 ReactComponent.prototype.setState = function(partialState, callback) {
   invariant(
     typeof partialState === "object" ||
       typeof partialState === "function" ||
       partialState == null,
      "setState(...): takes an object of state variables to update or a " +
        "function which returns an object of state variables.",
    );
 +  let callbackPromise;
 +  if (!callback) {
 +    class Deferred {
 +      constructor() {
 +        this.promise = new Promise((resolve, reject) => {
 +          this.reject = reject;
 +          this.resolve = resolve;
 +        });
 +      }
 +    }
 +    callbackPromise = new Deferred();
 +    callback = () => {
 +      callbackPromise.resolve();
 +    };
 +  }
    this.updater.enqueueSetState(this, partialState, callback, "setState");
 +
 +  if (callbackPromise) {
 +    return callbackPromise.promise;
 +  }
  }; 

我用 “+” 標注了對源碼所做的更改。如果開發者調用 setState 方法時,傳入的是一個 javascript 對象的話,那么會返回一個 promise,這個 promise 將會在 state 更新完畢后 resolve。
如果您看不懂的話,建議補充一下相關的基礎知識,或者留言與我討論。

解決方案有了,可是 React 官方會接受這個 PR 嗎?

很遺憾,答案是否定的。我們來從 React 設計思想上,和 React 官方團隊的回應上,了解一下否決理由。

sebmarkbage(Facebook 工程師,React 核心開發者)認為:解決異步帶來的困擾方案其實很多。比如,我們可以在合適的生命周期 hook 函數中完成相關邏輯。在這個場景里,就是在行組件的 componentDidMount 里調用 focus,自然就完成了自動聚焦。

此外,還有一個方法:新的 refs 接口設計支持接收一個回調函數,當其子組件掛載時,這個回調函數就會相應觸發。

所有上述模式都可以完全取代之前的問題方案,即使不能也不意味著要接受 promises 化這個PR。

為此,sebmarkbage 說了一段很扎心的話:

Honestly, the current batching strategy comes with a set of problems right now. I"m hesitant to expand on it"s API before we"re sure that we"re going to keep the current model. I think of it as a temporary escape until we figure out something better.

問題的根源在于現有的 batching 策略,實話實說,這個策略帶來了一系列問題。也許這個在后期后有調整,在 batching 策略是否調整之前,盲目的擴充 setState 接口只會是一個短視的行為。

對此,Redux 原作者 Dan Abramov 也發表了自己的看法。他認為,以他的經驗來看,任何需要使用 setState 第二個參數 callback 的場景,都可以使用生命周期函數 componentDidUpdate (and/or componentDidMount) 來復寫。

In my experience, whenever I"m tempted to use setState callback, I can achieve the same by overriding componentDidUpdate (and/or componentDidMount).

另外,在一些極端場景下,如果開發者確實需要同步的處理方式,比如如果我想在某 DOM 元素掛載到屏幕之前做一些操作,promises 這種方案便不可行。因為 Promises 總是異步的。反過來,如果 setState 支持這兩種不同的方式,那么似乎也是完全沒有必要而多余的。

在社區,確實很多第三方庫漸漸地接受使用 promises 風格,但是這些庫解決的問題往往都是強異步性的,比如文件讀取、網絡操作等等。 React 似乎沒有必要增加這么一個 confusing 的特性。

另外,如果每個 setState 都返回一個 promises,也會帶來性能影響:對于 React 來說,setState 將必然產生一個 callback,這些 callbacks 需要合理儲存,以便在合適時間來觸發。

總結一下,解決 setState 異步帶來的問題,有很多方式能夠完美優雅地解決。在這種情況下,直接讓 setState 返回 promise 是畫蛇添足的。另外,這樣也會引起性能問題等等。

我個人認為,這樣的思路很好,但是難免有些 Overengineering。

這一次為自己瘋狂,我和我的倔強

怎么樣,是否說服你了呢?如果沒有,在不能更改 React 源碼情況下,你就是想用 promise 化的 setState,怎么辦呢?

這里提供一個“反模式”的方案:我們不改變源碼,自己也可以進行改造,原理上就是直接對 this.setState 進行攔截,進而進行 promise 化,再封裝一個新的接口出來。

import Promise from "bluebird";

export default {
  componentWillMount() {
    this.setStateAsync = Promise.promisify(this.setState);
  },
};

之后,便可以異步地:

this.setStateAsync({
  loading: true,
}).then(this.loadSomething).then((result) => {
  return this.setStateAsync({result, loading: false});
});

當然,也可以使用原聲的 promises:

function setStatePromise(that, newState) {
    return new Promise((resolve) => {
        that.setState(newState, () => {
            resolve();
        });
    });
}

甚至...我們還可以腦洞大開使用 async/await。

最后,所有這種做法非常的 dirty,我是不建議這么使用的。

總結

其實研究一下 React Issue,深入源碼學習,收獲確實很多。總結也沒有更多想說的了,無恥滴做個廣告吧:

我的其他關于 React 文章:

React Redux 中間件思想遇見 Web Worker 的靈感(附demo)

通過實例,學習編寫 React 組件的“最佳實踐”

React 組件設計和分解思考

[從 React 綁定 this,看 JS 語言發展和框架設計]()

React 服務端渲染如此輕松 從零開始構建前后端應用

做出Uber移動網頁版還不夠 極致性能打造才見真章

解析Twitter前端架構 學習復雜場景數據設計

React Conf 2017 干貨總結1: React + ES next = ?

React+Redux打造“NEWS EARLY”單頁應用 一個項目理解最前沿技術棧真諦

一個react+redux工程實例

Happy Coding!

PS:
作者Github倉庫 和 知乎問答鏈接
歡迎各種形式交流。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/51194.html

相關文章

  • setState promise 化的探討 體會 React 團隊設計思想

    摘要:我們來從設計思想上,和官方團隊的回應上,了解一下否決理由。此外,還有一個方法新的接口設計支持接收一個回調函數,當其子組件掛載時,這個回調函數就會相應觸發。 從 setState 那個眾所周知的小秘密說起... 在 React 組件中,調用 this.setState() 是最基本的場景。這個方法描述了 state 的變化、觸發了組件 re-rendering。但是,也許看似平常的 th...

    caiyongji 評論0 收藏0
  • React 應用設計之道 - curry 化妙用

    摘要:右側展現對應產品。我們使用命名為的對象表示過濾條件信息,如下此數據需要在組件中進行維護。因為組件的子組件和都將依賴這項數據狀態。化應用再回到之前的場景,我們設計化函數,進一步可以簡化為對于的偏應用即上面提到的相信大家已經理解了這么做的好處。 showImg(https://segmentfault.com/img/remote/1460000014458612?w=1240&h=663...

    sewerganger 評論0 收藏0
  • React 應用設計之道 - curry 化妙用

    摘要:右側展現對應產品。我們使用命名為的對象表示過濾條件信息,如下此數據需要在組件中進行維護。因為組件的子組件和都將依賴這項數據狀態。化應用再回到之前的場景,我們設計化函數,進一步可以簡化為對于的偏應用即上面提到的相信大家已經理解了這么做的好處。 showImg(https://segmentfault.com/img/remote/1460000014458612?w=1240&h=663...

    LinkedME2016 評論0 收藏0
  • 阿里云前端周刊 - 第 23 期

    摘要:寫的姿勢前兩天去帝都參加第三屆前端開發者大會,見了很多老朋友,也認識了很多新朋友。 推薦 1. 組件化設計思維 – 從規范到工具的構建與探索 http://www.zcool.com.cn/artic... 阿里巴巴在中臺戰略的背景下,設計提效又再次推動著設計思維的變革。設計師們不僅僅需要出色地完成業務需求的設計,同時還需要思考設計的價值,也就是經常提到的最佳方案性價比。我們需要在設計...

    wuyangnju 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<