跳转到主要内容
Header
本文檔概述了在前端工作時應遵循的最佳實踐。

狀態管理

在代碼庫中,React 和 Recoil 負責狀態管理。

使用 useRecoilState 來存儲狀態

創建您需要的原子數量來存儲狀態是個好習慣。
多使用一些原子比試圖通過屬性傳遞來達到簡潔更好。
export const myAtomState = atom({
  key: 'myAtomState',
  default: 'default value',
});

export const MyComponent = () => {
  const [myAtom, setMyAtom] = useRecoilState(myAtomState);

  return (
    <div>
      <input
        value={myAtom}
        onChange={(e) => setMyAtom(e.target.value)}
      />
    </div>
  );
}

不要使用 useRef 來存儲狀態

避免使用 useRef 來存儲狀態。 如果您想存儲狀態,應使用 useStateuseRecoilState 如果您覺得需要通過 useRef 防止某些重繪發生,請參閱如何管理重繪

管理重繪

在 React 中管理重繪可能會很困難。 以下是避免不必要重繪的一些規則。 請記住,通過理解重繪的原因,可以始終避免重繪。

在根層級工作

通過在根層消除重繪,新功能中的重繪現在變得容易。 PageChangeEffect 側車組件僅包含一個 useEffect,該 useEffect 包含頁面更改時要執行的所有邏輯。 這樣您知道只有一個地方可以觸發重繪。

在代碼庫中添加 useEffect 前要三思。

重繪通常是由不必要的 useEffect 引起的。 您應該考慮是否需要 useEffect,或是否可以將邏輯移到事件處理函數中。 通常更容易將邏輯移到 handleClickhandleChange 函數中。 您也可以在像 Apollo 這樣的庫中找到它們:onCompletedonError 等。

使用兄弟組件提取 useEffect 或數據抓取邏輯

如果您覺得需要在根組件中添加 useEffect,您應考慮將其作為側車組件提取出來。 您可以將數據抓取邏輯以相同方式應用,使用 Apollo hooks。
// ❌ Bad, will cause re-renders even if data is not changing, 
//    because useEffect needs to be re-evaluated
export const PageComponent = () => {
  const [data, setData] = useRecoilState(dataState);
  const [someDependency] = useRecoilState(someDependencyState);

  useEffect(() => {
    if(someDependency !== data) {
      setData(someDependency);
    }
  }, [someDependency]);

  return <div>{data}</div>;
};

export const App = () => (
  <RecoilRoot>
    <PageComponent />
  </RecoilRoot>
);
// ✅ Good, will not cause re-renders if data is not changing, 
//   because useEffect is re-evaluated in another sibling component
export const PageComponent = () => {
  const [data, setData] = useRecoilState(dataState);

  return <div>{data}</div>;
};

export const PageData = () => {
  const [data, setData] = useRecoilState(dataState);
  const [someDependency] = useRecoilState(someDependencyState);

  useEffect(() => {
    if(someDependency !== data) {
      setData(someDependency);
    }
  }, [someDependency]);

  return <></>;
};

export const App = () => (
  <RecoilRoot>
    <PageData />
    <PageComponent />
  </RecoilRoot>
);

使用 recoil 家族狀態和 recoil 家族選擇器

Recoil 家族狀態和選擇器是避免重繪的好方法。 當您需要存儲多個項目的列表時,它們很有用。

不應使用 React.memo(MyComponent)

避免使用 React.memo() 因為它不解決重繪原因,而是打斷了重繪鏈,這可能導致意外行為並使代碼非常難以重構。

限制 useCallbackuseMemo 的使用

它們通常沒有必要,並且會使代碼變得難以閱讀和維護,無法注意到性能的提升。

Console.logs

console.log 語句在開發過程中很有價值,可提供對變量值和代碼流的即時見解。 但在生產代碼中遺留這些會導致多個問題:
  1. 性能:過度日誌記錄會影響運行時性能,特別是在客戶端應用程序上。
  2. 安全:記錄敏感數據可能會將關鍵信息暴露給任何檢查瀏覽器控制台的人。
  3. 簡潔性:填充日誌的控制台可能會遮擋開發者或工具需要查看的重要警告或錯誤。
  4. 專業性:最終用戶或客戶檢查控制台並看到大量日誌語句可能會質疑代碼的質量和光潔度。
