Uniapp

手机上运行的测试服一直弹出热更提示,并且更新完重进还是会有热更提示

运行包的应用版本不对,将mainifest的应用版本名称和版本号改为线上最新版本即可

为什么.gitignore 忽略了 unpackage,SourceTree 还显示该目录的未暂存文件

1. 项目初始化时,你还没写 .gitignore 文件,就直接执行了「第一次 git commit」 → 此时 Git 会把项目里所有文件 / 文件夹都默认加入追踪,其中就包含了 unpackage,之后你再补写 /unpackage/ 到.gitignore,已经晚了;

2. 开发中误操作:曾经手动在 SourceTree 里勾选了 unpackage 文件夹,执行了「暂存」或「提交」操作,导致它被 Git 记录为「需要追踪的目录」。

解决办法:

打开项目根目录的终端(vscode 终端 / 系统 cmd 都可以)依次执行下面 2 行命令,无需任何修改,直接复制粘贴(确保在执行命令前没有暂存的文件,小心被一起提交)

# 第1行:核心命令,移除Git对unpackage文件夹的「追踪标记」,本地文件保留不变!
git rm -r --cached unpackage

# 第2行:把「移除追踪」这个操作,提交到Git,让忽略规则永久生效
git commit -m "移除unpackage的git追踪,启用.gitignore忽略规则"

预防方法

 1:【项目初始化第一优先级】新建项目后,第一件事就是创建并编写 .gitignore 文件,写完后再执行任何 git add / git commit 操作!

这是黄金法则,99% 的忽略失效问题,都是因为「先提交,后写.gitignore」导致的。Git 的逻辑是「先到先得」,先提交的文件会被追踪,后加的规则无效。

 2:永远不要把「自动生成的产物目录、依赖目录」提交到 Git 仓库

比如 uni-app 的unpackage、所有前端项目的node_modules、打包后的dist、缓存文件、日志文件等,这些都是「可重新生成」的,提交了只会添乱,还会让仓库体积变大。

3:.gitignore 写好后,一定要「提交到 Git 仓库」

.gitignore 本身是一个普通的文本文件,只有把它提交到仓库,团队协作时,其他成员拉取项目后,才能复用你的忽略规则,避免团队其他人也踩同样的坑。

uni-app 项目 .gitignore 标准完整版
# 第三方依赖包
/node_modules

# uni-app 编译构建产物目录
/unpackage/

# 编译缓存文件
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# 本地配置文件(比如自己的环境变量、隐私配置)
/local_config.js
/.env.local
/.env.development.local
/.env.production.local

# npm 日志文件
npm-debug.log*
yarn-debug.log*
yarn-error.log*
tabBar跳转

uni.navigateTo和uni.redirectTo只能跳转应用内非 tabBar 的页面的路径 ,所以一个页面如果绑定了tabBar,页面上的navigator 的opentype为navigate或redirect,则跳转相应的页面就无效了

v-bind在绑定style

v-bind在绑定style的时候,一直用双引号包裹单引号很麻烦并且不方便扩充,所以可以把属性值当作对象来处理

<view class="box1" :style="`background-color: ${bgcolor}`"></view>
<!-- 效果与上面一样,注意命名格式-要替换成大写字母 -->
<view class="box1" :style="{backgroundColor: bgcolor}"></view>
data() {
	return {
		bgcolor:"yellow"
	}
}
v-bind绑定纯字符串记得带单引号,不然会被当成变量
<view :title="'普通字符串'"></view>
运行到android和ios,制作自定义底座

如果想运行到手机,需要提前创建自定义调试基座

基底打包完成后就可以运行到手机,选择运行到对应基座

nvue与vue的区别

nvue与vue有区别,例如nvue中文字内容必须用text组件,nvue 页面只能使用flex布局,不支持其他布局方式等,详情见页面 | uni-app官网

uniapp真机调试(安卓)

uniapp真机调试(安卓),点击运行--运行到手机或模拟器--显示webview调试控制台 

点击运行--运行到手机或模拟器-运行到基座,控制台会出现webview调试模块,点击箭头,就会出现控制台

width超出border
width: 100%;
padding: 10rpx;
// 当width为100%并且有padding的时候,可以看到大小会往右移,
// 这时候加box-sizing可以把padding变为内填充
box-sizing: border-box;
//ps:nvue盒模型的 box-sizing 默认为 border-box,
//即盒子的宽高包含内容、内边距和边框的宽度,不包含外边距的宽度

