Skip to main content

第一课:介绍和配置

(一)结构介绍

在现代化的H5网站开发中,鼓励前后端分离,也就是在一台服务器或多台服务器上,分别部署前端服务和后端服务,前端服务负责渲染页面和动态更新,并把用户的输入数据反馈给后端,后端服务只提供API来接受前端提交的数据,并进行更复杂的计算、逻辑和存储,并把结果返回给前端服务。

举个例子:我们可以使用 Flask 搭建简单的后端服务器,使用 React 搭建前端服务器。

而微信小程序和 H5 网站 的关键区别在于,微信小程序的 前端服务器 在小程序上线时,需要上传并托管在微信的官方服务平台上,而后端服务器可以使用自己的服务器。同时小程序不依赖于浏览器,因此使用的前端语言不是 HTML + CSS + JS,而是微信自己开发的 WXML + WXSS + JS

tip
  1. 小程序中的“前端服务器”并非传统意义上的 HTTP Server
    • 其实微信小程序没有传统意义的前端服务器进程,而是前端代码直接打包上传到微信平台,运行在微信客户端的沙箱中。所以它其实更像是“前端代码打包运行平台”而不是一个提供 HTTP 服务的服务器。
  2. 小程序不能像 React 一样热更新
    • 所有更新必须重新上传版本并通过审核(除非使用灰度发布或体验版),这与自己托管的前端服务有些不同。
  3. 微信前端限制较多
    • 不能直接使用 DOM 操作、windowdocument 等浏览器对象。
    • 文件上传/下载、WebSocket、网络请求等必须使用微信提供的 API。

(二)开发环境部署

1. 注册和下载工具

先去注册开发者账号:公众号, 一个手机号码只能注册5个小程序

微信的开发者文档在这里:文档

下载 微信web开发者工具,根据自己的操作系统下载对应的安装包进行安装即可。

安装好之后,需要登录,最终开发者界面如下

image-20250323200634355


2. 创建小程序项目

登录后点击「+ 创建项目」:

  • 项目名称:如 my-demo
  • 项目目录:选择一个空的本地文件夹
  • AppID:可以先选择「无 AppID」创建测试项目,也可以用你注册好的小程序 AppID
  • 开发模式:小程序image-20250323202559590

要找到注册好的小程序 AppID,请前往:https://mp.weixin.qq.com/wxamp/devprofile/get_profile

在 小程序 - 管理 - 开发管理 ,在这里可以找到你的 AppID

image-20250323201921127

最后在开发者工具中点击右下角的 ”创建“,现在这个项目就创建好了。

项目创建成功后,根目录在图中显示的:C:\Users\28121\WeChatProjects\miniprogram-2

使用 tree 命令查看目录结构:

├─miniprogram/             // 小程序主目录
│ ├─pages/ // 页面目录,每个子目录对应一个页面
│ │ ├─index/ // 首页页面
│ │ └─logs/ // 日志页面
│ └─utils/ // 工具函数目录(如封装通用工具方法)
└─typings/ // TypeScript 类型定义目录
└─types/
└─wx/ // 微信 API 类型定义(自动生成或从 SDK 拷贝)

使用 tree /f 查看详细的文件结构

│  package.json                    // 项目依赖和脚本配置文件(如 npm 包)
│ project.config.json // 微信开发者工具项目配置(项目设置、编译配置等)
│ project.private.config.json // 微信开发者工具的用户私密配置(不会上传版本)
│ tsconfig.json // TypeScript 编译配置文件

├─miniprogram/
│ │ app.json // 全局配置文件(定义页面路径、窗口样式、导航栏等)
│ │ app.ts // 小程序入口逻辑(注册生命周期、全局数据等)
│ │ app.wxss // 全局样式表(所有页面通用)
│ │
│ ├─pages/
│ │ ├─index/
│ │ │ index.json // 当前页面配置(标题、组件等)
│ │ │ index.ts // 页面逻辑代码(JS/TS)
│ │ │ index.wxml // 页面结构(类似 HTML)
│ │ │ index.wxss // 页面样式(类似 CSS)
│ │ │
│ │ └─logs/
│ │ logs.json // 日志页面配置
│ │ logs.ts // 日志页面逻辑
│ │ logs.wxml // 日志页面结构
│ │ logs.wxss // 日志页面样式
│ │
│ └─utils/
│ util.ts // 工具函数模块(如时间格式化等)

└─typings/
│ index.d.ts // 类型入口声明文件

└─types/
│ index.d.ts // 类型定义聚合入口

└─wx/
index.d.ts // 微信类型定义入口
lib.wx.api.d.ts // 微信 API 定义
lib.wx.app.d.ts // App 实例定义
lib.wx.behavior.d.ts // Behavior 定义(用于组件封装)
lib.wx.cloud.d.ts // 云开发 API 定义
lib.wx.component.d.ts// 自定义组件定义
lib.wx.event.d.ts // 事件对象定义
lib.wx.page.d.ts // 页面实例定义

