React more Reactive

Reactive 就是響應式,在如今已經算是個老概念了。爲何說 more reactive 呢,其實本文最終的主旨仍是要給尚未開始接觸 Hooks 或者對於 Hooks 不是那麼感冒的同窗安利一下。Hooks 不光是一組 API,他背後承載的是 React 團隊想要宣導的一套編程理念。這其中的一部分,就是咱們今天的主角——響應式。咱們今天就來看看,響應式給咱們帶來了什麼,以及 hooks 和他有什麼關係。
javascript

1、先談主要矛盾

在開始講正題以前,仍是先爲討論定下一個主旨。React 是開發 Web 應用用的一種框架,開發 Web GUI 是咱們的一個討論前提。當前的大背景下,咱們的前端開發工做中,主要矛盾之一就是 不斷增加的應用複雜度應用開發成本 之間的矛盾。人性就是想付出更少獲得更多,複雜度的增加是咱們沒法去改變的,咱們只能在成本上作更多的文章,這其中,讓一份程序更好寫也更好懂就是一個很常見的作法。這也是咱們今天的主角——響應式編程帶給咱們的東西。
前端

2、什麼是響應式編程

首先咱們能夠先看看學術派的 wikipedia 對於響應式有着什麼樣的解釋:





翻譯過來就是 響應式編程是一種關注於數據流與變動的傳播的聲明式編程範式。 從這段描述中咱們須要抓取幾個關鍵詞,【聲明式編程範式】、【數據流】、【變動傳播】。


聲明式編程範式是其核心思想,與其對應的能夠是命令式的。聲明式更像是咱們在數學課上學習的那些公式,是聲明以後就是一直有效的。上面文字對 a:=b+c 這一表達在兩種範式思惟下的解釋其實很好地表現了這一點。這種範式的好處是在於,咱們可以在其指導下,更清晰得去梳理咱們的邏輯,且更容易抽象拆分與組合。整個過程很像總結公式定理,公式的組合推論又造成新的公式。每一個公式聲明均可以很簡單,簡單的公式每每是穩定的,且易懂的,例如「兩點之間直線最短」或者「E=mc^2」等等。


數據流則是該範式中重點關注的對象。響應式編程其描述的主體就是數據流。這種描述以數據做爲串聯,弱化了聲明的先後順序的影響。



變動傳播則是實現數據流的核心機制。通常這種機制能夠分爲 Pull 和 Push,Pull 是下游主動去獲取數據,Push 則是變化源主動觸發依賴方的更新。在響應式的實現中,可能 Push 的形式會要更常見一些。方法有不少,經常使用的是發佈訂閱與監聽者模式,不少響應式編程的實現庫,也主要是基於這個方向去打造的。背後的思想其實就是爲了完成聲明式而實現的控制反轉。java




下面舉個不恰當的例子,來講明一下簡單的響應式編程。在電子系統邏輯設計中,咱們常以門電路圖來描述,用一樣的方式,咱們也能夠畫出這樣的一個代碼描述。A 和 B 能夠想象成任何上游操做器,對接整個電路的輸入端。實現上咱們暫時用 React Hooks 來表達。這裏的實現方式可能有點極端,不要在乎哈。


電路圖:                                                       
     


代碼描述邏輯:react

function Diag({ A, B }) {
  const U4 = useMemo(() => !B, [B]);
  const U5 = useMemo(() => !A, [A]);
  const U1 = useMemo(() => A && U4, [A, U4]);
  const U2 = useMemo(() => B && U5, [B, U5]);
  const X = useMemo(() => U1 && U2, [U1, U2]);
  return X;
}


能夠看到咱們每一行都很簡單,而且由於是聲明式的,先後執行順序並沒關係。最後會造成從 A,B 到 X 的數據流。代碼比較簡單,你們應該一看就會,體會一下便可。


在 Javascript 討論圈中,對於響應式編程也有另一種解釋方式,說他是一種關注異步數據流處理的編程範式,這裏的異步數據流也能夠是咱們前端常提到的事件這個概念。我我的理解就是對一系列不肯定什麼時間發生的事情,預先提供處理方式。在這種思路的指導下,咱們能夠將應用中一切的行爲、變動都以數據流的方式作描述,特別是異步的那些。而後咱們可使用一套針對數據流的操做符集合去組合邏輯。能夠發現這種解釋實際上是和上面總結的是相通的。最終仍是回到了數據流、變動傳播、聲明式範式的三件套。
編程

