有些時候我們會需要針對不同的環境設置不同的key、API...等,或者不希望把隱私的資料直接明晃晃寫出來,就可以將這些隱私資料存到環境變數(Enviroment Variables)中。
要在 RN 管理環境變數可以使用 react-native-dotenv 這個庫。
npm install -D react-native-dotenv @types/react-native-dotenv
// or
yarn add -D react-native-dotenv @types/react-native-dotenv
接著在 babel.config.js 的 plugins 加上 ['module:react-native-dotenv']:
module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
  plugins: [
    ['module:react-native-dotenv'],
    //...
  ]
}
這邊使用默認設置就好,如果需要進階設置可以參考官方文檔
在專案根目錄新增 .env:
API_URL=https://api.example.org
API_TOKEN=abc123
在需要使用環境變數的檔案中 import from '@env' :
import { API_URL, API_TOKEN } from '@env'
fetch(`${API_URL}/users`, {
  headers: {
    'Authorization': `Bearer ${API_TOKEN}`
  }
})
現在 import 環境變數時 IDE 應該會提示錯誤:

這是因為我們需要為 module 進行型別宣告。
在專案根目錄中建立一個 _types_ 資料夾,並且新增 env.d.ts
declare module '@env' {
  export const API_URL: string;
}
在裡面定義環境變數的型別後,這個錯誤提示就會消失,並且 IDE 會提示你有哪些變數可使用:

如果還是一樣提示錯誤,那就需要在 tsconfig.json 中指定 typeRoots:
{
  "extends": "@tsconfig/react-native/tsconfig.json",
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    },
    "typeRoots": ["./src/_types_"]
  }
}
題外話,如果希望描述環境變數的用處,可以使用 JSDoc:

declare module '@env' {
  /**
   * API URL
   */
  export const API_URL: string;
}
如果有多個環境需要設置不同的環境變數,比如說 dev, prod,就可以新建三個 .env:
.env
API_URL=http://192.168.0.xxx
.env.dev
API_URL=https://dev.api.xxxxxxx
.env.prod
API_URL=https://api.xxxxxxx
記得要將
.env,.env.dev,.env.prod加到 .gitignore 中。
接著在 package.json 中新增不同的啟動 script:
.env
npm run start:dev
npm run start:prod
{
  "scripts": {
    "start": "npx react-native start",
    "start:dev": "NODE_ENV=dev npx react-native start",
    "start:prod": "NODE_ENV=prod npx react-native start",
  }
}
EXPO_PUBLIC_ 作為環境變數名的前綴// .env
EXPO_PUBLIC_API_URL=https://staging.example.com
EXPO_PUBLIC_API_KEY=abc123
import { Button } from 'react-native'
function Post() {
  const apiUrl = process.env.EXPO_PUBLIC_API_URL
  async function onPress() {
    await fetch(apiUrl, { ... })
  }
  return <Button onPress={onPress} title="Post" />
}
多個環境的設置方法都一樣,只不過是把 react-native start 改為 expo start 而已:
{
  "scripts": {
    "start": "npx expo start",
    "start:dev": "NODE_ENV=dev npx expo start",
    "start:prod": "NODE_ENV=prod npx expo start"
  }
}
有兩種方式(其實不只兩種,不過這邊就簡單官方建議的兩種):
eas secret 管理,詳細的請參考官方文檔
npx expo install dotenv expo-constants
.env
// .env
API_URL=https://api.production.com
PROJECT_ID=xxxxxxxxxxxxxxxxxxxxx
app.json 或者 app.config.ts 的 extra 中定義環境變數:
// app.config.ts
import 'dotenv/config'
module.exports = ({ config }) => {
  return {
    ...config,
    extra: {
      API_URL: process.env.API_URL,
      eas: {
        projectId: process.env.PROJECT_ID,
      },
    },
  }
}
import Constants from 'expo-constants'
const API_URL = Constants.manifest.extra.API_URL;
console.log(API_URL) // https://api.production.com
如果使用 eas build 指令,需要先將 .gitignore 中的 .env 註釋後再 build,否則 expo 無法吃到環境變數的值。
2024-03-08 補充:可以使用
.easignore在 eas build 時取代.gitignore。
注意:如果在eas.json中設定requireCommit: true,則不支援.easignore
參考:How projects are uploaded to EAS Build
可以在 CI/CD 時將環境變數寫入應用的 .env 中,這邊以 github 和 gitlab 為例。
將環境變數新增至倉庫的 actions secrets


然後在 workflow yml 中添加以下 step:
echo "APP_ENV_KEY=${SECRET_KEY}" >> .env 即將環境變數的值寫入 .env 中sed -i "s@.env@@g" .gitignore 代表在 .gitignore 中註釋 .env,如果不在 .gitignore 中註釋掉 .env 的話 eas build 時是無法吃到環境變數的。env:
    BASE_URL: ${{ secrets.API_URL }}
steps:
    - name: Set Variables
      run: |
        echo "BASE_URL=${BASE_URL}" >> .env
        sed -i "s@.env@@g" .gitignore
# .env
BASE_URL=
在 repo 的 Settings - CI/CD - Variables 新增環境變數:


注意:設置為
protected的變數只有protected的分支可以獲取得到。
在 .gitlab-ci.yml 中使用 echo "APP_ENV_KEY=${REPO_VARIABLE_NAME}" >> .env 就可以將環境變數的值寫入 .env 中:
# .gitlab-ci.yml
init:develop:
  stage: init
  script:
    - echo "BASE_URL=${API_URL}" >> .env
    - sed -i "s@.env@@g" .gitignore
  artifacts:
    paths:
      - .env
      - .gitignore
// .env
BASE_URL=