红糖云服-小程序

1. 小程序开发

TodoList 小程序开发流程演示

本文用 待办清单(TodoList) 场景串一遍流程:使用 @htyf-mp/cli 从模板生成项目,在本地开发简单 TodoList 界面,再到 真机调试(扫码)验证。

你将获得什么

  • 一个可运行的 RN 小程序 TodoList 页面(可添加 / 勾选 / 删除)。
  • 一套从初始化到真机调试的可复用流程。
  • app-template 项目结构和导航注册方式的基础认知。

命令行里的菜单、初始化问答、克隆模板、创建成功提示可参考 快速开始 中的 「CLI 界面与流程(实拍)」(含主菜单、选择 app-template、GitHub 克隆、成功页等截图)。
下面示例与截图中的演示一致:目录名 todolist,应用名 小程序,模板 app-template

关于 `_apps_temp_`

CLI 从模板拉取并解压后的项目,目录结构与本仓库中的示例目录 _apps_temp_ 同类(含 app.jsonsrc/package.json 等)。
你在自己电脑上初始化时,文件夹名由你填写(例如 my-todo-app),不必与 _apps_temp_ 同名。

如何选择模板

如果你想走 Taro + React 的 Web 小程序 路线,请改看 Web 小程序 TodoList 流程演示(对应 web-template / _web_temp_)。
本篇只针对 app-template(RN 小程序模板)。


流程总览(Checklist)

阶段你要做的事
1. 新建项目空目录执行 npx @htyf-mp/cli🆕 初始化新小程序项目 → 目录名如 todolist → 模板 app-template → 镜像 GitHub (最新)
2. 安装依赖cd todolist(或你的目录名)→ npm install(或 yarn);可按成功页提示执行 npm run ios / npm run android
3. 开发 TodoList按下文 复制示例代码:新建 TodoList.tsx 并在 navigation/index.tsx 里注册到底部 Tab
4. 真机调试项目根(有 app.json 且含 htyf)执行 npm run htyf小程序 - 真机调试 → 输入版本 → 扫码
5.(可选)打包项目根执行 CLI → 小程序 - 打包小程序 → 得到构建产物(如 dist.*.dgz

详细步骤

1. 新建项目(CLI 初始化)

  1. 准备一个空目录(不要放在已有 app.json 的项目里)。
  2. 在终端执行:
npx @htyf-mp/cli
  1. 选择 🆕 初始化新小程序项目
  2. 按提示填写(与命令行界面一致),例如:
    • 应用程序目录名称todolist(可自定,需为合法目录名)
    • 应用程序名称:如 小程序
    • 模板类型app-template(与 _apps_temp_ 同类 RN 应用小程序模板)
    • 模板镜像:如 GitHub (最新)(会执行克隆模板仓库)

完成后会生成项目文件夹,内含已配置好的 app.jsonsrc 入口。成功页会提示 项目路径下一步操作

2. 安装依赖并启动开发

cd todolist   # 与你在上一步填写的目录名一致
npm install

按需启动原生工程(以 CLI 成功页与模板 README 为准):

npm run ios
# 或
npm run android

构建红糖云服小程序资源时,可使用模板提供的脚本,例如:

npm run htyf

Metro / 开发服务启动方式以项目 README 为准。

阶段验收:能正常启动项目,且看到默认首页。

3. 示例代码:TodoList 页面 + 注册到底部 Tab

下面示例与 app-template / _apps_temp_ 一致:使用 @react-navigation 静态配置,在 HomeTabs(底部 Tab) 里新增一页 TodoList
若你本地模板导航写法略有不同,请以实际文件为准,思路相同:新建 Screen → 在 Tab/Stack 中注册

提示

TodoList 先做内存状态即可;持久化(如 AsyncStorage)可在跑通真机后再加。

3.1 新建 src/navigation/screens/TodoList.tsx

import { useCallback, useState } from 'react';
import {
  FlatList,
  Pressable,
  StyleSheet,
  Text,
  TextInput,
  View,
} from 'react-native';
 
type TodoItem = { id: string; title: string; done: boolean };
 
export function TodoList() {
  const [text, setText] = useState('');
  const [items, setItems] = useState<TodoItem[]>([]);
 
  const add = useCallback(() => {
    const t = text.trim();
    if (!t) {
      return;
    }
    setItems((prev) => [
      ...prev,
      {
        id: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
        title: t,
        done: false,
      },
    ]);
    setText('');
  }, [text]);
 
  const toggle = useCallback((id: string) => {
    setItems((prev) =>
      prev.map((it) => (it.id === id ? { ...it, done: !it.done } : it)),
    );
  }, []);
 
  const remove = useCallback((id: string) => {
    setItems((prev) => prev.filter((it) => it.id !== id));
  }, []);
 
  return (
    <View style={styles.root}>
      <Text style={styles.h1}>TodoList</Text>
      <View style={styles.row}>
        <TextInput
          style={styles.input}
          value={text}
          onChangeText={setText}
          placeholder="输入待办,点添加"
          placeholderTextColor="#888"
          onSubmitEditing={add}
          returnKeyType="done"
        />
        <Pressable style={styles.addBtn} onPress={add}>
          <Text style={styles.addBtnText}>添加</Text>
        </Pressable>
      </View>
      <FlatList
        data={items}
        keyExtractor={(it) => it.id}
        ListEmptyComponent={
          <Text style={styles.empty}>暂无待办,先添加一条</Text>
        }
        renderItem={({ item }) => (
          <View style={styles.itemRow}>
            <Pressable onPress={() => toggle(item.id)} style={styles.check}>
              <Text style={styles.checkText}>{item.done ? '✓' : '○'}</Text>
            </Pressable>
            <Text
              style={[styles.itemTitle, item.done && styles.itemDone]}
              numberOfLines={2}
            >
              {item.title}
            </Text>
            <Pressable onPress={() => remove(item.id)} hitSlop={8}>
              <Text style={styles.del}>删除</Text>
            </Pressable>
          </View>
        )}
      />
    </View>
  );
}
 
const styles = StyleSheet.create({
  root: { flex: 1, padding: 16, paddingTop: 48 },
  h1: { fontSize: 22, fontWeight: '600', marginBottom: 16, color: '#111' },
  row: { flexDirection: 'row', gap: 8, marginBottom: 12 },
  input: {
    flex: 1,
    borderWidth: 1,
    borderColor: '#ccc',
    borderRadius: 8,
    paddingHorizontal: 12,
    paddingVertical: 10,
    fontSize: 16,
    color: '#111',
  },
  addBtn: {
    justifyContent: 'center',
    paddingHorizontal: 14,
    backgroundColor: '#007aff',
    borderRadius: 8,
  },
  addBtnText: { color: '#fff', fontWeight: '600' },
  empty: { textAlign: 'center', color: '#888', marginTop: 24 },
  itemRow: {
    flexDirection: 'row',
    alignItems: 'center',
    paddingVertical: 10,
    borderBottomWidth: StyleSheet.hairlineWidth,
    borderBottomColor: '#ddd',
    gap: 8,
  },
  check: { width: 36, alignItems: 'center' },
  checkText: { fontSize: 18, color: '#007aff' },
  itemTitle: { flex: 1, fontSize: 16, color: '#111' },
  itemDone: { textDecorationLine: 'line-through', color: '#888' },
  del: { color: '#ff3b30', fontSize: 14 },
});

3.2 修改 src/navigation/index.tsx

  1. 在文件顶部增加:
import { Text } from 'react-native';
import { TodoList } from './screens/TodoList';
  1. createBottomTabNavigatorscreens 里(与 HomeUpdates 同级)增加一项,例如:
    TodoList: {
      screen: TodoList,
      options: {
        title: 'Todo',
        tabBarIcon: ({ color, size }) => (
          <Text style={{ fontSize: size ?? 22, color }}>✓</Text>
        ),
      },
    },

保存后重新编译 / 刷新 Metro,底部应出现 Todo 页,即可在模拟器或真机调试里验证。

阶段验收:底部 Tab 出现 Todo,并能正常进入 TodoList 页面。

4. 真机调试(推荐用于联调)

  1. 确保 iPhone 已安装红糖云服 App,且与电脑 同一局域网(或已配置端口转发)。
  2. 项目根目录执行:
npm run htyf
  1. 选择 📦 小程序 - 真机调试
  2. 按提示输入版本号(如 x.y.z)。
  3. 构建完成后会出现 二维码,用红糖云服 App 扫码即可在真机上调试。

5.(可选)打包

在项目根执行 npx @htyf-mp/cli,选择 🔍 小程序 - 打包小程序,按提示输入版本。
产物与路径以 CLI 输出为准(常见在 dist 目录或配置的输出路径)。

常见卡点

1) 扫码后页面空白或加载失败

  • 先确认手机与电脑在同一网络。
  • 再确认你执行 npm run htyf 的目录就是项目根(含 app.json)。
  • 如果刚改完代码,先保存并重新构建一次再扫码。

