正交React組件的好處

?[譯] 正交React組件的好處

做者:Dmitri Pavlutinjavascript

原文連接html

正交性是幾何學中的術語,互爲直角的直角座標系就具備正交性; 在計算技術中表示不依賴性或解耦性。非正交的系統意味着系統中各組件互相高度依賴,這類系統中是再也不有局部修正的狀況了。

1. 爲何好的系統設計是重要的?

在5年前,我正在爲一家歐洲初創公司開發跨平臺移動應用。初期的功能是易於實現的,進展順利。java

6個月過去,須要不斷的在現有功能上添加新的功能,隨着時間的推移,對現有模塊的更改愈來愈困難。react

在部分需求上,開始拒絕某些新的功能和更新,由於它們將須要太多的時間實施。這個故事以移動應用程序徹底重寫爲原生應用而了結,主要是由於進一步的維護很是的困難。ios

我將上述問題歸咎於跨平臺框架中的錯誤,歸咎於客戶端需求變動。但這不是主要問題,我沒有意識到一點,我一直在於高度耦合的模塊組件作戰,就像堂吉柯德大戰風車同樣。編程

我忽略了組件易於更改的特性。我未遵循良好的設計原則,沒有賦予組件適應潛在的變化的特性。學習設計原則,一個特別有影響力的正交原理,它能夠隔離因爲不一樣緣由而變化的事物。axios

2. 正交組件

若是A和B正交的,則更改A不會更改B(反之亦然)。這就是正交性的概念。在廣播設備中,音量和電臺選擇控件是正交的。音量控制僅更改音量,而電臺選擇控件僅更改接收到的電臺。瀏覽器

想象一下廣播設備壞了,音量控制可更改音量,但也可修改選定的廣播電臺。音量控制和電臺選擇控制不是正交的:音量控制會產生反作用。當你嘗試向緊密耦合的組件中添加更改時,也會發生相同的狀況:你不得不面對更改產生的反作用。服務器

若是一個組件的更改不影響其餘組件,則兩個或多個組件正交。例如,顯示文章列表的組件應與獲取文章的邏輯正交。微信

一個好的React應用程序設計是正交的:

  • UI元素
  • 全局狀態管理
  • 持久性邏輯(本地存儲,cookie)
  • 獲取數據 (fetch library, REST or GraphQL)

將組件隔離,並獨立封裝。這將使你的組件正交,而且你所作的任何更改都將被隔離,而且僅集中在一個組件上。這就是可預測且易於開發的系統的訣竅。

3.使組件正交以獲取獲取

讓咱們來看看下面的例子:

import React, { useState } from 'react';
import axios from 'axios';
import EmployeesList from './EmployeesList';

function EmployeesPage() {
  const [isFetching, setFetching] = useState(false);
  const [employees, setEmployees] = useState([]);

  useEffect(function fetch() {
    (async function() {
      setFetching(true);
      const response = await axios.get("/employees");
      setEmployees(response.data);
      setFetching(false);
    })();
  }, []);
  
  if (isFetching) {
    return <div>Fetching employees....</div>;
  }
  return <EmployeesList employees={employees} />;
}

在以上代碼中<EmployeesPage>經過axios庫,執行GET請求獲取數據。

若是之後從axios和REST切換到GraphQL會發生什麼?若是應用程序具備數十個與獲取數據邏輯耦合的組件,則必須手動更改全部組件。其實有更好的方法,讓咱們從組件中分離出獲取數據邏輯細節。

一個很好的方法是使用React的新功能Suspense:

import React, { Suspense } from "react";
import EmployeesList from "./EmployeesList";

function EmployeesPage({ resource }) {
  return (
    <Suspense fallback={<h1>Fetching employees....</h1>}>
      <EmployeesFetch resource={resource} />
    </Suspense>
  );
}

function EmployeesFetch({ resource }) {
  const employees = resource.employees.read();
  return <EmployeesList employees={employees} />;
}

如今,直到<EmployeesFetch>讀取異步資源以前,<EmployeesPage>都會掛起.

重要的是<EmployeesPage>與獲取數據邏輯正交<EmployeesPage>不在意axios是否實現抓取,你能夠輕鬆地將axios更改成本地獲取、或遷移爲GraphQL:<EmployeesPage>不受影響。

React:Suspense 文檔

4.使視圖與滾動監聽器正交

假設您你要跳轉到頂部按鈕,以在用戶向下滾動500px以上時顯示。單擊該按鈕時,頁面將自動滾動到頂部。

<ScrollToTop>第一個簡單的實現:

import React, { useState, useEffect } from 'react';

const DISTANCE = 500;

function ScrollToTop() {
  const [crossed, setCrossed] = useState(false);

  useEffect(
    function() {
      const handler = () => setCrossed(window.scrollY > DISTANCE);
      handler();
      window.addEventListener("scroll", handler);
      return () => window.removeEventListener("scroll", handler);
    },
    []
  );

  function onClick() {
    window.scrollTo({
      top: 0,
      behavior: "smooth"
    });
  }

  if (!crossed) {
    return null;
  }
  return <button onClick={onClick}>Jump to top</button>;
}

<ScrollToTop>實現滾動監聽器並呈現一個將頁面滾動到頂部的按鈕,問題在於這些概念可能會以不一樣的形式變化。