(三)开发你的第一个页面

1. 创建页面

开发工具中的页面介绍

在这里插入图片描述

我们以一个“打招呼”的页面为例。

  1. 在pages文件夹右键建立新的文件夹 hello
  2. 在文件中右键新建page hello
  3. 哪个page在最上面,默认显示哪页

此时会创建对应的文件结构

image-20250323204925573

创建好页面之后,前往全局配置文件 app.json 中添加路径(可能已经自动加上了)

路径必须是相对于 miniprogram 目录的相对路径。

  "pages": [
    "pages/index/index",   // 第一个就是首页
    "pages/logs/logs",
    "pages/hello/hello"    // ← 新加这行
  ],

2. 编写页面内容

hello.wxml

<view class="container">
  <text>Hello, {{name}} 👋</text>
</view>
  • <view> 是微信小程序中最基础的容器组件,相当于 HTML 里的 <div>

  • class="container" 指定了该元素使用名为 container 的 CSS 类

  • <text> 是用于显示一段纯文本的组件,相当于 HTML 中的 <span>

  • {{name}}数据绑定语法,表示从页面的 data 中读取 name 变量,并将其插入到这里

    对应的 JS 语法在 hello.ts


hello.wxss

.container {
  padding: 40rpx;
  font-size: 36rpx;
  text-align: center;
}
  • .container 是一个 类选择器,用于匹配 WXML 中带有 class="container" 的元素。

  • padding: 40rpx; 为容器四周加上 40rpx 的内边距

  • font-size: 36rpx; 设置文字大小为 36rpx,适用于不同屏幕自动适配。

  • text-align: center; 设置文字居中对齐。

tip

什么是 rpx

rpx 是微信小程序特有的单位,代表 响应像素

设备宽度屏幕宽度对应的 rpx
iPhone 6750rpx = 375px
iPhone X750rpx = 屏幕宽度
小米 11750rpx = 屏幕宽度

也就是说,rpx相对设备宽度自适应的单位。比如:

  • 页面宽度永远是 750rpx;
  • 如果你写 width: 375rpx,它在所有设备上都会占据屏幕宽度的一半。

hello.ts

Page({
  data: {
    name: '小程序开发者'
  }
})
  • Page({...}) 这是 微信小程序的核心 API,用来注册一个页面实例。

    • 每一个页面文件(例如 pages/hello/hello.ts)都必须用 Page({...}) 来定义它的逻辑。
    • 这个函数的参数是一个对象,包含页面的 数据(data)生命周期函数事件处理函数(methods) 等等。
  • data: { name: '小程序开发者' } 这是页面的 初始数据对象,会被自动绑定到页面的 WXML 中

tip

小程序的数据绑定机制

  1. data 中的每一个字段,都可以用 {{字段名}} 在 WXML 中绑定。

  2. 若你想改变绑定的数据,不能直接赋值,要使用 this.setData()

    this.setData({
      name: '张三'
    })
    

    这会自动刷新页面上的绑定内容。

    不能直接修改 this.data.name = '张三',这样不会触发视图更新!


3. 增加跳转

index.wxml 页面为例,我们添加一个跳转按钮:

<view class="container">
  <button bindtap="goToHello">跳转到 Hello 页面</button>
</view>
  • bindtap="goToHello"bindtap 是微信小程序中的 事件绑定属性,表示绑定一个点击事件(tap 事件)。goToHello 是你在该页面的 .ts 文件中定义的函数名,例如:

    Page({
      goToHello() {
        wx.navigateTo({
          url: '/pages/hello/hello'
        })
      }
    })
    

​ 当用户点击按钮时,小程序会触发 tap 事件。bindtap="goToHello" 会调用 index.ts 中的 goToHello() 函数。goToHello() 函数中使用 wx.navigateTo() 实现页面跳转。


index.ts

原本的内容如下:

// index.ts
// 获取应用实例
const app = getApp<IAppOption>()
const defaultAvatarUrl = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0'

