大前端(移动端/桌面应用Electron/微信小程序/小程序、快应用框架)

移动端 Web

总体认识

客户端的所有形式:Native App(IOS、Android、Mac、Windows),小程序(微信、百度、支付宝、字节跳动),桌面端网页、移动端网页(浏览器H5、webview H5、微信H5),公众号机器人(自动回复 和 主动推送)

移动端 web 的存在形式:

Native App:React Native,Weex,cordova(phoneGap)、wap2app

Web App:浏览器H5,webview H5,微信H5

小程序:微信、百度、支付宝、字节跳动

跨平台框架:Taro、Chameleon、uni-app等

资源:

Apache Cordova 是Adobe PhoneGap 贡献给Apache的开源项目,Cordova 是 PhoneGap的核心程序。
基于Cordova的方案:ionic

不复杂的应用不用框架,只要用一些库:Zepto,underscore,axios.js

一些框架:JqueryMobile/jqmobi/SenchaTouch/ionicframework

移动web开发资源收集:https://github.com/jtyjty99999/mobileTech

优秀实践:

RequireJS(按需加载)+Backbone(组织代码与路由管理)+Zepto(轻量DOM操作) + fastclick.js(点击穿透与延迟处理)+Hammer.js(各种触屏事件)+iScroll5.js(滚动条处理)+Animate.css(CSS3动画)+Enquire.js(处理响应式布局)。

屏幕与视口

见文章《移动端屏幕与视口》

Flex 布局(弹性布局)

见文章《布局》


媒体查询

见文章《响应式布局》
在 <html>标签 设置属性 data-dpr="2",则可简单得通过 [data-dpr=2] .class 来适配不同分辨率,省去媒体查询。


click事件延迟

单击后,浏览器等300ms ,来确定会不会是双击。就导致单击事件回调,延迟了300ms。

zepto.js方案

解决:用zepto.js的tap事件代替click事件。$("#id").on('tap',function(){});
tap事件原理:对比touchstart、touchend的位置和时间间隔,来判断是不是单击
点透bug:场景(两个重叠的层,上层绑定tap事件,让上层消失),
         bug(会触发下层的click事件),
原因(点击后,浏览器等待300ms,此时上层已经消失,就认为是点击了下层)

解决(上层过渡300ms消失,下层也用tap事件)

fastclick.js方案

使用:FastClick.attach(document.body)

原理:touchend 冒泡到 body时,event.preventDefault()阻止后续的click事件,创建一个点击事件并触发

触摸事件
touchstart、touchend、touchmove、touchcancel

H5

H5 调用 native

//native代码:
 WebSettings webSettings = webView.getSettings();
 webSettings.setJavaScriptEnabled(true);                       // 打开JS通道
 webView.addJavascriptInterface(new JsInterface(), "control"); // 设置JS接口
 
 public class JsInterface {
        @JavascriptInterface  // android 4.2 以后,有这个注解的方法 才能被 JS 访问
        public void do(String s) {
            log(s);
        }
    }

//H5代码:
  javascript:control.do('some');
  
native 调用 H5
webView.loadUrl("javascript:do();");  // do 是一个全局函数

H5 会话:
1、native将会话信息 设置进 webview 的 cookie
2、native将会话信息 加到 url 的参数里
H5 登陆状态调试:
1、在web端登陆,复制其cookie中的会话信息,再在H5中用JS 将会话信息 写入cookie
2、H5坐一个登陆页面,未登陆就跳到此页面进行认证,登陆后再调试

真机调试

见文字《移动端H5调试》

 优化

移动端优化
1、优化等待体验:显式加载效果,滚到可视范围再加载响应资源
2、减少图片:用样式和iconfont代替图片
3、重视渲染优化,见上方
4、动画,过渡,转换使用CSS,而不是JS。可以触发GPU硬件加速

移动端H5调试

Chrome Remote Debug

参考:https://developers.google.com/chrome-developer-tools/docs/remote-debugging

PC准备:

1、安装chrome

2、chrome 打开 Remote devices,勾选 Discover USB devices

(1)地址方式打开:地址栏输入 chrome://inspect/#deviceswebview

(2)DevTool入口打开:打开DevTool -》右上角点点点 -》 More tools -》Remote devices

3、连外网

Android准备:

1、设置 -》开发者选项 -》打开USB调试

