I am a frontend developer
- Blog: https://github.com/vivipure/blog
- TIL: https://github.com/vivipure/til
- Email: [email protected]
Today I learned
I am a frontend developer
JS 中迭代器是指包含 next 方法的对象, next 方法返回一个包含 value 和 done 键值的对象。
a.next() // {value: 1, done: false}
a.next() // {value: 2, done: false}
a.next() // {value: undefined, done: true}
生成器 是 一种类型的迭代器, 生成器函数返回一个生成器对象,这个对象是可迭代的
function * genFunction() {
yield 1;
}
const g = genFunction()
g.next() // value: 1, done: false
g.next() // value: undefined , done: true
const customIterator = {
[Symbol.iterator]: function * () {
let i = 0;
while(i++ < 10) {
yield i
}
}
}
利用 [Symbol.iterator] 我们可以实现直接的迭代对象,该对象可以通过 for ... in...
进行遍历
当我们在生成器对象next 方法传入参数时,当前的 yield 会变成我们传入的参数
function * listener() {
while(true) {
let msg = yield
console.log(msg)
}
}
let l = listener()
l.next(1) // 1
l.next(2) // 2
纤程 是 更小的线程,在JS中,可以通过 generator 实现纤程的效果
TODO:demo
利用yield 结合 无限循环, 我们可以通过其他函数调用生成器函数,生产我们想要的值。实际上函数内部使用了 while(true)
的语法,但是执行线程不会进入死循环,十分的有趣。
最近在做浏览器合成PSD的功能,需要利用 canvas 生成 图层。
canvas 在绘制图片时,需要图片必须同源。 其实线上环境是ok的,主要是开发时,本地localhost和图片就产生跨域了。
直觉的方法就是本地配置一个反向代理,但是我还是嫌麻烦。查阅资料后,发现解决办法很简单。利用 fetch 即可
function imgUrlToCanvas(url: string, width: number, height: number) {
return new Promise((resolve, reject) => {
fetch(url)
.then(res => res.blob())
.then(res => {
const url = URL.createObjectURL(res)
const img = new Image()
img.onload = () => {
const canvas = document.createElement('canvas')
canvas.width = width
canvas.height = height
canvas.getContext('2d')?.drawImage(img, 0, 0, width, height)
resolve(canvas)
URL.revokeObjectURL(url)
}
img.src = url
img.onerror = reject
})
.catch(reject)
})
}
代码逻辑很简单, 利用fetch 获取 blob ,然后 blob 转化为 url, 然后再绘制。这样就不会跨域了,十分的nice
无状态,无连接的应用层协议,每次请求都需要和服务器建立一个TCP连接
1.0 的升级版,支持长连接,通过 Connection 字段设置 Keep-Alive 保持HTTP连接,避免每次连接都重复连接和断开TCP, 提高来连接效率
支持断点续传,请求头中新增 Range字段
支持 pipe 传输
支持长连接
1.基于UDP的QUIC协议
注意到React 一条新的PR, 是关于 rfc。
RFC: First class support for promises and async/await
链接地址: reactjs/rfcs#229
看了大致的内容,主要概括为两点
这个对React 本身的影响还是挺大的。进行客户端开发,极大的方便了我们撰写逻辑,解除了hooks的限制。
对于React 的生态,例如Nextjs, Remix等,都有很大的影响。 以前必须要写的那些获取数据的额外函数,都不在需要约定了,可以直接写在组件内部。
build: {
lib: {
entry: "packages/components/index.ts",
name: "funui",
formats: ["umd", "iife"],
fileName: "funui.js",
},
rollupOptions: {
external: ['vue'],
output: {
globals: {
vue: 'Vue'
}
}
}
},
在vite.config.ts中配置build.lib
属性, 其中format为打包的格式,提供给html直接使用可以选择iife
需要注意的的是,打包Vue组件时不应该把Vue的相关依赖打包进去,否则会运行失败,所以在rollup中声明vue为外置依赖
还有组件中的css,不能设置为scoped
页面在进行渲染时,同步加载的JS和CSS都会阻塞页面渲染。SPA 应用将所有逻辑打包成JS,导致浏览器加载js 时间过长,就会出现白屏情况
5备注:
prefetch 加载浏览器页面可能需要的资源,浏览器不一定加载这些资源,在Vue 生成的App 中我们可以看到 路由的资源基本使用prefetch
preload 告诉浏览器页面必需这些资源,浏览器也一定会加载
preload 将加载和执行分离,不阻塞渲染。
perload/prefetch 不仅可以用于js,也可以加载样式图片。defer只能异步加载js
Omit
可以忽略对象类型
中的某些属性
type User = {
name: string
age: number
id: string
}
type SimpleUser = Omit<User, "name" >
等同于
type SimpleUser = {
age: number;
id: string;
}
自身实现,利用Exclude 剔除属性
type OwnOmit<T , P extends keyof T> = {
[K in Exclude<keyof T, P>]: T[K]
}
Pick
的功能和 Omit
相反,Pick
筛选出 对象类型
中需要的属性
type SimpleUser = Pick<User, "name" >
等同于
type SimpleUser = {
name: string;
}
本身的实现也很简单
type OwnPick<T , P extends keyof T> = {
[K in P]: T[K]
}
Omit
的实现有用到 Exclude, 它的作用就是将第一个类型中,符合第二个类型中条件的类型剔除
对于 对象类型等,第二个类型是第一个类型的子集也可以达到效果
type TextExclude = Exclude<'number' | 'age', 'age'> // 'number'
自身实现
type MyExclude<T, U> = T extends U ? never : T
Extract
的功能和 Exclude
相反, 它的作用就是将第一个类型中,符合第二个类型筛选的类型保留
type TextExclude = Exclude<'number' | 'age', 'age'> // 'age'
自身实现
type MyExclude<T, U> = T extends U ? T: never
Partial
可以让对象类型
的每个属性变为可选
type UserPartial = Partial<User>
等同于
type UserPartial = {
name?: string | undefined;
age?: number | undefined;
id?: string | undefined;
}
自身实现
type OwnPartial<T> = {
[K in keyof T]?: T[K]
}
Required
可以让对象类型
的的每个属性都变为必须
type UserRequired = Required<UserPartial>
等同于
type UserRequired = {
name: string;
age: number;
id: string;
}
Required
的实现 使用了 -?
,语法奇怪但是很好理解
type OwnRequired<T> = {
[K in keyof T]-?: T[K]
}
Record
可以定义对象类型
键和值的类型
type A = Record<string, {name: string}>
自身实现
type OwnRecord<Key extends number | string | symbol , Value> = {
[key in Key]: Value
}
ReturnType
可以返回函数类型
的返回值类型
function test(username: string, password: string) {
return { username, password }
}
type TestReturnType = ReturnType<typeof test>
等同于
type TestReturnType = {
username: string;
password: string;
}
自身实现
type OwnReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R: unknown
Parameters
可以返回 函数类型
的参数类型
function test(username: string, password: string) {
return { username, password }
}
type TestParameterType = Parameters<typeof test>
等同于
type TestParameterType = [username: string, password: string, age: number]
它的实现也是通过 infer
来进行实现
type OwnParameters<T extends (...args: any[]) => any> = T extends (...args: infer P) => any ? P : unknown
对于 async
函数或者返回一个 promise
值时, 我们可以通过 Awaited
得到未被Promise
包裹的类型
async function testUser() {
return 2
}
type TestUserResult1 = ReturnType<typeof testUser> // Promise<number>
type TestUserResult = Awaited<ReturnType<typeof testUser>> // number
在油管上看到了 Vercel 关于 Nextjs 的Edge Function 的视频,这里做下总结。
视频地址: https://www.youtube.com/watch?v=WlP2TB2ORL4&ab_channel=Vercel
文章地址: https://www.netlify.com/blog/edge-functions-explained/
example : https://github.com/netlify/edge-functions-examples
在 Nextjs 中,我们可以使用SSR或者SSG模式。 在服务器返回静态内容时,可以被用户缓存,减少客户端渲染时间,提高访问速度。
Edge Function 是 Nextjs 提供的中间件能力,在我们访问路由时,可以在服务端自定义一些行为:
从功能上来说,Edge Function 对静态内容进行了增强。可以看成动态化的静态【哈哈,有点奇怪】。在合适的业务场景,Edge Function 可以提供强大的能力。
Nextjs 中提供配置文件,可以针对不同的路由进行不同的 Edge Function .从开发者的角度来说,可以看成访问不同路由返回不同的静态内容,在返回之前会执行一些其他的逻辑。
使用 getBoundingClientRect
方法可以获取元素的尺寸和位置。在页面滚动时,可以计算所有的图片元素,获取元素的距离视图的高度,和视图的高度。如果视图高度大于距离高度,则将懒加载的地址放到src 中
img.getBoundingClientRect().top < window.innerHeigh
IntersectionObserver 当元素进入到可视视图时就会触发回调
const imgIntersectionObserver = new IntersectionObserver((entries) => {
for (let img of entries) {
if(img.isIntersecting) {
img.src = img.dataset.src
imgIntersectionObserver .unobserve(img)
}
}
})
for (let img of imgList) {
imgIntersectionObserver .observe(img)
}
<img loading="lazy" src="" />
krpano内部在进行字符串比较相等时,有些字符不能进行比较。例如 scene-12 和scene-12 不相等,因为 - 在krpano中解析是有问题的
krpano 中的名称不能以数字开头,例如场景的name
1.react-zoom-pan-pinch: 缩放,拖拽容器
2.reaflow: 流程图库
Astro 是 snowpack 的作者开发的前端框架,支持多种组件化框架。提供SSG,SSR等能力。
npm create astro@latest
solid 可以通过命令直接添加模块,例如引入 solid-js
npm astro add solid
当然也可以手动引入 solid-js
和@astro/solid-js
,然后在 astro.config.mjs中引入
import { defineConfig } from 'astro/config';
import solid from '@astrojs/solid-js'
export default defineConfig({
integrations: [ solid()]
});
现代的开发框架都支持组件开发,astro自身支持 astro组件,也支持.tsx,.vue,.svelte等组件
甚至组件和组件之前可以各种嵌套。
---
import Layout from '../layouts/Layout.astro';
import Card from '../components/Card.vue';
import CodeEditor from '../components/CodeEditor'
---
<Layout>
<main>
<Card />
<CodeEditor />
<main>
</Layout>
package.json 作为 Node.js 包管理的配置文件,平时使用的频率太高了。但是对于其中有些字段的含义一直没有进行系统的学习。
因此特意记录下 各个字段的意义和功能,参考文档:https://docs.npmjs.com/cli/v8/configuring-npm/package-json
应用名,包名。可以在前面使用 @xx/xx, 标识 scope
版本号,可以参考 #10 (comment)
描述
关键词数组,用于 搜索
包首页,文档或者github
一般可以 issue 地址, npm bug 可以打开该地址
许可协议
作者信息
包所包含的文件
声明后,用户下载时在bin目录会增加一个 对应的 可执行shell,对应的js 需要加上 shebang
"bin": {
"cli": "./cli.js"
}
// cli.js
#!/usr/bin/env node
可以用来定义命令,用来 npm run 进行使用。除了用户自定义,也包含生命周期。
我在使用 husky 时就会使用 prepare ,在每次下载时,执行 prepare 对应的脚本
总共有 : prepare, prepublish, prepublishOnly, prepack, postpack
支持的系统
支持的CPU
monorepo 设置
type: 'module`
标识是 esm还是 commonjs
main 是 npm package 的入口文件,当我们使用 CommonJS 导入包时,实际上导入的是 main 所指向的文件
// package.json
{
"name": "dep",
"main": "./dist/index.js"
}
const dep = require('dep')
// 等同于
const dep = require('dep/dist/index.js')
moduele 是ESM 导入时的寻找字段,若没有则引入 main 字段。所以一般库 CommonJS 放在 main, ESM 放在 module 字段
main: './dist/index.js',
module: './dist/index.mjs',
exports 描述了子目录的访问路径,如果定义了 exports ,那么不在 exports 的模块,用路径也无法访问。
{
name: 'midash',
main: './index.js',
exports: {
'.': './dist/index.js',
'es': './dist/es/index.js'
}
}
engines 指定了当前项目所需要的环境,例如声明 node 最小版本,如果本地环境与版本不匹配,则会进行报错
"engines": {
"node": ">=14.0.0"
}
在浏览器使用时,标识使用哪个文件
作为常用的 CSS 预处理器之一,less 的使用频率是比较高的。相较于 Sass , less 的功能会相对简单。
作为开发者,使用嵌套写法只是基础的功能,还有写很多实用功能等着我们去探索。
npm i less -D
npm i less-loader -D
新增了了索引函数 at
const arr = [1,2,3]
arr.at(0) // 1
arr.at(-1) // 3
对数组,字符串,类型数组都起作用
新增hasOwn 方法判断自身属性,和 hasOwnProperty 没啥区别
实例化 Error 时可以穿入 cause
const error = new Error('test error', {cause: ' error reason '})
error.cause
允许await 在 async 函数之外使用,可以在顶级作用域使用
await Promise.resolve()
垃圾的私有变量语法还是通过了
class Sample {
a = 2; // public
#b = 4 // private
static #c(){} // private
static {} // 类声明时调用 ???
}
除了这恶心的 标识符, 还引入了判断方法,来判断私有属性。 可以在静态方法中 判断 私有属性是否在对象中
class Sample {
static is(obj) {
return #b in obj && #method in obj
}
}
新建 jsconfig.json
, 方便编辑器代码跳转,对构建无意义
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"exclude": ["node_modules"],
"include": ["src/**/*"]
}
在 tsconfig.json
中配置 paths
"compilerOptions": {
"paths": {
"@/*":["./src/*"]
}
}
webpack.config.js
配置 resolve
resolve: {
alias: {
'@': path.resolve('src'),
},
},
vite.config.js
中配置 resolve
resolve: {
alias: {
"@": path.resolve(__dirname, 'src')
}
}
下载插件babel-plugin-module-resolver
,在 babel.config.js
中配置
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
plugins: [
[
'module-resolver',
{
alias: {
'@': './src',
},
},
],
],
}
清除缓存:
npm cache clear --force
原因: 环境变量解析问题
解决: 在 pre-commit 中添加shell
PATH="/usr/local/bin:$PATH"
迁移电脑时,重新运行前端项目,都会报这个错。发现是node版本问题。nodejs 18.* 版本文件处理的方式有变化,导致webpack打包失败。
解决:nodejs 版本降级,使用16.18.1 版本即可
硬链接(hard link) 是指向文件本身
ln {source} {link}
软链接(soft link) 是指向文件名即路径
ln -s {source-filename} {symbolic-filename}
F(0) = 0, F(1) = 1
when n > 1
F(n) = F(n-1) + F(n-2)
function fibonacci(n) {
if(n < 2) return n
return fibonacci(n - 1) + fibonacci(n - 2)
}
递归模式在参数较大时,速度较慢,可以增加缓存机制
const cacheMap = new Map()
function fibonacci(n) {
if(cacheMap.has(n)) {
return cacheMap.get(n)
}
if(n < 2) {
cacheMap.set(n, n)
return n
}
const result = fibonacci(n - 1) + fibonacci(n - 2)
cacheMap.set(n, result)
return result
}
公司呆久了,总会有很多项目会被重构,重构的过程和结果都能让人很有收获。最近我又作为主程,开始了一个H5编辑器项目的重构。
重构过程中不仅涉及到UI的更改,还加了很多的新的功能。之前的数据结构也设计的不太合理,导致开发难度还是挺大的。
原技术栈: Vue 2 + VueX
升级后: Vue 2.7 + TS + Pinia
使用Vue 2.7 的原因
不用Vue3 是因为还有很多公用组件是Vue2 的,Vue3 升级成本较大。Vue 2.7 支持 setup 语法,加上 TS,开发体验俱佳。
升级TS 原因
编辑器项目,数据结构较多。各种数据处理和转化,有了TS才能得心应手
Pinia
pinia 代替 Vuex ,一是为了适配 composition api 的写法。二是对 store 进行抽离。Vuex 的设计导致 store 中的数据太过臃肿,且 mutation 确实没多大用。
首先是升级 Vue
npm i [email protected]
npm i pinia
加上 TS 支持
npm i ts-loader -D
npm i typescript -D
类型声明文件
// setup 宏支持
/// <reference types="vue/macros-global" />
// vue 组件定义
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
// 扩展window 属性
interface Window {
LOAD_STATUS: boolean
}
// 环境变量定义
declare var HTTP_ENV: 'prod' | 'test'
本来想把构建工具换成 Vite 的,但是项目依赖的 Module Federation 组件, vite 的插件还是存在一些问题。因此还是使用 webpack 5进行构建。后续会考虑增加对 Vite 的支持
总的来说,Vue 2.7 + TS + Pinia 这一套是真的爽。composition api 还好,主要是 TS, 使用 TS 重写了数据结构之后,各种开发十分方便,类型提示几乎杜绝了以前经常出现的解析和转化bug.
JavaScript 处理事件执行顺序的机制
JavaScript 是单线程语言,执行代码时即有同步代码也有异步代码。同步代码的执行顺序就是按顺序执行,而对于异步代码,就需要我们对Event Loop 有足够的了解。
Event Loop 负责执行代码、收集和处理事件以及执行队列中的子任务
通常我们在执行代码时,会有执行堆栈。同步代码都在堆栈中执行。当堆栈中的代码执行完成后,就会从队列中取出任务进行执行。
任务队列主要是 定时器,Promise 等操作的回调函数
定时器定义的回调,在时间达到后,会有 定时器线程将回调推入到 宏任务队列。
举例:setTimeout, setInterval, requestAnimationFrame,
举例
定义的Promise 回调在执行时,就会推入到 微任务队列。
举例:MutationObserver, Promise,
同步代码执行完成后,会将微任务队列中的任务执行完成,再执行宏任务队列中的任务,每执行一个后又会检查微任务队列,将微任务队列执行清空,重复此过程知道所有代码执行完成。
在Vue2中,Vue.extend将组件转化为构造函数,通过实例化生成组件实例。因此我们可以通过函数调用的方式生成业务所需要的一些弹窗性质的组件。
通过平时的业务积累,我总结了些使用这种方法存在的一些不足支持。
在new 构造函数的时候生成了组件的实例,这个时候就已经触发了created hook。 当我们通过函数将参数传递给实例后再挂载到dom时,发生在created中需要props值进行的相关操作都无法获得正常的props值。
如果有需要在生命周期调用函数使用props值的业务场景,我们需要将事件都放在mounted中,才能正常运行。
后面又发现了这个propsData属性,可以当作参数传入构造函数中,这样初始化时的prop值就是正确的值了。
之前遇到过 函数方式生成的组件中,this
获取不到自定义挂载到prototype上的属性。
原因也很简单,入口文件使用的Vue和Vue.extend
使用的Vue不是同一个Vue.所以在原型上找不到挂载的数据。
可以通过只引入一次,然后分别提供给入口和Vue.extend使用,就能解决这个问题
我在使用webpack5
的Module Federation时并未遇到这种情况,可能是由于在模块导出时直接声明了Vue为外部依赖,导致使用了同一个Vue, 所以才没出现这个问题。
由于Router和Store是主应用在实例化时应用的,然后将$store
属性和$router
属性只有主应用的Vue下的组件vm才拥有该属性,使用Vue.extend生成的组件由于不再主应用的tree,因此获取不到对应属性。
function vuexInit () {
const options = this.$options
// store injection
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
}
目前暂时没有解决办法。。。
XSS (cross site scripting) 跨站脚本攻击,可以理解为网站会执行用户提交的内容。
类型:
innerHTML
,eval
等操作。出现原因:未对用户输入进行转义,直接展示用户的输入,导致执行用户输入的代码。常见于评论区,个人信息,私信等功能。可能造成其他用户的用户信息泄漏,危害极大。
如何防御:
XSS 攻击本质上可以说是 HTML注入,执行用户输入的内容
git reset <commit_id>
重置某个提交,会将对应更改进行回撤,提交一个新的提交
git reset --hard <commit_id>
重置到某个提交,该提交之后的所有更改都会丢失,不会产生新提交
git reset --soft <commit_id>
重置到某个提交,该提交后的所有更改会回到暂存区,不会产生新提交
git commit --amend
可以对已经提交的内容进行追加
git reset --hard origin/master
丢弃本地提交,重置为远端最新提交
报错: Uncaught DOMException: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.
场景: 多个挂载到body下的弹窗在进行切换显示时报错
<Modal v-if="visible1">
<Modal1 v-if="visible2">
<Modal2 v-if="visible3">
原因:从报错中可知,Vue在DOM更新时,插入节点insertBefore方法报错。通过调试窗口得知 调用时实际传参为
visible1变为false
,visible2变为true
时, Modal组件已经销毁了,可是这里还是执行了插入方法,由于父节点不包含已经销毁的元素,所以会产生插入失败的报错.
解决方法:
<Modal v-if="visible1">
<Modal1 v-else-if="visible2">
<Modal2 v-else-if="visible3">
CommonJS 规范定义了 exports 导出模块,require引入模块。Node.js 实现了 CommonJS 规范
module.exports = {
name: "aa",
age: 12,
}
// 等同于
exports.name = 'aa'
exports.age = 12
// ❌
exports = {
name: "aa",
age: 12,
}
Node.js 中每个模块都可以看作一个 module 对象,表示当前模块的信息,通过 module.exports 进行导出。
Node.js 使用 require 进行导入模块
const a = require('./a'.js)
a.name
a.age
Node.js 的寻包机制: TODO
在平时开发时,使用 Chrome Devtools 可以快速调试我们的程序,定位问题。
代码断点标识了代码暂停的位置,代码运行时,遇到断点就会停止执行。
如何进入:
断点除了直接断点,也可以添加条件断点,当代码符合某个逻辑才暂停。可以根据具体场景进行使用
当JS操作DOM时,会更改DOM的属性或者结构,Devtools 提供 DOM断点,在DOM变化时进入断点逻辑
如何使用:
选中元素,右键选择 break on, 选择对应场景。
subtree modifications
:后代变化
attribute modifications
: 属性变化
node removal
: 节点删除
我们可以通过主动添加 XHR断点, 根据接口地址添加断点,当浏览器发送该请求时,就能进入当前逻辑内。
除此之外我们也可以通过 Network -> 请求 -> Initiator 中查看代码堆栈进入对应逻辑
在Elements 中选中具体元素,然后找到属性 EventListers ,找到对应事件,进入到代码逻辑中添加断点。
也可以在 Sources 中直接选中对应事件,然后勾选断点。当对应事件触发时,即触发断点
设置断点后就是调试代码,了解代码实际运行时的逻辑。
快捷键:
Watch 中可以监听变量的值,在处理一些循环时,我们可以讲关注的变量添加到 Watch 中方便我们查看变量变化
当前代码执行处所在的作用域,可以查看当前可以访问到的变量值
执行堆栈,可以通过执行堆栈精确定位到代码触发的位置
有些调试场景并不在本地,而是在网路上。当我们想直接改动代码来调试逻辑时,我们可以通过以下步骤实现代码的改动
通过 Devtools 进行Debug 不仅方便我们快速定位问题。熟练掌握能力后,对于常见的前端逆向推导,也十分有益处。
调试时注意函数入口和出口,观察变量变化。通过二分法快速断点,可以快速定位异常位置。
Transition 组件是 Vue 的内置组件,用于组件内部的发生变化时提供切换的动效。
场景:
Transition 组件在动画变化时,提供 className 的变化,也提供JS hook。
v
是 Transition 组件的name, 在使用CSS transition 时,我们可以定义 v-enter,v-leave-to 的状态,来配置动画
.slide-fade-enter-active {
transition: all .3s ease;
}
.slide-fade-leave-active {
transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to
/* .slide-fade-leave-active below version 2.1.8 */ {
transform: translateX(10px);
opacity: 0;
}
在使用 CSS animation 时,可以定义 v-enter-active, v-leave-active 的动画
.bounce-enter-active
{
animation: bounce-in .5s;
}.
bounce-leave-active {
animation: bounce-in .5s reverse;
}
@keyframes bounce-in {
0% { transform: scale(0); }
50% { transform: scale(1.5); }
100% { transform: scale(1); }
}
Transtion 也提供自定义className 的属性
注意点:
TODO
问题: AppStore 无法正常登陆
解决: 设置网络 DNS, 114.114.114.114 , 8.8.8.8
https://www.jianshu.com/p/2d180087e376
使用 xcworkspace
文件打开项目,不要使用 xcodeproj
打开项目
查看报错原因,优先排查是否新增依赖。有则 npm i
然后 cd ios
,pod install
不使用 ruby gem 进行下载,使用 homebrew 进行下载
遭遇 boost 文件无法通过http正常进行下载,可以找到镜像网站下载好对应依赖后,在依赖文件中从 文件中引用
使用adb 进行端口映射 adb reverse tcp:8081 tcp:8081
引入SDK 时注意 SDK 限制的架构,如果SDK 对架构有限制,记得在 ndk 中声明
iOS 端需要先JS端监听事件,再原生 sendEvent, 否则会报错listener not regist
, android 则不会有这个问题。因此处理时,可以将监听前置。
需要设置开发者账号,如果真机预览时 iOS 版本过高无法正常预览,可以将对应的依赖下载好后放到/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport
下载地址: https://github.com/filsv/iOSDeviceSupport
TypeError: null is not an object (evaluating 'RNGestureHandlerModule.flushOperations')
android 更新gradle ,ios重新pod install
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer/
在个人文件夹( (C:\Users\username.gradle 或 ~.gradle))下创建gradle.properties文件,添加
org.gradle.jvmargs=-Xmx1024m -XX:MaxPermSize=256m
到 Build Setting / Architectures / Excluded Architectures
添加 arm64
sduo arch -x86_64 gem install ffi
arch -x86_64 pod install
xcode 以rosetta 模式运行
podfile
post_install do |installer|
react_native_post_install(
installer,
)
# NOTE: Change IPHONEOS_DEPLOYMENT_TARGET to 12.4.
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.4'
end
end
__apply_Xcode_12_5_M1_post_install_workaround(installer)
end
函数柯里化(curry)是指将一个多参数的函数转化为多个单参数的函数。
function curry(fn) {
const judge = (...args) =>
args.length >= fn.length
? fn(...args)
: (...arg) => judge(...args, ...arg)
return judge
}
原理是这样的,JS中可以通过函数的length获取函数的参数个数,curry
函数将fn包裹,返回一个函数。函数内部比较参数的长度,如果大于等于就执行fn,否则就返回通过递归和闭包继续执行
在平时开发时,隐藏元素有几种方式。在不同场景可以根据相关特性选择隐藏的方式。
例如最近遇到元素设置为 display: none 时,swiper初始化异常,原因应该是计算滚动区域的问题。又遇到 overflow 结合 visibility 时在 iOS上渲染异常的问题【渲染穿透,渲染了应该隐藏的元素】。
由于CSS的复杂性和在不同平台的兼容性问题,开发时我们应该了解特性后做出合适的样式。
说了很多额外的,下面步入正题:
display | visibility | opacity | |
---|---|---|---|
占据页面空间 | ❌ | ✅ | ✅ |
是否影响子元素设置该属性 | ✅ | ❌ | ✅ |
触发事件 | ❌ | ❌ | ✅ |
影响遮挡元素事件 | ❌ | ❌ | ✅ |
transition | ❌ | ✅ | ✅ |
回流 reflow | ✅ | ❌ | ❌ |
重绘 repaint | ✅ | ✅ | (不一定)链接 |
上传 header 为 Content-Type: multipart/form-data
, 前端上传时需要使用 FormData 进行上传
const formData = new FormData()
formData.append('file', file)
axios.post(formData)
在文件大小超大时,需要对上传环境有要求,长时间的上传有失败的机率。因此大文件上传一般可以做切片上传,然后后端进行合并。
将文件进行切片,分割成 小的chunk 进行上传, chunk的名称可以用文件的名称加索引,方便后续合并
file.slice(cur, cur + size)
每个切片上传完成后,调用合并接口,通知后端进行合并文件
上传接口接收切片,将切片下载到对应的文件夹。
接受到前端的合并请求后,将文件夹的文件使用流进行合并。完成大文件的上传
断点续传需要建立在 切片上传的基础上, 使用 md5 计算库 在webworker 中计算整个文件的hash , 每个切片以 hash + 索引的方式进行命名
记录已经上传的文件的 hash 和对应索引, 跳过上传
上传前后端返回已经上传过的切片,前端再进行跳过
后端的方式比较好,没有浏览器的限制
上传前生成文件的hash ,验证是否上传过,如果上传过则跳过上传,实现秒传的效果
百度网盘就是类似的机制
上传切片时可以存储对应的请求实例, 使用 AbortController 可以取消对应请求。暂停时,将对应实例全部取消请求即可。
和断点续传类似
有些业务场景可能需要上传几百张图片,如果有请求失败的话,可能对业务有影响。有两种方式进行解决
单个请求失败后,可以进行重试。
static retry(fn: () => MyPromise, times: number, delay: number) {
return new this((resolve, reject) => {
function attemp() {
fn()
.then(resolve)
.catch((err) => {
if (times > 0) {
times--;
setTimeout(() => {
attemp();
}, delay);
} else {
reject(err);
}
});
}
attemp();
});
}
前端通过 JSzip 将图片压缩成压缩包,后端再进行解压。这样只会有一个文件上传请求。
注意: 可能在一些配置较低的电脑上存在内存溢出的情况
postcss
是 css 的 babel, 用来处理css的转换
npm i postcss
postcss.config.js
module.exports = {
plugins: [
require('...')
]
}
插件搜索: https://www.postcss.parts/
yarn add cssnano
功能: 压缩css
yarn add autoprefixer
功能: 自动添加浏览器兼容前缀
需要配置package.json 中的 browserslist,可以根据需求配置比例
"browserslist": {
"cover 99.5%"
}
yarn add postcss-preset-env
功能: 支持新的语法,向下兼容
// postcss.config.js
...
require('postcss-preset-env')({
stage: 0,
})
...
yarn add postcss-pxtorem
功能: 自动转化px为 rem
// postcss.config.js
...
require('postcss-pxtorem')({
rootValue: 16,
unitPrecision: 5,
propList: ['font', 'font-size', 'line-height', 'letter-spacing'],
selectorBlackList: [],
replace: true,
mediaQuery: false,
minPixelValue: 0,
exclude: /node_modules/i
})
...
data 声明的变量 不能以 _
或者 $
开头,Vue 不会将这些变量转化为响应式变量,且无法在实例上访问到该变量
// core/instance/state.ts
...
else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
...
// core/util/lang.ts
export function isReserved(str: string): boolean {
const c = (str + '').charCodeAt(0)
return c === 0x24 || c === 0x5f
}
在 Vue2 中,开发弹窗组件时,如果需要将DOM挂载到body下时,需要我们手动进行处理。
mounted() {
document.body.appendChild(this.$el)
}
在Vue3 中,使用setup语法时是无法使用this的,而使用ref获取dom的方式也比较麻烦。
不过,Vue3提供了 Teleport组件,方便讲组件挂在到指定的dom下。类似于 ReactDOM.createPortal
<Teleport to="body">
...
</Teleport>
开发中我们会用到 setTimeout
,setInterval
作为定时器,在做JS动画或者Canvas游戏时也会用到 requestAnimationFrame
这个 API, 接下来就好好讲一下 JS 中的定时器。
第二个参数作为延时时间,到达时间后会执行回调。定时器时间并不准确,有执行时间的损耗。在定时器线程负责时间,时间到达后会放入 宏任务队列等待执行,setTimeout
只执行一次,setInterval
执行多次
可以用 clearTimeout
,clearInterval
取消回调
requestAnimationFrame
会在 微任务执行后, repaint
之前执行。如果执行过程中产生了新的微任务,则会在当前代码执行后,继续执行微任务.
requestAnimationFrame
的回调不属于 宏任务,也不是微任务。根据浏览器的渲染机制来决定的,当浏览器判断需要更新屏幕时,则会开始重新绘制,触发 requestAnimationFrame
的回调。
可以用 cancelAnimationFrame
取消回调
requestIdleCallback
会在 repaint
之后,有空闲时间时进行执行,如果带有timeout参数,则会在超时后的下一帧强制执行。
HTML 通过 link 加载 CSS, 当 link 标签的 rel 属性为 alternative ,且 title 属性存在时,该样式会加载但不渲染。
当我们控制js将该标签的 disabled 设为 false 时,该 link 标签引用的 CSS 就会进行渲染,可以通过这个特性实现换肤效果。
优点:
参考资料:
由于浏览器同源策略问题,当页面和请求的协议,主机或者端口不同时,则会判定为不同的域。即跨域请求
同源策略
协议,主机,port
后端返回响应头加上 Access-Control-Allow-*
跨域时不同的请求的处理方式是不同的,简单分为两类,简单和需预检请求
简单请求:GET,POST,HEAD 。 此类请求不会发送预检请求
其他请求会发送 OPTIONS 请求,来判定 Access-Control-Allow-*
是否被允许
可以后端创建一个同源的服务,用来做代理,利用服务端不受限制的特性来做接口转发处理
以前很流行,现在很少用,只支持 GET请求
就是利用加载JS不跨域的特性,使用script src发送get请求,然后服务端返回将数据包裹在回调函数中进行返回。当请求返回回调函数时,该函数就会执行,实现跨域请求数据的需求。
原理相同的还有 ImgP
crossorigin="anonymouse"
加上JS返回允许跨域,即可显示详细堆栈WIP
MDN的答案: 渲染页面:浏览器的工作原理
掘金的文章: https://juejin.cn/post/6871947938701475847
curl -o <fileName> <download_url>
持续存储,最大存储5M.
页面浏览器用的临时存储,关闭页面就消失
useId 是 React 18
新增的hooks
, 可以生成独一无二的id。
当我们的组件html 里面包含 id 时,多个组件同时渲染,会表现为多个元素拥有同一个id,导致语法上不合理。虽然我们可以用随机数或者时间戳的方式生成id, 但是在函数组件中每次更新都会更改id,所以官方出了 useId
.
官方的文档也说明了 useId
出现的作用
具体使用:
function Checkbox() {
const id = useId();
return (
<>
<label htmlFor={id}>Do you like React?</label>
<input id={id} type="checkbox" name="react"/>
</>
);
};
这里比较有意思的是,生成的 id 并不是合法的 selector id, 也就是说无法通过 DOM
匹配都该元素,毕竟 React
推荐使用 ref
来操作操作 DOM
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.