computed使用

computed的每一个计算属性都会被缓存起来,只要计算属性所依赖的属性发生变化,计算属性就会重新执行,视图也会更新。下面代码中,计算属性demo,它依赖了text和title这两个属性,只要它们其中一个属性变化,demo就会重新执行。
 computed计算属性会被缓存,在下面代码中使用了两次demo,但在控制台只输出了一次 “this.title + this.text”。

    <input type="text" v-model="title" />
	<text>{{title}}</text>
	<view>---------------------</view>
    <text>{{demo}}</text>
    <text>{{demo}}</text>
...
    data(){
        return{
            title: "sada",
            text: "一一一",
        }
    },
...
    computed: {
	    demo() {
            console.log(this.title + this.text)
		    return this.title + this.text
	    }    
    },
自定义事件
1.创建自定义组件以及接收自定义属性

在这个目录中新建组件

组件pubTitle.vue内容

<template>
	<view class="pubTitle">
		<view class="time">
			{{time}}
		</view>
		<view class="big">
			{{title}}
		</view>
		<view class="small">
			{{subtitle}}
		</view>
		<view class="small">
			{{list}}
		</view>
		<view class="small">
			{{user}}
		</view>
		<view class="line">
		</view>
	</view>
</template>

<script>
	export default {
		name: "pubTitle",
		//props:类似data(),调用的时候给title赋值即可
		//接受自定义属性,不带默认值
		// props: ["title", "subtitle"],
		// 或者这样可以给属性赋值一个默认值,
		//其他页面调用的时候如果没给属性赋值,则会显示默认值
		props: {
			title: {
				type: String,
				default: "默认标题"
			},
			subtitle: {
				type: String,
				default: "默认副标题"
			},
			time: {
				type: Number,
				default: Date.now()
			},
			list: {
				type: Array,
				default () {
					return [1, 2, 3]
				}
			},
			user: {
				type: Object,
				default () {
					return {
						name: "匿名",
						gender: "保密"
					}
				}
			}
		},
		data() {
			return {

			};
		}
	}
</script>

<style lang="scss">
	.pubTitle {
		padding: 60rpx 30rpx;
		text-align: center;
		background: #95E1D3;

		.big {
			font-size: 50rpx;
			font-weight: 600;
			color: #333;
		}

		.small {
			font-size: 28rpx;
			color: #888;
		}

		.line {
			display: inline-block;
			width: 80rpx;
			height: 8rpx;
			background: #1AA034;
			border-radius: 10rpx;
		}
	}
</style>

其他页面调用组件只要用组件名标签并且给组件的props中的属性赋值即可

//index.vue
<template>
	<view>
        //标题文本内容为“首页”
		<pubTitle title="首页" subtitle="index page"></pubTitle>
        //标题内容为“forTest”
        <pubTitle :title="title" subtitle="for page"></pubTitle>
        //不传值则组件文本内容是默认标题和默认副标题
        <pubTitle></pubTitle>

        <pubTitle :time="time" subtitle="for page"></pubTitle>
		<pubTitle :time='987' subtitle="for page"></pubTitle>
		<pubTitle :list="[4,5,6]" subtitle="for page"></pubTitle>
		<pubTitle :list="list" subtitle="for page"></pubTitle>
		<pubTitle :user="user" subtitle="for page"></pubTitle>
		<pubTitle :user="{user:'李四',sex:'女'}" subtitle="for page"></pubTitle>
	</view>
    </view>
</template>
...
    data(){
        return{
            title: "forTest",
            time: 123,
			list: [9, 9, 9],
			user: {
				name: "张三",
				gender: "男",
			},
        }
    }
2.emit子向父传值
<template>
	<view>
		<!-- 我是子组件myevent -->
		{{title}}
		<view class="">
			<input type="text" placeholder="请输入.." @input="oninput" />
		</view>
		{{array}}
	</view>
</template>

<script>
	export default {
		name: "myevent",
		props: {
			title: {
				type: String,
				default: "111",
			},
			array: {
				type: Object,
				default () {
					return {
						name: "张三",
						age: "12",
					}
				}
			}
		},
		data() {
			return {

			};
		},
		methods: {
			oninput(e) {
				let a = "子组件传入" + e.detail.value
				this.array.name = a
				//创建自定义事件(用于引用该组件的父组件调用),事件名myenv,后面是返回
				this.$emit("myenv", this.array)

			}
		}
	}