2、安装驱动

(1)手动安装:https://developer.android.com/studio/run/oem-usb.html

(2)自动安装: PC上安装360手机助手,USB连接手机,即会启动自动安装

3、待调APP设置webview

WebView.setWebContentsDebuggingEnabled(true);

调试:

1、手机USB连接PC

2、手机上访问浏览器 或 webview

3、在 Remote devices 里 找到手机浏览器或webview访问的地址,点击inspect ,即可打开调试工具进行调试

Weinre

Weinre(WebInspector Remote) web远程检查器

1、安装命令行工具:npm -g install weinre

2、启动Debug Server(Agent):weinre --boundHost 本地IP

3、用浏览器访问服务说明:http://本地IP:8080/

4、植入Debug Target脚本:复制服务说明上”Target Script“ 下的<script>到项目代码里

5、在其他浏览器(例如手机浏览器)访问项目

6、访问Debug Client: 访问服务说明上“Access Points” 下的 “debug client user interface” 链接

7、选择Target:在 Debug Client 上 Remote -> Targets 下,点击一个链接,即选择了一个Target。(有多处同时访问项目就会有多个Target)

8、调试

vconsole

npm install vconsole

require('vconsole');

let vconsole = console;

vconsole.info(3);

electron 整合 vue

1、搭建vue + webpack 项目

2、在同一个目录下搭建 electron 项目

(1)安装electron,npm install --save-dev electron

(2)配置electron主进程入口 和 启动命令

package.json


{
  "main": "main.js",
  "scripts": {
    "start": "electron ."
  }
}

(3)main.js

const { app, BrowserWindow, Menu, globalShortcut } = require('electron')

let win;
function createWindow () {
    win = new BrowserWindow({
        width: 800,
        height: 600,
        icon: './favicon.ico',  // 窗口标题栏图标、系统任务栏图标;Ubuntu下需是完整路径,只能是png/jpg
        webPreferences: {
            nodeIntegration: true
        }
    })
    win.maximize();  // 窗口最大化
    win.loadURL('http://127.0.0.1');   // 打开网络地址
    win.loadFile('./dist/index.html')  // 打开本地文件

    // 自定义菜单
    const template = [
        {
            label:'视图',
            submenu:[
                {
                    label:'重新加载',
                    role:'reload',
                    accelerator:'Ctrl+R',
                },
                {
                    label:'开放者工具',
                    role: 'toggleDevTools',
                    accelerator:'Ctrl+D'
                }
            ]
        }
    ];
    const menu = Menu.buildFromTemplate(template);
    Menu.setApplicationMenu(menu);

    // 注册快捷方式
    globalShortcut.register('CommandOrControl+Shift+t', function () {
        win.webContents.openDevTools();  // 打开开放者工具
    });
}

# 避免打开多窗口
const singleInstanceLock = app.requestSingleInstanceLock();
if (!singleInstanceLock) {
    app.quit()
}
else{
    app.on('second-instance', (event) => {
        if (win) {
            if (win.isMinimized()) win.restore();
            win.focus();
        }
    });
    app.whenReady().then(createWindow);
}

(4)使用api

  const { app, BrowserWindow }  = require('electron').remote;  // 获取electron API
  console.log(app.getVersion())

  const fs = require('fs')    // 获取Nodejs API
  const root = fs.readdirSync('/')
  console.log(root)

  console.log(window.location.href);  // 获取window API

3、构建运行,npm run build;npm run start

Electron 整合 Vue 注意事项

1、在vue组件中 require('electron') 会导致webpack编译不通过,因为 electron模块 依赖了NodeJS环境的内置模块

解决方案一:

<!-- main.js 中 创建BrowserWindow时指定以下两参数 -->
const win = new BrowserWindow({
        webPreferences: {
            nodeIntegration: true,
            contextIsolation: false
        }
});

<!-- 在html中把API放到全局,再在组件中使用 -->
<script>
    window.child_process = require('child_process');
    window.iconv = require('iconv-lite');
    window.shell = require('electron').shell;
</script>

