☰
小程序全局四件套详解:app.js、app.json、app.wxss与project.config.json
2026/10/16 1:09:40 网站建设 项目流程

写小程序和写网页最不一样的地方,就是你点开新建项目后,会看到一堆同名不同后缀的文件——app.js、app.json、app.wxss、project.config.json,旁边还躺着一个pages文件夹。很多刚入门的同学会对着这一排文件发呆:这4个文件到底是干嘛的?哪个是入口?哪个删了会出事?别急,这正是这一篇要扒清楚的事。这篇是“小程序入门到精通”系列的第二讲,咱们把标题里说的4个重要文件逐个拆开,讲清职责、讲透原理、讲完坑点,再顺带说清楚它们和pages里每个页面那4个页内文件到底怎么分工。把这层关系理顺,你后面再学页面开发、组件通信、分包加载,都会轻松很多。

1. 项目根目录的4个文件:先认识全局四件套

1.1 新建项目后,你的目录长什么样

在开发者工具里新建一个最简项目后,典型的目录结构是这样的:

├── app.js ├── app.json ├── app.wxss ├── project.config.json ├── sitemap.json └── pages/ └── index/ ├── index.js ├── index.json ├── index.wxml └── index.wxss

严格说根目录里不止4个文件,标题里说的4个重要文件,指的是app.js、app.json、app.wxss、project.config.json这四位。sitemap.json属于辅助配置,它负责声明小程序内哪些页面允许被搜索索引,属于“锦上添花”而不是“地基”,真正决定项目能不能跑起来的,就是前面那4个。这4个文件各有各的主场:全局逻辑、全局配置、全局样式、工具配置,任何一个出了问题,轻则白屏报错,重则整个项目打不开。

我还想提前说一句:pages目录里每个页面也有4个文件,后缀同样是js、json、wxml、wxss。同样是“4个文件”,全局这4个和页面这4个职责完全不同,很多人在这里绕晕过。所以这一篇我们先把全局4个啃透,页面4件套放在第6节单独讲。

1.2 四个文件的分工一目了然

这4个文件的分工用一张表就能说清楚:

文件定位核心职责
app.js全局逻辑注册App实例,管理启动生命周期,存放全局数据
app.json全局配置注册页面、配置窗口外观、底部导航、编译行为
app.wxss全局样式公共样式、样式重置、rpx适配类
project.config.json工具配置记录项目名、appid、编译设置,供开发者工具识别项目

我的建议:第一次接触时,先看app.json,因为它决定“页面从哪里来”;再读app.js,看“启动做了什么”;接着翻app.wxss,知道“样式哪些全局可用”;最后没事别乱动project.config.json,知道它存在、知道它能干什么就够了。按这个顺序读代码,上手理解最快。

2. app.json配置详解:pages、window与tabBar一次讲透

2.1 pages数组:页面先“上户口”才能用

app.json里最关键的一个字段就是pages,它是个字符串数组,每一项都指向一个页面路径。路径不需要写后缀,开发者工具会自动补全。比如:

{ "pages": [ "pages/index/index", "pages/logs/logs", "pages/detail/detail" ] }

声明式的注册机制是很多人第一个想不通的点:为什么不能像后端路由那样写个通配符或者自动扫描目录呢?因为小程序运行时有自己天生的双线程架构:逻辑层和视图层分离,启动时需要通过一份静态可解析的配置,把页面的编译单位提前确定下来。目录扫描这种“运行时才知道有哪些页面”的方式,在双线程框架里不可靠。所以你可以把pages数组理解为“户口登记簿”,里面的每一个页面都是登记过户籍的合法居民。

这里有两个新手必踩的坑。第一,pages数组里排在第一位的是小程序启动后默认展示的页面,如果你想让某个页面成为首屏,就得把它挪到最前面。第二,如果你在pages文件夹里手动新建了一个页面目录,但忘了在app.json的pages里登记,编译通常不会直接报错,但一旦跳转到这个页面,就会出现找不到路径的报错,排查时很容易一头雾水。我自己初期就干过好几次这种事:页面文件创建了、路由跳转写了,唯独这行注册漏了。

