写在前面:为什么从 React Native 开始?

跨平台移动开发有很多编程框架,主流的有 React Native、Flutter 等。React Native 使用 JavaScript/TypeScript 和 React 构建移动应用,组件通常会映射为对应平台的原生视图,因此界面和交互更贴近系统原生风格。今天,我们就从零开始学习 React Native。

这一课的目标不是让你一口气成为高手,而是帮你把路线图画出来:知道 React Native 是什么、怎么运行、核心概念有哪些、如何写出第一个可交互的小应用。


一、理解 React Native 的技术基础

1.1 什么是跨平台开发?

做一个手机 App,希望 iPhone 和 Android 都能用。传统做法是 iOS 用 Swift 或 Objective-C 写一套,Android 用 Kotlin 或 Java 写一套。成本高:两套语言、两套工具链、两套代码库。

跨平台开发的目标,就是尽量用一套主要代码覆盖多个平台。React Native 让你用 JavaScript 或 TypeScript 写代码,再通过框架把这些代码变成手机上的真实界面。

这里要强调一点:React Native 并不是“在手机里跑一个网页”。它最终渲染的是原生控件,而不是网页元素。这是它和早期 Hybrid 方案(比如 Cordova、Ionic)最大的区别。

1.2 React Native 和 React 是什么关系?

React 是用于构建用户界面的 JavaScript 库,最早运行在浏览器中。React Native 是 React 思想在移动端的延伸。它们共享组件化、JSX、props 和 state、单向数据流、声明式 UI 等概念。

区别在运行环境:

  • React 在浏览器里,常见标签是 <div>、<span>;
  • React Native 在移动端,常见组件是 <View>、<Text>。

学过 React,学 React Native 会轻松很多;没学过,也不用怕,这一课会把必要概念讲清楚。

1.3 编程方式:声明式

React Native 是声明式的:你描述“界面应该是什么样”,而不是命令框架“怎么把界面改出来”。

看一个计数器。命令式写法:

// JavaScript + React Native
import { useRef } from 'react';                                    // 引入 useRef,用来创建跨渲染保持不变的引用
import { View, Text, Button } from 'react-native';                 // 引入 View、Text、Button 三个组件