<!-- 在vue组件种使用 API -->
window.child_process.exec("ipconfig", {encoding: 'binary'},   // 调用本地命令
    (err, stdout, stderr) => {
        if (stderr) {
            stderr = window.iconv.decode(new Buffer(stderr, 'binary'), 'cp936'); // 解决中文乱码
            console.log(stderr);
        } else {
            stdout = window.iconv.decode(new Buffer(stdout, 'binary'), 'cp936');
            console.log(stdout);
        }
     }
);
// openExternal 不能打开 createObjectURL 得到的 URL
window.shell.openExternal('http://baidu.com');

解决方案二:发送消息给主进程

/* preload.js */
const { contextBridge, ipcRenderer } = require('electron');
// 给渲染进程暴露的 API
contextBridge.exposeInMainWorld('electron', {
    download: (url) => ipcRenderer.send('download', url)   // 给主进程发送消息
});

/* main.js */
const { BrowserWindow, ipcMain } = require('electron');
const path = require('path');

    const win = new BrowserWindow({
        webPreferences: {
            preload: path.join(__dirname, 'preload.js'),  // 预加载
            nodeIntegration: true,
            contextIsolation: true
        }
    });
    ipcMain.on('download', (event, url) => {   // 接收渲染进程消息
        const webContents = event.sender;
        const win = BrowserWindow.fromWebContents(webContents);
        win.webContents.downloadURL(url);
    });

/* 渲染进程中 */
window.electron.download(url);  // 调用preload.js中暴露的API

Electron 打包发布 之 手动打包

1、在github下载Electron 的 prebuilt binaries,即Electron Release 里的 electron-**.zip,要对应合适的运行平台,例如 electron-v6.1.11-win32-x64.zip、electron-v21.3.3-linux-x64.zip

备注:网速不好要科学上网,选对地域

2、解压 prebuilt binaries

3、把web项目(package.json、main.js、dist)放到 resources\app 目录下,加上 node_modules 中被项目用到的 nodejs 依赖

4、【Windows】用 ResourceHacker 更改 electron.exe 的图标;双击 electron.exe 运行

5、【Linux】开通运行权限 chmod +x electron;./electron 运行

6、【Linux 图形化界面】设置桌面入口

在桌面新建 name.desktop,填入

[Desktop Entry]
Version=1.0
Name=应用名
Comment=
 
Exec=/**/electron
Icon=图标路径
 
StartupNotify=true
Terminal=false
Type=Application
Categories=Applications;

添加可执行权限:chmod a+x name.desktop

允许启动:右键 Allow Launching

Electron 打包发布 之 electron-packager

// 安装electron-packager
npm install electron-packager -g
npm install electron --save-dev
npm install electron-packager --save-dev

// 设置electron资源镜像
set ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron/
// 首次打包
electron-packager . appname -–platform=win32|linux -–arch=x64
// 后续打包
手动将改动的web文件放到 jpf-win32-x64\resources\app 下,新增的依赖放到 jpf-win32-x64\resources\app\node_modules 下

缓存目录:~/.config/项目名

其他技术

node-webkit(NW.js)

微信小程序

微信小程序开发
对比

与微信网页:微信网页 给 微信提供的是 URL,小程序给微信提供的是 源代码;微信给小程序提供了框架、组件、更多Native能力的API;小程序需要审核才能上线;小程序被收藏后有更多入口;

与MVVM:page 接近于 vue 的单文件组件,模板、样式、脚本完全分离,模板采用xml;检查模型变化的方式 this.setData() 接近于 react 的 this.setState()

代码示例
1、获取用户信息(昵称、头像、性别、省市县)

<template>
		<button open-type="getUserInfo" @getuserinfo="getUserInfoHandle" 
v-show="!userInfoAuth">获取用户信息</button>
</template>
 
<script>
	export default {
		data() {
			return {
				userInfoAuth: false,
                userInfo: {}
			}
		},
		onLoad() {
			this.getSetting();
		},
		methods: {
            // 查看已有权限
			getSetting() {
				wx.getSetting({
					success: (res) => {
						let authSetting = res.authSetting;
						if (authSetting['scope.userInfo']) {
							this.getUserInfo();  
						}
					}
				})
			},
            // 已授权,直接接调用wx.getUserInfo()得到userInfo
			getUserInfo() {
				wx.getUserInfo({
					success: (res) => {
						this.analysisUserInfo(res.userInfo);
					}
				})
			},
            // 未授权,使用<button>获得授权,并得到userInfo
			getUserInfoHandle(event) {     
				let userInfo = event.detail.userInfo;
				if (userInfo) {
					this.analysisUserInfo(userInfo);
				}
			},
			analysisUserInfo(userInfo) {
				this.userInfoAuth = true;
                this.userInfo = userInfo;
			}
		}
	}
