RN 起名,及 export / import 名

1. 組件 & 文件

說明: 組件是 UI 單元,RN 社區(qū)約定用 PascalCase(每個單詞首字母大寫)。文件名最好和組件名一致,這樣在編輯器里搜 Detail 能同時找到文件和組件。

// page/Detail/index.js
import React from 'react'
import { View, Text } from 'react-native'

function Detail({ navigation }) {
    return (
        <View>
            <Text>詳情頁</Text>
        </View>
    )
}

export default Detail
  • Detail → 組件名,PascalCase
  • index.js → 目錄名 Detail 與組件名對應(yīng),這是 RN 項目常見寫法

2. Hook

說明: 自定義 Hook 必須以 use 開頭,這是 React 的規(guī)定。React 靠這個名字判斷你是否在 Hook 里調(diào)用了 useState、useEffect 等。命名用 camelCase,后面跟「這個 Hook 做什么」。

// request/detail.js
import { useQuery } from '@tanstack/react-query'
import fetchApi from '../useHooks/useFetch'

function useDetail(cookId) {
    return useQuery({
        queryKey: ['cook', 'detail', cookId],
        queryFn: () => fetchApi.get(`/cookDetail/${cookId}`),
    })
}

export default { useDetail }
  • useDetailuse 表示 Hook,Detail 表示「獲取詳情數(shù)據(jù)」
  • 調(diào)用時:const detailQuery = useDetail(params.id)

3. export / import(最容易混)

說明: 導(dǎo)出有兩種方式,決定了導(dǎo)入時名字能不能自己取。

3.1 默認(rèn)導(dǎo)出 export default

導(dǎo)出的是一個「默認(rèn)模塊」,導(dǎo)入時名字隨便起。

// request/detail.js — 導(dǎo)出方
function useDetail(cookId) {
    return useQuery({
        queryKey: ['cook', 'detail', cookId],
        queryFn: () => fetchApi.get(`/cookDetail/${cookId}`),
    })
}

function useCollection() {
    const queryClient = useQueryClient()
    const collection = useMutation({
        mutationFn: (cookId) => fetchApi.get(`/userCollection/${cookId}`),
        onSuccess: () => {
            queryClient.invalidateQueries({ queryKey: ['cook', 'detail'] })
        },
    })
    return { collection }
}

export default {
    useDetail,
    useCollection,
}
// page/Detail/index.js — 使用方
import useData from '../../request/detail.js'

function DetailPage({ navigation }) {
    const { params } = navigation.state

    // useData 只是 import 時起的別名
    // detail.js 里并沒有名叫 useData 的變量
    const detailQuery = useData.useDetail(params.id)
    const { collection } = useData.useCollection()

    return null
}

下面三種寫法導(dǎo)入的是同一個對象

import useData from '../../request/detail.js'
import detailApi from '../../request/detail.js'
import request from '../../request/detail.js'

useData.useDetail(1)      // 一樣
detailApi.useDetail(1)    // 一樣
request.useDetail(1)      // 一樣

3.2 命名導(dǎo)出 export { xxx }

導(dǎo)出時用 {} 包住名字,導(dǎo)入時必須用相同名字(或用 as 改名)。

// utils/format.js — 導(dǎo)出方
export function formatTime(minutes) {
    return `${minutes}分鐘`
}

export const MAX_COOK_TIME = 120
// 使用方
import { formatTime, MAX_COOK_TIME } from '../utils/format.js'

formatTime(30)        // "30分鐘"
MAX_COOK_TIME         // 120

// 如果想改名,用 as
import { formatTime as fmtTime } from '../utils/format.js'
fmtTime(30)           // "30分鐘"

3.3 兩種方式對比

導(dǎo)出方式 導(dǎo)入寫法 名字能否自取
export default obj import 任意名 from '...'
export function foo() import { foo } from '...' 不能,必須叫 foo
export function foo() import { foo as bar } from '...' as 改成 bar