3、爲何這麼作更好

道理我都懂,可是這種作法究竟好在什麼地方呢?下面咱們從五個角度去分析一下。
redux

1. 易讀性更強,下降理解成本

都說代碼是寫給機器去運行的,同時也是寫給人看的,人看不懂的代碼不是好代碼。的確,排除競賽等特殊場景,代碼的易讀性、可維護性是其在工業生產中很重要的質量指標之一,特別是在前端開發中,甚至能夠排上首位。那爲何就說這樣寫易讀性就更好了呢,咱們先很少說,先拿一些簡單的例子你們感覺一下。框架

// 命令式的
let items = [];
items.push(1);
items.push(2);
items.push(3);
items.push(4);

const newItems = items
  .filter(item => item % 2 === 0)
  .map(item => item + 0.5);

newItems.forEach(item =>
  console.log(item)
);
// Output: 2.5
// Output: 4.5

items.push(5);
items.push(6);
items.push(7);
items.push(8);

// Call again
// 響應式的
import Rx from 'rxjs/Rx';

let items = new Rx.Subject();
items.onNext(1);
items.onNext(2);
items.onNext(3);
items.onNext(4);

const newItems = items
  .filter(item => item % 2 === 0)
  .map(item => item + 0.5);

newItems.forEach(item =>
  console.log(item)
);

items.onNext(5);
items.onNext(6);
// Output: 6.5
items.onNext(7);
items.onNext(8);
// Output: 8.5

實現的功能很簡單,就很少作解釋了。


總結一下,命令式的程序,就是你告訴計算機你每一步應該怎麼作,而響應式則是你告訴計算機,當發生了什麼事情以後應該怎麼作。聽上去是否是就是命令式的更爲保姆式一點呢,這種表達形式也與人平常的交流習慣不太同樣。並且越細節的行爲,可讀性相對就越差的。在響應式的寫法下,數據的生產與處理是能夠剝離的,每一塊的代碼能夠有本身專注的關注點。這也得益於響應式他的執行順序弱依賴的特性。
異步

2. 更符合交互類應用開發直覺

現代前端工程框架中,無一例外得都採用了一種簡單高效的方式去描述總體邏輯,那就是 UI=f(state)。這一樣也構成了整個思想中最重要的根基定理之一,界面只與應用狀態直接發生關係。數據流就是對應用狀態更爲具體的聲明描述。長期以來,UI 開發方式的演化證實了,UI 開發本質上就是聲明式的,由於 UI 上其數據變化的觸發源是多樣化的,數據流向是複雜可組合的,數據變化時機是無規律的,所以經過聲明式的表達方式能夠更簡潔明瞭地表達,而且行爲的可預期性能夠更強。畢竟優秀的程序須要讓人看懂。從而能同時下降開發與維護的成本。



svg

3. 依賴靜態上下文,而非運行時上下文

在響應式編程中,咱們更少得會去依賴運行時的上下文,由於咱們的數據關係是在書寫聲明時就肯定的,因此其依賴的是靜態上下文。在 JS 中,詞法分析階段就會確認惟一的做用域,這個就是上面說到的靜態上下文。


那這其中有哪些好處呢?第一就是咱們在閱讀代碼的時候就能根據上下文內容確認總體邏輯,減小熟悉邏輯和排查問題的時間成本。第二是咱們在編譯階段就可以進行靜態代碼分析,去保障咱們的數據流是不是正確的。第三是咱們在書寫響應式的具體邏輯時,更習慣用純函數去描述,這樣在測試時,也就更方便咱們去細分測試,而不依賴於運行時的一些總體信息。
模塊化

4. 與具體實現解耦

由於響應式編程是一種聲明式編程範式,你甚至可使用僞代碼進行編寫,或者使用圖來描述
image.pngimage.png
咱們並不須要特別關心其具體實現細節,就可以保證總體邏輯的正確性。在切換技術方案,或者自己技術升級時都能儘可能少的引入遷移成本。儘可能保持一致的設計理念。舉個例子,RxJava、RxJs 等,你能發現多種語言的實現版本。