</script>

<style lang="scss">

</style>
<!-- 我是父组件 -->
<!-- 使用自定义方法@myenv 类似@click-->
<myevent title="组件间的传值" @myenv="onmyenv"></myevent>

...
   methods: {
		// 绑定子组件的自定义方法,接收其返回值
		onmyenv(e) {
			console.log(e);
		}
	} 
3.组件中使用原生方法
<!-- 我是父组件 -->
//在组件中使用@click等原生方法需要带.native,不然会以为是子组件自定义方法,导致无法实现功能
<myevent title="组件间的传值" @myenv="onmyenv" @click.native="onClick()"></myevent>
...
    onClick() {
			console.log(12)
		}
4.sync关键字

当一个子组件改变了一个 prop 的值时,这个变化也会同步到父组件中所绑定。 .sync 它会被扩展为一个自动更新父组件属性的 v-on 监听器。

//父组件
    <mypop :state.sync="state"></mypop>
...
    data() {
		return {
			state: false
		};
	},
//子组件
...
    <button @click="onClose()">关闭</button>
...
    methods: {
	    onClose() {
				//改变父组件的state值,
				// 添加update:父组件就可以用.sync关键字直接获取state的值,
                //就不需要在父组件中重新写个方法来接收state的值了
				this.$emit("update:state", false)
			}
		},
	props: {
		state: {
			type: Boolean,
			default: false
		},

	}
for循环中btn点击变色

 使用this.isChangecolor[e - 1] = e ,用这种方式给isChangecolor添加元素无效 的原因是

Vue无法检测到直接通过索引设置数组项的变化(如:this.items[index] = newValue)。Vue的响应式系统需要特殊的方法来触发视图更新。


//for循环内的btn,通过判断isChangecolor的index位置是否有值来更改第index个btn的颜色

<button class="btn"
     :class="isChangecolor[index-1]===index?'changeBg':''"
	  @click="goBuy(index)">
¥28.00</button>


//script

 data() {
	return {
		isChangecolor: [],
    }
 },

goBuy(e) {
// this.isChangecolor[e - 1] = e ,用这种方式给isChangecolor添加元素(无效,改为用$(set))
	this.$set(this.isChangecolor, e - 1, e)
	console.log(this.isChangecolor, this.isChangecolor[0]);
			}

