最近项目事情很多,开发过程中也碰到了很多值得记录的问题,今天就早点来公司,把最近一些问题和经验总结一下,以便以后进行参考。
JS数值精度丢失
这其实是一个众所周知的问题,但他本身并不是js的bug,js采用的是IEEE 754 规范,采用双精度存储(double precision),占用 64 bit。所以这个BUG并不是只有js有,只要是采用了这个规范的语言都会有这个问题。
JS 中能精准表示的最大整数是 Math.pow(2, 53),十进制即 9007199254740992。所以当数值大于该值时就会出现精度丢失。在项目中后端传给前端的ID是数值并且超过了最大数值,最后导致了精度的丢失,当时也是找了很久才发现是这个原因,对于这个问题前端并没有很好的解决办法,只能是由后端将数值转为字符串传给前端解决问题。
async的使用
这是ES2017标准中引入的一个函数,其实并不是很新的东西,已经很多人在使用了,但是我一直还停留在使用promise的阶段,这次项目中碰见了需要加载很多图片,然后进行压缩的情况,想起来用async应该会很方便,然后就用上了,代码确实简介了不少,增强了代码的可读性,比起回调和promise又更加方便了一点。
async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。
await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try…catch代码块中。
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
// 另一种写法
async function myFunction() {
await somethingThatReturnsAPromise()
.catch(function (err) {
console.log(err);
});
多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
await命令只能用在async函数之中,如果用在普通函数,就会报错。也就是说不能用在foreach函数中,可以用for循环代替。
async function dbFuc(db) {
let docs = [{}, {}, {}];
// 报错
docs.forEach(function (doc) {
await db.post(doc);
});
}
//正确写法
async function dbFuc(db) {
let docs = [{}, {}, {}];
for (let doc of docs) {
await db.post(doc);
}
}
用上述的写法发送请求所有远程操作都是继发。只有前一个 URL 返回结果,才会去读取下一个 URL,这样做效率很差,非常浪费时间。我们需要的是并发发出远程请求。项目里需要请求上百张图片,然后对返回的图片压缩成zip包,之前就是用上述的方法进行压缩,效率真的很差,后面看到了阮一峰老师的写法,顿时感觉大神就是不一样。
async function compressionImage(imgUrlArray, id) {
const zip = new JSZip()
//并发发出远程请求。
const result = imgUrlArray.map(async url => {
const response = await props.getImageStream(url.material_path)
return { url: url.material_path, response }
})
//按顺序输出。
for (const elementPromise of result) {
const temp = await elementPromise
zip.file(temp.url, temp.response)
}
const content = await zip.generateAsync({ type: 'blob' })
let temp = formData
temp[id] = content
setFormData(temp)
}
react清空state
这个其实算不上什么难点和问题,当你在请求中使用setState时,由于请求是异步的,所以在你退出组件的时候可能请求还没有发完,这时候就有可能还会调用setState,所以需要在退出组件的时候清空state,之前react用的是class的写法,现在已经用react hook写了,在react hook中清空的写法与之前有些不同:
useEffect(() => {
if (!props.hideEdit) {
computerMaterialSize(dataSet.materials || [])
}
return () => {
setMaterialsSize = () => {
return
}
}
}, [])
这个写法其实并不是很好,如果你有很多个setState的话,你就得一个个return,可能是现在用hook的人并不是很多,所以也没有看到更好的写法,如果有的话,还请指点一下。
react hook高阶组件
这次的项目中用了全新的react hook,也尝试了用react hook来写高阶组件。
import React, { useEffect, useState } from 'react'
import { getfilesize } from '../../util/common'
//后台给出的每个图片url中有图片的大小信息,对每个组的数据大小进行计算,判断大小是否符合要求
export default function useSetsIsValid(modelData) {
const [allowEnd, setAllowEnd] = useState(false)
const [allSetsSize, setAllSetsSize] = useState(0)
const [everySetSize, setEverySetSize] = useState({})
useEffect(() => {
isAchieveTenImage()
computerAllImageSize()
return () => {
return
}
}, [modelData])
function isAchieveTenImage() {
if (modelData.data_sets) {
setAllowEnd(false)
for (const set of modelData.data_sets) {
if (!set.materials || set.materials.length < 10) {
setAllowEnd(true)
break
}
}
} else {
setAllowEnd(true)
}
}
function computerAllImageSize() {
if (modelData.data_sets) {
const dataSets = modelData.data_sets
let tempSize = 0
let tempSingleSizeArr = {}
dataSets.map(set => {
let tempSingleSize = 0
set.materials.map(material => {
tempSize = tempSize + material.file_size
tempSingleSize = tempSingleSize + material.file_size
})
// tempSingleSizeArr.push({ size: tempSingleSize, id: set.id })
tempSingleSizeArr[set.id] = tempSingleSize
})
setAllSetsSize(getfilesize(tempSize))
setEverySetSize(tempSingleSizeArr)
}
}
//返回结果
return [allowEnd, allSetsSize, everySetSize]
//在另一个组件中引用
const [allowEnd, allSetsSize, everySetSize] = useSetsIsValid(modelData)
}
首先最直观的感受就是相比于react class中高阶组件的写法要简洁了很多,特别是当你需要从一个已经写完的组件中提炼出能够复用的逻辑,将它写成可复用的高阶组件时,提炼的成本很小,你只需要简单的将需要复用的逻辑复制粘贴就好了。其次就是引用的时候也不需要像以前那样层层的嵌套,看起来明显更加的简洁和舒服。有一点需要注意的是,高阶组件的命名必须要以use开头,否则react不会去实时的监控这个高阶组件的状态的变化。
css object-fit
这是一个可以应用于img和video标签的属性,拥有以下的设置项:
contain
被替换的内容将被缩放,以在填充元素的内容框时保持其宽高比。 整个对象在填充盒子的同时保留其长宽比,因此如果宽高比与框的宽高比不匹配,该对象将被添加“黑边”。
cover
被替换的内容在保持其宽高比的同时填充元素的整个内容框。如果对象的宽高比与内容框不相匹配,该对象将被剪裁以适应内容框。
fill
被替换的内容正好填充元素的内容框。整个对象将完全填充此框。如果对象的宽高比与内容框不相匹配,那么该对象将被拉伸以适应内容框。
none
被替换的内容将保持其原有的尺寸。
scale-down
内容的尺寸与 none 或 contain 中的一个相同,取决于它们两个之间谁得到的对象尺寸会更小一些。
这个属性在IE浏览器中不支持,其他主流浏览器是支持的,contain属性可以在保持图像不被拉伸的情况下保持原有比例展示。