</script>

2、登录(获取openId,session_key、unionid)

	export default {
		onLoad() {
			this.checkSession();
		},
		methods: {
// 检查会话是否有效,即上次login得到的session_key(由后端从微信服务器获取)是否还有效
			checkSession() {
				wx.checkSession({
					success: ()=>{
						this.fetchMe();
					},
					fail: () => {
						this.login();
					}
				})
			},
// 检查会话是否还有效
			fetchMe(){
				wx.request({
					url: 'https://test.com/me',
                    success:(res)=>{
						console.log(res.data)
					},
					fail:(res)=> {
						this.login();
					}
				})
			},
			login() {
				wx.login({
					success:(res)=> {
						if (res.code) {
							this.loginServer(res.code);
						}
					}
				})
			},
			loginServer(code){
				wx.request({
					url: 'https://test.com/login',
					data: {
						code
					},
					success(res) {
						console.log(res.data)  // 用户已授权登录 微信开放平台同账号下的 公众号或移动应用 才会有 unionid
					}
				})
			}
		}
	}

3、获取手机号

前提:已登录

<template>
	<button open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">获取手机号</button>
</template>
 
<script>
	export default {
		methods: {
			getPhoneNumber(e) {
                // 得到加密过的数据
				if (e.detail.iv && e.detail.encryptedData) {
					this.getPhoneNumberFromServer(e.detail);
				}
			},
            // 发给后端解密,后端拿session_key去微信服务器解密
			getPhoneNumberFromServer(detail) {
				wx.request({
					url: 'https://test.com/phone',
					data: {
						iv: detail.iv,
						encryptedData: detail.encryptedData
					},
					success(res) {
						console.log(res.data)
					}
				})
			}
		}
	}
</script>

4、获取unionId

前提:微信开放平台上绑定了小程序,已经调用过wx.login(),后端已存有session_key

			getEncryptedData() {
				uni.getUserInfo({
					withCredentials: true,
					success: (res) => {
						this.decryptUnionId(res);  // 将加密数据传给后端解密
					}
				})
			},
			decryptUnionId(detail) {
				this.request({
					url: '/user/encrypted-data',
					method: 'PUT',
					data: {
						signature: detail.signature,
						rawData: detail.rawData,
						iv: detail.iv,
						encryptedData: detail.encryptedData
					},
					success: (res) => {
						res = res.data;
						if (res.code === 0) {
							this.globalData.$store.state.me.unionId = res.data;
							this.globalData.$store.commit("change");
						} else {
							this.showToast(res.msg || '获取失败');
						}
					}
				})
			}

5、登录与获取用户信息的结合


0、用户进入小程序时,有16种情况:有没有getUserInfo权限 2 * 我方服务器有没有记录这个用户 2 * 有没有登录我方服务器 2 * 有没有登录微信服务器 

1、虽然登录与获取用户信息可以分离,但是可以设计成 获取用户信息 是登录的前提,从而能采集一些用户信息;具体来说是,用户触发 getUserInfo 按钮 后再 wx.login(),并把用户信息保存到后端

2、用户触发过 getUserInfo 按钮,即可奖获取的信息保存到后端,但是有必要获取用户最新的信息,因此可以设计成每次登录都触发 getUserInfo

3、第一次获取用户信息 只能是 getUserInfo 按钮,之后可以是 wx.getUserInfo,可以设计成 任何情况都是 getUserInfo 按钮,从而不用区分两种情况

4、有getUserInfo权限的情况下,使用getUserInfo 按钮,不会弹出授权框,但能正常回调;因此,在显式登录的场景中,(type为getUserInfo的登录按钮 + wx.login) 不会比 (登录按钮 + wx.getUserInfo + wx.login)给用户带来更多负担;因此,在显式登录的场景中,无需判断是否有getUserInfo权限,统统使用getUserInfo 按钮

5、不在code2Session时获取unionId,因为此时往往没有unionId;可用在登录我方服务器后判断是否已存有unionId,没有的话再调用wx.getUserInfo({withCredentials: true})获取加密数据,发给后端解密出unionId(前提是 微信开放平台上绑定了小程序)

