雨雁 發表於 2024-9-14 16:42:00

小李移动开发成长记 —— 大话小程序

<h2 id="小李移动开发成长记--大话小程序">小李移动开发成长记 —— 大话小程序</h2>
<p>做<code>传统网站</code>前端开发的同学初次接触<code>小程序</code>,会有许多<code>困惑</code>:为什么没有div,view 是什么、怎么没有 ajax,wx.request 为什么是回调方式、预览怎么要用小程序开发者工具、APPID有什么用、安装npm包怎么还要构建、tabBar 是什么、语法怎么和vue很像但是有的部分又不同、@import 用法怎么和 css 中不同...</p>
<p>本篇通过微信小程序(发布较早,影响力较大)来介绍小程序,帮助你<code>快速认识小程序</code>,并解决以上困惑。主要介绍:</p>
<ol>
<li>小程序和<code>网站</code>的差异</li>
<li>小程序和 <code>vue</code> 语法差异</li>
<li>小程序通信模型和<code>运行机制</code></li>
<li>三方小程序<code>开发流程</code></li>
<li>如何新建项目,全局和局部配置是什么</li>
<li>小程序<code>基本语法</code>:wxsl、wxss、js(wxs)</li>
<li>小程序<code>API的Promise化</code></li>
<li>应用<code>生命周期</code>和页面生命周期</li>
<li>小程序组件和自定义组件</li>
<li>小程序<code>路由和状态</code>管理</li>
<li>分包是什么,有什么作用</li>
</ol>
<h3 id="背景">背景</h3>
<p>小李虽然会一些react、vue、js,但是移动端开发做的比较少,几乎<code>不会小程序</code>开发。</p>
<p>下一阶段的任务是<code>移动端开发</code>,涉及H5、小程序、公司内部自建的移动端的框架、调试工具、TS等。</p>
<p>如何快速上手这部分工作,经过三分钟的思考,决定出大致要加强的方向:</p>
<ul>
<li><em>小程序</em>:有许多小程序的开发</li>
<li><em>js 基础</em>:代码要写的优雅,比如许多 if 不是一个好习惯;不要看到别人的 Promise 就感觉生疏,别人的解构写法是否有问题也能看出来</li>
<li><em>react</em>:项目技术栈用到 react</li>
<li><em>加强TS</em>:许多代码有TS,否则看不懂,修改代码也不能通过编译</li>
<li><em>移动端开发流程</em>:熟悉公司移动开发流程,比如模拟器、云真机、实体机、公司的抓包工具</li>
<li><em>移动端玩法</em>:比如调用 jsAPI(getLocation)需要同时开启“系统地理位置、系统授权宿主(支付宝/小程序)地理位置权限、宿主给小程序地理位置权限”,否则可能会弹出”地理位置权限申请“的弹框;悬浮球的玩法;半屏展示;全屏展示页面(即webview 扩展到状态栏)</li>
</ul>
<h3 id="认识小程序">认识小程序</h3>
<h4 id="小程序-vs-网站">小程序 VS 网站</h4>
<p><code>小程序类似网站</code>,只是网站在浏览器中打开,而小程序通过小程序平台(微信、支付宝)打开。</p>
<p>两者<code>相似点</code>:</p>
<ul>
<li>跨平台:微信小程序在微信中打开,支付宝小程序在支付宝中打开;网站在浏览器中打开</li>
<li>无需下载和安装</li>
<li>实时更新:发布后,用户就会自动获取最新版本</li>
</ul>
<p>两者<code>不同点</code>:</p>
<ul>
<li>运行环境:小程序的宿主是小程序平台,例如微信、支付宝;网站运行在浏览器中;</li>
<li>功能和权限:小程序因为嵌在超级应用内(微信、支付宝),可以调用一些原生功能,比如支付、地理位置、摄像头,部分功能需要用户授权;网站受限于浏览器提供的API,总体上授权会更有限;</li>
<li>体验和性能:小程序在体验和性能上更接近原生应用,响应更快、UI更流畅</li>
<li>生态与流量:借助超级应用的平台生态,容易获取用户。比如微信小程序可以通过微信群、朋友圈等社交渠道快速传播;网站通过搜索引擎优化(SEO)、广告,相对小程序,推广难度可能大一些</li>
<li>开发语言和工具:使用特定的框架和工具集,例如微信小程序使用WXML、WXSS、JS;网站使用标准的HTML、CSS、JS以及各种前端框架和库(React、Vue)</li>
<li>开发模式:网站是浏览器+代码编辑器;微信小程序:申请小程序开发账号、安装小程序开发者工具、创建和配置小程序项目</li>
<li>代码托管:网站本地开发完提交到代码托管平台(github),之后会从托管平台部署到实际运行的服务器上;小程序代码在本地编写和调试后,直接上传到对应的小程序平台,这里涉及使用官方提供的开发者工具上传代码,平台审核,审核后发布;小程序平台负责代码的托管和版本控制。</li>
</ul>
<h4 id="微信小程序-vs-vue">微信小程序 VS Vue</h4>
<p>有人说小程序比 Vue 简单多了。我们来对比两者异同,会发现<code>小程序在语法上有许多和vue相似</code>。</p>
<p><code>相同点</code>:</p>
<ul>
<li>组件开发:微信小程序使用 wxml定义组件结构、wxss 定义样式、js 定义逻辑、json 用于配置;Vue 使用 .vue 进行单文件组件开发</li>
<li>数据绑定:微信小程序通过 <code>{{}}</code>进行数据绑定,类似 vue.js 模板语法</li>
<li>事件处理:微信小程序使用 bingdtap 或 catchtap 等事件绑定;vue 使用 v-on(或@) 进行事件绑定</li>
<li>条件渲染和列表渲染:小程序使用 wx:if 和 wx:for 指令;vue 使用 v-if 和 v-for 指令</li>
</ul>
<p><code>不同点</code>:</p>
<ul>
<li>运行环境:微信小程序运行在微信的容器环境中,只能在微信中使用,依赖于微信的API和平台;vue 运行在浏览器和node.js中。</li>
<li>文件结构:微信小程序,每个组件由4个文件组成(wxml, wxss, js, json);vue 在一个 .vue 文件中</li>
<li>样式处理:微信小程序 使用wxss 进行定义,类似CSS;vue 使用标准的CSS</li>
<li>框架特征:微信小程序:提供了一些特定微信环境的API,例如访问微信支付;Vue专注于UI层,提供了丰富的生态系统</li>
<li>生态系统和扩展:微信小程序,由微信官方提供丰富的API,社区贡献组件库和开发工具;Vue 有强大的生态系统,包括大量的第三方插件、组件库和开发工具</li>
</ul>
<h4 id="宿主环境">宿主环境</h4>
<p>手机<code>微信</code>是微信小程序的宿主环境,<code>支付宝</code>是支付宝小程序的宿主环境</p>
<p>小程序借助宿主环境提供的能力,可以完成许多普通网页无法完成的功能,如:微信登录、微信支付、微信扫码、地理定位...</p>
<p>通过宿主环境,小程序提供的能力包含:<code>通信模型</code>、<code>运行机制</code>、<code>组件</code>和<code>API</code></p>
<h5 id="通信模型">通信模型</h5>
<p>小程序通信主体包含:<code>渲染层</code>和<code>逻辑层</code></p>
<ul>
<li>wxml(类似 html) 和 wxss(类似 css) 工作在渲染层</li>
<li>js脚本工作在逻辑层</li>
</ul>
<p>小程序中的通信模型分两部分:</p>
<ul>
<li>渲染层和逻辑层通信:由微信客户端进行转发</li>
<li>逻辑层和第三方服务器之间的通信:由微信客户端进行转发</li>
</ul>
<h5 id="运行机制">运行机制</h5>
<p>小程序<code>启动过程</code>:</p>
<ol>
<li><strong>小程序代码下载到本地</strong>:用户首次打开或更新小程序时,微信客户端会从远程服务器下载小程序的代码包。这个过程会根据网络状况和代码包大小有所不同,微信平台会对代码包进行一定的优化和压缩处理以加快下载速度。</li>
<li><strong>解析 app.json 全局配置文件</strong>:下载完成后,微信客户端会首先解析app.json文件,这个文件包含了小程序的全局配置信息,如页面路径、窗口表现、网络超时时间、底部tab等。这些配置决定了小程序的基本框架和表现形式。</li>
<li><strong>执行 app.js</strong>,小程序入口文件:接下来,微信客户端会执行app.js文件,这是小程序的逻辑层入口。在app.js中,会调用App函数来创建小程序实例,并可以在这个函数中定义全局的数据和方法,进行一些初始化操作,如注册全局的生命周期回调函数(如onLaunch, onShow, onHide等)。</li>
<li><strong>渲染小程序首页</strong>:根据app.json中配置的首页路径,微信客户端会加载首页的.wxml(结构)、.wxss(样式)和.js(逻辑)文件,开始渲染小程序的首页。逻辑层会通过Page函数创建页面实例,并执行页面的生命周期函数,如onLoad,进行数据初始化、网络请求等操作。随后,渲染层依据逻辑层提供的数据渲染页面内容。</li>
<li><strong>小程序启动完成</strong>:当首页页面渲染完成并呈现给用户时,标志着小程序的启动过程结束,此时用户可以开始与小程序进行交互。同时,小程序的不同页面之间可以通过页面路由进行跳转,逻辑层与渲染层继续根据用户的操作进行数据更新和界面重绘。</li>
</ol>
<p><em>Tip</em>:上面是冷启动,对于已经打开过的小程序,再次进入可能就会有热启动的情况。比如代码下载可能就会被跳过</p>
<p><code>页面渲染过程</code>:</p>
<ol>
<li><strong>加载解析页面的 .json 配置文件</strong>:当需要渲染某个页面时,微信小程序框架首先会加载该页面对应的.json配置文件。这个文件定义了页面的窗口样式、导航栏样式等页面级的配置信息,这些配置会影响页面的外观和行为。</li>
<li><strong>加载页面的 .wxml 和 .wxss 样式</strong>:紧接着,框架会加载页面的结构文件.wxml和样式文件.wxss。.wxml文件定义了页面的结构和布局,类似于HTML;而.wxss文件则是用来控制页面元素样式的,类似于CSS。这两个文件共同决定了页面的外观。</li>
<li><strong>执行页面的 .js 文件</strong>,调用 Page() 创建页面实例:之后,框架会执行页面的逻辑文件.js。在这个文件中,通过调用Page函数来创建页面实例,并可以在其中定义页面的初始数据、生命周期函数(如onLoad、onShow、onHide等)、事件处理函数等。页面的初始化数据和逻辑处理都在这个阶段完成。</li>
<li><strong>页面渲染完成</strong>:当页面的结构、样式和数据都准备就绪后,微信小程序的渲染引擎会根据.wxml和.wxss以及页面实例中的数据来渲染页面。这个过程包括解析WXML模板,应用WXSS样式,绑定数据到模板,最终生成用户可见的界面。页面渲染完成后,用户就可以看到并开始与这个页面进行交互了。</li>
</ol>
<h5 id="api">API</h5>
<p>小程序官方把API分三类:</p>
<ul>
<li><code>事件监听API</code>:以 on 开头,监听某些事件。例如 wx.onWindowResize(callback)(小程序中没有windown) 监听窗口尺寸变化</li>
<li><code>同步API</code>:以Sync结尾的都是同步API,通过函数直接获取,如果执行出错会抛出异常。例如wx.setStorageSync('key', 'value') 向本地缓存写入数据</li>
<li>异步API:类似$.ajax,需要通过 success、fail、cpmplete 接收调用的结果,例如 wx.request 发起网络数据请求,通过 success 接收调用的结果</li>
</ul>
<h3 id="小程序研发流程">小程序研发流程</h3>
<p>小程序开发流程<code>不同于传统网站</code>。</p>
<p>传统网站开发:vscode编写代码,浏览器预览效果,git提交到代码。</p>
<p>小程序开发步骤大致如下(以微信小程序<code>三方</code>开发为例):</p>
<ol>
<li>申请小程序账号,获得 AppId(你要创建的小程序唯一标识)</li>
<li>通过小程序开发者工具创建项目</li>
<li>通过小程序开发者工具编译预览效果</li>
<li>通过小程序开发者工具把代码上传到微信平台</li>
<li>选择一个开发版本作为体验版</li>
<li>体验完成申请发布</li>
<li>发布到微信平台</li>
</ol>
<p><em>Tip</em>:一方开发通常指的是由小程序的所有者的开发,也是官方开发; 三方开发,是指由第三方开发者为小程序提供功能或服务;</p>
<h4 id="小程序账号和appid">小程序账号和APPID</h4>
<p>注册小程序账号,主要是为了获得APPID。</p>
<p><code>APPID</code> 是小程序唯一标识,用于在微信上识别和区分不同的小程序,在注册过程中,需要填写一些基本信息,如小程序名称、小程序介绍、小程序类别等。完成这些,微信会为你生成一个APPID。APPID将用于开发、发布和运营小程序各种操作,包含开发工具的配置等</p>
<p>大致流程如下:</p>
<ul>
<li>点击注册:进入微信官网(<code>https://mp.weixin.qq.com/cgi-bin/wx</code>),点击“注册”</li>
<li>注册小程序:包含账号信息(邮箱、密码...)、邮箱激活、信息登记(注册国家、主体类型-个人:身份证姓名、身份证、手机、短信)</li>
</ul>
<p>注册后就可以登录到小程序后台管理界面,在“开发”导航中就能找到APPID。</p>
<h4 id="小程序开发工具">小程序开发工具</h4>
<p>小程序你不用特定工具怎么预览效果?<code>浏览器又不认识小程序</code></p>
<p>微信开发者工具提供如下功能:</p>
<ul>
<li>快速创建小程序项目</li>
<li>代码查看和编辑</li>
<li>小程序进行调试、预览</li>
<li>小程序发布</li>
</ul>
<p>找到稳定版下载安装成功,在桌面会看到一个二维码,用自己的微信扫一扫,登录后就能打开“微信开发者工具”。</p>
<p>创建项目:可以指定项目所在目录、后端服务是否选择云开发、语言有javascript或 TypeScript。</p>
<p><code>小程序工具主界面</code>分五个部分:</p>
<ul>
<li>菜单栏:如帮助(里面有“开发者文档”)、设置、项目、工具(有<code>构建 npm</code>、插件)</li>
<li>工具栏:模拟器、编辑器、调试器、<code>编译</code>、<code>真机调试</code></li>
<li>模拟器:模拟微信(底部有:页面路径、<code>页面参数</code>)</li>
<li>代码编辑区</li>
<li>调试区:<code>console控制台</code>、Network、Storage</li>
</ul>
<h5 id="自定义编译模式">自定义编译模式</h5>
<p>通过小程序工具,<code>普通编译</code>会从小程序首页开始,而平时我们修改某页面逻辑,保存后想立刻看到效果,而不是又从首页切换好几次才到该页面。这是,我们可以使用“自定义编译条件”。</p>
<p>点击“普通编译”下的“添加编译模式”,选择要启动的页面,还可以传参,新建即可。下次就选择这个页面编译即可。</p>
<p>一个页面可以创建多个编译页面,比如有参的、无参的...</p>
<h4 id="协同工作">协同工作</h4>
<p>小程序通常不是一个人完成的。</p>
<p>微信小程序成员管理(<code>三方</code>)体现在管理员对小程序项目成员及体验成员的管理</p>
<ul>
<li>项目成员:参与开发、运营,可登录小程序后台,管理员可添加、删除成员,并设置成员角色</li>
<li>体验成员:参与小程序内测体验、可使用体验版小程序,不属于项目成员,管理员及项目成员可以添加、删除体验成员</li>
</ul>
<p>开发者的权限有:</p>
<ul>
<li>开发者权限</li>
<li>体验者权限</li>
<li>登录权限:登录小程序后台,无需管理员确认</li>
<li>开发设置:设置小程序服务器域名、消息推送及扫描普通二维码打开小程序</li>
</ul>
<p><em>Tip</em>:之所以有这些角色,因为小程序的开发流程不同于网站开发,小程序的代码由小程序平台管理。</p>
<h4 id="小程序版本">小程序版本</h4>
<p>小程序发布流程大致如下:上传代码到开发版本,多次迭代开发版本,根据开发版本生成体验版本,验证通过后提交审核,审核通过后发布。</p>
<ul>
<li><code>开发版本</code>:使用开发者工具,可将代码上传到开发版本中。开发版本只保留每人最新的一份上传的代码。点击提交审核,可以将代码提交审核。开发版本删除,不影响线上版本和审核中的版本。</li>
<li><code>体验版本</code>:选择某个开发版本作为体验版</li>
<li><code>审核中版本</code>:只能有一份代码处于审核中。有审核结果后可以发布到线上,也可以直接重新提交审核,覆盖原审核版本</li>
<li><code>线上版本</code>:线上所有用户使用的代码版本</li>
</ul>
<p><em>Tip</em>:微信小程序和支付宝小程序都提供了多版本开发和管理功能。体验版只能同时根据其中一个开发版本生成。</p>
<h4 id="推广和运营数据">推广和运营数据</h4>
<p>发布后就需要推广</p>
<p>推广可以基于微信码和小程序码。</p>
<p>小程序码的优势:</p>
<ul>
<li>样式上更具有辨识度</li>
<li>-更加清晰树立小程序品牌形象</li>
</ul>
<p>小程序可以通过后台查看<code>运营数据</code>,也可以使用“小程序数据助手”(微信搜索)查看已发布小程序相关数据:访问分析、实时同级、用户画像...</p>
<p>小程序也可以使用第三方埋点工具,例如:友盟友、神策数据...</p>
<h4 id="小程序对-npm-包支持和限制">小程序对 npm 包支持和限制</h4>
<p>微信小程序支持 NPM 包,但<code>小程序能用的 Npm 包却不多</code>。</p>
<p>下面是一些限制和注意事项:</p>
<ul>
<li>API 限制:不支持依赖 node.js 内置库、浏览器内置对象、C++插件的 npm 包</li>
<li>包大小限制:微信小程序的包大小有限制,单个包不能超过 2 MB,总体积不能超过 20 MB。因此,在使用 NPM 包时需要注意其体积,避免超出限制。</li>
<li>构建工具:NPM 包的使用需要通过微信开发者工具进行构建和处理,确保在开发者工具中启用了 "构建 NPM" 功能。</li>
</ul>
<h3 id="新建项目和配置">新建项目和配置</h3>
<h4 id="项目基本结构">项目基本结构</h4>
<p>创建一个微信小程序项目,<code>目录结构</code>如下:</p>
<pre><code>- pages: 存放所有小程序的页面
- utils:存放工具性质的模块
- app.js:小程序入口文件
- app.json:小程序全局配置文件。包含小程序所有页面路径、窗口外观、界面表现(所有页面的背景色、文字颜色、小程序组件所使用的样式版本)、底部tab
- project.config.json:项目配置文件。记录我们对小程序开发工具做的个性化配置,如项目名、小程序账号ID、编译相关配置(ES6转ES5、上传代码时自动压缩混淆、上传代码时样式自动补全)
- sitemap.json:配置小程序及其页面是否允许被微信索引。微信现已开放了小程序内搜索,类似网页的SEO。
</code></pre>
<p>小程序官方建议所有小程序页面都放在 <code>pages</code> 目录中,以单独文件夹存放:</p>
<pre><code>- pages
- index
    - index.js 页面脚本
    - index.wxml 页面结构
    - index.wxss 页面样式
    - index.json 当前页面的配置,如窗口的外观
- pageb
    - pageb.js
    - pageb.wxml
    - pageb.wxss
    - pageb.json
</code></pre>
<p><em>Tip</em>:小程序中有4种<code>json配置文件</code>(具体作用后面会介绍)</p>
<ul>
<li>项目根目录中的 app.json</li>
<li>项目根目录中的 project.config.json</li>
<li>项目根目录中的 sitemap.json</li>
<li>每个页面文件夹中的 json</li>
</ul>
<h4 id="新建小程序页面">新建小程序页面</h4>
<p>在 app.json-&gt;pages 中新增页面存放路径,ctrl+s保存,工具会<code>自动</code>创建对应页面文件。</p>
<pre><code>{
pages: [
    "pages/index/index",
    "pages/pageb/pageb",
+ "pages/pageb/pagec"
]
}
</code></pre>
<h4 id="修改项目首页">修改项目首页</h4>
<p>只需<code>调整</code> app.json-&gt;pages 数组中页面路径的<code>顺序</code>,小程序会把排在第一位的页面,当做项目首页渲染。</p>
<h4 id="全局配置">全局配置</h4>
<p>小程序根目录下的 <code>app.json</code> 是小程序全局配置文件。</p>
<p>常用配置:</p>
<ul>
<li>pages 记录当前小程序所有页面的存放路径</li>
<li>window 全局设置小程序窗口外观</li>
<li>tabBar 设置小程序底部的 tabBar 效果</li>
<li>style 是否启用新版的组件样式</li>
</ul>
<p>示例:</p>
<pre><code>{
"pages": [
    "pages/index/index",
    "pages/logs/logs"
],
"window": {
    "navigationBarTitleText": "小程序示例",
    "navigationBarBackgroundColor": "#ffffff",
    "navigationBarTextStyle": "black",
    "backgroundColor": "#eeeeee",
    "backgroundTextStyle": "light",
    "enablePullDownRefresh": true,
    "onReachBottomDistance": 50
},
"tabBar": {
    "color": "#7A7E83",
    "selectedColor": "#3cc51f",
    "borderStyle": "black",
    "backgroundColor": "#ffffff",
    "list": [
      {
      "pagePath": "pages/index/index",
      "text": "首页",
      "iconPath": "images/icon_home.png",
      "selectedIconPath": "images/icon_home_active.png"
      },
      {
      "pagePath": "pages/logs/logs",
      "text": "日志",
      "iconPath": "images/icon_logs.png",
      "selectedIconPath": "images/icon_logs_active.png"
      }
    ]
},
"style": "v2"
}
</code></pre>
<h5 id="窗口">窗口</h5>
<p>小程序窗口组成部分(从上到下):</p>
<ul>
<li>navigationBar <code>导航栏区域</code>:包含时间、电量、微信标题</li>
<li><code>background</code> 背景区域,默认不可见,下拉才显示</li>
<li><code>页面主体区域</code>,用了显示 wxml 中布局</li>
</ul>
<p>windown节点常用配置项:</p>
<ul>
<li>navigationBarTitleText <code>导航栏标题</code>文字 字符串</li>
<li>navigationBarBackgroundColor 导航栏背景颜色 默认#000000,类型 HexColor</li>
<li>navigationBarTextStyle 导航栏颜色(标题、电池等颜色) 仅支持 black/white,默认 white</li>
<li>backgroundColor 窗口背景色 默认#ffffff,类型 H3xColor</li>
<li>backgroundTextStyle 下拉loading 的样式,仅支持 dark/light,默认 dark</li>
<li>enablePullDownRefresh 是否全局开启下拉刷新。默认 false。开启后会作用于小程序每个页面。</li>
<li>onReachBottomDistance 页面上拉触底时间触发时距离底部距离,单位 px,默认 50,若无特殊需求,不建议修改。</li>
</ul>
<p><em>Tip</em>:<code>下拉刷新</code>,通常做法是在页面中单独开启,而非在这里全局开启。下拉刷新开启后,若要实现刷新,还得在 onPullDownRefresh 方法中处理下来刷新逻辑,这个方法会在用户触发下拉刷新操作时被调用。</p>
<p><code>注</code>:模拟器不能百分之百还原真机。例如下拉刷新,在模拟器中,下拉后,过了3秒,下拉自动合上;而在真机中,不会自动合上</p>
<h5 id="tabbar">tabBar</h5>
<p>小程序中 tabBar 是<code>导航组件</code>。特性有:</p>
<ul>
<li>位置: 通常位于小程序界面的底部。</li>
<li>图标和文字: 每个 tab 都可以包含图标和文字。</li>
<li>选中状态: 可以配置选中和未选中状态下的图标和文字颜色。</li>
<li>页面映射: 每个 tab 对应一个页面路径,点击 tab 会切换到相应的页面。</li>
</ul>
<p>以下是一个典型的 app.json 中 <code>tabBar 配置示例</code>:</p>
<pre><code>{
"tabBar": {
    "color": "#999999",
    "selectedColor": "#1c1c1b",
    "backgroundColor": "#ffffff",
    "borderStyle": "black",
    "list": [
      {
      "pagePath": "pages/home/index",
      "text": "首页",
      "iconPath": "/images/icon_home.png",
      "selectedIconPath": "/images/icon_home_active.png"
      },
      {
      "pagePath": "pages/search/index",
      "text": "搜索",
      "iconPath": "/images/icon_search.png",
      "selectedIconPath": "/images/icon_search_active.png"
      },
      {
      "pagePath": "pages/profile/index",
      "text": "我的",
      "iconPath": "/images/icon_profile.png",
      "selectedIconPath": "/images/icon_profile_active.png"
      }
    ]
}
}
</code></pre>
<p><code>注</code>:tabBar 只能配置最少2个,最多5个。当渲染顶部tabBar 时,<code>不显示 icon</code>,只显示文本。说tabBar 中的页面要放在 pages 前面,否则显示不出。</p>
<p>tabBar 有6个组成部分:</p>
<ul>
<li>color,tab上文字的颜色</li>
<li>selectedColor,tab文字选中时的颜色</li>
<li>backgroundColor,tabBar 背景色</li>
<li>borderStyle,tabBar 边框颜色</li>
<li>iconPath,未选中时图片路径</li>
<li>selectedIconPath,选中时图片路径</li>
</ul>
<p>tabBar 节点配置项:</p>
<ul>
<li>position,默认 bottom,可配置 top</li>
<li>borderStyle,默认 black,仅支持 black/white</li>
<li>color,hexColor 类型</li>
<li>selectedColor,hexColor 类型</li>
<li>backgroundColor,hexColor 类型</li>
<li>list,Array,必填,<code>最少2个,最多5个</code></li>
</ul>
<p>每个 tab 项配置选项:</p>
<ul>
<li>pagePath,必填,页面路径,页面必须在 pages 中预先定义</li>
<li>text,必填,tab上显示的文字</li>
<li>iconPath,未选中时图片路径;position 为top时不显示 icon</li>
<li>selectedIconPath,选中时图片路径;position 为top时不显示 icon</li>
</ul>
<h4 id="页面配置">页面配置</h4>
<p>在小程序中,全局配置和页面配置可以<code>定义页面的外观和行为</code>。当全局配置和页面配置冲突时,确实遵循就近原则,最终效果通常以页面配置为准。这意味着页面特定的配置会覆盖全局配置。这样可以确保页面的定制化效果。</p>
<p>页面配置中常用配置项:</p>
<ul>
<li>navigationBarTitleText 导航栏标题</li>
<li>navigationBarBackgroundColor 导航栏背景颜色</li>
<li>navigationBarTextStyle 导航栏文字颜色</li>
<li>backgroundColor 页面背景颜色</li>
<li>backgroundTextStyle 下拉loading 的样式</li>
<li>enablePullDownRefresh 是否全局开启下拉刷新</li>
<li>onReachBottomDistance 页面上拉触底时间触发时距离底部距离</li>
<li>disableScroll 禁止页面滚动</li>
<li>usingComponents 页面使用的自定义组件列表</li>
</ul>
<h3 id="小程序基本语法">小程序基本语法</h3>
<h4 id="wxml">wxml</h4>
<p>微信小程序的 wxml 类似网页中的 html。支付宝小程序中是 axml。</p>
<p><code>wxml 和 html 区别</code>:</p>
<ul>
<li>标签名称不同(比如用 view 代替 div):
<ul>
<li>HTML: div、span、img、a</li>
<li>wxml: view、text、image、navigator</li>
</ul>
</li>
<li>属性节点不同</li>
</ul>
<pre><code>&lt;a href="http://www.baidu.com"&gt;百度&lt;/a&gt;
&lt;navigator url="http://www.baidu.com"&gt;百度&lt;/navigator&gt;
</code></pre>
<ul>
<li>提供了类似 vue 的模板语法:数据绑定、列表渲染、条件渲染</li>
</ul>
<h5 id="数据绑定">数据绑定</h5>
<p>在 data 中定义数据,在 wxml 中使用。例如:</p>
<pre><code>Page({
data: {
    name: '张三',
    age: 18,
    url: 'http://....png',
    randomNum: Math.random() * 10,
}
})
</code></pre>
<p>用Mustache语法(<code>{{}}</code>)将变量包起来即可:</p>
<pre><code>&lt;view&gt;{{ name }}&lt;/view&gt;
&lt;view&gt;{{ randomNum &gt; 5 ? '大于5': '小于或等于5' }}&lt;/view&gt;
</code></pre>
<p>动态绑定属性不同于 vue 的 v-bind,小程序的动态绑定属性是直接在标签上写(<code>写法不同而已,死记即可</code>),例如:</p>
<pre><code>&lt;image src="{{ url }}"&gt;&lt;/image&gt;
</code></pre>
<p><em>Tip</em>: 数据在小程序开发工具控制台的 AppData tab中可以看到。</p>
<h5 id="条件渲染">条件渲染</h5>
<p>小程序和vue中条件渲染对比:</p>
<ul>
<li>语法差异:微信小程序使用 wx:if、hidden、block wx:if,vue中使用 v-if,v-show。</li>
<li>wx:if 和 v-if 类似,是真正的条件渲染</li>
<li>hidden 和 v-hsow 类似,都是通过 css 控制显隐,元素始终存在</li>
<li>block 类似 template,一次控制多个组件的展示与隐藏,且都不会渲染成实际的 dom 元素</li>
</ul>
<p>用法:在 wxml 中使用 wx:if、wx:elif、wx:else 标签,在 data 中定义变量,在 wx:if 中使用变量。</p>
<pre><code>&lt;view&gt;
&lt;view wx:if="{{ age &gt; 18 }}"&gt;
    你成年了
&lt;/view&gt;
&lt;view wx:elif="{{ age &lt; 18 }}"&gt;你未成年
&lt;/view&gt;
&lt;view wx:else&gt;
    你很少年
&lt;/view&gt;
&lt;/view&gt;
</code></pre>
<h5 id="列表渲染">列表渲染</h5>
<p>小程序和vue中列表渲染对比:</p>
<ul>
<li>语法差异:微信小程序使用 wx:for、wx:key,vue中使用 v-for和:key</li>
<li>都强调为列表渲染的每一项制定一个唯一的 key</li>
<li>vue 在列表渲染中提供了更丰富的功能</li>
<li>wx:for 和 v-for 类似,都是遍历数组,渲染成列表</li>
</ul>
<p>用法:在 wxml 中使用 wx:for 标签,在 data 中定义数组,在 wx:for 中使用数组。</p>
<p>默认当前循环项索引是 index,当前循环项是 item:</p>
<pre><code>&lt;view class="container"&gt;
&lt;block wx:for="{{items}}" wx:key="index"&gt;
    &lt;view&gt;
      &lt;text&gt;{{index}}: {{item}}&lt;/text&gt;
    &lt;/view&gt;
&lt;/block&gt;
&lt;/view&gt;

Page({
data: {
    items: ['Item 1', 'Item 2', 'Item 3']
}
});
</code></pre>
<p>wx:for-item 和 wx:for-index 用于自定义变量名,使得代码更加清晰和可读。</p>
<pre><code>&lt;view class="container"&gt;
&lt;block wx:for="{{items}}" wx:for-item="user" wx:for-index="idx" wx:key="id"&gt;
    &lt;view&gt;
      &lt;text&gt;Index: {{idx}}&lt;/text&gt;
      &lt;text&gt;ID: {{user.id}}&lt;/text&gt;
      &lt;text&gt;Name: {{user.name}}&lt;/text&gt;
    &lt;/view&gt;
&lt;/block&gt;
&lt;/view&gt;

Page({
data: {
    items: [
      { id: 1, name: 'Alice' },
      { id: 2, name: 'Bob' },
      { id: 3, name: 'Charlie' }
    ]
}
});
</code></pre>
<p><code>注</code>: 小程序的 key,直接是循环项中的属性,且不需要 <code>{{}}</code>。如果是vue,得通过循环项找到</p>
<pre><code>&lt;template v-for="item in items" :key="item.id"&gt;
</code></pre>
<h4 id="wxss">wxss</h4>
<p>小程序的样式,类似网页的 css。</p>
<h5 id="wxss-对比-css">wxss 对比 css</h5>
<p>wxss 具备 css 大部分特定,wxss 还对 css 进行了扩充和修改,以适应小程序的开发</p>
<p>wxss 和 css 区别:</p>
<ul>
<li>新增 <code>rpx</code>(responsive pixel,响应式像素)尺寸单位
<ul>
<li>css中实现响应式布局,需要手动进行像素转换(常用 rem)。比如设计师提供的是 750 稿子,我们可能会将 1rem 等于75,设计稿的75就是1rem</li>
<li>wxss 在底层支持新的尺寸单位 rpx,在不同屏幕上会自动进行换算,同样是 750 的稿子,75个大小直接写成 75rpx 即可。</li>
</ul>
</li>
<li>提供全局样式和局部样式
<ul>
<li>微信小程序:项目根目录中 app.wxss 会作用于素有小程序页面;局部页面的 .wxss 样式仅对当前页面生效</li>
<li>web 中CSS是全局作用,除非使用CSS模块化工具</li>
</ul>
</li>
<li>文件类型:微信小程序是 .wxss</li>
<li>媒体查询:微信小程序不支持传统CSS媒体查询,如<code>@media</code></li>
<li>css动画和过度:微信小程序支持部分 css 动画和过度,但有一些限制</li>
<li>wxss 仅支持部分常见的 css属性和选择器:.class和#id、element、并集选择器和后代选择器、::after和::before等伪类选择器</li>
<li>flex布局:微信小程序中的 flex 大部分与css一致,但具体表现有细微差异</li>
<li>引入样式:微信小程序通过 <code>@import</code> 引入其他 .wxss,不支持 @import url() 形式引入外部 css</li>
</ul>
<h5 id="rpx">rpx</h5>
<p>rpx 原理非常简单,把所有设备的屏幕从宽度上<code>等分 750 份</code></p>
<ul>
<li>在375的设备,1rpx 等于 0.5px</li>
<li>在1500的设备,1rpx 等于 2px</li>
</ul>
<p><em>Tip</em>:rem和 rpx 在实现响应式布局中,主要关注的是宽度的自适应。高度需要自行处理,比如等比扩展,或者限制最高高度。</p>
<p>iphone 屏幕宽度是 375px(逻辑像素),共有 750个像素点(物理像素),1个逻辑像素等于2个物理像素,等分750rpx。则:</p>
<ul>
<li>750rpx = 375px = 750 物理像素</li>
<li>1rpx = 0.5px = 1 物理像素</li>
</ul>
<p>开发举例:根据设计稿来,有的要求是1:1,有的是1:2,宽100px<em>200px的盒子,转成rpx 就是 200rpx</em>400rpx。</p>
<h5 id="import">@import</h5>
<p>@import 后根需要导入的外联样式的相对路径,用;表示结束。示例:</p>
<pre><code>@import "demo.wxss";
.box{

}
</code></pre>
<p>使用时是 class 而非 className。</p>
<p><em>Tip</em>:微信小程序支持使用 less,不过需要进行一些配置。</p>
<h5 id="全局样式和局部样式">全局样式和局部样式</h5>
<p>定义在 app.wxss 中的样式是全局样式,作用于每一个页面</p>
<p>定义在页面的 .wxss 中的样式是局部样式,只作用于当前页面。</p>
<p><code>注</code>:当局部样式和全局样式冲突,和 css 中一样:哪个权重高用哪个,如果权重相同,则使用就近原则(采取局部样式)</p>
<p><em>Tip</em>:把鼠标放到小程序工具中选择器上,会有选中提示,例如:Selector Specificity:(0, 1, 0)</p>
<h4 id="js">js</h4>
<p><em>Tip</em>:小程序中的 js 分3大类</p>
<ul>
<li>app.js:小程序入口文件,通过调用 App() 函数启动整个小程序</li>
<li>页面.js:页面的入口文件,通过调用 Page() 函数创建并运行页面</li>
<li>普通.js:普通功能模块文件,用来封装公共的函数或属性,供页面使用</li>
</ul>
<h5 id="wxs">wxs</h5>
<p>wxs在微信小程序中的作用类似 Vue.js中的<code>过滤器</code>(vue3 已废除过滤器)</p>
<p>小程序中的 wxs 和 javascript 是两种语言,区别有:</p>
<ul>
<li>wxs 在视图层中运行,js运行在逻辑层</li>
<li>wxs 隔离性。不能调用 js 中定义的函数,不能调用小程序的API</li>
<li>wxs 设计初衷为了提高数据处理性能,特别是与界面渲染密切相关场景,减少和逻辑层的通信</li>
</ul>
<p><em>Tip</em>: 在 ios 中,小程序内的 wxs 比 js 块 2~20倍;在安卓上无差异。</p>
<p>wxs的语法基于JavaScript,这意味着如果你熟悉JavaScript,学习wxs会相对容易:</p>
<ul>
<li>wxs 有自己的数据类型:number、string、boolean、array、object...</li>
<li>wxs 不支持类似es6+语法,不支持let、const、解构赋值、箭头函数;支持 var、function、es5语法</li>
<li>wxs 遵循 commonjs规范:module对象、require()函数、module.exports对象</li>
<li>wxs 可以编写在 <code>&lt;wxs&gt;</code>标签中,就像js写在 <code>&lt;script&gt;</code> 中,wxs 必须提供 module 属性,用来指定当前 wxs 模块名称</li>
</ul>
<p>外联wxs用法(src必须是相对路径):</p>
<pre><code>&lt;!-- index.wxml --&gt;
&lt;wxs module="utils" src="../../utils/utils.wxs"/&gt;
&lt;view&gt;
&lt;text&gt;{{utils.formatDate(new Date())}}&lt;/text&gt;
&lt;/view&gt;
</code></pre>
<pre><code>// utils.wxs
module.exports = {
formatDate: function(date) {
    var year = date.getFullYear();
    var month = date.getMonth() + 1;
    var day = date.getDate();
    return .map(this.formatNumber).join('-');
},

formatNumber: function(n) {
    n = n.toString();
    return n ? n : '0' + n;
}
};
</code></pre>
<h4 id="数据请求">数据请求</h4>
<p>小程序中网络数据,处于安全考虑,小程序官方对数据接口做了如下限制:</p>
<ul>
<li><code>只能请求 https 类型接口</code></li>
<li>必须将接口的域名添加到信任列表中</li>
</ul>
<p>假如希望在自己的微信小程序中,希望请求https://www.xxx.com/域名下的接口。配置步骤:登录微信小程序后台-&gt;开发-&gt;开发设置-&gt;服务器域名-&gt;修改request合法域名。注意:</p>
<ul>
<li>域名只支持 https</li>
<li>域名不能是 ip 地址或 localhost</li>
<li>域名必须经过 ICP 备案</li>
<li>服务器域名一个月内最多可申请5次修改</li>
</ul>
<p><em>Tip</em>: 如果后端仅提供http协议接口,为不耽误开发进度,可以在微信开发者工具中,临时开启「开发环境不校验请求域名、TLS版本及HTTPS证书」,跳过 request 合法域名校验,仅限在开发和调试阶段使用。</p>
<h5 id="get和post请求">get和post请求</h5>
<p>在微信小程序中,您可以使用 <code>wx.request</code> 方法来发起 HTTP GET 和 POST 请求。这个方法提供了一种简单的方式来与服务器进行数据交互:</p>
<p>请看示例:</p>
<pre><code>wx.request({
url: 'https://api.example.com/data',
method: 'GET',
data: {
    key1: 'value1',
    key2: 'value2'
},
header: {
    'content-type': 'application/json'
},
success: function(res) {
},
fail: function(err) {
}
});
</code></pre>
<pre><code>wx.request({
url: 'https://api.example.com/submit',
method: 'POST',
data: {
    key1: 'value1',
    key2: 'value2'
},
header: {
    'content-type': 'application/json'
},
success: function(res) {
},
fail: function(err) {
}
});
</code></pre>
<h5 id="小程序和跨域">小程序和跨域</h5>
<p><code>小程序没有常规的跨域问题,但本质上还是涉及一些</code>。但是对于前端开发,则无需处理跨域。</p>
<p>跨域(Cross-Origin Resource Sharing,简称 CORS)是指一个域名下的文档或脚本尝试请求另一个域名下的资源时,由于浏览器的同源策略(Same-origin policy)限制而导致的请求被阻拦的行为。这里的“同源”指的是协议、域名和端口号完全相同。同源策略是一种安全措施,旨在防止恶意网站通过脚本读取另一个网站的敏感数据。</p>
<p>跨域的本质是指浏览器出于安全考虑,实施的一种同源策略(Same-origin policy)</p>
<p>小程序的主体不是浏览器,而是小程序平台,所以没有常规的跨域问题。</p>
<p>因为小程序需要配置受信任域名,其实也在一定程度上有了安全保障,小程序的服务端也会涉及到CORS的配置</p>
<h5 id="小程序和ajax">小程序和Ajax</h5>
<p>Ajax核心依赖浏览器中的 XMLHttpRequest 对象,而小程序的宿主环境是微信客户端,所以<code>小程序不叫”发起ajax请求“,而叫”发起网络请求“</code></p>
<p>微信小程序没有直接使用 ajax 这个术语,但提供了类似异步HTTP请求能力,主要通过 wx.request 接口来完成 Get 或 Post 请求,这一过程和Ajax非常类似,都是异步获取数据并更新界面而不阻塞页面。所以小程序中不说”ajax“,但实际上具备异步获取数据的能力</p>
<p>wx.request 和 ajax 功能相似,运营环境和实现机制不同。</p>
<p>请看示例:</p>
<pre><code>wx.request({
url: 'https://api.example.com/data',
method: 'GET',
data: {
    key1: 'value1',
    key2: 'value2'
}, // 请求参数
header: {
    'content-type': 'application/json'
},
success: function(res) {
},
fail: function(err) {
}
});
</code></pre>
<p>wx.request 可以与 async/await 和 Promise.all 配合使用:</p>
<ul>
<li>封装一个使用 wx.request 的 Promise 函数</li>
</ul>
<pre><code>const request = (options) =&gt; {
return new Promise((resolve, reject) =&gt; {
    wx.request({
      ...options,
      success: res =&gt; resolve(res),
      fail: err =&gt; reject(err)
    });
});
};
</code></pre>
<ul>
<li>然后在 async/await 中使用它:</li>
</ul>
<pre><code>Page({
async onLoad() {
    try {
      const response = await request({
      url: 'https://example.com/api/data',
      method: 'GET'
      });
      console.log('Data:', response.data);
    } catch (err) {
      console.error('Error:', err);
    }
}
});
</code></pre>
<h5 id="小程序api的-promise-化">小程序API的 Promise 化</h5>
<p>在开发微信小程序时,许多原生 API 是基于<code>回调函数</code>的,这在现代 JavaScript 编程中可能不太方便。为了更好地处理异步操作,我们可以将这些回调函数形式的 API 转换为 <code>Promise</code> 形式</p>
<p>一种是手动封装</p>
<p>一种是用库(例如miniprogram-api-promise)。例如:<br>
安装,构建后,在你的项目中配置并使用:</p>
<pre><code>// app.js
import wxp from 'miniprogram-api-promise';

App({
onLaunch() {
    // 把所有 wx 函数 promise 化
    wxp.init();
}
});
</code></pre>
<p>在页面或组件中使用:</p>
<pre><code>// pages/index/index.js
Page({
data: {},

async onLoad() {
    try {
      const response = await wx.p.request({
      url: 'https://api.example.com/data',
      method: 'GET',
      });
      console.log(response.data);
    } catch (error) {
      console.error(error);
    }
}
});
</code></pre>
<h4 id="生命周期">生命周期</h4>
<p>小程序有两类生命周期:</p>
<ol>
<li><code>应用生命周期</code>:小程序从启动-&gt;运行-&gt;销毁</li>
<li><code>页面生命周期</code>:小程序中每个页面的加载-&gt;渲染-&gt;销毁</li>
</ol>
<p>页面的生命周期范围小,应用程序的生命周期范围大:<br>
小程序启动-&gt; 页面A的生命周期 -&gt; 页面B的生命周期 -&gt;页面C的生命周期 -&gt; 小程序结束</p>
<h5 id="应用生命周期">应用生命周期</h5>
<p>应用生命周期函数需要写在 app.js中:</p>
<pre><code>App({
onLaunch: function(opts) {},
onShow: function(opts){},
hoHide: function(){},
})
</code></pre>
<ul>
<li>onLaunch: 小程序启动后立即执行,全局只触发一次。适合做一些初始化设置,如登录、全局变量初始化等。</li>
<li>onShow: 小程序启动或者从后台进入前台显示时触发。可以在这里执行数据请求、恢复界面状态等操作。</li>
<li>onHide: 小程序从前台进入后台时触发。可以在此清理临时数据、暂停计时器等,以节省资源。</li>
<li>onError: 捕获小程序的异常错误,包括脚本错误、API调用错误等,对于监控小程序运行状态非常有用。</li>
<li>onUnhandledRejection (可选): 捕获未处理的Promise拒绝错误,这是较新的API,用于增强错误处理能力。</li>
</ul>
<p><em>Tip</em>:微信开发者工具有一个选项“切后台”,就可以模拟切到后台</p>
<h5 id="页面生命周期">页面生命周期</h5>
<p>每个小程序页面也有其独立的生命周期,主要用于控制页面的加载、渲染、显示、隐藏和卸载等过程。主要生命周期包括:</p>
<ul>
<li>onLoad: 页面加载时触发(<code>一个页面只调用一次</code>)。适合初始化页面数据、获取页面参数等。</li>
<li>onShow: 页面显示/切入前台时触发。可以在这里设置页面数据、响应上个页面传递的参数等。</li>
<li>onReady: 页面初次渲染完成时触发(<code>一个页面只调用一次</code>)。此时可以进行一些DOM操作(虽然一般推荐使用setData来改变界面)。</li>
<li>onHide: 页面隐藏/切后台时触发。可以在这里保存页面状态、停止定时器等。</li>
<li>onUnload: 页面卸载时触发。适合做一些清理工作,如取消网络请求、移除事件监听等。</li>
<li>onPullDownRefresh: 页面下拉刷新时触发,需要在页面配置中开启enablePullDownRefresh。</li>
<li>onReachBottom: 页面上拉触底时触发,用于分页加载更多数据。</li>
<li>onPageScroll: 页面滚动时触发,可以用来监控页面滚动位置。</li>
<li>onShareAppMessage: 用户点击页面内分享按钮时触发,用于自定义分享内容</li>
</ul>
<p><em>Tip</em>:后台进入前台,先执行全局的 onShow,再执行页面的 onShow。</p>
<h5 id="下拉刷新">下拉刷新</h5>
<p>启用下拉刷新有全局开启下拉和局部开启下拉,实际开发,推荐使用局部开启下来,也就是为需要的页面单独开启下拉。</p>
<p>这里说一下实现:</p>
<ul>
<li>开启局部页面下拉</li>
</ul>
<pre><code>{
"enablePullDownRefresh": true
}
</code></pre>
<ul>
<li>在页面中实现下拉刷新逻辑,注意调用 stopPullDownRefresh,否则真机中下拉效果一直显示,不会主动消失。</li>
</ul>
<pre><code>Page({
onPullDownRefresh: function() {
    // 这里写你的数据重新加载或更新逻辑
    console.log('正在刷新...');

    // 模拟异步数据加载过程,实际情况中可能是发起网络请求获取新数据
    setTimeout(() =&gt; {
      // 数据加载完毕,停止下拉刷新的动画
      wx.stopPullDownRefresh();
      console.log('刷新完成');
    }, 1000); // 延迟时间仅作为示例,实际应根据你的数据加载时间调整
},

// 页面的其他生命周期函数和方法...
})
</code></pre>
<h5 id="上拉触底">上拉触底</h5>
<p>前面提到配置中默认是距离底部50px时触发,没有特别要求不用改</p>
<p>现在要实现上拉触底逻辑,只需要在 onReachBottom 中编码即可:</p>
<pre><code>Page({
data: {
    itemList: [], // 初始数据列表
    page: 1,   // 当前页数,用于分页加载
    hasMore: true // 是否还有更多数据
},

onReachBottom: function() {
    // 当用户滑动到底部时触发此函数
    if (this.data.hasMore) {
      this.loadMoreData();
    } else {
      wx.showToast({
      title: '没有更多数据了',
      icon: 'none'
      });
    }
},
</code></pre>
<h4 id="behaviors">behaviors</h4>
<p>小程序 behaviors 和 vue 中 <code>mixins</code> 类似。相似点有:</p>
<ul>
<li>都可以定义组件的属性(properties 或 props)和数据(data)、方法</li>
<li>都可以定义生命周期函数(微信小程序中的 lifetimes,Vue 中的生命周期钩子函数如 created 等)</li>
</ul>
<p>mixins 有一些问题:</p>
<ul>
<li>命名冲突:当多个 mixins 和组件本身定义了相同名称的属性或方法时,会导致命名冲突。Vue 会采用一种优先级机制来决定使用哪个,但这可能导致意料之外的行为。</li>
<li>来源不明:当查看一个组件时,不清楚哪些属性和方法是来自于 mixins,这会使得代码理解和维护变得困难。在大型项目中,特别是多个 mixins 叠加使用时,这个问题尤其明显。</li>
<li>耦合性:mixins 将共享逻辑放在一起,但这些逻辑可能高度依赖于组件本身的数据结构和其他逻辑,这导致了高度的耦合性,使得 mixins 难以重用和测试。</li>
</ul>
<p>Vue 3 的组合 API(Composition API)在很多情况下可以替代 mixins,并且解决了某些 mixins 的不足之处,比如命名冲突和代码组织不清晰等问题</p>
<p>小程序 hehaviors 和 vue 中 mixins 区别:</p>
<ul>
<li>属性和数据的合并策略:Vue 提供了比较详细的合并策略(如数组合并和对象覆盖),而微信小程序的behaviors 主要是覆盖属性</li>
<li>多重继承:微信小程序的 behaviors 支持多重继承,即一个组件可以使用多个 behaviors。Vue 的 mixins 也支持多重混入,但是在冲突解决上,Vue 的策略更为复杂和灵活</li>
</ul>
<h4 id="事件">事件</h4>
<h5 id="事件绑定">事件绑定</h5>
<p><code>事件是渲染层到逻辑层的通讯</code>:事件将用户在渲染层产生的动作,反馈到逻辑层进行处理。</p>
<p>小程序中常用事件:</p>
<ul>
<li><code>tap</code>,绑定方式是 bindtap或bind:tap,手指触摸后马上离开,类似html中的click事件</li>
<li>input,绑定方式是 bindinput或bind:input,文本框的输入事件</li>
<li>change,绑定方式是 bindchange或bind:change,状态变化时触发</li>
</ul>
<p>在微信小程序中,推荐使用tap,而非传统html中的 click,因为小程序为了优化移动端触摸体验,特别设计了 tap 事件来处理用户点击。相对click,有几个优势:</p>
<ul>
<li>移动设备优化:click在移动设备存在 300 毫秒的延迟,这是为了区分单击和双击操作。而tap没有这种延迟</li>
<li>更好的触摸体验:tap专为触摸屏设计,更符合用户在移动设备上的操作习惯。</li>
</ul>
<p>请看示例:</p>
<pre><code>Page({
data: {
    message: '按钮尚未被点击'
},
// 方法不像vue需要写在 methods 中,和 data 同级即可。
handleTap: function (e) {
    this.setData({
      message: '按钮被点击了!'
    });
    wx.showToast({
      title: '你点击了按钮',
      icon: 'none'
    });
}
});
</code></pre>
<pre><code>&lt;view class="container"&gt;
&lt;button bindtap="handleTap"&gt;点击我&lt;/button&gt;
&lt;view class="message"&gt;{{message}}&lt;/view&gt;
&lt;/view&gt;
</code></pre>
<p>除了tap事件,小程序还提供了一些常见的触摸事件:longpress(长按)、touchstart(触摸开始)、touchemove(触摸移动)、touchend(触摸结束)、touchcancel(触摸取消)等</p>
<p>Tip:小程序中其他事件有</p>
<table>
<thead>
<tr>
<th>事件类型</th>
<th>事件</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>触摸事件</strong></td>
<td>touchstart</td>
<td>手指触摸动作开始</td>
</tr>
<tr>
<td></td>
<td>touchmove</td>
<td>手指触摸后移动</td>
</tr>
<tr>
<td></td>
<td>touchend</td>
<td>手指触摸动作结束</td>
</tr>
<tr>
<td></td>
<td>touchcancel</td>
<td>手指触摸动作被打断,如来电提醒</td>
</tr>
<tr>
<td></td>
<td>tap</td>
<td>手指触摸后马上离开</td>
</tr>
<tr>
<td></td>
<td>longpress</td>
<td>手指触摸后,超过350ms再离开</td>
</tr>
<tr>
<td></td>
<td>longtap</td>
<td>手指触摸后,超过350ms再离开(别名)</td>
</tr>
<tr>
<td><strong>表单事件</strong></td>
<td>submit</td>
<td>表单提交</td>
</tr>
<tr>
<td></td>
<td>reset</td>
<td>表单重置</td>
</tr>
<tr>
<td></td>
<td>input</td>
<td>输入框输入时触发</td>
</tr>
<tr>
<td></td>
<td>focus</td>
<td>输入框获得焦点时触发</td>
</tr>
<tr>
<td></td>
<td>blur</td>
<td>输入框失去焦点时触发</td>
</tr>
<tr>
<td><strong>媒体事件</strong></td>
<td>play</td>
<td>开始播放</td>
</tr>
<tr>
<td></td>
<td>pause</td>
<td>暂停播放</td>
</tr>
<tr>
<td></td>
<td>ended</td>
<td>播放结束</td>
</tr>
<tr>
<td></td>
<td>timeupdate</td>
<td>播放进度更新</td>
</tr>
<tr>
<td></td>
<td>error</td>
<td>播放错误</td>
</tr>
<tr>
<td></td>
<td>waiting</td>
<td>正在加载中</td>
</tr>
<tr>
<td><strong>图片事件</strong></td>
<td>load</td>
<td>图片加载完成时触发</td>
</tr>
<tr>
<td></td>
<td>error</td>
<td>图片加载错误时触发</td>
</tr>
<tr>
<td><strong>滚动事件</strong></td>
<td>scroll</td>
<td>滚动时触发</td>
</tr>
<tr>
<td></td>
<td>scrolltoupper</td>
<td>滚动到顶部/左边时触发</td>
</tr>
<tr>
<td></td>
<td>scrolltolower</td>
<td>滚动到底部/右边时触发</td>
</tr>
<tr>
<td><strong>开放能力事件</strong></td>
<td>contact</td>
<td>用户点击客服按钮时触发</td>
</tr>
<tr>
<td></td>
<td>getuserinfo</td>
<td>获取用户信息事件</td>
</tr>
<tr>
<td></td>
<td>getphonenumber</td>
<td>获取用户手机号事件</td>
</tr>
</tbody>
</table>
<h5 id="事件对象">事件对象</h5>
<p>当事件回调触发时,会有一个事件对象 event,其详细属性有:</p>
<ul>
<li>type,string,事件类型。如tap,其type 就是tap</li>
<li>target,Object,触发时间的组件的一些属性值集合(<code>常用</code>)</li>
<li>detail,Object,事件对象中其他属性(额外信息)(<code>常用</code>)</li>
<li>currentTarget,Object,当前触发事件的组件的一些属性值集合</li>
<li>touches,Array,触摸事件,当前停留在屏幕中的触摸点信息的数组(几个手指)</li>
<li>changedTouches,Array,触摸事件,当前变化的触摸点信息的数组</li>
<li>timeStamp,Integer,页面打开到触发事件所经历的毫秒数</li>
</ul>
<p><em>Tip</em>: target 和 currentTarget 的区别类似 web 中target 和 currentTarget。target 是触发改事件的源,CurrentTarget 则是当前事件绑定的组件。比如点击 view 中的 button,e.target 是按钮,而 e.currentTarget 是 view。</p>
<pre><code>&lt;view bind:tap="callback"&gt;
&lt;button&gt;btn&lt;/button&gt;
&lt;/view&gt;
</code></pre>
<h5 id="事件传参">事件传参</h5>
<p><code>小程序事件传参不同于 vue</code>。</p>
<p>在Vue中可以这么写:<code>&lt;button @click="handleClick(123)"&gt;Button 1&lt;/button&gt;</code></p>
<p>但小程序会将 bindtap 属性值统一当做事件名处理,相当于调用 handleClick(123) 的事件处理函数。</p>
<p>微信小程序:通过 <code>data-*</code> 属性传递参数,使用 event.currentTarget(或target).dataset 获取参数。请看示例:</p>
<pre><code>&lt;view class="container"&gt;
&lt;button data-id="1" data-name="button1" bindtap="handleTap"&gt;Button 1&lt;/button&gt;
&lt;button data-id="2" data-name="button2" bindtap="handleTap"&gt;Button 2&lt;/button&gt;
&lt;/view&gt;

Page({
handleTap: function(event) {
    const { id, name } = event.currentTarget.dataset;// 获取多个参数
    console.log('Button clicked:', id, name);
}
});
</code></pre>
<h4 id="数据同步">数据同步</h4>
<p>在微信小程序中,<code>this.setData</code> 是用于更新页面数据的主要方法。当数据改变时,视图会自动更新。this.setData 可以用来修改 Page 对象中的数据,并将数据的变化反映到界面上。</p>
<pre><code>&lt;!-- example.wxml --&gt;
&lt;view class="container"&gt;
&lt;text&gt;计数值: {{count}}&lt;/text&gt;
&lt;button bindtap="incrementCount"&gt;增加&lt;/button&gt;
&lt;button bindtap="decrementCount"&gt;减少&lt;/button&gt;
&lt;input placeholder="输入内容" bindinput="handleInput"/&gt;
&lt;text&gt;输入内容: {{inputValue}}&lt;/text&gt;
&lt;/view&gt;

// example.js
Page({
data: {
    count: 0,
    inputValue: ''
},

// 增加计数
incrementCount: function () {
    this.setData({
      count: this.data.count + 1
    });
},

// 减少计数
decrementCount: function () {
    this.setData({
      count: this.data.count - 1
    });
},

// 处理输入事件
handleInput: function (e) {
    this.setData({
      inputValue: e.detail.value
    });
}
});

</code></pre>
<p><em>Tip</em>:</p>
<ul>
<li>在Vue中通常直接修改数据,对于某些情况可能需要用上this.$set,但是到了 vue3 中,由于改用 proxy 响应式系统,可以自动检测和监听响应式属性的新增和删除,更加方便。</li>
<li>小程序的 setData 和 react 中的 useState 非常相似。合并状态都是合并,而非替换。请看示例:</li>
</ul>
<pre><code>Page({
data: {
    count: 0,
    inputValue: ''
},
incrementCount: function () {
    this.setData({
      count: this.data.count + 1
    });
},
handleInput: function (e) {
    this.setData({
      inputValue: e.detail.value
    });
}
});
</code></pre>
<h5 id="文本框和数据的同步">文本框和数据的同步</h5>
<p>对于文本框和数据的同步,小程序和vue实现原理类似。</p>
<p>vue中可以通过 v-model 实现双向绑定,但是 <code>v-model 的本质</code>是 value 的属性以及 @input 事件</p>
<pre><code>&lt;input type="text" v-model="message" placeholder="Enter text"/&gt;

&lt;input type="text" :value="message" @input="updateMessage" placeholder="Enter text"/&gt;

new Vue({
el: '#app',
data: {
    message: ''
},
methods: {
    updateMessage(event) {
      this.message = event.target.value;
    }
}
});
</code></pre>
<p>用微信小程序是这样:</p>
<pre><code>&lt;input type="text" value="{{inputValue}}" bindinput="handleInput" placeholder="Enter text"/&gt;


Page({
data: {
    inputValue: '',
    errorMsg: ''
},
handleInput: function(event) {
    const value = event.detail.value;
    let errorMsg = '';
    if (value.length &lt; 3) {
      errorMsg = 'Input must be at least 3 characters long';
    }
    this.setData({
      inputValue: value,
      errorMsg: errorMsg
    });
}
});
</code></pre>
<h3 id="小程序组件">小程序组件</h3>
<p>小程序中的组件也由宿主环境提供,开发者可以基于组件搭建出漂亮的页面。小程序的组件分类有:</p>
<ul>
<li>视图容器</li>
<li>基础内容</li>
<li>表单组件</li>
<li>导航组件</li>
<li>媒体组件</li>
<li>地图组件</li>
<li>canvas 画布组件</li>
<li>开放能力</li>
<li>无障碍访问</li>
</ul>
<p><em>Tip</em>: 微信小程序 vs 支付宝小程序常用组件对比。感觉几乎相同</p>
<table>
<thead>
<tr>
<th>功能/类别</th>
<th>微信小程序组件</th>
<th>支付宝小程序组件</th>
<th>备注</th>
</tr>
</thead>
<tbody>
<tr>
<td>视图容器</td>
<td><code>view</code></td>
<td><code>view</code></td>
<td>基本视图容器</td>
</tr>
<tr>
<td></td>
<td><code>scroll-view</code></td>
<td><code>scroll-view</code></td>
<td>可滚动视图容器</td>
</tr>
<tr>
<td></td>
<td><code>swiper</code></td>
<td><code>swiper</code></td>
<td>滑块视图容器</td>
</tr>
<tr>
<td></td>
<td><code>movable-view</code></td>
<td><code>movable-view</code></td>
<td>可移动的视图容器</td>
</tr>
<tr>
<td></td>
<td><code>cover-view</code></td>
<td><code>cover-view</code></td>
<td>覆盖在原生组件上的视图容器</td>
</tr>
<tr>
<td></td>
<td></td>
<td><code>list</code></td>
<td>列表视图容器</td>
</tr>
<tr>
<td>基础内容</td>
<td><code>text</code></td>
<td><code>text</code></td>
<td>文本标签</td>
</tr>
<tr>
<td></td>
<td><code>icon</code></td>
<td><code>icon</code></td>
<td>图标组件</td>
</tr>
<tr>
<td></td>
<td><code>rich-text</code></td>
<td><code>rich-text</code></td>
<td>富文本组件</td>
</tr>
<tr>
<td></td>
<td><code>progress</code></td>
<td><code>progress</code></td>
<td>进度条</td>
</tr>
<tr>
<td>表单组件</td>
<td><code>form</code></td>
<td><code>form</code></td>
<td>表单,用于收集数据</td>
</tr>
<tr>
<td></td>
<td><code>input</code></td>
<td><code>input</code></td>
<td>单行输入框</td>
</tr>
<tr>
<td></td>
<td><code>textarea</code></td>
<td><code>textarea</code></td>
<td>多行输入框</td>
</tr>
<tr>
<td></td>
<td><code>checkbox</code></td>
<td><code>checkbox</code></td>
<td>复选框</td>
</tr>
<tr>
<td></td>
<td><code>radio</code></td>
<td><code>radio</code></td>
<td>单选按钮</td>
</tr>
<tr>
<td></td>
<td><code>switch</code></td>
<td><code>switch</code></td>
<td>开关选择器</td>
</tr>
<tr>
<td></td>
<td><code>slider</code></td>
<td><code>slider</code></td>
<td>滑动选择器</td>
</tr>
<tr>
<td></td>
<td><code>picker</code></td>
<td><code>picker</code></td>
<td>选择器</td>
</tr>
<tr>
<td></td>
<td><code>picker-view</code></td>
<td><code>picker-view</code></td>
<td>嵌入页面的滚动选择器</td>
</tr>
<tr>
<td></td>
<td><code>label</code></td>
<td><code>label</code></td>
<td>标签,用于表单控件的说明</td>
</tr>
<tr>
<td>导航组件</td>
<td><code>navigator</code></td>
<td><code>navigator</code></td>
<td>页面导航</td>
</tr>
<tr>
<td>媒体组件</td>
<td><code>image</code></td>
<td><code>image</code></td>
<td>图片组件</td>
</tr>
<tr>
<td></td>
<td><code>video</code></td>
<td><code>video</code></td>
<td>视频组件</td>
</tr>
<tr>
<td></td>
<td><code>audio</code></td>
<td><code>audio</code></td>
<td>音频组件</td>
</tr>
<tr>
<td></td>
<td><code>camera</code></td>
<td><code>camera</code></td>
<td>相机组件</td>
</tr>
<tr>
<td></td>
<td><code>live-player</code></td>
<td><code>live-player</code></td>
<td>实时音视频播放组件</td>
</tr>
<tr>
<td></td>
<td><code>live-pusher</code></td>
<td><code>live-pusher</code></td>
<td>实时音视频推流组件</td>
</tr>
<tr>
<td>地图组件</td>
<td><code>map</code></td>
<td><code>map</code></td>
<td>地图组件</td>
</tr>
<tr>
<td>画布组件</td>
<td><code>canvas</code></td>
<td><code>canvas</code></td>
<td>画布组件,用于绘制图形</td>
</tr>
<tr>
<td>开放能力</td>
<td><code>open-data</code></td>
<td><code>contact-button</code></td>
<td>微信开放数据组件和支付宝客服按钮</td>
</tr>
<tr>
<td></td>
<td><code>web-view</code></td>
<td><code>web-view</code></td>
<td>嵌入网页内容</td>
</tr>
<tr>
<td></td>
<td><code>ad</code></td>
<td><code>ad</code></td>
<td>广告组件</td>
</tr>
<tr>
<td></td>
<td><code>official-account</code></td>
<td><code>lifestyle</code></td>
<td>微信公众号组件和支付宝生活号组件</td>
</tr>
<tr>
<td></td>
<td><code>login</code></td>
<td><code>button</code></td>
<td>登录按钮(不同场景使用)</td>
</tr>
<tr>
<td></td>
<td><code>pay-button</code></td>
<td><code>button</code></td>
<td>支付按钮(不同场景使用)</td>
</tr>
<tr>
<td>无障碍访问</td>
<td><code>aria-role</code></td>
<td><code>aria-role</code></td>
<td>无障碍角色</td>
</tr>
<tr>
<td></td>
<td><code>aria-label</code></td>
<td><code>aria-label</code></td>
<td>无障碍标签</td>
</tr>
</tbody>
</table>
<h5 id="常用视图容器组件">常用视图容器组件</h5>
<ul>
<li>view,普通视图区域,类似html中的div,是一个块级元素,常用于实现页面布局</li>
<li>scroll-view, 可滚动的视图区域</li>
<li>swiper和swiper-item,轮播图容器组件和轮播图 item 组件</li>
</ul>
<p><code>问</code>:为什么不用 div ,而要创建 view?<br>
<code>答</code>:微信小程序选择使用 view 等自定义组件而不是原生 HTML 标签,如 div,主要出于以下几个原因:</p>
<ol>
<li>框架设计:为适配小程序的架构和特性。</li>
<li>性能优化:提升移动端的渲染性能和用户体验。</li>
<li>一致性和兼容性:确保在不同平台和设备上的一致表现。</li>
<li>更好地支持小程序特性:与小程序的生命周期、事件系统和样式管理等深度集成。</li>
<li>方便管理和维护:提供完善的组件体系和 API,简化开发和维护工作。</li>
<li>安全性:避免直接操作 DOM 带来的安全问题。</li>
<li>适合移动开发:更好地适配移动端的开发和用户体验需求。<br>
通过使用 view 组件,微信小程序能够更好地控制和优化应用的表现,提供更高效和一致的开发和用户体验。</li>
</ol>
<p><code>问</code>:有了 view,为什么还得单独为了滚动创建 scroll-view?<br>
<code>答</code>:尽管 view 组件已经提供了基本的容器功能,但 scroll-view 组件作为专门的滚动容器,具有以下显著优势:</p>
<ol>
<li>专为滚动设计:提供了丰富的功能和配置选项,便于控制滚动行为。</li>
<li>平滑滚动与性能优化:经过优化,提供更好的滚动体验。</li>
<li>额外功能支持:支持弹性滚动、滚动条隐藏等移动端常见功能。</li>
<li>可组合性和复用性:使得代码更模块化、易读和可维护。</li>
<li>事件监听与处理:丰富的事件机制,便于处理滚动相关逻辑。</li>
<li>动态控制滚动位置:通过属性控制滚动位置,支持动画效果。</li>
<li>避免样式冲突:确保滚动区域的独立性和稳定性。<br>
总之,微信小程序引入 scroll-view 组件,是为了提供更强大、更优化的滚动功能,提升用户体验和开发效率。</li>
</ol>
<p><code>问</code>:就不能将 scroll-view 合并到 view?<br>
<code>答</code>:尽管将 scroll-view 的功能合并到 view 组件中在理论上是可行的,但在实践中会引入许多复杂性和技术挑战。微信小程序选择将 scroll-view 与 view 分开实现,是为了:</p>
<ol>
<li>保持组件的职责单一,简化开发和维护。</li>
<li>优化性能,提供更高效的滚动体验。</li>
<li>提供丰富的功能和事件支持,增强灵活性。</li>
<li>避免样式和布局冲突,确保向下兼容。<br>
分开实现虽然增加了学习和使用的成本,但从长期来看,能够更好地满足开发者和用户的需求,同时保持代码的简洁和高效。因此,微信小程序将 scroll-view 独立出来是一个经过深思熟虑的设计选择。</li>
</ol>
<h5 id="基础内容">基础内容</h5>
<p><code>text</code>: 长按选中(selectable属性)只能使用 text,放在 view 中的不可以。</p>
<p><code>rich-tex</code>t:通过其nodes 属性节点,可以把HTML字符串渲染成对应的UI结构</p>
<h5 id="其他常用组件">其他常用组件</h5>
<ul>
<li><code>button</code>:按钮组件,功能比html中的 button 按钮丰富(主色调、大按钮小按钮、警告按钮...),通过 open-type属性可以调用微信提供的各种功能(客服、转发、获取用户信息)</li>
<li><code>image</code>:图片组件,image组件默认宽度约300px,高度约240px。mode 属性可以用来指定图片的裁剪和缩放,aspectFill类似 cover,aspectFit类似contain。其中差异需要自己品味</li>
<li><code>navigator</code>:导航组件,类似html中的a标签,用于页面跳转</li>
</ul>
<h3 id="自定义组件">自定义组件</h3>
<p>小程序开发者工具也提供了创建组件的便捷方式,右键“新建 Component”</p>
<h4 id="局部组件和全局组件">局部组件和全局组件</h4>
<p>组件从引用方式分为:</p>
<ol>
<li><code>局部引用</code>:组件只能在当前被引用的页面中使用</li>
<li><code>全局引用</code>:组件每个小程序页面都可以使用</li>
</ol>
<ul>
<li>局部引用示例:</li>
</ul>
<pre><code>components/
my-component/
    my-component.wxml
    my-component.wxss
    my-component.js
    my-component.json
</code></pre>
<pre><code>{
"usingComponents": {
    "my-component": "/components/my-component/my-component"
}
}
</code></pre>
<pre><code>&lt;view&gt;
&lt;my-component text="Welcome to My Component"&gt;&lt;/my-component&gt;
&lt;/view&gt;
</code></pre>
<pre><code>// my-component.json
{
"component": true
}
</code></pre>
<ul>
<li>全局引用示例:</li>
</ul>
<pre><code>components/
my-global-component/
    my-global-component.wxml
    my-global-component.wxss
    my-global-component.js
    my-global-component.json
</code></pre>
<p>在 app.json 中进行全局引用配置</p>
<pre><code>{
"pages": [
    "pages/index/index",
    "pages/logs/logs"
],
"window": {
    "backgroundTextStyle": "light",
    "navigationBarBackgroundColor": "#fff",
    "navigationBarTitleText": "WeChat",
    "navigationBarTextStyle": "black"
},
"usingComponents": {
    "my-global-component": "/components/my-global-component/my-global-component"
}
}
</code></pre>
<h4 id="页面和组件的区别">页面和组件的区别</h4>
<p>在小程序中,页面和组件在开发解构和使用方式上有许多相似之处,但他们用途和特性有所不同</p>
<table>
<thead>
<tr>
<th>特性</th>
<th>页面(Page)</th>
<th>组件(Component)</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>功能</strong></td>
<td>用户交互的独立视图</td>
<td>可复用的功能模块或UI元素</td>
</tr>
<tr>
<td><strong>组成文件</strong></td>
<td><code>.wxml</code>, <code>.wxss</code>, <code>.js</code>, <code>.json</code></td>
<td><code>.wxml</code>, <code>.wxss</code>, <code>.js</code>(调用Component()函数、事件需要定义到 methods中), <code>.json</code>(需要 <code>"component": true</code>)</td>
</tr>
<tr>
<td><strong>生命周期</strong></td>
<td><code>onLoad</code>, <code>onShow</code>, <code>onReady</code>, <code>onHide</code>, <code>onUnload</code></td>
<td><code>created</code>, <code>attached</code>, <code>ready</code>, <code>moved</code>, <code>detached</code></td>
</tr>
<tr>
<td><strong>路由和导航</strong></td>
<td>支持路由和导航 API</td>
<td>不支持路由和导航</td>
</tr>
<tr>
<td><strong>组合和嵌套</strong></td>
<td>不能嵌套在其他页面中</td>
<td>可以嵌套在页面或其他组件中</td>
</tr>
<tr>
<td><strong>复用性</strong></td>
<td>通常独立使用</td>
<td>高,可在多个页面中引用</td>
</tr>
</tbody>
</table>
<h4 id="组件样式隔离">组件样式隔离</h4>
<p>在微信小程序中,组件的样式是<code>默认隔离</code>的。这意味着组件的样式不会影响到外部页面或其他组件,而外部样式也不会影响到组件内部。这种样式隔离机制有助于提高组件的独立性和可复用性</p>
<p>如果希望外界影响到组件,也可以通过设置 `"styleIsolation" 来修改。微信小程序的样式隔离有三种模式:</p>
<ul>
<li>isolated(默认):完全隔离,组件的样式不会影响到外部,外部的样式也不会影响到组件内部。</li>
<li>apply-shared:组件样式不影响外部,但外部的全局样式可以影响组件内部。</li>
<li>shared:组件样式和外部样式互相影响。</li>
</ul>
<p><em>Tip</em>:说只有class选择器会有样式隔离效果,id选择器、属性选择器、标签选择器不会受样式隔离影响</p>
<h4 id="数据方法和属性">数据、方法和属性</h4>
<p>组件中的数据、方法和属性,请看示例:</p>
<pre><code>Component({
properties: {
    max: {
      type: Number,
      value: 10
    }
},
data: {
    name: 'pjl'
},
methods: {
    handleFn() {
      // true
      console.log(this.data === this.properties);
      // 使用 setData 修改 properties 值
      this.setData({max: this.properties.max + 1})
    }
}
})
</code></pre>
<p><em>Tip</em>:说小程序中properties 属性和 data 数据用法相同,都是<code>可读可写</code></p>
<h4 id="数据监听">数据监听</h4>
<p>微信小程序中的 <code>observers</code> 和 Vue.js 中的 watch 功能相似,都是用于监听数据变化并做出响应。然而,Vue.js 的 watch 提供了更多选项和更大的灵活性,适用于更复杂的监听需求。微信小程序的 observers 则较为简单和直接。</p>
<p>语法:</p>
<pre><code>Compoment({
observers: {
    '字段A, 字段B': function(字段A的新值, 字段B的新值) {
    }
}
})
</code></pre>
<ul>
<li>监听多个数据</li>
</ul>
<pre><code>observers: {
    'countA, countB': function(newCountA, newCountB) {
      console.log(`CountA has changed to: ${newCountA}, CountB has changed to: ${newCountB}`);
      this.setData({
      sum: newCountA + newCountB
      });
    }
)
</code></pre>
<ul>
<li>监听对象中的多个属性</li>
</ul>
<pre><code>observers: {
    'obj.v1, obj.v2': function(newFirstElement, newSecondElement) {
      console.log(`First element has changed to: ${newFirstElement}, Second element has changed to: ${newSecondElement}`);
    }
}
</code></pre>
<ul>
<li>监听对象中所有属性</li>
</ul>
<pre><code>observers: {
'obj.**': function(newObj) {
   
}
</code></pre>
<h4 id="纯数据字段">纯数据字段</h4>
<p>微信小程序有<code>纯数据字段</code>,其主要作用:</p>
<ul>
<li>减少数据传输:使用 setData 方法时,所有的非纯数据字段都会被序列化并发送到视图层。如果某些数据仅在逻辑层使用,并且不需要渲染到视图中,可以将这些数据标记为纯数据字段,以避免不必要的传输,从而提高性能</li>
<li>状态管理:纯数据字段可以用于存储组件内部的一些临时状态或计算结果,这些状态或结果不需要被渲染到视图中。例如,缓存一些计算结果或者维护一些内部状态。</li>
<li>代码的可维护性:标记纯数据字段可以帮助开发者更清楚地区分哪些数据是需要渲染的,哪些数据仅用于逻辑处理。这有助于提高代码的可读性和可维护性。</li>
</ul>
<p>在 options 中使用 <code>pureDataPattern</code>。请看示例:</p>
<pre><code>Component({
// 组件的属性列表
properties: {
    initialValue: {
      type: Number,
      value: 0
    }
},

// 组件的初始数据
data: {
    displayResult: 0,
    __internalCache: 0 // 纯数据字段,不会被传递到视图层
},

// 定义纯数据对象的匹配模式
options: {
    pureDataPattern: /^__/
},
</code></pre>
<h4 id="组件生命周期">组件生命周期</h4>
<ul>
<li>created(常用): 组件实例被创建时触发,此时组件的属性值、数据等尚未初始化,不能进行数据绑定操作(即不能使用 setData 方法)。</li>
<li>attached(常用):组件实例进入页面节点树时触发,可以访问属性值和数据,适合在这个阶段进行数据绑定和初始化工作。通常用于初始化数据、监听某些事件等。</li>
<li>ready:组件布局完成,即视图层的渲染已经完成,此时可以对组件的 DOM 结构进行操作。</li>
<li>moved:组件实例被移动到节点树另一个位置</li>
<li>detached(常用):组件实例被从页面节点树中移除时触发。<br>
适合在这个阶段进行清理工作,例如取消事件监听、清除定时器等,防止内存泄漏。</li>
<li>error:每当组件方法抛出错误时执行</li>
</ul>
<p>小程序组件,生命周期可以直接定义在 Component 构造器一级参数中,也可以写在 lifetimes 字段内(推荐方式,优先级更高)</p>
<pre><code>Component({
// 低优先级
error(err) {
   
},
lifetimes: {
    error(err) {
   
    }
}
});
</code></pre>
<h4 id="组件在页面的生命周期">组件在页面的生命周期</h4>
<p>有时,自定义组件行为依赖于页面状态的变化,这时就得用到组件所在页面的生命周期。比如每当触发页面的 show 声明周期时,希望重新生成一个数。</p>
<p>组件所在页面的生命周期有3个:</p>
<ul>
<li>show:组件所在页面展示时触发</li>
<li>hide:组件所在页面隐藏时触发</li>
<li>resize:组件所在页面尺寸变化时触发</li>
</ul>
<p>例如:</p>
<pre><code>Component({
pageLifetimes: {
    show() {
      console.log('Component in page show');
      // 页面显示时执行的逻辑
    }
}
});
</code></pre>
<h4 id="插槽">插槽</h4>
<p>和 vue 中类似,没有作用域插槽。</p>
<p>有单个插槽和多个插槽</p>
<h4 id="组件通信">组件通信</h4>
<p>微信小程序中组件通信,和vue中类似,父传子用属性,子传父用事件。</p>
<p><em>Tip</em>:微信小程序还有父组件通过 this.selectComponent() 获取组件实例(应该要少用)</p>
<p>子组件向父组件传递数据示例:</p>
<pre><code>// 子组件
Component({
methods: {
    incrementCount() {
      // 触发自定义事件,传递数据
      this.triggerEvent('countChange', { count: this.data.count + 1 });
    }
}
});

&lt;view class="my-component"&gt;
&lt;button bindtap="incrementCount"&gt;Increment&lt;/button&gt;
&lt;/view&gt;
</code></pre>
<pre><code>// 父组件
&lt;view class="container"&gt;
&lt;my-component bind:countChange="handleCountChange"&gt;&lt;/my-component&gt;
&lt;/view&gt;

Page({
handleCountChange(e) {
    // e.detail获取子组件传递的数据
    const newCount = e.detail.count;
   
}
});
</code></pre>
<h4 id="微信小程序安装-vant-weapp">微信小程序安装 vant weapp</h4>
<p>vant weapp 是一套小程序UI组件库。</p>
<p>小程序使用npm 包的和传统网站有一些不同。比如:</p>
<ul>
<li>安装和引用:传统网站npm 包会安装在 node_modules 目录中;小程序开发者工具会将 node_modules 中的包处理后放在 miniprogram_npm 目录,引用npm包时,需要使用 miniprogram_npm 路径,例如:"miniprogram_npm/@vant/weapp/button/index"。</li>
<li>构建:小程序需要在微信开发者工具中额外执行 “构建 NPM” 操作,将 NPM 包从 node_modules 构建到 miniprogram_npm 目录</li>
<li>包体积大小限制:传统网站没有严格限制包体积大小</li>
</ul>
<p>微信小程序安装 vant weapp,大概步骤(详细看vant官网):</p>
<ul>
<li>通过 npm 安装</li>
<li>构建 npm 包</li>
<li>修改 app.json</li>
</ul>
<p><em>Tip</em>:小程序比较特殊,每安装一个包都得构建才能使用。建议先删除 miniprogram_npm 这个包在构建,否则容易构建失败等问题</p>
<h3 id="路由导航">路由导航</h3>
<p>导航就是页面中相互跳转,浏览器中有 <code>&lt;a&gt;</code>、<code>location.href</code></p>
<p>vue 的单页面中有<code>编程式导航</code>和<code>命令行导航</code>,在微信小程序中也有编程式导航和命令行导航</p>
<h4 id="小程序和单页应用">小程序和单页应用</h4>
<p>先说一下传统的单页应用:</p>
<ul>
<li>单个HTML页面</li>
<li>前端路由</li>
<li>无刷新体验</li>
<li>前后端分离</li>
</ul>
<p>微信小程序在某种程度上与单页应用有相似的用户体验和部分技术实现,但在<code>严格技术定义来看,它并不是单页应用</code>。</p>
<p>微信小程序采用多页面框架,每个页面独立存在。切换页面的时候就可以和原生一致,可以做到滑动的效果。</p>
<p>小程序和单页的相似:</p>
<ul>
<li>无刷新体验</li>
<li>客户端路由:通过客户端的API进行页面导航</li>
<li>前后端分离</li>
</ul>
<p>小程序和单页的差异:</p>
<ul>
<li>页面独立:每个小程序的页面都是独立的,有自己的文件和生命周期。传统的apa则是在一个HTML文件内动态渲染和更新内容</li>
<li>页面加载:小程序页面切换时会加载相应的文件</li>
</ul>
<h4 id="vue路由-vs-小程序路由">vue路由 vs 小程序路由</h4>
<p>vue 中编程式导航和命令式导航,就像这样:</p>
<pre><code>this.$router.push({ path: '/some/path', query: { key: 'value' } });

&lt;router-link :to="{ name: 'routeName', params: { userId: 123 } }"&gt;Go to User&lt;/router-link&gt;
</code></pre>
<p>微信小程序中的命令式导航主要通过页面的 WXML 文件中的 <code>&lt;navigator&gt;</code> 组件实现,类似于 HTML 的 <code>&lt;a&gt;</code> 标签。</p>
<pre><code>&lt;navigator url="/pages/somePath/somePath"&gt;Go to Some Path&lt;/navigator&gt;
&lt;navigator url="/pages/tabPage/tabPage" open-type="switchTab"&gt;Go to Tab Page&lt;/navigator&gt;
</code></pre>
<p>微信小程序中的编程式导航通过 wx.navigateTo、wx.redirectTo、wx.switchTab 和 wx.reLaunch 等方法实现。这些方法允许开发者在 JavaScript 代码中进行页面跳转</p>
<pre><code>// 保留当前页面,跳转到应用内的某个页面
wx.navigateTo({
url: '/pages/somePath/somePath'
});

// 关闭当前页面,跳转到应用内的某个页面
wx.redirectTo({
url: '/pages/somePath/somePath'
});

// 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面
wx.switchTab({
url: '/pages/tabPage/tabPage'
});

// 关闭所有页面,打开到应用内的某个页面
wx.reLaunch({
url: '/pages/somePath/somePath'
});
</code></pre>
<h5 id="对比分析">对比分析</h5>
<p>编程式导航:</p>
<ul>
<li>Vue Router:通过 this.$router.push 等方法进行导航,支持多种导航方式(path、name、params、query)。</li>
<li>微信小程序:通过 wx.navigateTo、wx.redirectTo 等方法进行导航,功能丰富,但需要指定具体的 URL。</li>
</ul>
<p>命令式导航:</p>
<ul>
<li>Vue Router:通过 <code>&lt;router-link&gt;</code> 组件进行导航,语义化强,结构清晰,易读。</li>
<li>微信小程序:通过 <code>&lt;navigator&gt;</code> 组件进行导航,功能类似<code> &lt;router-link&gt;</code>,但没有 Vue 的路由命名和参数传递功能,需要通过 URL 进行导航。</li>
</ul>
<p>参数传递:</p>
<ul>
<li>Vue Router:支持通过 params 和 query 传递参数,非常灵活。</li>
<li>微信小程序:参数需要拼接在 URL 中,不够直观,参数传递相对复杂。</li>
</ul>
<p>适用场景:</p>
<ul>
<li>Vue Router:适用于复杂的单页应用(SPA),需要强大的路由管理功能和灵活的参数传递。</li>
<li>微信小程序:适用于小程序开发,注重简单和快速导航,符合小程序的设计哲学。</li>
</ul>
<p>通过对比,可以看出 Vue Router 在单页应用中的复杂导航管理方面更<code>强大</code>,而微信小程序的导航设计则更加<code>简洁</code>和快速,符合小程序快速开发的需求</p>
<ol>
<li>编程式导航:Vue Router 和微信小程序都提供了强大的编程式导航功能,前者通过 this.$router.push 等方法,后者通过 wx.navigateTo 等方法。Vue Router 更加灵活,参数传递更方便;微信小程序的编程式导航功能比较简单,需指定具体 URL。</li>
<li>命令式导航:Vue Router 使用 <code>&lt;router-link&gt;</code>,微信小程序使用 <code>&lt;navigator&gt;</code>。两者功能类似,都是用于声明式地定义导航结构,但 Vue Router 提供了更强大的路由命名和参数传递功能。</li>
</ol>
<h4 id="声明式导航">声明式导航</h4>
<ul>
<li>导航到<code>tabBar页面</code>: url 必须以 / 开头;open-type表示跳转方式,必须为 switchTab。请看示例:</li>
</ul>
<pre><code>&lt;navigator url="/pages/page1/page1" open-type="switchTab"&gt;导航到page1&lt;/navigator&gt;
</code></pre>
<ul>
<li>导航到非<code>tabBar页面</code>:url 必须以 / 开头;open-type表示跳转方式,必须为 navigate(可省略)。请看示例:</li>
</ul>
<pre><code>&lt;navigator url="/pages/page1/page1" open-type="navigate"&gt;导航到page1&lt;/navigator&gt;
</code></pre>
<ul>
<li>后退导航,比如后退上一页或多级页面:open-type必须是 navigateBack,表示后退导航;delta 必须是数字(默认是1,可省略),表示后退层级。请看示例:</li>
</ul>
<pre><code>&lt;navigator open-type="navigateBack" delta="1"&gt;返回上一页&lt;/navigator&gt;
</code></pre>
<h4 id="编程式导航">编程式导航</h4>
<ul>
<li>导航到 tabBar 页面:调用 wx.switchTab(Object obj)。obj 中属性有:url(必填,路径后不能带参数)、success、fail、complete</li>
</ul>
<pre><code>wx.switchTab({
url: '/pages/tabBar/home/home',
success: function(res) {
    // 成功回调
},
fail: function(err) {
    // 失败回调
}
});
</code></pre>
<ul>
<li>导航到非 tabBar 页面:调用 wx.navigateTo(Object obj)。obj 中属性有:url(必填,路径后能带参数)、success、fail、complete</li>
</ul>
<pre><code>wx.navigateTo({
url: '/pages/page1/page1',
success: function(res) {
    // 成功回调
},
fail: function(err) {
    // 失败回调
}
});
</code></pre>
<ul>
<li>后退导航:调用 wx.navigateBack(Object obj)。obj 中属性有:delta(默认是1,可省略)、success、fail、complete</li>
</ul>
<pre><code>wx.navigateBack({
delta: 1,
success: function(res) {
    // 成功回调
},
fail
</code></pre>
<h4 id="导航传参">导航传参</h4>
<ul>
<li>声明式导航传参:直接写在后面</li>
</ul>
<pre><code>&lt;navigator url="/pages/page1/page1?name=pjl&amp;age=18"&gt;导航到page1&lt;/navigator&gt;
</code></pre>
<ul>
<li>编程式导航传参</li>
</ul>
<pre><code>wx.navigateTo({
      url: '/pages/detail/detail?itemId=123&amp;itemName=ExampleItem'
    });
</code></pre>
<p>新页面接收参数:</p>
<pre><code>Page({
onLoad: function(options) {
    // options 对象包含了传递的参数
    console.log(options.itemId); // 输出: 123
    console.log(options.itemName); // 输出: ExampleItem
}
});
</code></pre>
<h3 id="状态管理">状态管理</h3>
<p>全局数据共享有:vuex、mobx、Redux等</p>
<p>小程序中可以使用 <code>mobx</code> 管理小程序状态。大概步骤:</p>
<ul>
<li>安装 MobX 和 MobX 的微信小程序支持库 mobx-miniprogram 和 mobx-miniprogram-bindings</li>
<li>构建 npm 包</li>
<li>创建 Mobx store:在项目根目录下创建一个 store 文件夹,然后在里面创建 index.js 文件,定义你的 MobX store</li>
</ul>
<pre><code>// store/index.js
import { observable, action } from 'mobx-miniprogram';

export const store = observable({
// 定义状态
count: 0,

// 定义计算属性
get doubleCount() {
    return this.count * 2;
},

// 定义动作
increment: action(function() {
    this.count += 1;
}),
decrement: action(function() {
    this.count -= 1;
})
});
</code></pre>
<ul>
<li>
<p>将 Store 注入小程序</p>
</li>
<li>
<p>使用 MobX 绑定页面:在页面中使用 mobx-miniprogram-bindings 库来绑定 MobX store</p>
</li>
</ul>
<pre><code>// pages/index/index.js
import { createStoreBindings } from 'mobx-miniprogram-bindings';
import { store } from '../../store';

Page({
// 初始化 Store Bindings
onLoad() {
    this.storeBindings = createStoreBindings(this, {
      store,
      fields: ['count', 'doubleCount'],
      actions: ['increment', 'decrement']
    });
},

// 销毁 Store Bindings
onUnload() {
    this.storeBindings.destroyStoreBindings();
}
});
</code></pre>
<p>通常建议每个页面都有自己的 Store</p>
<p>全局store和页面store混合使用也是一种很好的实践。</p>
<h3 id="分包">分包</h3>
<p>小程序中的<code>分包</code>(subpackage)是指将小程序的代码分割成多个子包(subpackage),每个子包可以独立开发、测试、发布,最终合并成一个完整的小程序</p>
<p><code>分包的优点</code>:</p>
<ul>
<li>通过将小程序的资源按需加载,可以减少首次加载时的资源量,提高启动速度。</li>
<li>多团队共同开发,解耦协作</li>
</ul>
<h4 id="分包类型">分包类型</h4>
<p>分包中三种包:</p>
<ul>
<li>主包(Main Package):小程序的核心包,包括小程序的入口文件(如 app.js、app.json 和 app.wxss)以及小程序根目录下的资源。主包在小程序启动时加载。</li>
<li>分包(Subpackage):除了主包之外的其他包,按需加载。可以包含页面、组件及其他资源。</li>
<li>独立分包(Independent Subpackage):一种特殊的分包形式,独立分包可以独立于主包运行,适用于需要快速启动的小程序模块。</li>
</ul>
<h4 id="分包加载规则">分包加载规则</h4>
<p>分包后,小程序项目:1个主包+多个分包</p>
<ul>
<li>主包:通常只包含项目启动页面或Tabbar页面、以及所有分包需要的公共资源</li>
<li>分包:只包含当前分包的页面和私有资源(图片、js、wxss、wxs...)</li>
</ul>
<p>小程序启动时,默认下载主包并启动主包内页面,当用户进入分包某页面时,客户端会把对应分包下载下来后再展示</p>
<h4 id="分包配置">分包配置</h4>
<p>假设我们有一个主包和两个分包 subpackageA 和 subpackageB。</p>
<p>项目目录结构如下:</p>
<pre><code>├── app.js
├── app.json
├── app.wxss
├── pages
│   ├── index
│   └── logs
├── subpackageA
│   ├── pages
│   │   ├── pageA1
│   │   │   ├── pageA1.js
│   │   │   ├── pageA1.json
│   │   │   ├── pageA1.wxml
│   │   │   └── pageA1.wxss
│   │   ├── pageA2
│   │       ├── pageA2.js
│   │       ├── pageA2.json
│   │       ├── pageA2.wxml
│   │       └── pageA2.wxss
├── subpackageB
│   ├── pages
│   │   ├── pageB1
│   │   │   ├── pageB1.js
│   │   │   ├── pageB1.json
│   │   │   ├── pageB1.wxml
│   │   │   └── pageB1.wxss
│   │   ├── pageB2
│   │       ├── pageB2.js
│   │       ├── pageB2.json
│   │       ├── pageB2.wxml
│   │       └── pageB2.wxss

</code></pre>
<p>在 app.json 中配置分包信息配置(subPackages)如下:</p>
<pre><code>// app.json
{
"pages": [
    "pages/index/index",
    "pages/logs/logs"
],
"subPackages": [
    {
      "root": "subpackageA",
      "pages": [
      "pages/pageA1/pageA1",
      "pages/pageA2/pageA2"
      ]
    },
    {
      "root": "subpackageB",
      "pages": [
      "pages/pageB1/pageB1",
      "pages/pageB2/pageB2"
      ]
    }
]
}
</code></pre>
<p>Tip:分包的体积是有一定限制的,分包体积可以在“小程序开发者工具”中查看。</p>
<h4 id="打包原则">打包原则</h4>
<ul>
<li>小程序会安装 subpackages 的配置进行分包,subpackages之外的目录会被打包到主包中</li>
<li>tabBar 页面必须在主包内</li>
<li>分包直接不能相互嵌套</li>
</ul>
<h4 id="分包引用规则">分包引用规则</h4>
<ul>
<li>分包可以引用主包内公共资源</li>
<li>主包不能引用分包内私有资源</li>
<li>分包之间不能相互引用私有资源</li>
</ul>
<h4 id="独立分包">独立分包</h4>
<p>独立分包是微信小程序提供的一种特殊分包形式,允许某些分包独立于主包运行。这对于需要快速启动的模块尤其有用,例如登录模块、功能独立的插件模块等。使用独立分包可以显著提高小程序的启动速度和用户体验。</p>
<p>独立分包的特点:</p>
<ul>
<li>独立运行:独立分包无需加载主包即可启动,具有独立的入口文件(如 app.js、app.json、app.wxss)。</li>
<li>快速启动:由于独立分包不依赖主包,可以显著提高这些模块的启动速度,适用于需要快速启动的场景。</li>
<li>资源隔离:独立分包的资源相对主包和其他分包是隔离的,适用于功能比较独立的模块。</li>
</ul>
<p>将分包配置成独立分包,只需要一个配置:independent。请看示例</p>
<pre><code>// app.json
{
"pages": [
    "pages/index/index",
    "pages/logs/logs"
],
"subPackages": [
    {
      "root": "subpackageA",
      "pages": [
      "pages/pageA1/pageA1",
      "pages/pageA2/pageA2"
      ],
      "independent": true
    }
]
}
</code></pre>
<h4 id="分包预下载">分包预下载</h4>
<p>分包预下载:是指进入小程序某页面时,框架自动下载可能需要的包。</p>
<p>例如进入 tabBar pageB 页面时下载 packageA。</p>
<p>通过 preloadRule 配置。就像这样:</p>
<pre><code>{
"pages": [
    "pages/pageA/index",
    "pages/pageB/index"
],
"tabBar": {
    "list": [
      {
      "pagePath": "pages/pageB/index",
      "text": "PageB"
      }
    ]
},
"subPackages": [
    {
      "root": "packageA/",
      "pages": [
      "pageC/index"
      ]
    }
],
"preloadRule": {
    "pages/pageB/index": {
      // wifi、2g、3g、4g。wifi 不包含 2g。
      "network": "all",
      "packages": ["packageA"]
    }
}
}
</code></pre>


</div>
<div id="MySignature" role="contentinfo">
    <section class="m-reprintStatement" style="white-space:normal;/* 防止break-all与nowrap矛盾 */word-break:break-all;">
    作者:彭加李<br>
    出处:https://www.cnblogs.com/pengjiali/p/18409870<br>
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
</section><br><br>
来源:https://www.cnblogs.com/pengjiali/p/18409870
頁: [1]
查看完整版本: 小李移动开发成长记 —— 大话小程序