2.2 window:窗口外观与导航栏配置

window字段负责全局窗口表现,包括顶部导航栏、背景色、下拉刷新等。常用配置项先看这张表:

配置项作用示例值
navigationBarTitleText导航栏标题文字"首页"
navigationBarBackgroundColor导航栏背景色"#1f8ceb"
navigationBarTextStyle导航栏标题颜色,只支持black/white"white"
backgroundColor窗口背景色"#f5f6f8"
backgroundTextStyle下拉/加载时的样式,只支持dark/light"dark"
enablePullDownRefresh是否开启全局下拉刷新false
onReachBottomDistance页面上拉触底的距离,单位px50

一个完整示例:

{ "window": { "navigationBarTitleText": "我的小程序", "navigationBarBackgroundColor": "#1f8ceb", "navigationBarTextStyle": "white", "backgroundColor": "#f5f6f8", "backgroundTextStyle": "dark" } }

这块配置的特点是“全局默认+页面覆盖”。你可以在某个页面的json文件里直接写同样的顶层字段,比如navigationBarTitleText,这个页面就按页面自己的配置显示,其它页面继续跟全局走。这个就近优先的规则在后面第6节还会细说,你可以先记住:页内配置永远优先于全局配置。

2.3 tabBar与其他全局配置

如果你的应用有底部导航,需要用tabBar字段声明。tabBar要求最少2项、最多5项,每一项的pagePath必须已经存在于pages数组中,否则工具会直接报错。示例:

{ "tabBar": { "color": "#333333", "selectedColor": "#1f8ceb", "list": [ { "pagePath": "pages/index/index", "text": "首页" }, { "pagePath": "pages/logs/logs", "text": "日志" } ] } }

tabBar的list配置里没有强制要求iconPath,但实际产品里一般都会配上图标。图标的推荐尺寸是81px x 81px,格式png,并且不要超过40KB,否则真机上可能出现加载缓慢或显示异常。

除了pages、window、tabBar,还有几个我在实际项目里几乎每次都会用到的全局配置:

  • style: "v2",表示启用新版基础组件样式,让按钮、表单等组件的默认风格更现代,否则若干组件外观会停留在老版本。
  • sitemapLocation: "sitemap.json",声明搜索索引配置文件的位置,如果你的页面不需要被搜索,这行配一个空的json文件也不会出问题。
  • lazyCodeLoading: "requiredComponents",按需注入组件代码,启动时只加载首屏必须的组件,可以明显改善启动耗时。

一个容易忽略的点:app.json在编译时必须是一个合法的JSON文件,任何多余逗号、注释都会导致编译直接失败。很多入门同学喜欢在配置里写注释,结果一启动就是满屏红色报错,这不是小程序不支持注释,而是JSON格式本身不允许注释。

3. app.js全局数据与生命周期:启动时到底该做什么

3.1 App()方法:小程序是怎么被“叫醒”的

app.js的第一行代码通常就是App(...)调用,里面传的对象不是普通配置对象,而是小程序实例的“开机自检清单”。这个文件不写、或者没有正确调用App(),整个项目根本跑不起来,这是程序启动前必须完成的一步。

一个最简全局逻辑长这样:

App({ onLaunch() { console.log('小程序启动'); }, onShow() { console.log('小程序切回前台'); }, onHide() { console.log('小程序进入后台'); }, globalData: { userInfo: null, token: '' } });

小程序实例的生命周期会按固定顺序触发:首次打开时onLaunch先执行,紧接着是onShow;按下Home键或切到其它应用,onHide触发;再从后台回前台,onShow会再触发一次。注意onLaunch只在冷启动时执行一次,而onShow会在每次回到前台时都执行。这两个的区别很重要:想做初始化任务,放在onLaunch里;想根据“前后台切换”做状态刷新,放在onShow里。

为什么加载数据、更新检查都往onLaunch里塞,但页面数据却不在这里拿?因为onLaunch执行时,页面还没完成渲染,此时任何依赖页面DOM状态的操作都拿不到结果。小程序里没有传统网页那种window全局环境,页面数据获取应该在各自的Page生命周期里做,app.js只负责那些与页面无关的全局任务。

3.2 globalData:全局数据的正确使用姿势

App实例的globalData是所有页面共享数据最直接的方式。例如登录成功后在app.js里存一份用户信息,任意页面通过getApp().globalData.userInfo就能读取。但有几个使用细则必须注意:

第一,globalData是一个普通内存对象,不是响应式的。你在某个页面里直接改globalData.userInfo,其它页面不会自动收到更新。想让界面同步变化,必须配合页面data和setData,常见做法是把globalData的值读出来,通过setData更新到页面本地,页面再展示。

第二,globalData不会持久化。小程序一旦被系统回收或者用户手动销毁,所有内存数据都会丢失。需要跨启动、跨会话保留的数据,比如登录token,要写入本地缓存或者服务端会话,不能用globalData硬扛。很多业务bug就是这么来的:模拟器里一切正常,锁屏过一阵再回来,用户“凭空消失”。

第三,存放数据不要贪多。globalData适合放用户信息、设备信息、小程序版本这类全局必需的轻量数据,不适合放一个页面的大列表、大对象。全局数据被所有页面共享,体积过大不仅增加内存压力,还会让出问题的排查链路变长。

3.3 启动阶段适合做的事与不要做的事

实战里,app.js的onLaunch经常承担这些正经任务:读取本地缓存的登录状态、拿code换session、检查版本更新、上报启动日志、初始化全局的事件通道。这些事有一个共同点:与具体页面无关,又需要尽快执行。

但你也要管住自己,别把所有逻辑都堆在onLaunch里。启动阶段执行越久,用户看到的白屏或者等待时间就越长。比如同步请求一个慢接口后再渲染首页,体验会非常糟糕。我的做法是:必须的同步信息提前缓存,能异步的尽量异步,实在等不了的就让页面先渲染骨架屏,数据到了再补。启动速度这事,从第一天写app.js就要放在心上。

4. app.wxss全局样式:rpx适配与优先级规则实战

4.1 wxss和css的差:rpx是怎么做到适配的

小程序提供了一套自己的样式语言wxss,长得和css几乎一样,支持选择器、继承、媒体查询。但有一个核心差异:它引入了rpx这个响应式像素单位。

rpx的原理不复杂:不管屏幕宽度是多少,小程序都把它换算成750rpx。设计稿按750宽度出图时,你在设计稿上量到多少px,代码里就写多少rpx,不用再做换算。比如设计稿上一个按钮宽300px,代码就写width: 300rpx,在375px宽的手机上它显示为187.5px物理宽度,在414px宽的手机上显示为207px,视觉比例保持一致。

简单换算公式:1rpx = 屏幕宽度 / 750 px。这个逻辑理解后,你会发现小程序做适配非常省心,几乎不用写一堆媒体查询。需要提醒的是,rpx最适合宽度类属性,给字号、边框这类元素使用时,不同屏幕下字号差异会比较明显,个人建议字号尽量用px固定,间距宽度等优先用rpx。

4.2 全局样式生效范围与页面覆盖规则

app.wxss里的样式对所有页面生效,这是它叫“全局样式”的根本原因。但全局并不是无敌的,样式规则遵循“就近优先 + 同权覆盖”:页面wxss里的同名类,在优先级相同的情况下会覆盖app.wxss里的同名类;但如果全局样式选择器权重更高,比如app.wxss写的是.container .item,页面只写了.item,页面样式不一定能赢。优先级规则和css完全一致。

还有两种情况容易被忽略。第一,很多原生组件在基础库版本演进后改成原生渲染,全局样式未必能穿透进去,最典型的是原生输入框、原生地图这类,要么用覆盖样式要么用同层渲染方案处理。第二,不要把所有样式都堆进app.wxss,想让某个页面独有、短时间又不想新建文件的样式,放进页面wxss更合适,避免全局文件越滚越大、互相污染。

4.3 公共样式表里值得放什么

app.wxss最适合放三类东西:

第一类,样式重置。比如清除默认的按钮边框、页面背景、默认字重,让组件在不同设备上表现一致。第二类,高频工具类。比如flex布局常用的居中类、一行省略类、安全区padding类。第三类,设计变量。小程序基础库已经支持var()自定义属性,你可以把主色、圆角、间距这类常量定义在page级别或公共类里,后续改主题时只动一处就行。

我自己的习惯还会额外放一层“逻辑兜底”,比如给所有页面容器默认一个最小高度,避免内容太少时底部背景色不一致。全局样式不是越多越好,而是越稳定、越收敛越好,你希望每个页面都有、又不想重复写的,才值得拉进app.wxss。

5. project.config.json的作用:为什么团队项目一定要带上它

5.1 开发者工具的“记忆库”里存了什么

如果说app.js解决运行逻辑,app.json解决运行配置,那么project.config.json解决的是“开发者工具怎么理解这个项目”。它把你在工具里做的偏好设置沉淀下来,比如appid、项目名、编译类型、基础库版本,以及一堆编译开关。

一个简化版本:

{ "appid": "touristappid", "compileType": "miniprogram", "libVersion": "3.0.0", "projectname": "my-miniapp", "setting": { "urlCheck": true, "es6": true, "postcss": true, "minified": true } }

appid如果写占位符touristappid,说明当前项目是游客模式,很多能力比如上传代码、真实接口请求都会受限。setting里的urlCheck控制着是否校验合法域名,调试阶段如果不想每次都被域名拦截,可以把urlCheck临时设为false,但上线前一定要改回来,这是很多团队在联调环境反复踩过的坑。

5.2 个人配置与项目配置:private.config是怎么回事

新版开发者工具会把文件拆成两层:project.config.json是项目级公共配置,会进入版本库,团队成员共用;project.private.config.json是个人本地配置,只存在于自己电脑上,不会自动进版本库。

为什么要拆?因为个人偏好,比如本地调试的urlCheck开关、编辑器字体大小、启动自动打开的项目路径,都不适合强加给全组。把个人配置和个人偏好拆出去后,团队成员克隆仓库、打开项目,得到的是同一套编译行为,但各自又能保留本地习惯。这个分工很实用,我第一次意识到它,是帮同事排查一个问题:代码一模一样,我的工具不会报错,他却报了一堆域名校验失败,最后发现是他的project.private.config.json里把urlCheck打开了。

所以团队项目必须把project.config.json提交进版本库,提交时留意一下里面不要出现本地路径、真实登录态等敏感信息。如果你在多人协作,把project.private.config.json加入忽略列表,这应该是一名合格开发者从一开始就具备的协作意识。

6. 页面4件套详解:js、wxml、wxss、json如何与全局配合

6.1 一个页面的完整四件套长什么样

每个页面都是一个独立目录,目录里放着4个同前缀文件。以pages/index/index这个页面为例,4个文件分别是index.js、index.json、index.wxml、index.wxss。它们的分工是:

  • index.wxml:页面的结构,相当于页面里的HTML。
  • index.wxss:页面的样式,只作用于当前页面。
  • index.js:页面的逻辑,数据、事件、生命周期都在这。
  • index.json:页面的自定义配置,可以覆盖全局window的某些项。

一个最简单页面四件套如下。页面结构:

<view class="container"> <text class="title">{{ message }}</text> <button bindtap="onTap">点我</button> </view>

页面逻辑:

Page({ data: { message: 'Hello, Mini Program' }, onTap() { this.setData({ message: '你点击了按钮' }); } });

页面样式:

