React Native 开发者的 JS/TS 语言入门

本文由 AI 协助更新

本文面向 React Native 初学者:已有其它语言经验(如 Java、Objective-C)。

内容仅涉及语言与类型,不包含 React/React Native 框架、状态管理或项目特定模式。

示例均以「待办事项管理应用」场景为例。


1. 变量与基本类型

1.1 声明:const 与 let

项目中只用 constlet,不用 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 即类型标注。常见基础类型:stringnumberboolean

// api/todo.ts
function getTodoList(platform: "iOS" | "Android"): Promise<TodoListResponse> {
  return http.get("todos/list", { params: { platform } });
}

可为空时常用 联合类型string | undefinednumber | null

// services/todo.ts
let errorMsg: string | undefined = "";
// types/todo.ts
export interface TodoItem {
    title: string | null | undefined;
    note: string | null | undefined;
    ...
}

undefined 与 nullundefined 表示“未赋值或缺失”,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 TodoItemas string 等,即表示此处由开发者保证类型,编译器按该类型检查。

const data = JSON.parse(str) as TodoItem; // 解析结果当作 TodoItem 使用

3. 运算符与表达式

3.1 严格相等 ===

比较两个值是否相等时使用 严格相等 ===:只有值与类型都相同才为 true,避免 == 的隐式类型转换带来的歧义。如 status === 1title === '待办'。项目内通常由 ESLint 约束使用 ===

1 === 1; // true
1 === "1"; // false(类型不同)
null === undefined; // false

3.2 可选链 ?.

可选链 ?.:当左侧为 nullundefined 时短路,不继续访问,整体结果为 undefined

// utils/i18n.ts
message?.[locale] ?? message?.en;
// 模板字符串中
`[TodoService] item updated: ${item?.done} ${loading}`;

3.3 空值合并 ??

空值合并 ??:仅当左侧为 nullundefined 时取右侧值;与 || 不同,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 TodoItemexport 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 结果一致。

常用包管理器与命令

常见包管理器有 npmyarnpnpm,一个项目通常只使用其中一种(看项目根目录存在哪个 lock 文件或文档说明)。

常用命令(以 npm 为例,yarn/pnpm 类似):

操作npmyarnpnpm
安装依赖npm installyarnpnpm install
添加依赖npm install 包名yarn add 包名pnpm add 包名
运行脚本npm run 脚本名yarn 脚本名pnpm run 脚本名

例如执行 package.json 里 scripts.startnpm run start(或 yarn startpnpm run start)。


9. ESLint

ESLint 对 JavaScript/TypeScript 做静态检查,发现潜在错误和风格问题,统一写法(如要求 ===、限制未使用变量等)。保存或提交时编辑器、CI 常会自动跑 ESLint,报错会标在代码里或终端;不通过时可能无法通过检查。

项目中的配置:配置文件多为根目录下的 .eslintrc.jseslint.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')。


附录:参考资料

上次更新: