React Native 开发者的 JS/TS 语言入门
本文由 AI 协助更新
本文面向 React Native 初学者:已有其它语言经验(如 Java、Objective-C)。
内容仅涉及语言与类型,不包含 React/React Native 框架、状态管理或项目特定模式。
示例均以「待办事项管理应用」场景为例。
1. 变量与基本类型
1.1 声明:const 与 let
项目中只用 const 和 let,不用 var。
const:常量引用,不能重新赋值;若值为对象/数组,其内部属性/元素仍可修改。let:可重新赋值的变量。
// services/todo.ts
const todos: TodoItem[] = [];
let loading = false;
// utils/format.ts
const parts = version.split('.').map(Number);
for (let index = 0; index < parts.length; index++) { ... }
1.2 类型标注(TypeScript)
在变量、参数、返回值后加 : Type 即类型标注。常见基础类型:string、number、boolean。
// api/todo.ts
function getTodoList(platform: "iOS" | "Android"): Promise<TodoListResponse> {
return http.get("todos/list", { params: { platform } });
}
可为空时常用 联合类型:string | undefined、number | null。
// services/todo.ts
let errorMsg: string | undefined = "";
// types/todo.ts
export interface TodoItem {
title: string | null | undefined;
note: string | null | undefined;
...
}
undefined 与 null:undefined 表示“未赋值或缺失”,null 表示“有意为空”。二者在可选链 ?. 和空值合并 ?? 中都会被视为“空”,触发短路或使用默认值。
1.3 字面量类型
类型可以是具体字面量的联合,表示“只能是这几个值之一”。
// 例如参数或变量的类型标注:platform 只能是 'iOS' 或 'Android'
function getTodoList(platform: 'iOS' | 'Android') { ... }
// types/todo.ts
status: 0 | 1; // 0 未完成,1 已完成
priority: 0 | 1 | 2; // 低 / 中 / 高
syncState: "idle" | "syncing" | "success" | "failed";
2. 对象与类型定义(TypeScript)
2.1 对象字面量与属性访问
用 对象字面量 { key: value } 创建对象,多个属性用逗号分隔。
const item = { id: "1", title: "待办", done: false };
属性访问:用 . 访问已知属性名(obj.key);用 方括号 obj[key] 访问动态 key(如变量或含特殊字符的 key)。
item.title; // '待办'
item["done"]; // false
const k = "id";
item[k]; // '1'
下面用 interface 描述这类对象的“形状”(有哪些属性、类型是什么)。
2.2 interface:描述对象形状
interface 描述对象有哪些属性及其类型,项目中大量用于 API 数据、Props、状态等。
// types/todo.ts
interface TodoStore {
items: TodoItem[];
loading: boolean;
fetchTodos: () => Promise<void>;
addTodo: (title: string) => Promise<void>;
}
// types/todo.ts(节选)
export interface TodoItem {
id: string;
title: string;
done: boolean;
createdAt: { value: number };
priority?: 0 | 1 | 2;
...
}
2.3 可选属性 ?:
属性名后加 ? 表示该属性可以不存在(相当于 undefined)。
// types/todo.ts
remindAt?: number; // 提醒时间戳,可选
tags?: string[]; // 标签,可选
metadata?: { ... };
// types/todo.ts
export interface TodoItem {
...
link?: string; // 关联链接,可选
}
2.4 type:联合、泛型与别名
type 可定义联合类型、对象形状、泛型别名等。
// types/todo.ts
// 联合类型:TodoFilter 只能是 'all'、'active'、'done' 其中之一
export type TodoFilter = "all" | "active" | "done";
// 泛型类型别名:ApiPayload<T> 中的 T 是类型参数,用的时候再指定(如 ApiPayload<TodoItem>)
// state.data 的类型就是 T,metadata、version 是固定结构
export type ApiPayload<T> = {
state: { data: T };
metadata: { updatedAt: number };
version: number;
};
// 函数类型别名:TodoListener 表示“一个函数,接收 Partial<TodoItem> 类型的参数,无返回值”
// 常用于回调、监听器的类型标注
export type TodoListener = (payload: Partial<TodoItem>) => void;
2.5 泛型
前文 2.4 的 type 里出现的 <T> 就是泛型参数,下面展开用法。泛型表示“类型参数”,在函数、接口、类型中复用同一结构、不同元素类型。
// api/todo.ts
interface PageResult<T> {
data: T[];
has_next: boolean;
has_prev: boolean;
pagenum: number;
...
}
function fetchTodoList(...) {
return http.get<PageResult<TodoItem>>('/todos/list', { ... });
}
常见工具类型:
Partial<T>:把 T 的所有属性变为可选。Record<K, V>:键为 K、值为 V 的对象类型。
// types/todo.ts
export type TodoListener = (payload: Partial<TodoItem>) => void;
// Record 示例:Record<'all' | 'active' | 'done', TodoItem[]>
2.6 as const:只读字面量推断
as const 让 TypeScript 把字面量推断为最窄类型(只读、字面量本身),而不是放宽为 string 等。
// api/http.ts
export const DefaultRequestConfig = {
headers: { "X-Request-Source": "app" },
} as const;
// 返回值字面量推断为只读
return { todos, loading, refresh } as const;
2.7 类型断言 as
类型断言 value as Type 告诉编译器“把该值当作类型 Type”,仅影响类型检查,不改变运行时。读项目代码时若遇到 as TodoItem、as string 等,即表示此处由开发者保证类型,编译器按该类型检查。
const data = JSON.parse(str) as TodoItem; // 解析结果当作 TodoItem 使用
3. 运算符与表达式
3.1 严格相等 ===
比较两个值是否相等时使用 严格相等 ===:只有值与类型都相同才为 true,避免 == 的隐式类型转换带来的歧义。如 status === 1、title === '待办'。项目内通常由 ESLint 约束使用 ===。
1 === 1; // true
1 === "1"; // false(类型不同)
null === undefined; // false
3.2 可选链 ?.
可选链 ?.:当左侧为 null 或 undefined 时短路,不继续访问,整体结果为 undefined。
// utils/i18n.ts
message?.[locale] ?? message?.en;
// 模板字符串中
`[TodoService] item updated: ${item?.done} ${loading}`;
3.3 空值合并 ??
空值合并 ??:仅当左侧为 null 或 undefined 时取右侧值;与 || 不同,0、''、false 不会被替换。
// utils/i18n.ts
const text = message?.[locale] ?? message?.en;
// 从可能为空的 item 中解构,空时用默认对象
const { title, done, createdAt } = item ?? {};
3.4 展开运算符 ...
展开 ...:数组或对象“展开”成元素或属性,常用于复制、合并。
// 展开:合并两个数组得到新数组
const merged = [...state, ...data];
// 展开:复制数组
const todoIds = [...allTodoIds];
3.5 模板字符串
反引号 ` 与 ${表达式}:在字符串中嵌入表达式。
// 模板字符串中嵌入表达式
`[TodoService] item changed: ${item?.done} ${loading}`;
console.warn(`[TodoList] save failed: ${error.message}`);
title: `待办 ${index + 1}`,
4. 函数
4.1 箭头函数
箭头函数:(参数) => 返回值或函数体,常用作回调、简短函数。
// 合并数组:返回新数组,不修改原数组
const append = (state: TodoItem[], newItems: TodoItem[]) => [
...state,
...newItems,
];
// 箭头函数作为回调:从对象中“选”出部分字段
const pick = (item: TodoItem) => ({ id: item.id, done: item.done });
// 数组方法中常用箭头函数
todos.map((item, index) => (selectedIds.includes(item.id) ? index : -1));
todos.findIndex((item) => item.id === currentId);
4.2 默认参数
参数可带默认值,调用时可省略。
// 默认参数:pagesize 省略时为 20
async function fetchTodoPage(pagenum: number, pagesize = 20) { ... }
// store/todoStore.ts
export function filterTodos(status: string, tag: string, excludeIds: string[] = []) { ... }
4.3 解构参数
在参数位置解构对象,直接得到属性名作为变量。变量声明也可以解构:从右侧对象或数组中取出字段或元素,赋给左侧列出的变量(如 3.2 节的 const { title, done, createdAt } = item ?? {} 就是从 item 或默认对象中解构出属性)。
// 解构参数:从第一个参数对象中取出属性
function openScreen({ sceneId }: { sceneId: string }) { ... }
// 解构参数 + 空值合并后解构;变量声明也可解构
function showTodo(opts: { item: TodoItem }) {
const { item } = opts;
const { title, done, createdAt } = item ?? {};
...
}
5. 数组与常用方法
数组基础:
字面量与访问:用数组字面量
[]创建,用length取长度,用 下标arr[0]访问或修改元素。const arr = [1, 2, 3]; arr.length; // 3 arr[0]; // 1 arr[1] = 10; // 修改后 arr 为 [1, 10, 3].push(x):在末尾添加一个或多个元素,修改原数组,返回新长度。const a = [1, 2]; a.push(3); // 返回 3,a 变为 [1, 2, 3] a.push(4, 5); // 返回 5,a 变为 [1, 2, 3, 4, 5].pop():移除并返回最后一个元素,修改原数组。const a = [1, 2, 3]; a.pop(); // 返回 3,a 变为 [1, 2].shift():移除并返回第一个元素,修改原数组。const a = [1, 2, 3]; a.shift(); // 返回 1,a 变为 [2, 3].unshift(x):在开头添加一个或多个元素,修改原数组,返回新长度。const a = [2, 3]; a.unshift(1); // 返回 3,a 变为 [1, 2, 3] a.unshift(0, -1); // 返回 5,a 变为 [0, -1, 1, 2, 3].splice(start, deleteCount?, ...items):从下标start起删除deleteCount个元素,可选地插入items;修改原数组,返回被删除元素组成的数组。const a = [1, 2, 3, 4]; a.splice(1, 2); // 从下标 1 删 2 个,返回 [2, 3],a 变为 [1, 4] a.splice(1, 0, 2, 3); // 从下标 1 删 0 个、插入 2 和 3,a 变为 [1, 2, 3, 4]
以下方法不修改原数组,而是返回新数组或单个值:
.map(fn):对每项执行回调,用返回值组成新数组。典型用法:列表转 UI 数据、提取 id 数组等。const ids = todos.map((item) => item.id); const parts = version.split(".").map(Number);.filter(fn):保留回调返回 true 的项,得到新数组。典型用法:筛未完成、按标签筛选等。const undone = todos.filter((item) => !item.done);.find(fn):返回第一个使回调返回 true 的元素(若无则undefined)。典型用法:按 id 取单条待办。const item = todos.find((t) => t.id === currentId);.findIndex(fn):返回第一个使回调返回 true 的下标(若无则-1)。典型用法:定位当前选中项索引。const idx = todos.findIndex((item) => item.id === currentId);.reduce(fn, initialValue):从左到右依次归并,回调接收“累计值”与“当前项”,返回新的累计值,最终得到单一结果。典型用法:求和、聚合为对象等。const sum = [1, 2, 3].reduce((acc, n) => acc + n, 0); // 6
6. 异步与错误处理
6.1 Promise
Promise 是 JavaScript 中表示“异步操作最终结果”的对象:要么成功得到一个值(fulfilled),要么失败得到一个原因(rejected),在完成前处于进行中(pending)。网络请求、定时器、读写文件等异步 API 常返回 Promise。
- 状态:
pending(进行中)→fulfilled(成功,有结果值)或rejected(失败,有错误原因)。 - 不可逆:一旦从 pending 变为 fulfilled 或 rejected,就不会再变。
- 类型:
Promise<T>表示“将来会得到类型为 T 的值”(或失败)。
创建 Promise:用 new Promise((resolve, reject) => { ... })。传入的函数称为执行器(executor):Promise 一经创建,执行器便会立即执行,此时 Promise 处于 pending 状态;在异步逻辑里调用 resolve(值) 表示成功,reject(错误) 表示失败。
function delay(ms: number): Promise<void> {
return new Promise((resolve) => {
setTimeout(() => resolve(), ms);
});
}
消费 Promise:用 .then(onFulfilled, onRejected?) 在“完成后”执行回调;用 .catch(onRejected) 只处理失败;用 .finally(callback) 无论成败都执行(如关 loading)。
getTodoList("iOS")
.then((res) => {
console.log(res.data);
})
.catch((err) => {
console.warn(err);
})
.finally(() => {
loading = false;
});
链式调用:.then 返回新的 Promise,其结果是回调的返回值,因此可以连续 .then。若回调里返回 Promise,则“等待该 Promise 完成”后再进入下一个 .then。
getTodoList("iOS")
.then((res) => res.data)
.then((data) => data.filter((t) => !t.done))
.then((undone) => console.log(undone));
6.2 async / await
async/await 是基于 Promise 的语法糖:用“像写同步代码”的方式写异步逻辑,可读性更好,也方便用 try/catch 统一处理错误。
- async:函数前加
async,该函数一定返回 Promise。若 return 普通值,会被自动包成Promise.resolve(值);若 throw,会得到Promise.reject(错误)。 - await:只能在 async 函数内部使用。
await promise会暂停当前函数,等该 Promise 完成后:- 若成功,表达式的结果就是 Promise 的成功值;
- 若失败,会抛出异常,可用 try/catch 捕获。
// async 函数返回 Promise<TodoListResponse>
async function loadTodos(): Promise<TodoListResponse> {
const res = await getTodoList("iOS"); // 等待完成,得到 res
return res;
}
等价关系:下面两段含义相同。
// 用 then
function load() {
return getTodoList("iOS").then((res) => res.data);
}
// 用 async/await
async function load() {
const res = await getTodoList("iOS");
return res.data;
}
顺序与并行:多个异步操作若互不依赖,可同时发起再 await,减少总耗时。
// 顺序:先 A 再 B,总时间 ≈ 时间A + 时间B
const a = await fetchA();
const b = await fetchB(a.id);
// 并行:同时发 A 和 B,总时间 ≈ max(时间A, 时间B)
const [list, config] = await Promise.all([getTodoList("iOS"), getConfig()]);
6.3 try / catch / finally
在 async 函数中常用 try/catch/finally 处理 await 带来的异常:await 的 Promise 若 reject,会在此处抛出,由 catch 捕获。
// 对象方法中
async fetchTodos() {
try {
const result = await todoApi.getTodoList(...);
this.items = result.data;
} catch (error) {
console.warn('fetchTodos failed', error);
}
}
// 普通 async 函数 + try/finally
async function fetchTodoPage(pagenum: number, pagesize = 20) {
try {
loading = true;
const result = await fetchTodoList(pagenum, pagesize);
return result.data;
} catch (error) {
throw error;
} finally {
loading = false;
}
}
注意:只有 await 了 Promise,其 reject 才会变成同步抛出的异常,才能被 try-catch 捕获;若函数内没有 await 该 Promise,错误发生在“未等待”的 Promise 上,try-catch 捕获不到。
// 捕获不到:fetchTodoList() 未 await,reject 不会抛到当前函数
async function load() {
try {
fetchTodoList(1); // 未 await,错误不会进 catch
} catch (e) {
console.warn(e); // 不会执行
}
}
// 能捕获:await 后 reject 会抛到当前函数
async function loadOk() {
try {
await fetchTodoList(1);
} catch (e) {
console.warn(e); // 会执行
}
}
6.4 .catch()
不写 try/catch 时,可在 Promise 链上使用 .catch() 处理错误。
// 某处调用
loadTodos().catch((error) => {
// 处理错误
});
7. 模块与导入导出
用 ES 模块 组织代码:一个文件是一个模块,通过 import 引入、通过 export 导出。
默认导出:一个文件只能有一个默认导出,导入时名称可自取。
// api/todo.ts
export default {
getTodoList,
addTodo,
};
// 其它文件(按相对路径引入,如与 api 同级的 services 里)
import todoApi from "./api/todo";
命名导出:可导出多个,导入时名称需与导出一致,或用 as 重命名。
// store/todoStore.ts
export function getTodoById(id: string) { ... }
export function addTodo(title: string) { ... }
export function removeTodo(id: string) { ... }
// 其它文件
import { getTodoById, addTodo, removeTodo } from "./store/todoStore";
类型导入与导出:若只用于类型标注、不参与运行,可用 import type { TodoItem } from '...',编译后会被擦除。导出类型用 export type { TodoItem } 或 export interface TodoItem、export type TodoFilter = 'all' | 'done' 等。
同一文件可既有默认导出又有命名导出:
// services/todo.ts
export default function getTodoList() { ... }
export interface TodoItem {
id: string;
title: string;
done: boolean;
}
export function formatTodo(item: TodoItem) { ... }
8. 包管理器与 package.json
package.json 中的相关字段
package.json 位于项目根目录,记录项目信息和依赖,供包管理器读取。常用字段如下:
| 字段 | 含义 |
|---|---|
name | 项目名称 |
version | 项目版本号(语义化版本,如 "1.0.0") |
scripts | 脚本命令,如 "start": "react-native start",通过包管理器的 run 命令执行 |
dependencies | 生产环境依赖,运行、发布时会安装 |
devDependencies | 开发环境依赖,仅开发、构建时需要(如 TypeScript、测试库) |
示例(节选):
{
"name": "my-todo-app",
"version": "1.0.0",
"scripts": {
"start": "react-native start",
"android": "react-native run-android"
},
"dependencies": {
"react": "18.2.0",
"react-native": "0.72.0"
},
"devDependencies": {
"typescript": "5.0.0",
"@types/react": "18.0.0"
}
}
依赖版本号前的 ^ 与 ~:^1.2.3 表示可升级到同一主版本内的最新版(如 1.3.0),~1.2.3 表示只可升级修订号(如 1.2.4)。安装后包管理器会生成 package-lock.json(或 yarn.lock、pnpm-lock.yaml),锁定实际安装的精确版本,保证团队与 CI 结果一致。
常用包管理器与命令
常见包管理器有 npm、yarn、pnpm,一个项目通常只使用其中一种(看项目根目录存在哪个 lock 文件或文档说明)。
常用命令(以 npm 为例,yarn/pnpm 类似):
| 操作 | npm | yarn | pnpm |
|---|---|---|---|
| 安装依赖 | npm install | yarn | pnpm install |
| 添加依赖 | npm install 包名 | yarn add 包名 | pnpm add 包名 |
| 运行脚本 | npm run 脚本名 | yarn 脚本名 | pnpm run 脚本名 |
例如执行 package.json 里 scripts.start:npm run start(或 yarn start、pnpm run start)。
9. ESLint
ESLint 对 JavaScript/TypeScript 做静态检查,发现潜在错误和风格问题,统一写法(如要求 ===、限制未使用变量等)。保存或提交时编辑器、CI 常会自动跑 ESLint,报错会标在代码里或终端;不通过时可能无法通过检查。
项目中的配置:配置文件多为根目录下的 .eslintrc.js 或 eslint.config.js;package.json 的 scripts 里可有 "lint": "eslint ...",执行 npm run lint 即手动检查。
简要配置示例(.eslintrc.js):
module.exports = {
root: true,
extends: ["eslint:recommended"],
rules: {
eqeqeq: ["error", "always"], // 要求使用 ===
"no-unused-vars": "warn",
},
};
extends 继承预设规则集,rules 里可覆盖或开启单条规则('error' / 'warn' / 'off')。