函數式響應式編程 - Functional Reactive Programming

来源:https://www.cnblogs.com/apolis/archive/2019/08/31/11437688.html
-Advertisement-
Play Games

我們略過概念,直接看函數式響應式編程解決了什麼問題。 故事從下麵這個例子展開: 兩個密碼輸入框,一個提交按鈕。 密碼、確認密碼都填寫並一致,允許提交;不一致提示錯誤。 HTML 如下: 常規做法 初始版 加強版 問題: 輸入密碼時,確認密碼還是空的,出現密碼不一致錯誤提示,干擾用戶輸入。 期望: 確 ...


我們略過概念,直接看函數式響應式編程解決了什麼問題。

故事從下麵這個例子展開:

兩個密碼輸入框,一個提交按鈕。

密碼、確認密碼都填寫並一致,允許提交;不一致提示錯誤。

HTML 如下:

<input id="pwd" placeholder="輸入密碼" type="password" /><br />
<input id="confirmPwd" placeholder="再次確認" type="password" />
<label id="errorLabel"></label><br />
<button id="submitBtn" disabled>提交</button>

常規做法

初始版

const validate = () => {
  const match = pwd.value === confirmPwd.value;
  const canSubmit = pwd.value && match;
  errorLabel.innerText = match ? "" : "密碼不一致";
  if (canSubmit) {
    submitBtn.removeAttribute("disabled");
  } else {
    submitBtn.setAttribute("disabled", true);
  }
};

pwd.addEventListener("input", validate);
confirmPwd.addEventListener("input", validate);

加強版

問題: 輸入密碼時,確認密碼還是空的,出現密碼不一致錯誤提示,干擾用戶輸入。

期望: 確認密碼沒輸入過時,不提示錯誤。

為解決這個問題,用 isConfirmPwdTouched 標識確認密碼輸入框是否輸入過內容。

let isConfirmPwdTouched = false;
pwd.addEventListener("input", () => {
  if (isConfirmPwdTouched) validate();
});
confirmPwd.addEventListener("input", () => {
  isConfirmPwdTouched = true;
  validate();
});

測試同學又發現了一個 bug:
不輸密碼,直接輸入確認密碼,這時又出現了錯誤提示。

為解決這個問題,再加入一個標識位 isPwdTouched

let isConfirmPwdTouched = false;
let isPwdTouched = false;
pwd.addEventListener("input", () => {
  isPwdTouched = true;
  if (isPwdTouched && isConfirmPwdTouched) validate();
});
confirmPwd.addEventListener("input", () => {
  isConfirmPwdTouched = true;
  if (isPwdTouched && isConfirmPwdTouched) validate();
});

旗艦版

問題: 確認密碼輸入框輸入第一個字元時就會提示密碼不一致,干擾用戶輸入。

期望: 連續輸入時,不提示錯誤。

為解決這個問題,高級一點的做法是使用高階函數 debounce,否則又要多個標識位。

const debounce = (fn, ms) => {
  let timeoutId;
  return (...args) => {
    if (timeoutId !== undefined) clearTimeout(timeoutId);
    timeoutId = setTimeout(fn.bind(null, ...args), ms);
  };
};

const validate = () => {
  const match = pwd.value === confirmPwd.value;
  const canSubmit = pwd.value && match;
  errorLabel.innerText = match ? "" : "密碼不一致";
  if (canSubmit) {
    submitBtn.removeAttribute("disabled");
  } else {
    submitBtn.setAttribute("disabled", true);
  }
};

const debouncedValidate = debounce(validate, 200);

let isConfirmPwdTouched = false;
let isPwdTouched = false;
pwd.addEventListener("input", () => {
  isPwdTouched = true;
  if (isPwdTouched && isConfirmPwdTouched) debouncedValidate();
});
confirmPwd.addEventListener("input", () => {
  isConfirmPwdTouched = true;
  if (isPwdTouched && isConfirmPwdTouched) debouncedValidate();
});

常規做法的問題

可以看出:隨著交互越來越複雜,常規做法的標識位越來越多,代碼的邏輯越來越難理清。

常規做法實際實現了下圖的邏輯:

圖看起來清晰易懂,但可惜的是 代碼和這張圖長得並不像。

有沒有一種辦法,讓我們的代碼和上圖一樣邏輯清晰呢?
答案就是:函數式響應式編程。

用它寫代碼就像是在畫上面那張圖。


函數式響應式做法

這裡使用的庫是rxjs

const { fromEvent, combineLatest } = rxjs;
const { map, debounceTime } = rxjs.operators;

const pwd$ = fromEvent(pwd, "input").pipe(map(e => e.target.value));
const confirmPwd$ = fromEvent(confirmPwd, "input").pipe(
  map(e => e.target.value)
);

combineLatest(pwd$, confirmPwd$)
  .pipe(
    debounceTime(200),
    map(([pwd, confirmPwd]) => ({
      match: pwd === confirmPwd,
      canSubmit: pwd && pwd === confirmPwd
    }))
  )
  .subscribe(({ match, canSubmit }) => {
    errorLabel.innerText = match ? "" : "密碼不一致";
    if (canSubmit) {
      submitBtn.removeAttribute("disabled");
    } else {
      submitBtn.setAttribute("disabled", true);
    }
  });

沒看出代碼和上面那張圖有什麼相似?我們來拆解一下。

const pwd$ = fromEvent(pwd, "input").pipe(map(e => e.target.value));
const confirmPwd$ = fromEvent(confirmPwd, "input").pipe(
  map(e => e.target.value)
);