Component({
  data: {
    motto: 'Hello World',   // 展示口号
    userInfo: {              // 用户信息
      avatarUrl: defaultAvatarUrl,  // 默认头像
      nickName: '',    		// 用户昵称
    },
    hasUserInfo: false,      // 是否已获取用户信息
    canIUseGetUserProfile: wx.canIUse('getUserProfile'),    // 兼容性判断
    canIUseNicknameComp: wx.canIUse('input.type.nickname'), // 兼容性判断
  },
    
  methods: {
    // 事件处理函数
     
    //点击按钮等组件时触发跳转,跳转到日志页。
    bindViewTap() {
      wx.navigateTo({
        url: '../logs/logs',
      })
    },
      
    // 用于处理用户从微信头像组件中选择头像的事件。如果用户选择了头像,则更新 `userInfo.avatarUrl`。
    onChooseAvatar(e: any) {
      const { avatarUrl } = e.detail
      const { nickName } = this.data.userInfo
      this.setData({
        "userInfo.avatarUrl": avatarUrl,
        hasUserInfo: nickName && avatarUrl && avatarUrl !== defaultAvatarUrl,
      })
    },
      
    //当用户输入昵称时触发(如绑定到 `<input>` 上)更新 `userInfo.nickName`,并判断头像和昵称是否都已填,来决定 `hasUserInfo`。
    onInputChange(e: any) {
      const nickName = e.detail.value
      const { avatarUrl } = this.data.userInfo
      this.setData({
        "userInfo.nickName": nickName,
        hasUserInfo: nickName && avatarUrl && avatarUrl !== defaultAvatarUrl,
      })
    },
      
   //微信推荐方式:获取用户头像和昵称,需要用户主动确认授权。获取到信息后写入 `userInfo`,并更新 `hasUserInfo` 为 `true`。
    getUserProfile() {
      // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认,开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
      wx.getUserProfile({
        desc: '展示用户信息', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
        success: (res) => {
          console.log(res)
          this.setData({
            userInfo: res.userInfo,
            hasUserInfo: true
          })
        }
      })
    },
  },
})

代码解释:

  • const app = getApp<IAppOption>()

  • getApp() 是微信提供的全局 API,用于获取 App() 注册时返回的全局应用实例

  • IAppOption 是你自定义的 TypeScript 接口,用于约束 app 实例的结构(例如是否包含一些全局属性、方法等)。

  • const defaultAvatarUrl = 'https://...' 声明一个默认的头像 URL,用于在用户未上传头像时显示默认头像图片

  • Component({...}) 微信小程序的 组件定义函数,用于注册一个组件实例(或页面)。 如果你在页面中使用了 Component() 而不是 Page(),那说明这个页面是一个 可复用的 自定义组件,不是标准页面。

  • data: {...} 用于声明组件/页面的初始状态

  • wx.canIUse() 是微信提供的 兼容性检查 API,防止在旧版本客户端调用不支持的新特性。

  • methods: {...} 定义该组件内的交互行为(即事件处理函数)。


现在修改成如下

// index.ts
const defaultAvatarUrl = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0'

Page({
  data: {
    motto: 'Hello World',
    userInfo: {
      avatarUrl: defaultAvatarUrl,
      nickName: '',
    },
    hasUserInfo: false,
    canIUseGetUserProfile: wx.canIUse('getUserProfile'),
    canIUseNicknameComp: wx.canIUse('input.type.nickname'),
  },

  // 页面跳转函数
  goToHello() {
    wx.navigateTo({
      url: '/pages/hello/hello'
    })
  },

  // 以下为原 methods 中的函数
  bindViewTap() {
    wx.navigateTo({
      url: '../logs/logs',
    })
  },

  onChooseAvatar(e: any) {
    const { avatarUrl } = e.detail
    const { nickName } = this.data.userInfo
    this.setData({
      "userInfo.avatarUrl": avatarUrl,
      hasUserInfo: nickName && avatarUrl && avatarUrl !== defaultAvatarUrl,
    })
  },

  onInputChange(e: any) {
    const nickName = e.detail.value
    const { avatarUrl } = this.data.userInfo
    this.setData({
      "userInfo.nickName": nickName,
      hasUserInfo: nickName && avatarUrl && avatarUrl !== defaultAvatarUrl,
    })
  },

  getUserProfile() {
    wx.getUserProfile({
      desc: '展示用户信息',
      success: (res) => {
        console.log(res)
        this.setData({
          userInfo: res.userInfo,
          hasUserInfo: true
        })
      }
    })
  },
})

这里最核心的修改在于把你原来这段代码从:

Component({
  data: { ... },
  methods: { ... }
})

改成:

Page({
  data: { ... },
  // 不再写 methods,而是直接写函数名
  bindViewTap() { ... },
  onChooseAvatar(e) { ... },
  ...
})

因为 index 是一个页面(因为它被写入了 app.json),必须用 Page({...}) 来定义页面的行为。否则会出现点击按钮没有反应,跳转失败,生命周期 onLoad() 等不触发的问题。

Page 和 Component 的本质区别

特性Page({...})Component({...})
用途页面文件(对应路由)可复用组件(嵌入页面或其他组件中)
生命周期有页面生命周期:onLoadonShow有组件生命周期:createdattached
路由支持✅ 可以通过 wx.navigateTo() 跳转❌ 不能单独跳转到组件
页面注册会被 app.json 中注册为页面不会被注册为页面
模板结构1个 .json + .wxml + .wxss + .ts/js1个组件的 .json + .wxml + .wxss + .ts(带 component: true

举个例子

使用场景你应该用
pages/index/index.ts 首页Page({...})
自定义按钮控件 components/my-button/my-button.tsComponent({...})


4. 运行 & 测试

  • 保存所有文件。
  • 在模拟器点击按钮,应该会成功跳转到 Hello 页面,并看到“Hello, 小程序开发者 👋”。