5. 邏輯的可組合性

現代工業崛起的核心是規模化生產,而規模化生產的基石我認爲是標準統一與模塊化細分。響應式編程的順序弱關聯,依賴串聯等特性使邏輯的自由組裝更易實現。而且由於有了一致的編程範式,不一樣目的的邏輯模塊的組合成本也會更低。

4、use Hooks, thinking in Reactive

很幸運,咱們在使用 React 框架,React 自己就是基於響應式理念開發出來的。不過本來生命週期函數的寫法,多多少少有點其餘編程方式的影子,有點命令式一點,不夠 Reactive。隨着 Hooks 的加入,大大增強了 React 開發過程當中響應式的短板。


Hooks 中我認爲有一個很是重要的概念,就是 deps,依賴。他改變了咱們原先在書寫組件時更關注在什麼時機執行什麼的思路。讓咱們更關注數據自己的變化,真正經過數據去驅動應用的行爲。這背後的重點是他改變了咱們編程的思考方式,讓咱們從深刻理解 React 在執行的哪一個時間點會作什麼事情、調用咱們的哪一個生命週期函數這種事情中解放出來(畢竟有時候生命週期還會調整)。咱們只須要關注,在當前的組件上下文中(這個在框架體系內逃不開,並且我以爲 functional 的書寫方式自然解決了一部分 immutable 的問題也未必是壞事)中,數據的變化會如何傳遞,會觸發哪些反作用就能夠了。代碼也會變得更爲簡潔。對比一下下:

// Classical React Component
class Chart extends Component {
  state = {
    data: null,
  }
  componentDidMount() {
    const newData = getDataWithinRange(this.props.dateRange)
    this.setState({data: newData})
  }
  componentDidUpdate(prevProps) {
    if (prevProps.dateRange != this.props.dateRange) {
      const newData = getDataWithinRange(this.props.dateRange)
      this.setState({data: newData})
    }
  }
  render() {
    return (
      <svg className="Chart" />
    )
  }
}
// Component with hooks
const Chart = ({ dateRange }) => {
  const [data, setData] = useState()
  useEffect(() => {
    const newData = getDataWithinRange(dateRange)
    setData(newData)
  }, [dateRange])
  return (
    <svg className="Chart" />
  )
}


第二個我以爲頗有意思的地方是 Hooks 與組件的實例不在書寫層面上作強關聯,所以邏輯能夠獨立存在,促進了邏輯與視圖的進一步分離,再也不須要使用過去相對笨重的萬物皆組件的思路去拆分代碼。這背後還有一個奇特的隱式上下文,我認爲他對響應式編程最大的做用,在於可以將多個不相關的做用域作疊加。


目前 Hooks 中提供的功能都還比較基礎。還較難知足複雜的數據流聲明。之前經常使用的 redux + saga 的實現則更像是一個事件模式,其本質可能只算是響應式中的「觸發」環節。有一個叫作 redux-observable 的庫是將 redux 的 action 模型做爲觸發器,將其與 Rxjs 的數據流描述能力組合在一塊兒,可以使用更加複雜的數據流聲明方式,是一個不錯的嘗試。與 Hooks 結合就能造成一個閉環。

5、小結一下

本文並非想說 Hooks 是萬能的。只是在一個啓發下,認爲響應式編程也許更適合前端開發的模式。對於一部分 OOP 的鼓吹者,我認爲應該反思一下在咱們編寫前端 UI 時,是否真的用到了 OOP 的多種特性。例如,繼承、多態等,JS 自己就難以支持真正的多態,其繼承能力在實際使用中也漸漸少用了,畢竟在 UI 領域,組合大於繼承仍是比較主流的。OOP 更適合用於抽象複雜的數據模型,不過這個就不在此次的討論範圍內了,咱們能夠下次再水一篇文章,名字能夠暫定《面向對象是否是已通過氣了》。


最後點一下題,要想代碼漂亮,邏輯清晰。響應式編程理念值得你一試。Make your React App more Reactive. Thanks for reading :)

文章可隨意轉載,但請保留此原文連接。
很是歡迎有激情的你加入 ES2049 Studio,簡歷請發送至 caijun.hcj@alibaba-inc.com
相關文章
相關標籤/搜索