1.0.4 • Published 2 years ago

@rybit/eslint-config-moov-base v1.0.4

Weekly downloads
-
License
-
Repository
-
Last release
2 years ago

ESLint

ESLint, not Eslint.

Packages

  • eslint-config-base
  • eslint-config-typescript
  • eslint-config-react
  • eslint-config-express

Usage

// .eslintrc.js
{ 
  // Plain JavaScript project
  extends: [
    'moov-base',
  ],

  // React project with TypeScript
  extends: [
    'moov-base',
    'moov-typescript',
    'moov-react',
  ],

  // Express project with JavaScript
  extends: [
    'moov-base',
    'moov-express',
  ],
}
// package.json
{
  "devDependencies": {
    "eslint-config-moov-base": "git+http://git.jetsbike.com/jetsbike/eslint-config-base",
    "eslint-config-moov-react": "git+http://git.jetsbike.com/jetsbike/eslint-config-react",
    "eslint-config-moov-typescript": "git+http://git.jetsbike.com/jetsbike/eslint-config-typescript"
  },
}

描述

各個 eslint config 主要繼承自 airbnb 所使用的 eslint rule,再視專案情況停用了部份 rules。每個 eslint config 之間不互相依賴,需依專案類型來決定使用哪些

其中,'moov-base'有定義到了 import-order,會將 import/require 分為四個 group:

  • React/Express 這類 framework
  • 其它 library,像是 lodash 等等的
  • 專案中以@/開頭(套用 path aliases)的 import
  • 其它 import
import React, { useState } from 'react';

import ProTable, { ProColumns, ProTableProps } from '@ant-design/pro-table';
import { Button } from 'antd';
import _, { isNull } from 'lodash';
import moment from 'moment';

import { IntlProviderLocal, DownloadButton } from '@/components';
import { getFilterSearch, setFilterSearch } from '@/utils/utils';

import TableForm from './components/TableForm';

每個 group 再依英文字母順序排列其內容

解決它

大多的 error 可以依以下幾種方法解決

  • eslint --fix 自動修正,或者 JetBrains 系列 IDE 可以用 action: "Fix ESLint Problems"。能解決像是 import-order 或者 prefer-const 這類簡單的問題
  • 依 lint message 上的提示解決
  • 在網路上搜尋這條 rule 的名稱,看看大家在什麼情況下遇到此 error 以及如何解決的
  • 與你的好同事討論該怎麼辨
  • 在加上eslint-disable-next-line之前,請務必再三確認你以及團隊成員都認同這麼作是 ok 的

常見的 lint error 解法

PropType is defined but prop is never used(react/no-unused-prop-types)

interface Props {
  detailLoading?: boolean; // <-
}

const SomeComponent: React.FC<Props> = (props) => {}

刪掉 component 的這個 prop,並且記得找到它的 usage,將原本有 pass 這個 prop 的地方也去掉,再確認有沒有 unused code

艾尼 (any)

interface Props {
  // Don't use `{}` as a type. `{}` actually means "any non-nullish value".
  fetch: (params: FilterParams) => {};
  // Unexpected any. Specify a different type.(@typescript-eslint/no-explicit-any)
  auth: any;
}

大多情況下看到any時,請先翻找使用它的地方以及它是如何被使用的來確認它真正的 type,有時會需要回推到 call 該 api 的後端專案,或是從 db schema 中得知。如果它沒有一個固定的 type 的話,也可以考慮使用泛型(Generics)解決

如果到最後判定它真的只能是any的話,還是可以試著將之改為Record<string, unknow>至少能分別它是 Array、Object 還是 string, number 等等的基本型別。TypeScipt 中常常會以Record<string, unknow>來替代Object, {}或者其實它是Objectany

Unexpected use of '|'.(no-bitwise)

const failedCount = result?.length | 0;

確定沒有把length || 0誤打成length | 0嗎?