6、登录功能可以设计成一个抽屉,能被各处调用

总结:

方案一:不要自动wx.login(),用户要进行登录后才能有的操作时,弹出登录抽屉,抽屉里放置getUserInfo 按钮;用户触发getUserInfo 按钮后,再调用wx.login(),并将最新的用户信息保存到后端;缺点,用户每次都要显式的登录

方案二:先用wx.getSetting判断有没有getUserInfo权限,有的话,自动wx.getUserInfo并wx.login,并将最新的用户信息保存到后端;还没有getUserInfo权限的话,再按方案一

场景一:在有退出功能的小程序中,不能自动wx.login(),需要显式的登录,此场景采用方案一

6、登录与获取手机号的结合

需求:获取用户手机号即登录

0、获取手机号之前必须先wx.login,且后端存有session_key

1、只有一个按钮获取手机号的话,那么得不到getUserInfo权限,但是可以通过<open-data>显示用户基本信息

方案

1、刚进入小程序就自动wx.login(),并登录我方服务器,如果已有phoneNumber,则返回给前端,如果没有phoneNumber,当作未登录
2、未登录的情况下,用户要进行登录后才能有的操作时,弹出登录抽屉,抽屉里放置getPhoneNumber 按钮,用户触发getPhoneNumber 按钮后,调用敏感数据解密接口,得到phoneNumber

3、用<open-data>显示用户基本信息

7、Cookie


小程序不支持cookie机制,但可以读取响应头的Set-Cookie,有Storage机制。

可以用现成的组件使得小程序支持cookie:https://github.com/charleslo1/weapp-cookie

8、canvas

获取CanvasContext实例的三中方法:

<canvas canvas-id="poster-canvas" />
let ctx = wx.createCanvasContext('poster-canvas')
<canvas type="2d" class="poster-canvas" />
wx.createSelectorQuery().in(this).select('.poster-canvas').context((res)=>{
  let ctx = res.context;
}).exec();
let canvas = wx.createOffscreenCanvas();
let ctx = canvas.getContext('webgl');  // 离屏canvas 目前只支持 webgl

9、<web-view>打开第三方网页

问题:需要配置域名白名单,iframe 引用的域名也需要

方案1:在 nginx 中配置代理,将请求转发到第三方;缺点:会受防盗链限制

location / {
	proxy_pass https://www.目标.com;
	proxy_set_header Referer '';      # 清除Referer,冲破防盗链
	proxy_set_header Accept-Encoding "";  # 避免 gzip压缩,使得sub_filter能正常进行

    sub_filter_once off;
    sub_filter '第三方域名.com' 'www.我方域名.cn'; # 替换,让对第三方资源的请求都改成对我方的请求,我方转发时 改造 Referer 等字段,已规避 第三方的 防盗链机制
}

方案2:小程序关联的公众号网页中嵌入ifram(待试)

10、自定义TabBar

1、参考官方文档的示例代码

2、修改 path 和 icon 路径,注意有没有前面的 /

3、在TabBar组件中切换页面

    switchTab(e) {
      const data = e.currentTarget.dataset
      wx.switchTab({url: data.path})
    }

4、由于每个Tab页都有各自的TabBar组件实例,因此在TabBar组件中无法知道当前实例属于哪个Tab页,因此在Tab页中告知TabBar组件实例其所在的index

		created(){
			this.getTabBar().setData({  // 在uni-app中要写成 this.$mp.page.getTabBar()
			  selected: 1  // selected初始值可以设置为 -1,避免一开始就有菜单被选中
			})
		}

11、iPhoneX 底部横条避让

const model = wx.getSystemInfoSync().model;
this.setData({
			isIphoneX: /iphone\sx/i.test(model) || /iphone\s1/i.test(model) || (/iphone/i.test(model) && /unknown/i.test(model) && !/8/.test(model))
		});

12、注意事项

IOS 不支持 webp 图片

IOS 抖音小程序 不支持引用网络字体文件,需要把字体文件放到项目中

小程序、快应用 框架

运行平台

小程序平台:百度智能小程序、支付宝小程序、微信小程序、QQ小程序、字节跳动小程序

快应用平台:努比亚手机、联想手机、一加手机、小米手机、vivo手机、华为手机、OPPO手机、金立手机、魅族手机、中兴手机

跨平台框架