確保在將代碼推送到生產前刪除所有 console.logs

命名

變量命名

變量名前應準確描述變量的用途或功能。

通用名稱的問題

程序中的通用名稱並不理想,因為它們缺乏特⾊,導致模糊並降低代碼可讀性。 這類名稱未能傳達變量或函數的目的,使開發者難以在不深入調查的情況下理解代碼目的。 這會導致調試時間增加,更容易出錯,維護和合作困難。 相比之下,描述性命名使代碼自我解釋且更易於管理,提高代碼質量和開發者的生產力。 這類名稱未能傳達變量或函數的目的,使開發者難以在不深入調查的情況下理解代碼目的。 這會導致調試時間增加,更容易出錯,維護和合作困難。 相比之下,描述性命名使代碼自我解釋且更易於管理,提高代碼質量和開發者的生產力。
// ❌ Bad, uses a generic name that doesn't communicate its
//    purpose or content clearly
const [value, setValue] = useState('');
// ✅ Good, uses a descriptive name
const [email, setEmail] = useState('');

在變量名稱中應避免使用某些詞

  • dummy

事件處理器

事件處理器名稱應以 handle 開頭,on 是用於命名組件屬性中的事件的前綴。
// ❌ Bad
const onEmailChange = (val: string) => {
  // ...
};
// ✅ Good
const handleEmailChange = (val: string) => {
  // ...
};

可選屬性

避免為可選屬性傳遞默認值。 示例 參考下面定義的 EmailField 組件:
type EmailFieldProps = {
  value: string;
  disabled?: boolean;
};

const EmailField = ({ value, disabled = false }: EmailFieldProps) => (
  <TextInput value={value} disabled={disabled} fullWidth />
);
使用方法
// ❌ Bad, passing in the same value as the default value adds no value
const Form = () => <EmailField value="username@email.com" disabled={false} />;
// ✅ Good, assumes the default value
const Form = () => <EmailField value="username@email.com" />;

組件作為屬性

儘量將未實例化的組件作為 props 傳遞,這樣子組件就能決定需要傳遞什麼 props。 最常見的例子就是圖標組件:
const SomeParentComponent = () => <MyComponent Icon={MyIcon} />;

// In MyComponent
const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => {
  const theme = useTheme();

  return (
    <div>
      <MyIcon size={theme.icon.size.md}>
    </div>
  )
};
為了讓 React 理解組件是組件,你需要使用 PascalCase,然後用 <MyIcon> 來實例化。

屬性傳遞:保持最少

在 React 中,屬性傳遞是指通過多個組件層級傳遞狀態變量及其設定器,即使中間層組件不使用它們。 雖然有時是必要的,但過度的屬性傳遞可能導致:
  1. 可讀性降低:在結構深入的組件中,追蹤某個屬性來源或使用的位置會變得複雜。
  2. 維護挑戰:一個組件的屬性結構改變可能需要調整幾個組件,即使他們不直接使用這個屬性。
  3. 組件重用性降低:一個接收大量屬性僅為傳遞的組件,由於不夠通用,難以在不同的上下文中重用。
如果您覺得正在使用過多的屬性傳遞,請參考state 管理最佳實踐

導入

導入時,選擇設計的別名而不是指定完整或相對路徑。 處理別名
{
  alias: {
    "~": path.resolve(__dirname, "src"),
    "@": path.resolve(__dirname, "src/modules"),
    "@testing": path.resolve(__dirname, "src/testing"),
  },
}
使用方法
// ❌ Bad, specifies the entire relative path
import {
  CatalogDecorator
} from '../../../../../testing/decorators/CatalogDecorator';
import {
  ComponentDecorator
} from '../../../../../testing/decorators/ComponentDecorator';
// ✅ Good, utilises the designated aliases
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from 'twenty-ui/testing';

架構驗證

Zod 是無類型對象的架構驗證器:
const validationSchema = z
  .object({
    exist: z.boolean(),
    email: z
      .string()
      .email('Email must be a valid email'),
    password: z
      .string()
      .regex(PASSWORD_REGEX, 'Password must contain at least 8 characters'),
  })
  .required();

type Form = z.infer<typeof validationSchema>;

重大變更

在進行之前總是要進行全面的手動測試,以確保修改未在其他地方引起破壞,因為測試還未得到廣泛集成。