Site icon May's Notes

React Native 奇幻之旅(7)-簡化 import 區塊

React Native logo

這是我在2023第十五屆 iThome 鐵人賽發表的系列文章。https://ithelp.ithome.com.tw/users/20136637/ironman/6408

下方為一個組件中的 import 區塊,現在看起來還算是簡潔的因為 import 的東西不多,但當 import 的內容越來越多時就會顯得雜亂,該如何簡化呢?

import React, { useState } from "react"
import { Text } from "react-native"

import { Input } from "../components/atoms/Input"
import { TextArea } from "../components/atoms/TextArea"
// ...

Index file

從上面的例子可以看出來,Input 跟 TextArea 組件都是從 ../components/atoms/ 裡面引入的,如果能將它們合併在一起就能簡化一部份:

import { Input, TextArea } from "../components/atoms"

可以使用 index file 來管理資料夾底下的組件 exports

新建一個 index.ts 到 components/atoms 中,並將 atoms 中的所有組件 exports:

// index.ts
export * from './Input'
export * from './ResizeImage'
export * from './TextArea'
// Input.tsx
export const Input = () => {
    // ...
}

// ResizeImage.tsx
export const ResizeImage = () => {
    // ...
}

// TextArea.tsx
export const TextArea = () => {
    // ...
}

這樣寫的意思是,將 Input.tsx, ResizeImage.tsx, TextArea.tsx 中所有 export 的內容全部放在 index.ts 中一起 exports

// Usage
import { Input, TextArea } from "../components/atoms"

如果組件是 export default 要這麼寫:

// index.ts
export { default as CustomInput } from './Input'
export { default as ResizeImage } from './ResizeImage'
export { default as CustomTextArea } from './TextArea'
// Input.tsx
const Input = () => {
    // ...
}

export default Input

// ResizeImage.tsx
const ResizeImage = () => {
    // ...
}

export default ResizeImage

// TextArea.tsx
const TextArea = () => {
    // ...
}

export default TextArea

Path alias

雖然使用 index file 後已經簡潔了不少,但是還可以再更簡潔一點。

如果今天專案結構比較複雜,很可能 import from '../../../components' 這麼深,所以我們還需要把 ../../../ 這段相對路徑給簡化。

import { Input, TextArea } from "../../../components/atoms"

這是目前的專案結構:

設置 path alias 的方式是需要先安裝 babel-plugin-module-resolver 模組解析器:

npm install --save-dev babel-plugin-module-resolver
// or
yarn add --dev babel-plugin-module-resolver

接著在 babel.config.js 中新增一個 plugin module-resolver,寫法如下:

// babel.config.js
module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
  plugins: [
      // ...
      [
          'module-resolver',
          {
              root: '.',
              alias: {
                '@components': './src/components',
                '@hooks': './src/hooks',
                '@provider': './src/provider',
                '@screens': './src/screens'
              },
              extensions: ['.ios.js', '.android.js', '.js', '.jsx', '.ts', '.tsx', '.json']
          }
      ]
  ]
}

進階設置可以參考:https://github.com/tleunen/babel-plugin-module-resolver/blob/master/DOCS.md

設置完成後,就可以將所有相對路徑改為別名了:

import { ImagePage } from '@screens'
import { ModalProvider } from '@provider'
import { useModal } from "@hooks"

或者也可以只將 src 設為 @

// babel.config.js
alias: {
    '@': './src',
}
import { ImagePage } from '@/screens'
import { ModalProvider } from '@/provider'
import { useModal } from "@/hooks"

這樣一來也能提升開發效率,因為 import 時就不需要再去想需要向上移動多少層才能找到。

如果只是單純想基於 root 引入的話,可以使用 babel-plugin-root-import

IDE 錯誤提示

此時,如果你直接使用剛剛定義好的路徑別名,應該會有紅色波浪線提示你找不到模組:

這是因為使用 TS 開發的時候,在 babel.config.js 中定義還不夠,還要在 tsconfig.json 中也定義:

// tsconfig.json
{
  "extends": "@tsconfig/react-native/tsconfig.json",
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@components/*": ["src/components/*"],
      "@hooks/*": ["src/hooks/*"],
      "@provider/*": ["src/provider/*"],
      "@screens/*": ["src/screens/*"]
    }
  }
}

現在 import 時 IDE 也會自動將路徑轉換為別名:

在 ./index.js 引入 src/App.tsx

如果要將 src 設為基本路徑,可以這樣改:

// tsconfig.json
{
  "extends": "@tsconfig/react-native/tsconfig.json",
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@components/*": ["components/*"],
      "@hooks/*": ["hooks/*"],
      "@provider/*": ["provider/*"],
      "@screens/*": ["screens/*"]
    }
  }
}

IDE 提示的路徑也會將 src 省略:

在 ./index.js 引入 src/App.tsx

現在 import 區塊已經變得十分簡潔,如果要再更簡潔的話,還可以使用 eslint,這邊就不繼續展開了。

Exit mobile version