npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@rybit/eslint-config-moov-base

v1.0.4

Published

ESLint, not Eslint.

Downloads

102

Readme

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;
};