.container { padding: 32rpx; } .title { font-size: 36rpx; color: #333; }

页面配置:

{ "navigationBarTitleText": "首页", "enablePullDownRefresh": true }

为什么要把同一个页面拆成4个文件而不是一个文件里面写4种内容?因为小程序的双线程架构要求逻辑和视图分开:js跑在逻辑层,wxml和wxss跑在视图层,json则是纯粹的静态配置。这种拆分看起来“碎”,换来的是逻辑层和视图层可以并行处理,渲染性能更可控,也给分包、按需加载提供了基础。

6.2 页面json覆盖window的规则

前面在app.json那节说过“就近优先”,这里展开讲。页面json里可以直接写window中的顶层字段,不需要再包一层window,比如:

{ "navigationBarTitleText": "详情页", "navigationBarBackgroundColor": "#111111", "enablePullDownRefresh": true }

当页面json出现这些字段时,这个页面就不再使用app.json window里的同名配置。页面json里没有写的字段,继续从全局window继承。这种设计是为了方便做差异化:全局统一风格,特殊页面单独调整,比如详情页导航栏深色、首页浅色,完全不需要写任何条件判断,纯配置就搞定。

6.3 全局与页面协作的完整路径

把全局4个文件和页面4件套串在一起,一次完整的小程序行为大概是这样的:用户打开小程序,基础库先读app.json,确定启动页面是哪个、导航栏和tabBar长什么样;接着执行app.js里的App(),完成全局初始化;然后加载启动页面的4个文件,运行页面js里的Page(),渲染wxml并套用app.wxss与页面wxss,最终把界面呈现在屏幕上。

这个链路里,全局文件的职责是“打底”,页面文件的职责是“定制”。全局配置决定默认行为,页面配置做局部覆盖;全局样式做公共设计,页面样式做局部调整;全局逻辑处理通用任务,页面逻辑处理业务细节。认清这个分工,你在遇到“样式怎么不生效”“标题怎么改不过来”“启动逻辑到底运行在哪”的时候,至少能判断该去哪个文件里找答案。

7. 高频报错排查与我的习惯建议

7.1 新手必看:高频报错速查表

把入门阶段最高频的几类问题做成速查表,遇到时照着对就行:

现象大概率原因处理方式
提示页面路径找不到页面没有在app.json的pages里注册在pages数组里补上路径
编译报JSON语法错误app.json或页面json格式不对检查多余逗号、引号,JSON不支持注释
js里调用方法提示undefined方法没有写在Page()对象里确认方法挂在Page()第一层
页面样式被全局覆盖页面wxss与全局样式同名冲突类名加前缀或提升页面选择器权重
globalData改完界面不变内存对象不触发页面更新读取后通过setData更新页面数据
工具里能跑,真机请求失败未配置合法域名或关闭校验后台配置域名,调试期可临时关闭urlCheck
删了页面文件后启动报错app.json或tabBar仍引用旧路径同步清理pages数组、tabBar里的引用

除了这张表,还有一条我要特别强调:任何一次批量改动文件前,先看一眼app.json。因为它是全局配置里的“总索引”,删文件、改路径、调整目录结构,都必须在配置里同步做手术,否则就会出“编译能过但运行报错”这种隐性问题。

7.2 几个我从实践中总结的小习惯

写得越多越觉得,文件本身不复杂,复杂的是使用习惯。分享几条私人心得:

第一,目录和文件统一用纯小写加连字符命名。比如pages/user-profile/user-profile,别用大写字母,避免不同平台路径解析差异。第二,全局样式只放公共内容,页面专属样式坚决放在页面wxss里,从源头防止全局污染。第三,app.json每次改动都格式化一下再保存,好习惯能避免一半的配置语法错误。第四,遇到线上样式问题,先用手机预览和开发者工具的“真机调试”对比,很多工具环境正常而真机异常的问题,都能在这里现出原形。

这几条不能让你马上变成高手,但至少能让你从“文件总在报错”熬到“文件老实听话”的阶段。

这4个文件说多不多,说少不少,但它们搭起了整个小程序项目的初始骨架。个人体会是,很多进阶问题——分包加载、自定义组件、插件化——追根溯源,最后都会回到今天讲的这套文件分工上:哪些配置归全局、哪些归页面、哪些只属于开发者工具。把这些基础文件理解透彻,后续的每个知识点都等于站在一块稳固的地基上。下一篇我会继续往下走,聊聊页面生命周期和数据的流转,把4个文件里的js那部分真正跑起来。如果你在跟着练习的时候卡在某个文件上,欢迎在评论区把报错贴出来,一起对一对是谁在捣乱。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询