Skip to main content

2-3

1 ThreeJs 初探

问题一:ThreeJs 与 WebGL 和 OpenGL 的关系是什么?

总体来说,openGL 是规范,webGL 是实现者,threeJs 对 webGL 封装了一层

2 Ts 中如何进行忽略 this

// @ts-ignore
fn.apply(this, 123)

3 Iconify

介绍:iconify 是一个开源的图标库,统一的图标框架,超过 100 个图标集,一个库。 超过 100,000 个开源矢量图标。

它有很多个项目,@iconify/react 是其中一个,用于 react 库

使用示例

先安装

yarn add --dev @iconify/react

导入,并使用

import { Icon } from '@iconify/react';

<Icon icon="mdi-light:home" />

mdi-light:home 为图标,可以从这里查询.https://icon-sets.iconify.design/

[1] @iconify/react.https://www.npmjs.com/package/@iconify/react

4 [ts] JSX 元素类型'ReactNode'不是 JSX 元素的构造函数。类型“未定义”不能分配给类型“ ElementClass”。[2605]

解决方案:用 JSX.Element 来代替 ReactNode

interface Props {
children: JSX.Element[] | JSX.Element
}

问题一:那 ReactNode 和 JSX.Element 有什么区别❓

回答:JSX.ElementReactElementReactNode

interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
type: T
props: P
key: Key | null
}

// 可以看出props的默认值为any

// 从TypeScript网站:https:github.com/Microsoft/TypeScript/issues/6471
// 建议的做法是将props类型写为{children ?: any}

来看一下 JSX.Element 定义

namespace JSX {
// ...
interface Element extends React.ReactElement<any, any> {}
// ...
}

// 可以看出JSX.Element继承自ReactElement,且没有扩展,
// 可以认为两者

[1] ReactElement、ReactNode 以及 JSX.Element.https://www.jianshu.com/p/95ce2266450a

5 ReactRouter6 中的 Outlet-嵌套

它从 React Router 库中挑选了一个名为 Outlet 的最佳元素,为特定路由呈现任何匹配的子元素

import { Outlet } from 'react-router-dom'

[1] React Router v6 使用指南.https://segmentfault.com/a/1190000023684163

6 Yup

一个转化和验证数据的 js 库

[1] Yup.https://github.com/jquense/yup

7 InferType-ts 推断类型

type User = InferType<typeof userSchema>
/* {
name: string;
age: number;
email?: string | undefined
website?: string | null | undefined
createdOn: Date
}*/

8 Nestjs/crud

问题一:@nestjs/crud 是什么?怎么用?

[1] nestjsx/crud.description.https://github.com/nestjsx/crud/wiki/Controllers#description

9 在本机创建多个 github 用户

ssh-keygen -t rsa -C "[email protected]"
ssh-keygen -t rsa -C "[email protected]"
git config --global user.name "yhonismx"
git config --global user.email "[email protected]"

git config --global user.name "ginlink"
git config --global user.email "[email protected]"

问题一:如何在本机创建多个 github 用户?

问题二:如何使用指定账户提交代码(交互)?

9.1 创建多个 github 账户的私钥

这里以创建 master0master1 两个账号为例,其邮箱分别为

# 通过邮箱,在本机创建master0的秘钥
ssh-keygen -t rsa -b 4096 -C "[email protected]"

# 会有如下提示,请将新的秘钥保存到/Users/you/.ssh/id_rsa_master0
# Enter a file in which to save the key (/Users/you/.ssh/id_rsa):

# 之后一路回车
# 同理,通过邮箱,在本机创建master1的秘钥
ssh-keygen -t rsa -b 4096 -C "[email protected]"

# 保存到/Users/you/.ssh/id_rsa_master1

9.2 关联本机 ssh 和 github 服务器

拷贝 master0 公钥,注意是公钥

cat ~/.ssh/id_rsa_master0.pub

前往github.com登录master0账号,在Settings -> SSH and GPG keys里,点击New SSH key

填入拷贝的公钥

同理,拷贝 master1 公钥,之后登录 github 填入公钥