//css
.btn {
			width: 150rpx;
			height: 50rpx;
			font-size: 25rpx;
			line-height: 50rpx;
			border-radius: 47rpx;
			background: linear-gradient(90deg, #FCC334 0%, #FBEF9B 100%);

			&.changeBg {
				background: #fff;
			}
		}
web-view

在云服务器上部署了cocos小游戏之后,想要显示在uniapp项目中,并且实现与uniapp的信息传输,就需要web-view组件

cocos部分

下载uni.webview.1.5.6.js

uni-app/dist/uni.webview.1.5.4.js · DCloud/uni-app - AtomGit | GitCode

创建构建模板,找到构建模板文件夹(通常是 build-templates/web-mobile/index.html,如果没有就从 build/web-mobile/index.html 复制一份或者直接修改构建后的文件)

在index.html的body中引入,构建后记得把 uni.webview.1.5.5.js 文件放到构建目录里,和 index.html 同级

<!-- 引入 uni-app 的 SDK -->
<script type="text/javascript" src="./uni.webview.1.5.5.js"></script>
<script>
  document.addEventListener('UniAppJSBridgeReady', function() {
    console.log('UniApp JS SDK Ready');
    // 这里可以通知游戏 SDK 加载完毕,但通常不需要
  });
</script>

cocos在游戏结束面板弹出时(init 方法中),向 UniApp 发送消息。

 init(score: number, bestScore: number) {
        ...

        // -----------------------------------------------------------
        // 将分数发送给 UniApp
        // -----------------------------------------------------------
        this.sendScoreToUniApp(score, bestScore);
    }

    /**
     * 发送数据给 UniApp
     */
   sendScoreToUniApp(score: number, bestScore: number) {
        // 检查环境,避免在编辑器或普通浏览器报错
        // @ts-ignore: 忽略 ts 对 window.uni 的类型检查
        if (window.uni && window.uni.postMessage) {
            console.log("Cocos: 向 UniApp 发送分数");
            
            // uni.postMessage 的格式必须是 { data: {} }
            // @ts-ignore
            window.uni.postMessage({
                data: {
                    action: 'updateScore', // 消息类型标记
                    currentScore: score,
                    bestScore: bestScore
                }
            });
        } else {
            console.log("Cocos: 当前非 UniApp 环境,无法发送消息");
        }
    }

Cocos 构建与部署

  1. 在 Cocos Creator 中点击 构建 (Build)

  2. 平台选择 Web Mobile

  3. 构建完成后,将构建生成的文件夹(假设叫 web-mobile)重命名为 2048game(名字随意)。

  4. 关键步骤: 将整个 2048game 文件夹复制到 UniApp 项目的 /hybrid/html/ 目录下。

    • UniApp 规定本地 HTML 必须放在 /hybrid/html/ 才能被 <web-view> 加载。

    • 路径结构应为:你的UniApp项目/hybrid/html/2048game/index.html。

    • 或者把2048game部署到云服务器上,这样在h5上也能看到画面,使用本地路径在浏览器打开会报错

        

uniapp部分
<template>
  <view class="container">
    <!-- 顶部展示区:显示从Cocos传回来的分数 -->
    <view class="info-bar">
      <text>UniApp本地最高分: {{ savedBestScore }}</text>
    </view>

    <!-- 嵌入 Cocos 游戏 -->
    <!-- src 指向本地的 index.html -->
    <!-- @message 用于监听 Cocos 发来的 uni.postMessage -->
    <web-view 
      class="game-view"
      src="/hybrid/html/2048game/index.html" 
      @message="handleMessage":webview-styles="webviewStyles" :update-title="false"
    ></web-view>
  </view>
</template>
......
data() {
			return {
				savedBestScore: 0,
				webviewStyles: {
					width: '430px',
					height: '650px',
				} // 别忘了这个样式
			};
		},
......
onLoad() {
			// 1. 页面加载时,先从本地缓存读取旧的最高分
			const score = uni.getStorageSync('MY_2048_BEST_SCORE');
			if (score) {
				this.savedBestScore = score;
			}
			// 如果是 H5 环境,手动监听 window message
			// #ifdef H5
			window.addEventListener('message', this.handleH5Message);
			// #endif
		},
		// 
		onUnload() {
			// #ifdef H5
			window.removeEventListener('message', this.handleH5Message);
			// #endif
		},
		methods: {
			/**
			 * 处理来自 Cocos 的消息
			 */
			handleMessage(evt) {
				console.log('【App端】收到消息:', evt.detail);

				// 注意:evt.detail.data 是一个数组!
				const dataList = evt.detail.data;
				if (dataList && dataList.length > 0) {
					const msg = dataList[dataList.length - 1]; // 取最新一条
					this.processLogic(msg);
				}
				// 现在运行到app上会报错,解决办法是把游戏部署到服务器上使用http/https协议
				// 本地方案(必须修改代码): 手动注入一段代码,用 XMLHttpRequest (XHR) 来“欺骗”浏览器,替换掉不兼容的 fetch。
			},
			/**
			 * 情况2:H5 端通过 window.message 触发
			 */
			handleH5Message(evt) {
				console.log('【H5端】收到消息:', evt.data);
				// H5传过来的直接就是数据对象,可能包裹在 data 里
				const msg = evt.data && evt.data.data ? evt.data.data : evt.data;
				console.log("msg1", msg);
				this.processLogic(msg.arg);
			},
			/**
			 * 统一处理逻辑
			 */
			processLogic(msg) {
				if (msg && msg.action === 'updateScore') {
					console.log("成功提取最高分:", msg.bestScore);
					this.savedBestScore = msg.bestScore;
					uni.setStorageSync('MY_2048_BEST_SCORE', msg.bestScore);

					// 提示一下用户
					uni.showToast({
						title: '分数已同步',
						icon: 'success'
					});
				}
			}
		}

注意点:

1.uni.postMessage 在 H5 浏览器环境 下(即用浏览器预览 UniApp)通常是不生效的,或者行为与 App 不一致。强烈建议使用真机调试(连接手机运行基座) 或者使用 App 模拟器。h5环境访问会有跨域问题,想在h5上看可以通过 window.message 触发

2.app的vue中 web-view的属性只能用webview-styles控制,不受class属性的影响,并且webview-styles只有宽高和loading三个属性,并且会自动铺满除去原生导航栏之外的所有区域,并且是最高层级,不好调整样式,

解决办法:app的web-view放在nvue中

  • .vue 页面里的 <web-view> 是原生图层,不受 CSS 控制。

  • .nvue 页面(Weex 引擎)里的 <web-view> 是一个普通组件,它完全支持 flex 布局!你可以像排列两个 div 一样排列 info-bar 和 web-view。

3.web-view的src用本地的src如果想用浏览器查看会报错

css

hover-class与position

父元素使用“hover-class”属性修改scale的时候的时候,子元素如果设置了定位(position),可能会导致按压事件区域可能异常,使用时需要注意

一个元素添加不同的class的样式覆盖
<view class=" a  b">实际宽度是200px,因为在style中b比a后写,所以b会覆盖掉a</view>

<style>
.a{
    width:100px;
}
.b{
    width:200px
}
</style>
nvue中view使用了hover属性,导致点击按钮之后按钮位置偏移

一、偏移的核心原因

1. hover 样式修改了盒模型属性

如果 hover 时改变了 padding/margin/width/height 等盒模型属性,nvue 原生渲染引擎会实时重排布局 —— 比如 hover 时给按钮加了 padding: 10rpx,点击后样式未及时恢复,或原生布局计算延迟,导致按钮位置偏移。

2. transform: scale 导致的视觉 / 布局偏移

若 hover 用了 transform: scale() 做缩放效果:

  • nvue 中 transform 的锚点默认是元素中心,缩放后可能因父容器的 flex/absolute 布局,导致元素视觉位置 “错位”;

  • 若缩放后未及时重置(比如 touchend 事件延迟),会残留缩放状态,表现为位置偏移。

3. nvue 对 hover-class 的兼容性问题

nvue 原生渲染对 hover-class 的支持不完善,尤其是结合 position: absolute/fixed 时,hover 触发的样式可能导致原生视图的布局计算误差,出现偏移。

4. 事件触发顺序与样式恢复延迟

手动用 @touchstart(加 hover 样式)和 @touchend(恢复样式)模拟 hover 时,若 touchend 触发延迟(比如有事件冒泡 / 阻止默认行为),会导致样式恢复不及时,看起来像位置偏移。

层级调试

在ios的nvue中调试层级的时候碰到了一个问题:同一个父容器,先写子容器的代码z-index=3,后面添加的组件的z-index=9,按道理来说组件的层级要比子容器的层级高,但是实际情况是组件层级在子容器之下

原因:子容器的position是:fixed,组件的position是absolute,

解决方法:组件的position修改为fixed即可显示在子容器上

ai分析:

你前面的 <view> 结构所在的父容器设置了 position: fixed,而组件 <positionFourBtn> 是 position: absolute。在 nvue 原生渲染逻辑中:

  • position: fixed 的容器及其子元素,原生层级优先级高于同一父容器下的 absolute 元素(即使组件 z-index 更高);
  • 且 DOM 中先出现的 fixed 相关元素,会被原生引擎优先渲染为 “更高层级视图”,后出现的 absolute 组件自然被覆盖
background-image要记得添加属性

使用background-image的时候,开发时能点看图片,说明路径是对的,但运行后没显示,可能是

样式属性缺失导致图片被隐藏,图片尺寸远大于元素尺寸,且未设置 background-size,导致只显示空白,缺少 background-repeat 或 background-position,图片重复或偏移出可视区域

.cItem {
  width: 100rpx;
  height: 100rpx;
  background-image: url("@/static/wenzibj.png"); 
  background-size: 100% 100%; // 让图片铺满元素
  background-repeat: no-repeat; // 禁止重复
  background-position: center; // 居中显示
}

JavaScript

JavaScript 数组引用赋值问题

要copy数组的时候可以用...展开运算符 ,.slice(),Array.from() 这些浅拷贝或者深拷贝

let a = [1, 2, 3];
let b = a;  // 这里不是创建新数组,而是将b指向a的引用
b.push(4);  // 通过b修改了a引用的数组

console.log(a); // [1, 2, 3, 4] ← a也被改变了
-----------------------------------------------
let a = [1, 2, 3];
let b = a.slice(); // 方法1:使用slice创建新数组
// 或
let b = [...a];    // 方法2:使用展开运算符
// 或
let b = Array.from(a); // 方法3

b.push(4);

console.log(a); // [1, 2, 3] ← 保持不变
console.log(b); // [1, 2, 3, 4]
---------------------------------------
const a = [[1], [2], [3]];
const b = JSON.parse(JSON.stringify(a)); // 深拷贝
浅拷贝与深拷贝
特性 浅拷贝 深拷贝
复制深度 只复制第一层属性/元素 递归复制所有嵌套层级
内存引用 嵌套对象/数组保持原引用 所有层级都是全新的独立副本
修改影响 修改嵌套内容会影响原对象 修改任何内容都不会影响原对象
性能 快(只复制一层) 慢(需要递归复制所有层级)
内存占用 较少(共享嵌套对象) 较多(完全独立副本)
原对象:
{
  name: "张三",
  age: 25,
  address: {
    city: "北京",
    street: "长安街"
  }
}

浅拷贝后:
{
  name: "张三", ← 新值
  age: 25,     ← 新值
  address: {   ← 仍然是原对象的引用!
    city: "北京",
    street: "长安街"
  }
}

深拷贝后:
{
  name: "张三", ← 新值
  age: 25,     ← 新值
  address: {   ← 全新的对象!
    city: "北京",
    street: "长安街"
  }
}
.map(Number)
let digits = [1, 2, 3]
	let str = ""
	for (var a = 0; a < digits.length; a++) {
	str = str + digits[a]
	}
    //map() 会对数组的每个元素调用提供的函数,并返回一个新数组。
    //Number 是一个内置函数,用于将值转换为数字。
    //所以 map(Number) 相当于:
    //const numberArray = stringArray.map(item => Number(item));
	return (Number(str) + 1).toString().split("").map(Number)
两种防抖函数
//在工具类中添加方法

//第一个防抖函数,通过setTimeout计时器防止玩家频繁点击按钮
let debounceTimeOut = null

function debounceFunc(func, wait = 2500, notShowTip) {
	if (debounceTimeOut !== null) {
		if (!notShowTip) {
			uni.showToast({
				title: '请稍候进行操作',
				icon: 'none',
				duration: 2000
			});
		}
		return
	}

	if (typeof func === 'function') {
		debounceTimeOut = setTimeout(() => {
			clearTimeout(debounceTimeOut);
			debounceTimeOut = null;
		}, wait);

		func();
	}
}

//第二个防抖函数,根据点击时间差来判断是否点击多次
let oldTime = 0
let newTime = 0

function debounceFunc2(func, wait = 1500, notShowTip) {
	newTime = Date.now();
	if (newTime - oldTime < wait) {
		oldTime = newTime;
		if (!notShowTip) {
			uni.showToast({
				title: '请稍候操作',
				icon: 'none',
				duration: 2000
			});
		}
		return
	}

	oldTime = newTime;
	if (typeof func === "function") {

		func();
	}
}

...
//记得导出函数
export {
    debounceFunc, 
	debounceFunc2, 
}
v-for的key

最好不要直接用:key =index,建议直接使用:key = ”‘字符串’+item.id(唯一标识符)“防止在冗长的代码中在同层级下出现两个index还不知道在哪找起

异步函数Promise和async和await和.then
console.log("A: 正常排队");

new Promise((resolve) => {
    console.log("B: Promise 内部其实是 VIP,立刻执行!"); 
    // 假设这里去发网络请求
    resolve(); // 拿到结果了,通知 .then
}).then(() => {
    console.log("D: 收到 resolve,我最后才执行");
});

console.log("C: 正常排队");
//实际输出顺序: A -> B -> C -> D。
//Promise 内部的代码立刻执行,但遇到 resolve() 时,它不会立刻执行 .then,而是
//把 .then 里的任务塞进“微任务队列”里,等外面的同步代码(C)全跑完,
//再回头执行 .then(D)。
  • Promise(承诺机制): new Promise 内部的代码是同步、立即执行的。它的作用是发起一个耗时操作(如网络请求),并承诺在未来通过调用 resolve()reject() 来交出结果。在它等待结果的期间,外部的同步代码会继续不受影响地往下跑。

  • .then()(监听与回调): 相当于一个结果接收器。它必须等待 Promise 内部调用 resolve()reject() 之后,才会被推入任务队列,随后再执行 .then() 里面的代码。

  • async(异步标记与包装): 用于标识一个函数是异步操作。除了作为标记,它在底层还会自动将该函数的最终返回值包装成一个 Promise 对象,方便外部继续对它使用 .then()await

  • await(异步串行化神器): 它只能在 async 函数体内使用。它的核心作用是只阻塞当前 async 函数内部的执行——必须死等当前的 Promise 拿到 resolvereject 结果后,才会放行执行下一行代码。它的终极意义是:用看起来像同步代码的直观写法,把原本非阻塞、满天飞的异步操作,变成了可控的串行执行。

标准异步请求 (Try-Catch-Finally)

这是最稳健的工业级写法。

const handleRequest = async () => {
  loading.value = true; // 1. 开始转圈
  try {
    // 2. 阻塞等待:等结果回来
    const res = await uni.request({ url: '...' }); 
    // 3. 成功后的逻辑
    data.value = res.data;
  } catch (error) {
    // 4. 容错:网络断了或接口崩了
    uni.showToast({ title: '加载失败', icon: 'none' });
  } finally {
    // 5. 闭环:无论成败,都要关掉转圈
    loading.value = false;
  }
};
性能优化 (Promise.all)

当页面一打开需要调好几个接口时,用这个。

*数组里的每个成员必须是 Promise 对象

onMounted(async () => {
  try {
    // 三个请求并发,总耗时 = 最慢的那一个
    const [list, user, time] = await Promise.all([
      getListApi(),
      getUserApi(),
      getSystemTimeApi()
    ]);
    
    // 结构赋值,一次性拿到所有数据
    giftList.value = list;
    userInfo.value = user;
    serverTime.value = time;
  } catch (err) {
    console.error("初始化页面失败", err);
  }
});

Promise.all 的工作逻辑是:

  1. 检查数组里的每一个元素。

  2. 如果是 Promise,就等它状态变为 resolved

  3. 如果数组里混进了一个不是 Promise 的东西(比如一个普通函数或数字),Promise.all 会认为它已经“瞬间完成了”,不会产生任何等待效果。

没有 awaitasync 是虚假的:如果你不写 await,代码会一溜烟跑完,不会等异步结果。

  • await 只挡住函数内部async 函数内部在死等,但它不会阻塞外面的同辈方法执行(保证了 UI 不卡死)。

  • Promise.all 是一票否决制:数组里只要有一个请求 Reject(失败),整个 Promise.all 就会直接跳进 catch

 

new Promise 的本质:一份“延期交付”的合同

你可以把它想象成你和 JavaScript 签的一份合同:“我现在开始干一件费时间的事,干完了我拨动 resolve 开关,干砸了我拨动 reject 开关。”

const myTask = new Promise((resolve, reject) => {
  // 这里写“费时间”的代码(比如定时器、网络请求、读文件)
  
  const success = true; 
  
  if (success) {
    resolve("想要传给后面的数据"); // 👈 拨动“成功”开关
  } else {
    reject("报错信息");          // 👈 拨动“失败”开关
  }
});
  • 必须要拨动开关:如果你在 new Promise 内部既没写 resolve() 也没写 reject(),那么外面的 await 就会永久死等下去,直到浏览器崩溃。

  • 只能改一次状态:一旦执行了 resolve,这个 Promise 的状态就锁死了。你后面再写 reject 也是没用的。

  • 立刻执行:当你 new Promise 的那一刻,内部的代码是立即开始运行的,它不会等你调用 await 才开始。

使用场景:

① 包装“老旧”的回调函数(Promisify)

② 模拟延迟 / 强制等待

③ 预加载资源(如图片)

vue3+ts

pinia

引入uniapp
# 如果使用 npm
npm install pinia

# 如果使用 yarn
yarn add pinia
第二步:在 main.js (或 main.ts) 中挂载
import { createSSRApp } from 'vue'
import * as Pinia from 'pinia' // 1. 导入 Pinia
import App from './App.vue'

export function createApp() {
  const app = createSSRApp(App)

  // 2. 创建 Pinia 实例并挂载
  app.use(Pinia.createPinia())

  return {
    app,
    Pinia // uniapp 环境下建议导出一份,以便某些插件使用
  }
}
第三步:定义你的 Store

在项目根目录创建一个 store 文件夹,然后新建一个文件(例如 counter.js):

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

// 你可以使用 'Setup' 风格,这和 Composition API 一模一样
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)
  
  function increment() {
    count.value++
  }

  return { count, doubleCount, increment }
})
第四步:在页面中使用
<template>
  <view>
    <text>当前计数:{{ counter.count }}</text>
    <text>双倍计数:{{ counter.doubleCount }}</text>
    <button @click="counter.increment">点我加1</button>
  </view>