function App() {                                                    // 定义函数组件 App
  const textRef = useRef(null);                                     // 创建 ref,初始为 null,之后指向 Text
  const countRef = useRef(0);                                       // 创建 ref,初始为 0,保存当前计数

  const handlePress = () => {                                       // 定义按钮点击处理函数
    countRef.current = countRef.current + 1;                        // 把 countRef 中的数字加一
    textRef.current.setNativeProps({                                // 通过 ref 找到 Text,修改它的原生属性
      text: `你点击了 ${countRef.current} 次`,                        // 把 Text 文字替换成新的计数文字
    });                                                             // 结束 setNativeProps 调用
  };                                                                // 结束 handlePress 函数

  return (                                                          // 返回界面描述
    <View>                                                          // 最外层容器
      <Text ref={textRef}>你点击了 0 次</Text>                        // 文本,绑定 ref,初始显示 0 次
      <Button title="点我" onPress={handlePress} />                 // 按钮,点击执行 handlePress
    </View>                                                         // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 App 组件

这段代码手动维护计数,手动找到文本,手动修改内容。每一步都要你亲自指挥,所以叫命令式。

声明式写法:

// JavaScript + React Native
import { useState } from 'react';                                   // 引入 useState,用来创建状态
import { View, Text, Button } from 'react-native';                  // 引入 View、Text、Button 组件

function App() {                                                    // 定义函数组件 App
  const [count, setCount] = useState(0);                            // 创建状态 count,初始值 0

  return (                                                          // 返回界面描述
    <View>                                                          // 外层容器
      <Text>你点击了 {count} 次</Text>                               // 文本直接绑定 count
      <Button title="点我" onPress={() => setCount(count + 1)} />   // 点击时更新 count
    </View>                                                         // 结束 View
  );                                                                // 结束 return
}                                                                   // 结束 App 组件

你只描述:count 是多少,界面就显示什么。点击按钮,只更新 count。界面怎么变,框架负责。

一句话对比:命令式告诉你每一步怎么做;声明式告诉你结果是什么样,过程自己想办法。

1.4 函数是一等公民

JavaScript 中,函数是“一等公民”:可以赋值给变量、当参数传递、作为返回值。

// JavaScript
const sayHello = () => console.log('你好');                         // 定义箭头函数,赋值给 sayHello

function run(fn) {                                                  // 定义 run,参数 fn 是一个函数
  fn();                                                             // 调用传入的 fn
}                                                                   // 结束 run 函数

run(sayHello);                                                      // 把 sayHello 作为参数传给 run

React Native 把这个特点用得很充分:组件是函数,事件处理是函数,useState 也是函数。

// JavaScript + React Native
import { Text } from 'react-native';                                // 引入 Text 组件

function Welcome({ name }) {                                        // 定义组件 Welcome,解构出 name
  return <Text>欢迎,{name}</Text>;                                  // 返回一个 Text,内容包含 name
}                                                                   // 结束 Welcome 组件
// JavaScript + React Native (JSX)
<Button title="点我" onPress={() => setCount(count + 1)} />         // onPress 接收一个函数,点击时执行

因为函数可以像普通值一样传来传去,React Native 才能用很小的单元组合出复杂的界面。

1.5 函数组件的本体:普通函数返回界面描述

现代 React Native 最主流的组件写法是函数组件:

// JavaScript + React Native
import { Text } from 'react-native';                                // 引入 Text 组件

function Hello() {                                                  // 定义函数组件 Hello
  return <Text>你好,React Native</Text>;                            // 返回一个 Text 组件
}                                                                   // 结束 Hello 组件

它没有继承类,也没有手动创建原生视图。它只是返回一段界面描述。这段看起来像 HTML 的代码是 JSX,会被转换成普通 JavaScript 对象,也就是一份“描述”:这里放一个 Text,内容是“你好”。

React Native 拿到这份描述后,把它映射成对应平台的原生视图。所以,函数组件的本体不是直接操作视图,而是返回界面描述。组件是一个纯函数,输入是数据,输出是对界面的描述。至于怎么把描述变成真实界面,交给框架。

1.6 本质:视图 = 函数(状态)

React Native 的核心思想可以压缩成一句话:

// 概念表达
视图 = 函数(状态)                                                    // 视图是状态经过函数计算的结果

界面不是手动拼出来、改出来的,而是由状态经过函数计算出来的。

// JavaScript + React Native
import { useState } from 'react';                                   // 引入 useState Hook
import { View, Text, Button } from 'react-native';                  // 引入 View、Text、Button 组件

function Counter() {                                                // 定义函数组件 Counter
  const [count, setCount] = useState(0);                            // 创建状态 count,初始值 0

  return (                                                          // 返回界面描述
    <View>                                                          // 外层容器
      <Text>当前计数:{count}</Text>                                  // 显示当前计数
      <Button title="加一" onPress={() => setCount(count + 1)} />   // 点击后 count 加一
    </View>                                                         // 结束 View
  );                                                                // 结束 return
}                                                                   // 结束 Counter 组件
  • count 是状态;
  • Counter 是函数;
  • 返回的 JSX 是界面描述;
  • count 变化时,Counter 重新执行,得到新界面描述,框架更新界面。

你不需要说“把那个 Text 改成 1”,只需要说“count 变成 1 了”。界面自然会变。

状态是输入,视图是输出。状态变,函数重新执行,输出新视图。框架比较新旧视图,高效更新原生界面。

这里有一个隐含前提:函数必须是“纯”的。相同输入返回相同输出,不在渲染过程中修改外部变量或发请求。这些事应该放在事件处理或副作用里,第4课会专门讲。

1.7 React Native 是怎么工作的?

你写 JavaScript,屏幕上显示的是原生控件。React Native 提供了一层“翻译”机制:

  • <View> 在 Android 上对应原生视图容器,在 iOS 上对应 UIView 一类视图;
  • <Text> 对应 Android 的 TextView 或 iOS 的 UILabel 一类文本控件;
  • 样式属性被转换为各平台可理解的布局和绘制指令。

React Native 应用通常看起来和原生应用比较接近。注意,是“比较接近”,不是“完全一样”。不同平台有不同设计规范,开发者也可以通过样式和组件做出自己的风格。

早期 React Native 使用 Bridge 在 JavaScript 和原生代码之间通信,异步消息带来了一些性能限制。后来引入并逐步完善的新架构,包括 JSI、TurboModules、Fabric 等,让 JavaScript 和原生之间的调用更直接,渲染和通信效率都有提升。自 React Native 0.76 起,新架构默认启用,框架仍在持续演进。

1.8 前置知识

你不需要事先精通 React、Android 或 iOS,但需要掌握 JavaScript 基础:

  • 变量:const、let;
  • 函数:普通函数、箭头函数;
  • 对象和数组;
  • 解构赋值;
  • 模板字符串;
  • 模块导入导出:import、export;
  • 数组方法:map、filter 等。

不熟的话,先补一补再回来。后面的代码会大量用到。


二、搭建开发环境

2.1 两条路线:Expo 与 React Native CLI

Expo:React Native 的推荐框架之一,适合大多数初学者和很多实际项目。它处理了大量原生构建配置,不需要一开始就安装 Android Studio 或 Xcode,也能在真机或模拟器上运行应用。

React Native Community CLI:需要编写自定义原生代码、接入特定原生 SDK,或项目对原生层有特殊要求时使用。更灵活,但环境配置更繁琐。

本文以 Expo 为主要路线,关键地方会提到 CLI 的差异。

2.2 安装 Node.js

Node.js 是 JavaScript 运行环境,npm 是包管理器。

macOS:

# Bash 终端命令
brew install node                                                   # 用 Homebrew 安装 Node.js(自动带上 npm)

Windows:访问 Node.js 官网,下载 LTS 版本安装。

验证:

# Bash 终端命令
node --version                                                      # 查看 Node.js 版本
npm --version                                                       # 查看 npm 版本

建议使用当前 LTS 版本,例如 Node.js 22 或更高。具体以官方文档为准。

2.3 安装 Watchman(macOS 推荐)

# Bash 终端命令
brew install watchman                                               # 安装 Watchman 文件监听工具

Watchman 能提升热重载体验。Windows 用户一般可以跳过。

2.4 创建 Expo 项目

# Bash 终端命令
npx create-expo-app@latest MyFirstApp                               # 创建名为 MyFirstApp 的 Expo 项目
cd MyFirstApp                                                       # 进入项目目录

不同 Expo 模板的目录结构可能不同。有的用 App.js 或 App.tsx 作为入口,有的用 app 目录和文件路由。本文以传统的 App.js 结构为例。

2.5 安装编辑器

推荐 VS Code,可安装这些扩展:

  • ES7+ React/Redux/React-Native snippets;
  • React Native Tools;
  • Prettier;
  • ESLint。

2.6 运行第一个 App

# Bash 终端命令
npx expo start                                                      # 启动 Expo 开发服务器

终端出现二维码后:

  • 用手机下载 Expo Go,扫码运行;
  • 按 a 在 Android 模拟器运行;
  • 按 i 在 iOS 模拟器运行(仅 macOS,需 Xcode)。

看到欢迎界面,环境就搭建成功了。


三、Hello World:理解第一个 React Native 应用

3.1 打开 App.js

// JavaScript + React Native (Expo)
import { StatusBar } from 'expo-status-bar';                        // 引入 StatusBar,控制状态栏样式
import { StyleSheet, Text, View } from 'react-native';              // 引入样式表、文本、容器

export default function App() {                                     // 定义并默认导出 App 组件
  return (                                                          // 返回界面描述
    <View style={styles.container}>                                 // 外层容器,应用 container 样式
      <Text>Open up App.js to start working on your app!</Text>     // 显示默认提示文字
      <StatusBar style="auto" />                                    // 状态栏组件,自动选择亮色或暗色
    </View>                                                         // 结束容器
  );                                                                // 结束 return
}                                                                   // 结束 App 组件

const styles = StyleSheet.create({                                  // 创建样式表
  container: {                                                      // container 样式
    flex: 1,                                                        // 占满父容器全部可用空间
    backgroundColor: '#fff',                                        // 背景色为白色
    alignItems: 'center',                                           // 交叉轴(水平)居中
    justifyContent: 'center',                                       // 主轴(垂直)居中
  },                                                                // 结束 container 样式
});                                                                 // 结束样式表

3.2 import 语句

// JavaScript
import { StatusBar } from 'expo-status-bar';                        // 从 expo-status-bar 取出 StatusBar
import { StyleSheet, Text, View } from 'react-native';              // 从 react-native 取出 StyleSheet、Text、View

import { ... } from '...' 是 JavaScript 的模块语法:从某个包里取出某些东西来用。花括号里是具名导入。如果包里有一个默认导出,可以不用花括号,比如 import React from 'react'。

3.3 函数组件

// JavaScript + React Native
export default function App() {                                     // 定义函数组件 App 并默认导出
  return (                                                          // 返回界面描述
    // ...                                                          // 中间省略
  );                                                                // 结束 return
}                                                                   // 结束 App 组件

组件是 React Native 的基本单元。函数组件就是一个返回 JSX 的 JavaScript 函数。export default 表示默认导出,其他文件可以导入它。

3.4 JSX:在 JavaScript 中写界面

// JavaScript + React Native (JSX)
return (                                                            // 开始返回 JSX
  <View style={styles.container}>                                   // 外层 View,应用 container 样式
    <Text>Open up App.js to start working on your app!</Text>       // 文本内容
    <StatusBar style="auto" />                                      // 状态栏组件
  </View>                                                           // 结束 View
);                                                                  // 结束 return

JSX 允许你在 JavaScript 中写类似 XML 的结构。<View> 是容器组件,类似 Web 的 <div>;<Text> 是文本组件,所有文字通常都要放在它里面;<StatusBar> 是状态栏组件。

在 React Native 中,不能把文字直接放在 <View> 里,必须包在 <Text> 中。因为文本的渲染和布局在移动端和普通视图不同,Text 负责字体、行高、换行、选择等能力。

3.5 样式

// JavaScript + React Native
const styles = StyleSheet.create({                                  // 创建样式表
  container: {                                                      // container 样式
    flex: 1,                                                        // 占满父容器可用空间
    backgroundColor: '#fff',                                        // 白色背景
    alignItems: 'center',                                           // 交叉轴居中
    justifyContent: 'center',                                       // 主轴居中
  },                                                                // 结束 container 样式
});                                                                 // 结束样式表

React Native 用 JavaScript 对象定义样式,通过 StyleSheet.create 创建。

React Native 默认 flexDirection 是 column,所以主轴是垂直方向,交叉轴是水平方向。内容会水平居中、垂直居中。

3.6 热重载

把 <Text> 内容改成“你好,React Native”,保存。Expo 自动刷新,手机上立刻看到变化。


四、核心概念深入:组件、Props 与 State

4.1 核心组件

React Native 提供了一套跨平台核心组件,它们会映射为各平台原生控件。

View——最基础的容器:

// JavaScript + React Native (JSX)
<View style={{ padding: 20 }}>                                      // 容器,内边距 20
  <Text>内容</Text>                                                  // 容器内的文本
</View>                                                             // 结束 View

Text——显示文本,支持嵌套:

// JavaScript + React Native (JSX)
<Text style={{ fontSize: 18 }}>                                     // 外层文本,字号 18
  这是 <Text style={{ color: 'red' }}>红色</Text> 文字                // 嵌套一个红色文本
</Text>                                                             // 结束外层文本

Image——显示图片:

// JavaScript + React Native (JSX)
<Image                                                              // 图片组件
  source={{ uri: 'https://example.com/photo.jpg' }}                 // 网络图片地址
  style={{ width: 200, height: 200 }}                               // 宽高各 200
/>                                                                  // 自闭合结束

TextInput——文本输入框:

// JavaScript + React Native (JSX)
<TextInput                                                          // 输入框组件
  placeholder="请输入名字"                                           // 占位提示
  onChangeText={(text) => console.log(text)}                        // 输入变化时打印内容
/>                                                                  // 自闭合结束

ScrollView——可滚动容器:

// JavaScript + React Native (JSX)
<ScrollView>                                                        // 可滚动容器
  <Text>很多内容...</Text>                                           // 内部文本
</ScrollView>                                                       // 结束 ScrollView

Button——基础按钮:

// JavaScript + React Native (JSX)
<Button title="点击我" onPress={() => alert('被点击了')} />            // 按钮,点击弹出提示

此外还有 FlatList、SectionList、Pressable、Modal 等组件,后续会逐步学习。

4.2 Props:从外部传入数据

// JavaScript + React Native
import { Text, View } from 'react-native';                          // 引入 Text 和 View 组件

function Greeting(props) {                                          // 定义组件 Greeting,接收 props
  return (                                                          // 返回界面描述
    <View>                                                          // 外层容器
      <Text>你好,{props.name}!</Text>                              // 使用 props.name 显示问候语
    </View>                                                         // 结束 View
  );                                                                // 结束 return
}                                                                   // 结束 Greeting 组件

export default function App() {                                     // 定义并默认导出 App 组件
  return (                                                          // 返回界面描述
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>  // 居中容器
      <Greeting name="小明" />                                       // 使用 Greeting,传入 name="小明"
      <Greeting name="小红" />                                       // 使用 Greeting,传入 name="小红"
    </View>                                                         // 结束 View
  );                                                                // 结束 return
}                                                                   // 结束 App 组件

Greeting 接收 name,每次传入不同值,就显示不同问候。这就是组件复用的基础。

也可以用解构:

// JavaScript + React Native
function Greeting({ name }) {                                       // 直接解构出 name
  return (                                                          // 返回界面描述
    <View>                                                          // 容器
      <Text>你好,{name}!</Text>                                    // 显示问候语
    </View>                                                         // 结束 View
  );                                                                // 结束 return
}                                                                   // 结束 Greeting 组件

Props 是只读的。从“视图 = 函数(状态)”的角度看,props 也是状态的一部分。父组件传入的 props 变了,子组件函数重新执行,返回新的界面描述。

4.3 State:组件内部的数据

// JavaScript + React Native
import { useState } from 'react';                                   // 引入 useState Hook
import { Text, View, Button } from 'react-native';                  // 引入 Text、View、Button 组件

export default function App() {                                     // 定义并默认导出 App 组件
  const [count, setCount] = useState(0);                            // 创建状态 count,初始值 0

  return (                                                          // 返回界面描述
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>  // 居中容器
      <Text style={{ fontSize: 24 }}>你点击了 {count} 次</Text>      // 显示点击次数
      <Button title="点我" onPress={() => setCount(count + 1)} />   // 点击后 count 加一
    </View>                                                         // 结束 View
  );                                                                // 结束 return
}                                                                   // 结束 App 组件

useState(0) 返回数组:当前值 count 和更新函数 setCount。点击按钮后调用 setCount,组件重新渲染,界面自动更新。

4.4 Props 与 State 对比

特性PropsState
来源父组件传入组件内部定义
可变性只读可通过 set 函数更新
用途配置组件、传递数据管理组件内部动态数据
更新触发渲染父组件重新传入set 函数被调用

4.5 Flexbox 布局基础

React Native 使用 Flexbox 布局,和 Web 的 Flexbox 相似,但默认值不同:

  • flexDirection 默认是 column,不是 row;
  • 所有尺寸都是无单位的逻辑像素。

常用属性:

  • flexDirection:主轴方向,row 或 column;
  • justifyContent:主轴对齐方式;
  • alignItems:交叉轴对齐方式;
  • flex:占据剩余空间的比例。
// JavaScript + React Native (JSX)
<View style={{ flex: 1, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>  // 横向排列、两端对齐、交叉轴居中
  <Text>左</Text>                                                   // 左侧文本
  <Text>右</Text>                                                   // 右侧文本
</View>                                                             // 结束 View

4.6 再次理解:视图 = 函数(状态)

  • 你写一个函数组件,比如 App、Greeting、Counter;
  • 它接收 props,内部可能有 state;
  • 它返回 JSX,也就是界面描述;
  • 当 props 或 state 变化,函数重新执行;
  • React Native 比较新旧界面描述,更新真正的原生视图。

组件不是“一个长期存在的、需要你手动修改的界面对象”,而更像“一个根据输入计算输出的函数”。你给它状态,它还你界面。状态变,界面重新计算。


五、练习题

练习题1:选择题——React Native基础概念

题目:下列关于 React Native 的说法,哪一项是正确的?

A. React Native 使用 HTML 标签如 <div> 来构建界面
B. React Native 的 <View> 最终会映射为原生视图
C. React Native 只能在 iOS 上运行
D. React Native 使用 CSS 文件来定义样式

参考答案:B

解读:React Native 使用 <View>、<Text> 等组件,会映射为各平台的原生控件,而不是 HTML 标签。它支持 iOS 和 Android 等多个平台。样式通过 JavaScript 对象定义,而不是 CSS 文件。


练习题2:填空题——JSX与组件

题目:补全代码,使 App 组件在屏幕上显示“你好,React Native”。

// JavaScript + React Native
import { View, Text } from 'react-native';                          // 引入 View 和 Text 组件

export default function App() {                                     // 定义并默认导出 App
  return (                                                          // 返回界面描述
    <View>                                                          // 外层容器
      <____>你好,React Native</____>                                // 空缺处应填 Text
    </View>                                                         // 结束 View
  );                                                                // 结束 return
}                                                                   // 结束 App 组件

参考答案:Text

解读:所有文字必须放在 <Text> 组件内,不能直接写在 <View> 或其他容器中。Text 负责文本的渲染、字体、换行等能力。


练习题3:改错题——样式与单位

题目:下面的样式代码有一处错误,请指出并修正。

// JavaScript + React Native
const styles = StyleSheet.create({                                  // 创建样式表
  container: {                                                      // container 样式
    flex: 1,                                                        // 占满父容器
    padding: 20px,                                                  // 错误:React Native 不支持 px 单位
    backgroundColor: '#fff',                                        // 背景白色
  },                                                                // 结束 container 样式
});                                                                 // 结束样式表

参考答案:将 padding: 20px 改为 padding: 20。

解读:React Native 中的尺寸使用无单位的逻辑像素,框架根据设备密度自动适配。写 px 会导致样式无效或报错。所有尺寸属性(width、height、margin、padding、fontSize 等)都不需要单位。


练习题4:代码阅读题——Props

题目:阅读代码,说出屏幕上显示的内容。

// JavaScript + React Native
import { Text, View } from 'react-native';                          // 引入 Text 和 View 组件

function Welcome({ name }) {                                        // 定义组件 Welcome,解构出 name
  return <Text>欢迎,{name}!</Text>;                                // 返回问候文本
}                                                                   // 结束 Welcome 组件

export default function App() {                                     // 定义并默认导出 App 组件
  return (                                                          // 返回界面描述
    <View>                                                          // 外层容器
      <Welcome name="小明" />                                       // 使用 Welcome,传入 name="小明"
      <Welcome name="小红" />                                       // 使用 Welcome,传入 name="小红"
    </View>                                                         // 结束 View
  );                                                                // 结束 return
}                                                                   // 结束 App 组件

参考答案:屏幕上会显示两行文字:“欢迎,小明!”和“欢迎,小红!”。

解读:Welcome 通过 props 接收 name。父组件 App 使用了两次 Welcome,分别传入不同的 name 值,渲染出不同问候语。函数是一等公民,组件就是函数,接收 props 作为输入,返回界面描述作为输出。


练习题5:动手题——State计数器

题目:创建一个计数器组件,显示当前数字,并提供一个按钮,点击后数字加 1。

参考答案:

// JavaScript + React Native
import { useState } from 'react';                                   // 引入 useState Hook
import { View, Text, Button, StyleSheet } from 'react-native';      // 引入需要的组件

export default function Counter() {                                 // 定义并默认导出 Counter 组件
  const [count, setCount] = useState(0);                            // 创建状态 count,初始值 0

  return (                                                          // 返回界面描述
    <View style={styles.container}>                                 // 应用 container 样式
      <Text style={styles.countText}>{count}</Text>                 // 显示当前计数
      <Button title="加一" onPress={() => setCount(count + 1)} />   // 点击加一
    </View>                                                         // 结束 View
  );                                                                // 结束 return
}                                                                   // 结束 Counter 组件

const styles = StyleSheet.create({                                  // 创建样式表
  container: {                                                      // container 样式
    flex: 1,                                                        // 占满父容器
    justifyContent: 'center',                                       // 主轴居中
    alignItems: 'center',                                           // 交叉轴居中
  },                                                                // 结束 container 样式
  countText: {                                                      // countText 样式
    fontSize: 48,                                                   // 字号 48
    fontWeight: 'bold',                                             // 加粗
    marginBottom: 20,                                               // 底部外边距 20
  },                                                                // 结束 countText 样式
});                                                                 // 结束样式表

解读:useState(0) 创建初始值为 0 的状态 count 和更新函数 setCount。点击按钮时调用 setCount(count + 1),状态更新后组件重新渲染。Counter 是函数,count 是状态,界面就是 Counter(count) 的结果。


练习题6:简答题——Props与State区别

题目:用自己的话简述 Props 和 State 的区别。

参考答案:Props 是父组件传给子组件的数据,子组件只能读取,不能直接修改;State 是组件内部自己管理的数据,可以通过 set 函数修改,修改后会触发组件重新渲染。

解读:Props 用于组件之间的数据传递和配置,是只读的;State 用于管理组件内部动态变化的数据。它们共同构成“视图 = 函数(状态)”中的“状态”输入。


练习题7:样式题——Flexbox居中

题目:写一个 View 的样式对象,使其子元素在水平和垂直方向都居中。

参考答案:

// JavaScript + React Native
{
  flex: 1,                                                          // 占满父容器
  justifyContent: 'center',                                         // 主轴居中(默认垂直方向)
  alignItems: 'center',                                             // 交叉轴居中(默认水平方向)
}

解读:默认 flexDirection 为 column,主轴是垂直方向,交叉轴是水平方向。justifyContent: 'center' 让子元素在主轴(垂直)居中,alignItems: 'center' 让子元素在交叉轴(水平)居中。配合 flex: 1 占满父容器,即可完全居中。


练习题8:事件处理题——按钮点击

题目:创建一个按钮,点击后控制台输出“按钮被点击了”。

参考答案:

// JavaScript + React Native (JSX)
<Button title="点击我" onPress={() => console.log('按钮被点击了')} />  // 按钮,点击打印日志

解读:Button 的 onPress 接收一个函数,点击时执行。console.log 输出会显示在 Metro 终端中,是常用调试方式。这里也体现了函数是一等公民。


练习题9:列表渲染题——map

题目:有一个数组 const fruits = ['苹果', '香蕉', '橙子'],请用 map 方法将其渲染成一组 <Text> 列表。

参考答案:

// JavaScript + React Native (JSX)
{fruits.map((fruit, index) => (                                     // 遍历 fruits,每个元素接收 fruit 和 index
  <Text key={index}>{fruit}</Text>                                  // 生成一个 Text,key 用索引
))}                                                                 // 结束 map 表达式

解读:map 遍历数组并返回新的 JSX 元素数组。每个元素需要唯一的 key 属性,帮助 React 高效更新列表。实际项目中如果列表会动态增删,最好使用稳定唯一的 id。


练习题10:受控组件题——TextInput

题目:创建一个文本输入框,当用户输入内容时,在控制台打印输入的文字。

参考答案:

// JavaScript + React Native (JSX)
<TextInput                                                          // 输入框组件
  placeholder="请输入内容"                                           // 占位提示
  onChangeText={(text) => console.log(text)}                        // 每次输入变化时打印输入的文字
/>                                                                  // 自闭合结束

解读:TextInput 的 onChangeText 接收一个函数,每次输入内容变化时调用,参数是当前输入的文本。这是处理用户输入的基础方式。


练习题11:条件渲染题——登录状态

题目:根据 isLoggedIn 状态,显示“欢迎回来”或“请登录”。请写出对应的 JSX 代码。

参考答案:

// JavaScript + React Native (JSX)
{isLoggedIn ? <Text>欢迎回来</Text> : <Text>请登录</Text>}          // 三元表达式根据 isLoggedIn 决定显示哪段文字

解读:JSX 中可以使用三元运算符进行条件渲染。界面随状态变化,正是“视图 = 函数(状态)”的直接体现。


练习题12:图片显示题——Image

题目:使用 Image 组件显示一张网络图片,地址为 https://example.com/photo.jpg,宽度和高度均为 200。

参考答案:

// JavaScript + React Native (JSX)
<Image                                                              // 图片组件
  source={{ uri: 'https://example.com/photo.jpg' }}                 // 网络图片地址
  style={{ width: 200, height: 200 }}                               // 宽高均为 200
/>                                                                  // 自闭合结束

解读:Image 的 source 接收一个对象,网络图片使用 uri 字段指定地址。必须设置宽度和高度,否则图片可能无法显示。本地图片可以使用 require('./path/to/image.png')。


练习题13:滚动视图题——ScrollView

题目:当页面内容超过屏幕高度时,应该使用什么组件包裹内容以实现滚动?

参考答案:ScrollView

解读:ScrollView 是可滚动容器,当子内容超出屏幕范围时,用户可以通过滑动查看全部内容。ScrollView 会一次性渲染所有子组件,如果列表很长,建议使用 FlatList 以获得更好的性能。


练习题14:综合布局题——个人名片

题目:创建一个个人名片组件,要求:左侧是一个圆形头像(用 View 模拟),右侧上方显示姓名,下方显示职位,整体水平排列。

参考答案:

// JavaScript + React Native
import { View, Text, StyleSheet } from 'react-native';              // 引入 View、Text、StyleSheet

export default function BusinessCard() {                            // 定义并默认导出 BusinessCard
  return (                                                          // 返回界面描述
    <View style={styles.card}>                                      // 名片容器
      <View style={styles.avatar} />                                // 头像方块
      <View style={styles.info}>                                    // 右侧信息容器
        <Text style={styles.name}>张明</Text>                       // 姓名
        <Text style={styles.title}>前端工程师</Text>                 // 职位
      </View>                                                       // 结束信息容器
    </View>                                                         // 结束名片容器
  );                                                                // 结束 return
}                                                                   // 结束 BusinessCard 组件

const styles = StyleSheet.create({                                  // 创建样式表
  card: {                                                           // card 样式
    flexDirection: 'row',                                           // 水平排列
    alignItems: 'center',                                           // 交叉轴居中
    padding: 16,                                                    // 内边距 16
    backgroundColor: '#fff',                                        // 白色背景
    borderRadius: 8,                                                // 圆角 8
    margin: 16,                                                     // 外边距 16
  },                                                                // 结束 card 样式
  avatar: {                                                         // avatar 样式
    width: 60,                                                      // 宽 60
    height: 60,                                                     // 高 60
    borderRadius: 30,                                               // 圆角半径 30,变成圆形
    backgroundColor: '#4A90D9',                                     // 蓝色背景
    marginRight: 16,                                                // 右侧间距 16
  },                                                                // 结束 avatar 样式
  info: {                                                           // info 样式
    flex: 1,                                                        // 占满剩余宽度
  },                                                                // 结束 info 样式
  name: {                                                           // name 样式
    fontSize: 20,                                                   // 字号 20
    fontWeight: 'bold',                                             // 加粗
    marginBottom: 4,                                                // 底部间距 4
  },                                                                // 结束 name 样式
  title: {                                                          // title 样式
    fontSize: 14,                                                   // 字号 14
    color: '#888',                                                  // 灰色文字
  },                                                                // 结束 title 样式
});                                                                 // 结束样式表

解读:flexDirection: 'row' 让头像和信息区域水平排列,alignItems: 'center' 让它们垂直居中。borderRadius: 30 配合宽高各 60 将正方形 View 变成圆形。info 区域的 flex: 1 让它占据剩余宽度。


练习题15:综合应用题——简单待办事项

题目:创建一个简单的待办事项应用,要求:

  1. 初始有三条待办事项;
  2. 点击某条事项可以切换其完成状态,完成的事项显示删除线;
  3. 底部有一个按钮,点击后添加一条新的待办事项。

参考答案:

// JavaScript + React Native
import { useState } from 'react';                                   // 引入 useState Hook
import { View, Text, Button, StyleSheet, ScrollView } from 'react-native';  // 引入需要的组件

export default function TodoApp() {                                 // 定义并默认导出 TodoApp 组件
  const [todos, setTodos] = useState([                              // 创建状态 todos,初始三条数据
    { id: 1, text: '学习 React Native 基础', done: false },         // 第一条,未完成
    { id: 2, text: '完成练习题', done: false },                     // 第二条,未完成
    { id: 3, text: '搭建开发环境', done: true },                    // 第三条,已完成
  ]);                                                               // 结束 useState

  const toggleTodo = (id) => {                                      // 定义切换完成状态的函数
    setTodos(                                                       // 更新 todos
      todos.map((todo) =>                                           // 遍历每一项
        todo.id === id ? { ...todo, done: !todo.done } : todo       // id 匹配则翻转 done,否则原样返回
      )                                                             // 结束 map
    );                                                              // 结束 setTodos
  };                                                                // 结束 toggleTodo

  const addTodo = () => {                                           // 定义添加新待办的函数
    const newId = todos.length                                      // 判断 todos 是否为空
      ? Math.max(...todos.map((t) => t.id)) + 1                     // 有数据时取最大 id + 1
      : 1;                                                          // 没数据时从 1 开始
    const newTodo = {                                               // 构造新待办对象
      id: newId,                                                    // 新 id
      text: `新待办事项 ${newId}`,                                   // 文本
      done: false,                                                  // 未完成
    };                                                              // 结束 newTodo
    setTodos([...todos, newTodo]);                                  // 用展开运算符追加
  };                                                                // 结束 addTodo

  return (                                                          // 返回界面描述
    <View style={styles.container}>                                 // 外层容器
      <Text style={styles.heading}>我的待办</Text>                   // 标题
      <ScrollView style={styles.list}>                              // 可滚动列表
        {todos.map((todo) => (                                      // 遍历 todos
          <Text                                                     // 每条渲染一个 Text
            key={todo.id}                                           // key 用唯一 id
            style={[styles.todoItem, todo.done && styles.todoDone]} // 组合样式,完成时加删除线
            onPress={() => toggleTodo(todo.id)}                     // 点击切换完成状态
          >                                                         // 开始 Text 标签
            {todo.done ? '✓ ' : '○ '}{todo.text}                    // 前缀符号 + 文本
          </Text>                                                   // 结束 Text 标签
        ))}                                                         // 结束 map
      </ScrollView>                                                 // 结束 ScrollView
      <Button title="添加新事项" onPress={addTodo} />                // 添加按钮
    </View>                                                         // 结束 View
  );                                                                // 结束 return
}                                                                   // 结束 TodoApp 组件

const styles = StyleSheet.create({                                  // 创建样式表
  container: {                                                      // container 样式
    flex: 1,                                                        // 占满父容器
    padding: 20,                                                    // 内边距 20
    paddingTop: 60,                                                 // 顶部内边距 60
  },                                                                // 结束 container 样式
  heading: {                                                        // heading 样式
    fontSize: 28,                                                   // 字号 28
    fontWeight: 'bold',                                             // 加粗
    marginBottom: 20,                                               // 底部间距 20
  },                                                                // 结束 heading 样式
  list: {                                                           // list 样式
    flex: 1,                                                        // 占满剩余空间
    marginBottom: 16,                                               // 底部间距 16
  },                                                                // 结束 list 样式
  todoItem: {                                                       // todoItem 样式
    fontSize: 18,                                                   // 字号 18
    padding: 12,                                                    // 内边距 12
    borderBottomWidth: 1,                                           // 底部分隔线宽 1
    borderBottomColor: '#eee',                                      // 分隔线颜色
  },                                                                // 结束 todoItem 样式
  todoDone: {                                                       // todoDone 样式
    textDecorationLine: 'line-through',                             // 删除线
    color: '#aaa',                                                  // 灰色文字
  },                                                                // 结束 todoDone 样式
});                                                                 // 结束样式表

解读:toggleTodo 通过 map 生成新数组,用展开运算符创建新对象,体现“不直接修改原状态”的原则。addTodo 用展开运算符追加新事项。key 帮助 React 高效识别列表项。整个 TodoApp 是函数,todos 是状态,界面是 TodoApp(todos) 的结果。状态一变,函数重新执行,界面自动更新。


六、常见问题与学习建议

6.1 为什么文字必须放在 Text 里?

React Native 的文本渲染和布局与普通视图不同。Text 负责字体、行高、换行、选择等能力。把文字直接放在 View 中通常不会按预期显示。

6.2 样式为什么没有单位?

React Native 中尺寸是无单位的逻辑像素,框架根据设备密度适配。不要写 px。

6.3 如何调试?

  • console.log 输出到 Metro 终端;
  • 利用热重载和快速刷新;
  • 在 Expo 中使用开发者菜单。

6.4 学习建议

  • 先跑起来,再理解原理;
  • 多改代码,观察变化;
  • 不要死记 API,理解组件、props、state、样式和布局;
  • 遇到错误先看终端和红屏提示;
  • 循序渐进,先做小界面,再做小交互,再做小应用;
  • 时刻记住:React Native 是声明式 UI,函数是一等公民,函数组件返回界面描述,视图是状态的函数。

写在最后

这一课我们从零开始,了解了 React Native 的定位、开发环境、第一个应用、核心组件、Props、State、Flexbox,以及 15 道循序渐进的练习题。

重点讲清了三件事:

  1. React Native 是声明式 UI 编程范式:你描述“界面是什么样”,框架负责“怎么更新”。
  2. 函数是一等公民:组件、事件处理、useState,都是函数。
  3. 函数组件的本体是普通函数,返回界面描述;本质可以概括为:视图 = 函数(状态)。

信息量大很正常。第一课的目标是建立整体认识,不需要一次记住所有细节,只要知道遇到问题该往哪个方向找答案。


第2课预告

下一课,我们进入 【React Native娓娓道来】第2课 声明式UI本质:视图 = 函数(状态)在React Native中的具体体现。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