2) 新增页面后看不到 Tab

  • 检查 src/navigation/index.tsx 是否真的把 TodoList 注册到了底部导航。
  • 检查导入路径是否正确(./screens/TodoList)。

模板结构对照(与 _apps_temp_ 同类)

初始化完成后,典型包含:

路径说明
app.jsonhtyftypeappidentryversion
src/index.tsx(或 README 指明的入口)htyf.entry 对应
package.json依赖中通常包含 @htyf-mp/cli@htyf-mp/js-sdk

与模板 README 对齐的配置要点

基于 _apps_temp_/README.mdapp-template 重点关注 app.json 中这几个字段:

  • type:应为 app(RN 小程序)。
  • appUrlConfig:客户端从这里拉取应用配置/元数据。
  • zipUrl:客户端从这里下载资源包(用于分发与更新)。
  • version:版本号,与打包/调试流程配套维护。
  • engines:引擎版本声明(与模板要求一致)。

可这样记:appUrlConfig 管“配置从哪来”,zipUrl 管“代码包从哪来”。

README 命令口径

模板 README 常用 yarn 安装依赖;本文写 npm install 只是等价写法,二选一即可。
打包/真机调试命令保持一致:npm run htyf

对外分发的最小闭环

  1. 本地按 RN 正常开发并验证功能。
  2. app.json 配好 appUrlConfigzipUrl
  3. 执行 npm run htyf 产出资源。
  4. 上传资源到 zipUrl 对应地址,供他人拉取更新。

延伸阅读

On this page