[codes=c#]
// yarn add vuex-persistedstate
import createPersistedState from "vuex-persistedstate";
let store = new Vuex.Store({
plugins:[
createPersistedState({
reducer:data=>{
return{
// 天选之子,存在localstorage中,更多option参看文档
city:data.city
}
}
})
],
modules:{
cinema,
tabbar,
city
}
})
[/codes]
Pinia 是 Vue.js 的轻量级状态管理库,最近很受欢迎。它使用 Vue 3 中的新反应系统来构建一个直观且完全类型化的状态管理库。
Pinia的成功可以归功于其管理存储数据的独特功能(可扩展性、存储模块组织、状态变化分组、多存储创建等)。
pinia优点:
符合直觉,易于学习
极轻, 仅有 1 KB
模块化设计,便于拆分状态
Pinia 没有 mutations,统一在 actions 中操作 state,通过this.xx 访问相应状态虽然可以直接操作 Store,但还是推荐在 actions 中操作,保证状态不被意外改变
store 的 action 被调度为常规的函数调用,而不是使用 dispatch 方法或 MapAction 辅助函数,这在 Vuex 中很常见
支持多个Store
支持 Vue devtools、SSR 和 webpack 代码拆分
pinia缺点:
不支持时间旅行和编辑等调试功能
yarn add pinia@next
[codes=c#]
// @ts-check
import { defineStore, acceptHMRUpdate } from 'pinia'
import { useUserStore } from './user'
export const useCartStore = defineStore({
id: 'cart',
state: () => ({
/** @type {string[]} */
rawItems: [],
}),
getters: {
/**
* @returns {Array<{ name: string; amount: number }>}
*/
items: (state) =>
state.rawItems.reduce((items, item) => {
const existingItem = items.find((it) => it.name === item)
if (!existingItem) {
items.push({ name: item, amount: 1 })
} else {
existingItem.amount++
}
return items
}, []),
},
actions: {
/**
* Add item to the cart
* @param {string} name
*/
addItem(name) {
this.rawItems.push(name)
},
/**
* Remove item from the cart
* @param {string} name
*/
removeItem(name) {
const i = this.rawItems.lastIndexOf(name)
if (i > -1) this.rawItems.splice(i, 1)
},
async purchaseItems() {
const user = useUserStore()
if (!user.name) return
console.log('Purchasing', this.items)
const n = this.items.length
this.rawItems = []
return n
},
},
})
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useCartStore, import.meta.hot))
}
[/codes]
穿梭门:
https://pinia.esm.dev/
在线demo https://stackblitz.com/github/piniajs/example-vue-3-vite{>
搜索json2ts或
https://marketplace.visualstudio.com/items?itemName=eriklynd.json-tools
或在线https://apihelper.jccore.cn/jsontool
[codes=c#]
// 复制JSON到粘贴板,然后ctrl+alt+v
{
"id": 1,
"name": "111",
"address": "11",
"ext": {
"phone":"13312121212",
"mail":"xx@xx.com"
}
}
// 生成的代码
export interface Ext {
phone: string;
mail: string;
}
export interface RootObject {
id: number;
name: string;
address: string;
ext: Ext;
}
[/codes]
vscode定义一个class implements 这个interface,然后再使用出new即可
[codes=c#]
import { setgroups } from "process";
// Model
interface Ext {
Address: string,
Phone: Number,
}
export interface UserInfo {
Id: number,
Name: string,
Ext: Ext,
}
// 生成的代码可以使用=赋值
export class UserInfoModel implements UserInfo {
Id: number = NaN;
Name: string = '';
Ext: Ext = {
Address: '',
Phone: NaN
};
}
// Use
setup() {
const state reactive({
userInfo: new UserInfoModel(),
});
return {
state,
}
}
[/codes]
//testCp
//use testCp
[/codes]
Midway 是一个适用于构建 Serverless 服务,传统应用、微服务,小程序后端的 Node.js 框架。
Midway 可以使用 Koa,Express 或 Egg.js 作为基础 Web 框架。它还提供了独立使用的基本解决方案,例如 Socket.io,GRPC,Dubbo.js 和 RabbitMQ 等。
此外,Midway 也适用于前端/全栈开发人员的 Node.js 无服务器框架。构建下一个十年的应用程序。可在 AWS,阿里云,腾讯云和传统 VM /容器上运行。与 React 和 Vue 轻松集成。
仅当 “module“ 选项设置为 “esnext“ 或 “system“,并且 “target“ 选项设置为 “es2017“ 或更高版本时,才允许使用顶级 “await“ 表达式。ts(1378)
[codes=c#]
{
"compileOnSave": true,
"compilerOptions": {
"moduleResolution": "node",
"alwaysStrict": true,
"charset": "utf8",
"declaration": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"inlineSourceMap": true,
"module": "esnext", // 修改 commonjs -- esnext
"newLine": "lf",
"noFallthroughCasesInSwitch": true,
"noImplicitAny": false,
"noImplicitThis": true,
"noUnusedLocals": false,
"outDir": "dist",
"pretty": true,
"skipLibCheck": true,
"strict": true,
"strictPropertyInitialization": false,
"stripInternal": true,
"importHelpers": true,
"target": "ES2018"
},
"exclude": [
"app/public",
"app/views",
"dist",
"node_modules",
"test"
]
}
[/codes]
如果你在 Chrome Dev Tools 控制台中输入 JSON.parse('{"taskid": 9007199254740993}') 运行结果返回的将会是 {taskid: 9007199254740992}。为什么 parse 后的数值会不一致?
双精度浮点数 IEEE 754
JavaScript 采用双精度浮点数( IEEE 754 标准)来表示它的 Number 类型。一个数字占用 64 bits 存储空间(这里的每一位都只能存放 0 或 1):
如果我们将符号位和指数位共 12 个 bits 表示为 16 进制(4 个二进制 bits 1111 得到 1 个 16 进制的 f),那么它的取值范围为 [000, 7ff]。其中,规范约定当取值 7ff 时,可以表示无穷大或 NaN。
所以双精度浮点数能表示的最大 16 进制数为 0x7fef_ffff_ffff_ffff,转为十进制约为 1.79 ×10 的 308 次方。能表示的数的范围非常大,但受限于尾数的长度,能“精确”表示的数字并不多,我们来看看这个数到底是多少。
最大安全整数
从以上表示公式我们能看到,当指数部分只取 1 位,尾数部分取满 52 位时,可以精确表示出 JavaScript 里的整数,其 16 进制形式为 0x001f_ffff_ffff_ffff ,即 9007199254740991。
它等于 2 的 53 次方减 1,在 ES6 中,可以通过 Number.MAX_SAFE_INTEGER 引用到这个数值。
Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1 // true Number.MAX_SAFE_INTEGER === 0x001f_ffff_ffff_ffff // true Number.MAX_SAFE_INTEGER === 9007199254740991 // true Number.MAX_SAFE_INTEGER === -Number.MIN_SAFE_INTEGER // true
超过这个最大安全整数的运算,都可能因为发生进位溢出,造成精度丢失。
前后端大数传输方案
大数的运算和前后端传输是前端开发领域中的一个重要知识点。
本文开头提到的问题,源自于一个真实的项目案例,taskid 是 MySQL 数据库中的 bigint 类型字段。在 MySQL 中,一个 bigint 存储占用 8 Bytes 的空间,即 64 bits。当取值为无符号整型时,能表示的范围是 0 到 2 的 64 次方减 1,即 18446744073709551615。
当 taskid 取值在 (9007199254740991, 18446744073709551615] 之间时,后端程序(受语言特性和第三方库影响)通常能正确的执行 JSON 序列化操作,并通过 HTTP 接口返回给前端,而前端执行 JSON.parse 解码时,会因为语言本身的限制发生精度丢失,引发 bug。
大数转字符串类型
为了解决大数传递精度丢失的问题,常见的方案是“将大数转为字符串类型”。具体的做法如下:
后端程序先将大数转为 string 类型,再进行 JSON encode,传给前端。前端拿到数据后 decode 成 string 类型,直接展示。当需要大数运算时,将 string split 成多段安全整数字符串,每段单独转为 number 类型,在安全范围内计算完成后,再 join 成 string 类型进行展示。
一些第三方库(如 json-bigint)之所以能正确的处理大数 parse ,且不造成精度丢失,其实现原理也是类似。在拿到接口的 JSON 数据时,并不直接 JSON.parse,而是先将整块数据当作 text 字符串,将其中的大数以 string 类型进行存储和标记,再使用定制化的 JSON.parse。
类型语义丢失
我们知道前端往后端 POST 数据时,有两种常见的编码形式 application/x-www-form-urlencoded 和 application/json 。
当我们需要传递一个 number 类型的 id 给接口时,application/x-www-form-urlencoded 在 HTTP Request Body 中传输的是 id=1,而 application/json 的 Body 则是 {"id":1} 。我们之所以认为后者的语义更好,是因为后者能正确地反映出 id 的真实类型为 number。
而当这个 id 为 String 类型时,前者传输的依然是 id=1,后者则变为了 {"id":"1"}。对于后端程序来说,这层类型语义能让参数类型校验和计算更加准确和方便。
而如果前后端采用将“大数转为字符串”的方案,当 taskid 以 string 类型返回时,调用方将无法判断出它在业务和 DB 中到底是 char 字符类型存储的,还是 bigint 类型存储,导致类型语义丢失的情况发生。
类型语义有那么重要吗?这是另外一个话题了,但从 TypeScript 的发展趋势来看,为 JavaScript 加一个明确的类型,有很重大的意义。
ECMAScript 与 JSON 标准中的冲突
为了解决大数运算的问题,ECMAScript 标准中引入了 BigInt 类型(当前处于 Stage 3,且 Chrome 已经支持),通过在数字后面加一个 n,可以显式的声明一个 BigInt 类型对象,在进行运算时,将不再会发生精度丢失。
0x001f_ffff_ffff_ffffn + 2n === 9007199254740993n // true 2n**64n - 1n === 18446744073709551615n // true
在前端环境中,可以极其方便地进行大数运算。但这种做法,在进行 JSON 编解码时却遇到了大难题。
JSON 标准(IETF 7159)中定义了 JSON 支持的数据展示类型为 string、number、boolean、null 和这四个基础类型所组成的 object、array 结构。其他 JavaScript 类型在编解码时都会被静默消除或转换。
JSON.stringify({a:undefined, b: NaN, c: Symbol('c'), d:new Date(), e: new Set([1,2,3]), f:()=>{}}) // {"b":null,"d":"2019-07-31T10:21:47.848Z","e":{}}
从开发者的直观感受上,BigInt 作为 Number 类型的补充,应当在 JSON 标准中当作 Number 类型被支持。但从语言设计的角度来看,1 和 1n 是完全不同的对象类型,如果使用同一种表示方式,那么必然会发生“类型语义丢失”的现象。
更麻烦的地方在于,JSON 标准属于更广泛的标准,对 JSON 标准的改动,会影响到其他所有语言的实现,这可不是 JavaScript 弟弟能 hold 得住的。作为 ES 标准的制定者,TC39 委员会的大神们搁置了这个问题,而调皮的 Chrome 则在开发者试图 stringify 一个 BigInt 时,抛出了 Do not know how to serialize a BigInt 的异常。
事实上 JSON 标准中已经预料到,如果不设定 Number 的精度标准,可能会在不同系统传递数值时发生精度丢失的问题,所以也有建议开发者按照双精度浮点数规范来约束自己的系统。
如何利用 JavaScript BigInt 类型在不造成类型语义丢失的前提下,解决前后端接口大数的传输,是一个既有趣又有挑战的话题,同时也相当考验标准制定者和开发者的智慧了。