我們把 pwd$, confirmPwd$ 稱作流,可以把它們想象成河流,裡面流淌著數據。

map 把流中的 input event 轉換為輸入框的 value

combineLatest(pwd$, confirmPwd$);

combinLatest 的作用在這裡有兩個。

  1. combine:把 pwd$, confirmPwd$ 合成一個新流
  2. latest:新流中的數據為 pwd$, confirmPwd$ 最新的數據的組合
    1. pwd$ 產生數據 a 時,confirmPwd$ 還沒產生過數據,新流不產生數據;
    2. pwd$ 產生數據 ab 時,confirmPwd$ 還沒產生過數據,新流不產生數據;
    3. confirmPwd$ 產生數據 a 時,
      由於 pwd$, confirmPwd$ 都產生過數據了,pwd$ 流最新產生的數據為 ab
      新流產生數據 [ab, a]
    4. confirmPwd$ 產生數據 ab 時,
      由於 pwd$, confirmPwd$ 都產生過數據了,pwd$ 流最新產生的數據為 ab
      新流產生數據 [ab, ab]
combineLatest(pwd$, confirmPwd$).pipe(
  debounceTime(200),
  map(([pwd, confirmPwd]) => ({
    match: pwd === confirmPwd,
    canSubmit: pwd && pwd === confirmPwd
  }))
);

debounceTime(200) 的作用和普通做法里的 debounce 功效一樣。

  1. 上游流產生 [ab, a] 時,新流不立刻把數據傳給下游,而是要延遲 200ms。
  2. 200ms 不到,上游流又傳來數據 [ab, ab],新流丟棄之前的數據。
  3. 200ms 後,上游流沒有傳來新數據,新流將 [ab, ab] 傳給下游。

map[ab, ab] 轉化為 { match: true, canSubmit: true }


再比較一下,是不是很像呢?


總結

函數式響應式編程創造的初衷就是解決 listener callback 邏輯表達不直觀,代碼亂成一團麻 的問題。

至於它為什麼叫函數式響應式編程,是因為它的實現借鑒了函數式、響應式編程思想。
例如:

  • declarative
    關註做什麼,而不是怎麼做。隱藏了很多細節。
  • reactive
    函數式響應式做法,input 輸入有變化,button 狀態就會跟著變。
    相比較 input 輸入變了、再調一遍函數、根據函數輸出修改 button 狀態,要自動化。
    這句話說的有漏洞,常規做法也很自動化。先跳過吧,以後寫一篇響應式編程的文章。
  • ......
  • ......

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 我們知道過去對於Kafka的定義是分散式,分區化的,帶備份機制的日誌提交服務。也就是一個分散式的消息隊列,這也是他最常見的用法。但是Kafka不止於此,打開最新的官網。 我們看到Kafka最新的定義是:Apache Kafka® is a distributed streaming platform ...
  • 五、限定查詢和排序顯示 5.1、限定查詢 5.1.1 認識限定查詢 例如 :如果一張表中有 100w 條數據,一旦執行了 “ SELECT FROM 表 ” 語句之後,則將在屏幕上顯示表中全部數據行的記錄,這樣既不方便瀏覽,也可能造成死機的問題,所以此時就必須對查詢的結果進行篩選,只選出對自己有用的 ...
  • 現象: MYSQL在安裝完成後,系統能正常運行,但是第二天出現瞭如下一個提示框,如下圖: 給個人人都看得懂的如下圖: 解決辦法: 這個是新版本MySQL服務自帶的一個定時任務,每天23:59:59執行的任務,我們只需要在本地系統的“任務計劃程式”中將這個定時任務幹掉就OK了。開始 -> 在 “ 搜索 ...
  • 有時候不用的指標的絕對值不能比,但是轉轉為百分比的形式就容易看出波動了,是數據分析的好用的一個分析函數20:00:24 SYS@orcl> conn scott/tiger;Connected.20:00:30 SCOTT@orcl> create table test20:01:22 2 (20:... ...
  • 如需轉載,請註明出處:Flutter學習筆記(25)--ListView實現上拉刷新下拉載入 前面我們有寫過ListView的使用:Flutter學習筆記(12)--列表組件,當列表的數據非常多時,需要使用長列表,比如淘寶後臺的訂單列表,手機通訊錄等,這些列表項數據很多,長列表也是使用ListVie ...
  • (馬蜂窩技術公眾號原創內容,ID: mfwtech) 熟悉馬蜂窩的朋友一定知道,點擊馬蜂窩 App 首頁的發佈按鈕,會發現發佈的內容已經被簡化成「圖文」或者「視頻」。 長期以來,游記、問答、攻略等圖文形式的形態一直是馬蜂窩發展的優勢所在。將短視頻提升至與圖文併列的位置,是因為對於今天的移動互聯網用戶 ...
  • 1.map組件的高度如果想要鋪滿屏幕,要是使用height:100vh樣式2.獲取位置要在app.json中標明許可權3.先使用wx.getLocation獲取自己的位置,然後再回調中使用setData方法,賦予數據給前臺頁面展示標註點 index.js index.wxml app.json ...
  • 步驟: 1.進入鬥魚直播; 2.按下f12; 3.點擊Console; 4.複製下方代碼黏貼至Console下,回車就會自動輸彈幕; 友情提醒:若想終止請在console下輸入stop()--或--按f5; 代碼如下: const area = document.getElementsByClass ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...