4. Props(父子組件傳參)

說明: Props 是父組件傳給子組件的參數(shù),用 camelCase。約定俗成的后綴:

  • 普通數(shù)據(jù):直接名詞,如 name、id
  • 布爾值:is/has/show 開頭,如 isVisible、userCollected
  • 回調(diào)函數(shù):on 開頭,如 onPress、onCollection(表示「當(dāng) xxx 發(fā)生時調(diào)用我」)

子組件 — 聲明接收哪些 props:

// page/Detail/components/Bottom.js
import React from 'react'
import { View, Text, TouchableOpacity } from 'react-native'

function Bottom({
    id,                 // 普通數(shù)據(jù)
    name,               // 普通數(shù)據(jù)
    userCollected,      // 布爾:是否已收藏
    onComment,          // 回調(diào):點擊評論時觸發(fā)
    onCollection,       // 回調(diào):點擊收藏時觸發(fā)
}) {
    return (
        <View>
            <Text>{name}</Text>
            <TouchableOpacity onPress={onComment}>
                <Text>評論</Text>
            </TouchableOpacity>
            <TouchableOpacity onPress={() => onCollection(() => {})}>
                <Text>{userCollected ? '已收藏' : '收藏'}</Text>
            </TouchableOpacity>
        </View>
    )
}

export default Bottom

父組件 — 傳入具體值:

// page/Detail/index.js
import Bottom from './components/Bottom'
import useData from '../../request/detail.js'

function DetailPage({ navigation }) {
    const { params } = navigation.state
    const detailQuery = useData.useDetail(params.id)
    const { collection, cancelCollection } = useData.useCollection()

    const bottomProps = {
        id: params.id,
        name: detailQuery.data?.name,
        userCollected: detailQuery.data?.isCollection,
        onComment: () => {
            setModalVisible(true)
        },
        onCollection: (callback) => {
            if (detailQuery.data?.isCollection) {
                cancelCollection.mutateAsync(params.id).then(callback)
            } else {
                collection.mutateAsync(params.id).then(callback)
            }
        },
    }

    return <Bottom {...bottomProps} />
}

5. 事件函數(shù)

說明: 同一個函數(shù),在組件內(nèi)部定義作為 prop 傳給子組件時,常用不同前綴,方便區(qū)分「誰定義的」和「誰觸發(fā)的」:

位置 前綴 含義
組件內(nèi)部 handle 我自己寫的處理邏輯
傳給子組件 on 告訴子組件「發(fā)生 xxx 時調(diào)這個」
function DetailPage() {
    const [modalVisible, setModalVisible] = useState(false)

    // 內(nèi)部定義:handle 開頭
    const handleComment = () => {
        setModalVisible(true)
    }

    const handleCollection = (callback) => {
        collection.mutateAsync(params.id).then(callback)
    }

    return (
        <Bottom
            onComment={handleComment}         // prop 名用 on
            onCollection={handleCollection}   // 函數(shù)體用 handle
        />
    )
}

6. State & Ref

說明:

  • StateuseState,返回 [當(dāng)前值, 修改函數(shù)],修改函數(shù)固定為 set + 首字母大寫的值名
  • RefuseRef,命名常以 Ref 結(jié)尾,表示「某個 DOM/組件的引用」
function DetailPage({ navigation }) {
    const { params } = navigation.state
    const detailQuery = useData.useDetail(params.id)

    // state:存會變化、觸發(fā)重渲染的數(shù)據(jù)
    const [detail, setDetail] = useState(null)
    const [modalVisible, setModalVisible] = useState(false)

    // ref:存不觸發(fā)重渲染的引用(DOM、定時器、臨時對象等)
    const scrollViewRef = useRef(null)
    const commentData = useRef({ stars: 1 })

    // 接口數(shù)據(jù)回來后寫入 state
    useEffect(() => {
        if (detailQuery.data) {
            setDetail(detailQuery.data)
        }
    }, [detailQuery.data])

    return (
        <ScrollView ref={scrollViewRef}>
            <Text>{detail?.name}</Text>
        </ScrollView>
    )
}

