函數式響應式編程 - 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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...