雖然以這個 case 來說它剛好也能正常運作 (undefined | 0 === 0),但大多情況下你不需要用 bitwise,若真要使用的話請加eslint-disable

JavaScript 中也存在著許多好用、簡明,但容易被誤用或可讀性不佳的寫法,airbnb 的 ESLint rule 中已經把大多這類情況加入他們的 ESLint rule 中了,依循他們的規範,能在大多情況下使我們的專案更加得好維護

'params' is already declared in the upper scope.(@typescript-eslint/no-shadow)

const { data, params } = useRequest(...);

function fetchData(params: FilterParams) {
//                 ^^^^^^
  props.fetch(params);
}

這個菜市場名已經有人用,撞名了

若直接換成params2的話就太沒創意了,可以考慮將useRequestparams改為const { params: requestParams }的這種寫法

React Hook useEffect has missing dependencies: 'fetchData'. (react-hooks/exhaustive-deps)

function fetchData(params: FilterParams) { /* .... */ }

useEffect(() => {
  fetchData( ... );
}, []); // <- [¯\_(ツ)_/¯]

雖然以目前情況下不作任何改動的話程式一樣會如預期的執行,但這個規則還是值得去注意一下

一般在寫一個沒有 dependencies 的useEffect(() => {}, [])即代表著希望在 component 載入時(componentDidMount)作一些資料的初始化。

而這條 rule 是在提示你說應當注意這些 function 或 object 會在每次 component render 時重建,並提示你應當也將它加入 useEffect的 dependencies 中,讓useEffect的 callback function 一併隨之重建

這時若直接把fetchData放到useEffect的 dependencies 的話,又會有另一個 lint error 出現

// ESLint: The 'fetchData' function makes the dependencies of useEffect Hook (at line 90) change on every render. To fix this, wrap the definition of 'fetchData' in its own useCallback() Hook.(react-hooks/exhaustive-deps)
function fetchData(params: FilterParams) { /* .... */ }

它會提示說應當要改成useCallback(感覺麻煩)。好吧如果把fecthData函式移至useEffect中呢?

useEffect(() => {
  function fetchData(params: FilterParams) { /* .... */ }
  fetchData();
}, []);

lint error 消失了,但是問題就在這個fetchData除了這邊用到之外,filter 條件改變後使用者按 submit button 時也會使用

所以我們還是乖乖得照建議把它放useCallback

const fetchData = useCallback(
  (params: FilterParams) => {
    // do something to fetch
    dispatch({ type: 'fetch', params })
  },
  // some other dependencies
  [dispatch],
);

useEffect(() => {
  fetchData( ... );
}, [fetchData]);

沒什麼困難的對吧,那如果這個 component 的 filter 有個 defaultFilter,並且要存至 state 中呢

const defaultFilter = {
  date: moment(),
  regionLevel: 'country',
  regionNames: [CountryList[0].value],
};
const [filterParams, setFilterParams] = useState(defaultFilter);

const fetchData = useCallback(
  (params: FilterParams) => {
    dispatch({ type: 'fetch', params: searchFormValue })
  },
  [dispatch],
);

useEffect(() => {
  fetchData(defaultFilter);
}, [fetchData]); // <- missing dependencies: 'defaultFilter'

它又會再度提示你那個 defaultFilter 沒有被加入 dependencies 中,而且還有另一個問題是這個 defaultFilter 在每次 render 時都會重建,也就是說上面由moment()產生的 date filed 在每次 render 時都會重新計算一次,浪費了一點點的效能並可能造成預期外的效果

而解法還是一樣繼續拿 hook 來用,這次用useMemo把 defaultValue 包起來

const defaultFilterParams = useMemo<OrderReportSearchValue>(
  () => ({
    date: moment(),
    regionLevel: 'country',
    regionNames: [CountryList[0].value],
  }),
  [CountryList], // <- 別忘了加 dependencies
);
const [filterParams, setFilterParams] = useState(defaultFilterParams);