</template>

<script setup>
import { useCounterStore } from '@/store/counter.js'

const counter = useCounterStore()
</script>

总结

Pinia 并不是为了“替代”接口请求,而是作为 API 数据的暂存区

  • 作用: 解决跨页面、跨组件的数据共享。

  • 心法: 服务器是“真理源头”,Pinia 是“本地投影”。页面只管看 Pinia,Pinia 负责跟服务器同步。

注意事项

  • 响应式解构 (storeToRefs): 直接解构 const { money } = store 会丢失响应性。

    • 必须使用 storeToRefs(store) 才能让页面随着数据的变化而自动刷新。

  • 核心思维: 模块化。让专门的 Store 管专门的事,互相之间通过 Action 通信。

Bug

报错

报错了不要只看web 可以运行小程序看看,page.json的错会在小程序编译的时候告诉位置,web不会

list.push()
...
    list1: [1, 2],
...
    // 错误:以为l1是新数组,实际上l1是数字(数组长度)
    let l1 = this.list1.push(1);

    // 数组引用赋值问题,实际上l2和list1指向的是同一个数组
    let l2 = this.list1
    // this.list1也会改变
	l2.push(1) 

    // 使用展开运算符copy数组,再push就不会改变list1了
    let l3 = [...this.list1]
    l3.push(1) 