cat ~/.ssh/id_rsa_master1.pub

这样,本机就可以通过 ssh 连上 github 服务器了

9.3 测试连通性

1.测试连通性之前,先配置 config

vim ~/.ssh/config

2.填入,并保存

# master0
Host master0 # 通过Host来区别
HostName github.com # git服务器地址
User master0 # 用户
IdentityFile ~/.ssh/id_rsa_master0 # 私钥路径

# master0
Host master1
HostName github.com
User master1
IdentityFile ~/.ssh/id_rsa_master1

3.测试连通性

ssh -T git@master0
ssh -T git@master1

如果出现,则连通

Hi master0! You've successfully authenticated, but GitHub does not
provide shell access.

9.4 测试切换账号提交代码

通过 9.3 我们的两个账号都已经可以和 github 通讯了,那么接下来就是指定账号进行提交代码了。

# 使用master0
git config --local user.name "master0"
git config --local user.email "[email protected]"

# 使用master1
git config --local user.name "master1"
git config --local user.email "[email protected]"

以上是仓库级别的切换,如果想了解 local、global、system 的区别,请查看[1]

9.5 注意

  • 先切换账号再 commit 代码,否则是上一个用户的提交记录

[1] git config 配置.https://www.cnblogs.com/fireporsche/p/9359130.html

[2] 管理切换多个 Git 账号.https://windomz.github.io/2017/03/01/%E5%A4%9Agit%E8%B4%A6%E5%8F%B7%E5%88%87%E6%8D%A2/

10 红胡子

https://www.kuaiyunyy.com/vodplay/285178-1-1.html

11 React 错误边界 ErrorBoundary

问题一:ErrorBoundary 是什么?解决了什么问题?

问题二:ErrorBoundary 不能处理什么?那这些问题如何解决?

12 如何整合@nestjs/crud 和 jwt 认证?

项目:yue-code-api

13 Js 中的连等赋值

var a = { n: 1 }
var b = a
a.x = a = { n: 2 }
alert(a.x) // --> undefined
alert(b.x) // --> {n: 2}

问题一:为什么 a.x 为 undefined?

因为 a.x = a = {n: 2}; 这一句 先确定引用 ,再进行右结合赋值

所以 a.x 中的 a 还是原来的 a,而非新生成的 a

14 Js 中数组排序 b-a 为降序

15 Js 中 call(null | undefined) 表示 window 对象

16 Js 中 IIEF 中 this 指向 window

17 暂时性死区

存在于 let 和 const 声明的变量,表示变量无法先被使用再声明

function main() {
console.log('[]:', a) // 报引用错误

let a = 123
}
// ================================
function main() {
console.log('[]:', a) // undefined

var a = 123
}

18 Promise.all 如何不被一个错误打断?

async function main() {
const reses = await Promise.all(
[
Promise.reject({ code: 500, msg: "服务异常" }),
Promise.resolve({ code: 200, list: [] }),
Promise.resolve({ code: 200, list: [] })
].map(p => p.catch(e => e))
)
}

=>> res
[
{ code: 500, msg: "服务异常" },
{ code: 200, list: [] },
{ code: 200, list: [] }
]

注意

但这样写会有一个弊端,无法通过 catch 获取错误,此时所有请求都会成功,只能通过一些特征去判断错误

[1] Promise.all 哪怕一个请求失败了也能得到其余正确的请求结果的解决方案.https://blog.csdn.net/zwwgoodwill/article/details/105050693?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ELandingCtr%7EHighlightScore-1.queryctrv2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ELandingCtr%7EHighlightScore-1.queryctrv2&utm_relevant_index=2

19 react-helmet-async

介绍:是 react-helmet 的异步版本,用于管理页面的 head

作用:安全

const Page = forwardRef(({ children, title = '', ...other }, ref) => (
<Box ref={ref} {...other}>
<Helmet>
<title>{title}</title>
</Helmet>
{children}
</Box>
))

export default Page

20 change-case

介绍:转换一个字符串在 camelCase, PascalCase, Capital Case, snake_case, param-case, CONSTANT_CASE 和其他的库