omix:是腾讯omi项目的子项目,是腾讯webstore项目的进化版,是原生微信小程序项目的状态管理组件、响应式组件

腾讯 WePY(类vue):支持输出 微信小程序
腾讯 kbone(纯vue,模拟浏览器环境):支持输出 微信小程序、H5

美团点评 mpvue(纯vue):支持输出 微信小程序、H5

滴滴 MPX(微信小程序原生语法增强为类vue):支持输出 微信/QQ/支付宝/百度/头条小程序

滴滴 Chameleon(CML,类vue):支持输出 微信/QQ/支付宝/百度/头条小程序、快应用、H5、Weex

DCLOUD uni-app(纯vue):支持输出 微信/QQ/支付宝/百度/头条小程序、快应用、H5、Weex

京东 Taro(纯react):支持输出 微信/QQ/支付宝/百度/头条小程序、快应用、H5、React Native

阿里巴巴 Rax(类React):支持输出 支付宝/微信小程序、H5(PWA)、Weex、Flutter

小程序框架指标

跨端支持度:小程序、H5、Native App、快应用

学习成本:是否独立DSL(Domain Specific Language,领域特定语言)、目前掌握的是vue还是react

组件丰富度:官方(内置)组件库、第三方组件库、是否支持原生(H5、小程序)组件库

坑数:跨端数越多,bug就会越多,性能就会越差,使用各端的原生能力就越难;增强型框架跨平台

热度:社区活跃度、更新频率

微信小程序组件库

有赞-Vant Weapp、微信-weui-miniprogram、TalkingData - iView Weapp、蘑菇街-MinUI、Wux Weapp(个人项目,组件最多)

京东-TaroUI(基于Taro框架)、ColorUI(WXSS框架)、腾讯-WeUI-wxss(WXSS框架)

转译型 与 增强型

转译型框架(MPX以外):将其他的语法规范转译为小程序语法规范

转译型框架的缺点:不支持源框架的一些语法特性,不支持原生组件库(H5、小程序)

增强型框架(MPX):基于某一小程序语法规范,使用Vue中优秀的语法特性进行增强;但在跨平台编译时,仍然是转译

增强型框架的优点:可以从原生小程序项目渐进迁移、一定程度上支持原生小程序组件库

增强型框架的缺点:由于是基于某一小程序的语法规范,跨平台编译时,更难抹平平台差异;一旦抹平了,也就有了转译型框架的缺点

总结:不跨平台编译时,用MPX作为语法增强,是可行的。需要跨平台编译时,会出现很多不支持转译的语法特性

MPX跨平台编译

思路:新平台不支持的内容,要么抹平 要么 再编译前进行差异化

抹平案例:微信小程序代码 编译成 头条小程序
问题:vant-weapp组件库用到了<wxs>,不能跨平台编译成 头条小程序
解决思路:去掉vant-weapp里的<wxs>
步骤:
1、把 /wxs 目录下的 wxs文件 改为 .js,在wxss中引入
2、把这些文件里的wxs语法改为js语法,例如 模块导入导出语法、getRegExp()改为new RegExp()、判断是否是数组array.constructor === 'Array' 改为  Array.isArray(array)
3、template里调用方法 {{f(x)}} 改用 computed 方法实现
4、template里wx:for 内 调用方法 {{f(x)}} 得想办法展开成表达式 或者 数据预处理

kbone

不能用现成的第三方vue组件库;使用了小程序原生组件,则web端用不了;使用kbone-ui,才能两端通用

启动时打开指定路径:每个页面要设置成单独的page,而不能是一个单页多个路由;打开新页要用window.open(route); 否则会出现页面空白、没有返回按钮、没有Home按钮等问题

从外部(公众号菜单、分享到对话框的卡片、分享到朋友圈的卡片、服务消息、小程序码等)打开指定路径要用 pages/xx/index?type=share&targeturl=${encodeURIComponent(location.href)},这里坑比较多,比如生成小程序码时不能带参

uniapp

1、微信小程序不支持指定tabBar高度,开发者工具模拟器某些机型下,tabBar文字会贴边,不代表真机会贴边

2、全局变量:Vue.prototype里的常量只能在JS中用,不能在template里用;写在App.globalData里的常量,可以通过computed用在template里;写在Vuex.Store里的变量,可以通过computed用在template里

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值