props 应该是单向向下传递的

报错:Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "state"

这个错误是 Vue 的警告信息,表明您正在直接修改一个 prop 的值,这违反了 Vue 的单向数据流原则。

  1. state 是作为 prop 从父组件传递下来的

  2. 子组件中直接修改了 this.state

  3. 这违反了 Vue 的设计原则:props 应该是单向向下传递的

//父组件
<button @click.native="onClick()">开启</button>
<mypop :state="state"></mypop>
...
    methods: {
			// 绑定子组件的自定义方法,接收其返回值
			onmyenv(e) {
				console.log(e);
			},
			onClick() {
				this.state = true
			}
		},
//子组件
methods: {
  click() {
    this.state = false  // 直接修改了prop,导致报错
  }
},
props: {
  state: {  // 从父组件接收的prop
    type: Boolean,
    default: false
  }
}
大数精度丢失

大数精度丢失是指 JavaScript 在处理非常大或非常小的数字时,无法精确表示这些数字,导致计算结果不准确的现象。

JavaScript 使用 IEEE 754 双精度浮点数标准来表示数字,这意味着:

  • 最大安全整数Number.MAX_SAFE_INTEGER = 9007199254740991(2⁵³ - 1)

  • 最小安全整数Number.MIN_SAFE_INTEGER = -9007199254740991