import {
camelCase,
capitalCase,
constantCase,
dotCase,
headerCase,
noCase,
paramCase,
pascalCase,
pathCase,
sentenceCase,
snakeCase,
} from 'change-case'

[1] change-case.https://www.npmjs.com/package/change-case

21 列表排序算法

function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
if (b[orderBy] < a[orderBy]) {
return -1
}
if (b[orderBy] > a[orderBy]) {
return 1
}
return 0
}

type Order = 'asc' | 'desc'

function getComparator<Key extends keyof any>(
order: Order,
orderBy: Key
): (a: { [key in Key]: number | string }, b: { [key in Key]: number | string }) => number {
return order === 'desc'
? (a, b) => descendingComparator(a, b, orderBy)
: (a, b) => -descendingComparator(a, b, orderBy)
}

使用示例

const order = 'asc' // 'asc' | 'desc'
const orderBy = 'age'
const rows = [
{ name: 'John0', age: 18 },
{ name: 'John1', age: 20 },
{ name: 'John2', age: 10 },
]

rows.sort(getComparator(order, orderBy))

22 Eva Icons

[1] Eva Icons.https://akveo.github.io/eva-icons/#/

23 加入币种到钱包的逻辑

import { getTokenLogoURL } from './../components/CurrencyLogo/index'
import { Currency, Token } from 'plugins/@uniswap/sdk-core'
import { useCallback, useState } from 'react'
import { useActiveWeb3React } from 'hooks/web3'

export default function useAddTokenToMetamask(currencyToAdd: Currency | undefined): {
addToken: () => void
success: boolean | undefined
} {
const { library } = useActiveWeb3React()

const token: Token | undefined = currencyToAdd?.wrapped

const [success, setSuccess] = useState<boolean | undefined>()

const addToken = useCallback(() => {
if (library && library.provider.isMetaMask && library.provider.request && token) {
library.provider
.request({
method: 'wallet_watchAsset',
params: {
//@ts-ignore // need this for incorrect ethers provider type
type: 'ERC20',
options: {
address: token.address,
symbol: token.symbol,
decimals: token.decimals,
image: getTokenLogoURL(token.address),
},
},
})
.then((success) => {
setSuccess(success)
})
.catch((error) => {
setSuccess(false)
})
} else {
setSuccess(false)
}
}, [library, token])

return { addToken, success }
}

24 点击外部取消弹窗

import { RefObject, useEffect, useRef } from 'react'

export function useOnClickOutside<T extends HTMLElement>(
node: RefObject<T | undefined>,
handler: undefined | (() => void)
) {
const handlerRef = useRef<undefined | (() => void)>(handler)
useEffect(() => {
handlerRef.current = handler
}, [handler])

useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (node.current?.contains(e.target as Node) ?? false) {
return
}
if (handlerRef.current) handlerRef.current()
}

document.addEventListener('mousedown', handleClickOutside)

return () => {
document.removeEventListener('mousedown', handleClickOutside)
}
}, [node])
}

注意

  • 同一个页面不可以出现相同的 node,否则无法正常工作,特别是

25 自动切换到响应以太坊网络

try {
await ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: '0xf00' }],
})
} catch (switchError) {
// This error code indicates that the chain has not been added to MetaMask.
if (switchError.code === 4902) {
try {
await ethereum.request({
method: 'wallet_addEthereumChain',
params: [
{
chainId: '0xf00',
chainName: '...',
rpcUrls: ['https://...'] /* ... */,
},
],
})
} catch (addError) {
// handle "add" error
}
}
// handle other "switch" errors
}

[1] wallet_switchEthereumChain.https://docs.metamask.io/guide/rpc-api.html#unrestricted-methods

26 如何使用 Environment secrets?

一个完整的示例,主要 environment 起作用

name: build_and_deploy_web_to_110.99
on: workflow_dispatch

jobs:
build:
runs-on: ubuntu-latest
environment: deployEnv
env:
DOCKER_ACCESS_NAME: ${{ secrets.DOCKER_ACCESS_NAME }}
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
HOST_IP_99: ${{ secrets.HOST_IP_99 }}
HOST_ADMIN_99: ${{ secrets.HOST_ADMIN_99 }}