7. Style 樣式

說明: RN 的樣式對象是 JS 對象,屬性名必須用 camelCase(不能寫 CSS 的 font-size,要寫 fontSize)。樣式名按用途起,不要按顏色值起。

import { StyleSheet, View, Text } from 'react-native'

const styles = StyleSheet.create({
    container: {
        flex: 1,
        paddingHorizontal: 16,
    },
    title: {
        fontSize: 18,
        color: '#121212',
    },
    subtitle: {
        fontSize: 14,
        color: '#777777',
    },
    activeTab: {
        fontWeight: '500',
        color: '#AB8C5E',
    },
})

function DetailPage() {
    const isSelected = true

    return (
        <View style={styles.container}>
            <Text style={styles.title}>紅燒肉</Text>
            <Text style={[styles.subtitle, isSelected && styles.activeTab]}>
                簡單
            </Text>
        </View>
    )
}
  • styles.title → 標(biāo)題樣式
  • [styles.subtitle, isSelected && styles.activeTab] → 數(shù)組寫法,可疊加多個樣式

8. 常量

說明: 整個應(yīng)用不變、多處復(fù)用的值,用 UPPER_SNAKE_CASE(全大寫 + 下劃線),一眼看出「這是常量,不要改」。

// useHooks/deviceProtocol/protocol.js
export const EVENT_LIST_NAME = {
    collectionCookControl: 'collectionCookControl',
}

// useHooks/useFetch/index.js
const API_BASE_URL = 'https://dreamecook.xin/cookbook/appPlugin'
// 使用
import { EVENT_LIST_NAME } from '../../useHooks/deviceProtocol/protocol.js'

deviceState.sendAction({
    name: EVENT_LIST_NAME.collectionCookControl,
    value: `1,${params.cIndex}`,
})

React Query 的 queryKey 雖不是常量寫法,但也是固定字符串?dāng)?shù)組,用于標(biāo)識緩存:

queryKey: ['cook', 'detail', cookId]
queryKey: ['category', 'cook', params]

9. 目錄結(jié)構(gòu)

說明: 目錄名反映內(nèi)容類型。頁面和組件用 PascalCase(和組件名一致),工具、Hook、請求層用 camelCase。

main/
├── page/
│   └── Detail/                 # 頁面,PascalCase
│       ├── index.js            # 導(dǎo)出 Detail 組件
│       └── components/
│           └── Bottom.js       # 頁面私有子組件
├── components/
│   └── MyModal/                  # 公共組件,PascalCase
│       └── index.js
├── request/
│   └── detail.js                 # 接口 Hook,camelCase
├── useHooks/
│   └── useFetch/
│       └── index.js              # 請求封裝
└── utils/
    └── UIConfig.js               # 工具函數(shù)

10. 速查表

場景 風(fēng)格 示例 說明
組件 PascalCase function Detail() {} UI 單元
Hook use + camelCase function useDetail(id) {} 必須以 use 開頭
default import 自取 import useData from './detail.js' 名字與導(dǎo)出方無關(guān)
命名 import 固定 import { useQuery } from '...' 名字必須對應(yīng)
props 數(shù)據(jù) camelCase name={data.name} 普通傳參
props 布爾 is/has userCollected={true} 表示 true/false
props 回調(diào) on onCollection={fn} 事件回調(diào)
內(nèi)部函數(shù) handle const handlePress = () => {} 組件內(nèi)定義
state camelCase [detail, setDetail] 會觸發(fā)重渲染
ref xxxRef scrollViewRef 不觸發(fā)重渲染
style camelCase title: { fontSize: 18 } RN 樣式對象
常量 UPPER_SNAKE API_BASE_URL 全局不變值
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容