更好的正交設計應將滾動監聽器與UI隔離,讓咱們將滾動監聽器邏輯提取到自定義鉤子useScrollDistance()中:

import { useState, useEffect } from 'react';

function useScrollDistance(distance) {
  const [crossed, setCrossed] = useState(false);

  useEffect(function() {
    const handler = () => setCrossed(window.scrollY > distance);
    handler();
    window.addEventListener("scroll", handler);
    return () => window.removeEventListener("scroll", handler);
  }, [distance]);

  return crossed;
}

而後,在組件<IfScrollCrossed>中使用useScrollAtBottom()

function IfScrollCrossed({ children, distance }) {
  const isBottom = useScrollDistance(distance);
  return isBottom ? children : null;
}

<IfScrollCrossed>僅在用戶滾動特定距離時才顯示,最後,這是單擊時滾動到頂部的按鈕:

function onClick() {
  window.scrollTo({
    top: 0,
    behavior: 'smooth'
  });
}

function JumpToTop() {
  return <button onClick={onClick}>Jump to top</button>;
}

如今,若是你想使一切正常工做,只需將<JumpToTop>放在<IfAtBottom>的中便可:

import React from 'react';
// ...
const DISTANCE = 500;

function MyComponent() {
  // ...
  return (
    <IfScrollCrossed distance={DISTANCE}>
      <JumpToTop />
    </IfScrollCrossed>
  );
}

重要的是<IfScrollCrossed>隔離滾動監聽器,UI元素的更改也隔離在<JumpToTop>組件中,在這裏滾動監聽器邏輯和UI元素是正交的。另外一個好處是你能夠將<IfScrollCrossed>與任何UI結合使用。例如,當用戶向下滾動300px時,您能夠顯示新聞表單:

import React from 'react';
// ...
const DISTANCE_NEWSLETTER = 300;

function OtherComponent() {
  // ...
  return (
    <IfScrollCrossed distance={DISTANCE_NEWSLETTER}>
      <SubscribeToNewsletterForm />
    </IfScrollCrossed>
  );
}

5.「Main」組件

儘管將變化隔離到單獨的組件中是正交性的所有內容,可是可能因爲不一樣的緣由改變組件。這些就是所謂的「Main」組件(也稱爲「App」)組件。

你能夠在最外層的index.jsx(或app。jsx)文件內找到「Main」組件:即啓動應用程序的組件。它知道有關該應用程序的全部細節:初始化全局狀態提供程序(如Redux),配置獲取庫(如GraphQL Apollo),將路由與組件關聯等等。

你可能有幾個「Main」組件:用於客戶端(在瀏覽器中運行)和用於服務器端(實現服務器端渲染)。

6.正交設計的好處

易於修改

當組件是正交設計時,對組件所作的任何更改都將隔離在組件內。

易讀

因爲正交組件僅負責一個任務,所以更容易瞭解該組件的功能,它不被不屬於這裏的細節所困擾。

易測試

正交組件僅專一於執行單個任務,你要作的只是測試組件是否正確執行任務。一般,非正交組件須要大量的模擬和手動設置才能進行測試,並且,若是難以測試。而如今你只需修改單個組件。

7.思考設計原則

我喜歡新的React功能,例如hookssuspense等。可是,我也嘗試着更普遍地思考,探索這些功能是否有助於我遵循良好的設計。

  • 爲何要使用React hooks?它們使UI渲染邏輯state反作用邏輯正交
  • 爲何Suspense獲取?它使獲取的細節和組件正交

8. 權衡

讓咱們回想一下「星球大戰之西斯的復仇」電影中的一幕。在阿納金·天行者被他的前導師歐比·旺·克諾比(Obi-Wan Kenobi)擊敗後,後者說:

給原力帶來平衡,不要把它留在黑暗中

阿納金·天行者被選爲絕地武士,在黑暗與光明的兩面之間取得平衡。

正交設計經過YAGNI: 「You ain't gonna need it」原則來平衡。

YAGNI成爲極限編程的原則:

始終在真正須要它們時執行這些事情,永遠不要在僅僅預見到可能須要它們時才執行。

(個人理解:只有真正須要時才使用)

回顧一下文章的開頭部分個人故事:我最終得到了一個困難且更改爲本很高的應用程序,個人錯誤是:我無心中建立了並不是爲更改而設計的組件。這是YAGNI的極端狀況。

另外一方面,若是每條邏輯正交,那麼你最終將建立過多不須要的抽象,這是正交設計的極限。

實際的方法是預見變化,詳細研究你的應用程序解決的領域問題,並提供潛在功能的列表。若是你認爲某個地方會發生變化,請使用正交設計原則。

8.關鍵點

編寫軟件不只與實現應用程序的要求有關,一樣重要的是,要努力設計好組件。

良好設計的關鍵原則是隔離最有可能改變的邏輯:使其正交。這使你的整個系統具備靈活性,而且能夠適應更改或添加新功能。

你想知道更多嗎?你的下一步是能夠閱讀全英版:The Pragmatic Programmer

ps: 微信公衆號:Yopai,有興趣的能夠關注,每週不按期更新。不斷分享,不斷進步

相關文章
相關標籤/搜索