strategy:
matrix:
node-version: [14.x]

steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: restore yarn
uses: actions/cache@v2
with:
path: |
node_modules
*/*/node_modules
key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: build
run: ./sh/build_web_to_110.99.sh
- name: deploy
run: ./sh/deploy_web_to_110.99.sh

注意

  • 要加上 environment 属性,否则认为 secrets 为项目上下文
  • 要在 jobs 属性下加入,否则提示 environment 错误

27 在 Nginx 中配置二级域名

该内容和 [27 用certbot管理https证书] 连用

共需要一个步骤,如下:

步骤一:修改 nginx 配置文件

我本机 nginx 配置路径为: /etc/nginx/conf.d/default.conf

详细解释,请参阅:[1]

server {
listen 80;
server_name _;
return 301 https://$host$request_uri;
}

server {
listen 443 ssl;
server_name gincool.com;
root /usr/share/nginx/html;
index index.html index.htm;

ssl_certificate /etc/letsencrypt/live/gincool.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/gincool.com/privkey.pem; # managed by Certbot
}

server {
listen 443 ssl;
server_name api.gincool.com;

location / {
proxy_pass http://localhost:3050/;
}

ssl_certificate /etc/letsencrypt/live/gincool.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/gincool.com/privkey.pem; # managed by Certbot
}

附 1:nginx 重启命令

nginx -s reload

[1] 在 Nginx 中配置二级域名.https://mincong.io/cn/nginx-subdomains/

27 用 certbot 管理 https 证书

步骤一:访问 cerbot 网站

https://certbot.eff.org/instructions?ws=nginx&os=ubuntufocal

选择自己的软件和操作系统

步骤二:根据网页下面的步骤进行操作

image-20220218095959826

附 1:安装 snapd

# 安装snap
sudo apt-get install snapd

# 安装snapcraft
sudo apt-get install snapcraft

一般到第 7 步就完成了

最终 certbot 修改 default.conf 的文件内容为:

server {
listen 443 ssl;
server_name gincool.com;
root /usr/share/nginx/html;
index index.html index.htm;

# certbot加入了这些内容
ssl_certificate /etc/letsencrypt/live/gincool.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/gincool.com/privkey.pem; # managed by Certbot
}

server {
listen 443 ssl;
server_name api.gincool.com;

location / {
proxy_pass http://localhost:3050/;
}

ssl_certificate /etc/letsencrypt/live/gincool.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/gincool.com/privkey.pem; # managed by Certbot
}

附 2:更新证书

sudo certbot --nginx

28 以太坊网络中如何预估 gas 费呢?

export interface FeeData {
maxFeePerGas: null | BigNumber
maxPriorityFeePerGas: null | BigNumber
gasPrice: null | BigNumber
}

问题一:以太坊网络中有几种费用?

在 EIP-1559 之前,只有 gasPrice

在 EIP-1559 之后,出现了一些新名词

  • baseFeePerGas:基本费用,由每个区块头生成的每 gas 的基本费用

  • maxPriorityFeePerGas:小费,由用户设置

  • maxFeePerGas:用户愿意为交易支付的最大 Gas 费用,由用户设置

    maxFeePerGas = baseFeePerGas + maxPriorityFeePerGas

所以 gasPrice 和 maxFeePerGas 不能同时使用,否则会报错

问题二:为什么有 EIP-1559?

为了消除链上 Gas 费较低的交易通常会长时间处于未决状态,EIP-1559 引入了一个更复杂、更公平的 gas 费用系统

问题三:那一条链既支持 EIP-1559 又支持传统非 EIP-1559 的交易,那该通过哪种方式发送交易呢?

建议通过 EIP-1559,虽然该方式手续费可能会高一些,但能够确保交易快速被执行,特别是在拥堵的链上。

如果在不拥堵的链上(测试网),那么用哪种都可以,因为手续费再低,也会有矿工帮忙