超过这个范围的整数会出现精度问题。

let digits = [6,1,4,5,3,9,0,1,9,5,1,8,6,7,0,5,5,4,3];
let str = digits.join(''); // "6145390195186705543"

// 这个数字超过了 MAX_SAFE_INTEGER (9007199254740991)
console.log(Number(str)); // 6145390195186705000 ← 精度丢失!
// 最后三位 543 变成了 500
解决方法

使用 BigInt(ES2020)

// 在数字后面加 n 表示 BigInt
const bigNum1 = 9007199254740992n;
const bigNum2 = 9007199254740993n;

console.log(bigNum1 + bigNum2); // 18014398509481985n ← 精确!
//字符转bigint并且做加法
let str="6145390195186705543"
BigInt(str) + 1n //6145390195186705544n

服务器

云服务器安装宝塔

购买完服务器可以先重置密码,后启动实例的远程连接

搜索宝塔面板,复制宝塔linux安装脚本

if [ -f /usr/bin/curl ];then curl -sSO https://download.bt.cn/install/install_panel.sh;else wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh;fi;bash install_panel.sh ssl251104

按y同意后会开始安装,等待5分钟左右会出现宝塔外网ipv4面板地址,内网面板地址,账号,密码(要记住),并且提示请安全组放行“宝塔”端口

在安全组的入方向添加对应的宝塔端口,重启实例,再复制外网地址到浏览器上即可看到宝塔面板

把cocos文件放入云服务器

在php项目中添加一个站点,文件放在宝塔的对应目录下,云服务器和宝塔都放行对应的端口号,重启云服务器后浏览器输入http://外网地址:端口号即可运行cocos游戏

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