useEffect(() => {
  fetchData(defaultFilterParams);
}, [fetchData, defaultFilterParams]); 

只要確保CountryList的值不會變動的話,便可以放心得給useEffect多加上這個有CountryList dependencies 的defaultFilter

另外,如果直接在useEffect中 dependent 到filterParams這個 state 的話,會導致filterParams每次被更新時都立即fetchData,而不是按 submit button 時才作

const [filterParams, setFilterParams] = useState();

useEffect(() => {
  fetchData(filterParams);
}, [fetchData, filterParams]);

當然,你也可以說這不是 bug,是 feature

JSX props should not use functions(react/jsx-no-bind)

function handleShowSizeChange(current: number, pageSize: number) {
  fetchData({ pageNo: current, pageSize });
}

<TableForm
  onShowSizeChange={handleShowSizeChange}
/>

其實這也不是什麼大問題,只是個效能的 issue,因為這會導致每次 render 時都重建一次上面的handleShowSizeChange函式物件,增加 GC 的負擔而已

解法一樣簡單,給它包useCallback並妥善控管它 dependencies

const handleShowSizeChange = useCallback((current: number, pageSize: number) => {
  fetchData({
    pageNo: current,
    pageSize,
  });
}, [fetchData]);

<TableForm
  onShowSizeChange={handleShowSizeChange}
/>

React Hook "useState" is called conditionally. (react-hooks/rules-of-hooks)

const SomeComponent: React.FC<Props> = (props) => {
  if (props.loading) {
    return <Spin style={{ width: '100%', height: '100%' }} />;
  }

  const [step, setStep] = useState(0);
  // ...
}

會這麼作,代表還沒理解useState是怎麼運作的。把useState放到會 return 的 condition 前即可

const SomeComponent: React.FC<Props> = (props) => {
  const [step, setStep] = useState(0);
  
  if (props.loading) {
    return <Spin style={{ width: '100%', height: '100%' }} />;
  }
  // ...
}

Missing trailing comma.(@typescript-eslint/comma-dangle)

const defaultFilter = {
  regionLevel: 'country',
  regionNames: [CountryList[0].value]
//                                   ^
};

這是為了因應 git commit 時,如果你要加一個 property 在這個 object 上時,會同時改到前一行沒加,的,讓人以為那個regionNames也是這次 commit 修改的

const defaultFilter = {
  regionLevel: 'country',
- regionNames: [CountryList[0].value]
+ regionNames: [CountryList[0].value],
+ bikeType: ['24']
};

如果regionNames原本就有 trailing comma 的話就只會有bikeType一行的改動

const defaultFilter = {
  regionLevel: 'country',
  regionNames: [CountryList[0].value],
+ bikeType: ['24'],
};

沒什麼大問題,prettier 一下它會自動幫你加好加滿,還會一併把整份 code 都一併 prettier 掉

Missing return type on function.(@typescript-eslint/explicit-module-boundary-types)

export default function InputPrice(props: Props) { /* ... */ }
//             ^^^^^^^^

雖然 return type 還是可以靠 TypeScript 的 Type Inference 自動推斷,但如果函式本身就有定 type 的話便可以讓看的人更快速知道它會回傳什麼,也能讓 TypeScript engine 運作的更有效率

如果用的是 JetBrains 的 IDE,可以把游標移至函式名稱上,按⌥Enter來 Show Context Actions,然後按 Specify type explicitly 來自動補全 return type

export default function InputPrice(props: Props): JSX.Element { /* ... */ }

另外,定 model type 時,用 interface 來定也會比用 type 定要來的高效,雖然 type 確實能用得比較靈活w

interface PropsWithInterface {
  defaultValue?: number;
  disabled?: boolean | undefined;
}

type PropsWithType = {
  defaultValue?: number;
  disabled?: boolean | undefined;
};
1.0.4

2 years ago

1.0.3

2 years ago

1.0.2

2 years ago