RN 起名,及 export / import 名

1. 组件 & 文件

说明: 组件是 UI 单元,RN 社区约定用 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 与组件名对应,这是 RN 项目常见写法

2. Hook

说明: 自定义 Hook 必须以 use 开头,这是 React 的规定。React 靠这个名字判断你是否在 Hook 里调用了 useStateuseEffect 等。命名用 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 表示「获取详情数据」
  • 调用时:const detailQuery = useDetail(params.id)

3. export / import(最容易混)

说明: 导出有两种方式,决定了导入时名字能不能自己取。

3.1 默认导出 export default

导出的是一个「默认模块」,导入时名字随便起

// request/detail.js — 导出方
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
}

下面三种写法导入的是同一个对象

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 命名导出 export { xxx }

导出时用 {} 包住名字,导入时必须用相同名字(或用 as 改名)。

// utils/format.js — 导出方
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 两种方式对比

导出方式 导入写法 名字能否自取
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 是父组件传给子组件的参数,用 camelCase。约定俗成的后缀:

  • 普通数据:直接名词,如 nameid
  • 布尔值:is/has/show 开头,如 isVisibleuserCollected
  • 回调函数:on 开头,如 onPressonCollection(表示「当 xxx 发生时调用我」)

子组件 — 声明接收哪些 props:

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

function Bottom({
    id,                 // 普通数据
    name,               // 普通数据
    userCollected,      // 布尔:是否已收藏
    onComment,          // 回调:点击评论时触发
    onCollection,       // 回调:点击收藏时触发
}) {
    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. 事件函数

说明: 同一个函数,在组件内部定义作为 prop 传给子组件时,常用不同前缀,方便区分「谁定义的」和「谁触发的」:

位置 前缀 含义
组件内部 handle 我自己写的处理逻辑
传给子组件 on 告诉子组件「发生 xxx 时调这个」
function DetailPage() {
    const [modalVisible, setModalVisible] = useState(false)

    // 内部定义:handle 开头
    const handleComment = () => {
        setModalVisible(true)
    }

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

    return (
        <Bottom
            onComment={handleComment}         // prop 名用 on
            onCollection={handleCollection}   // 函数体用 handle
        />
    )
}

6. State & Ref

说明:

  • StateuseState,返回 [当前值, 修改函数],修改函数固定为 set + 首字母大写的值名
  • RefuseRef,命名常以 Ref 结尾,表示「某个 DOM/组件的引用」
function DetailPage({ navigation }) {
    const { params } = navigation.state
    const detailQuery = useData.useDetail(params.id)

    // state:存会变化、触发重渲染的数据
    const [detail, setDetail] = useState(null)
    const [modalVisible, setModalVisible] = useState(false)

    // ref:存不触发重渲染的引用(DOM、定时器、临时对象等)
    const scrollViewRef = useRef(null)
    const commentData = useRef({ stars: 1 })

    // 接口数据回来后写入 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 → 标题样式
  • [styles.subtitle, isSelected && styles.activeTab] → 数组写法,可叠加多个样式

8. 常量

说明: 整个应用不变、多处复用的值,用 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 虽不是常量写法,但也是固定字符串数组,用于标识缓存:

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

9. 目录结构

说明: 目录名反映内容类型。页面和组件用 PascalCase(和组件名一致),工具、Hook、请求层用 camelCase。

main/
├── page/
│   └── Detail/                 # 页面,PascalCase
│       ├── index.js            # 导出 Detail 组件
│       └── components/
│           └── Bottom.js       # 页面私有子组件
├── components/
│   └── MyModal/                  # 公共组件,PascalCase
│       └── index.js
├── request/
│   └── detail.js                 # 接口 Hook,camelCase
├── useHooks/
│   └── useFetch/
│       └── index.js              # 请求封装
└── utils/
    └── UIConfig.js               # 工具函数

10. 速查表

场景 风格 示例 说明
组件 PascalCase function Detail() {} UI 单元
Hook use + camelCase function useDetail(id) {} 必须以 use 开头
default import 自取 import useData from './detail.js' 名字与导出方无关
命名 import 固定 import { useQuery } from '...' 名字必须对应
props 数据 camelCase name={data.name} 普通传参
props 布尔 is/has userCollected={true} 表示 true/false
props 回调 on onCollection={fn} 事件回调
内部函数 handle const handlePress = () => {} 组件内定义
state camelCase [detail, setDetail] 会触发重渲染
ref xxxRef scrollViewRef 不触发重渲染
style camelCase title: { fontSize: 18 } RN 样式对象
常量 UPPER_SNAKE API_BASE_URL 全局不变值
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容