Skip to main content

9-3

1 Vscode 本地安装插件

打包源码为vsix

svg-viewer

2 Mui 组件设置默认值

[1][React Material UI v5 styled with defaultProps](https://stackoverflow.com/questions/71556007/react-material-ui-v5-styled-with-defaultprops)

首先可以通过 组件.defaultProps 修改默认值

其次,修改默认值时要把原有值给带上

Badge2.defaultProps = {
...Badge2.defaultProps,
variant: 'standard'
}

3 封装 Mui 库

TODO

需求:

  • 设置好主题
  • 覆盖常见组件,例如:布局的Row、Column,按钮,菜单等

4 LowDB

lowdb是用JSON为基本存储结构基于lodash开发的,有lodash的加持

lowdb操作

分为增删改查

操作关键示例
增加defaults | setdb.defaults({ posts: [], user: {}, count: 0 }).write()
删除removedb.unset('user.name').write()
更新updatedb.update('count', n => n + 1).write()
db.get('posts').insert({ title: 'xxx', content: 'xxxx' }).write()
db.set('user.name', 'typicode').write()
查询getdb.get('posts').find({ id: 1 }).value()

5 Figma

[1][Figma 中文社区](https://www.figma.cool/)

[2][传智教育Figma从零到精通视频教程,全网最完整的Figma基础操](https://www.bilibili.com/video/BV1qQ4y167Vd)

6 Recoil

[1][官方中文文档](https://recoiljs.org/zh-hans/docs/introduction/motivation)

[2][recoil-nexus](https://www.npmjs.com/package/recoil-nexus)

Recoil 定义了一个有向图 (directed graph),正交同时又天然连结于你的 React 树上。状态的变化从该图的顶点(我们称之为 atom)开始,流经纯函数 (我们称之为 selector) 再传入组件

经历

7 Mui-dynamic-form

[1][react-dynamic-material-form](https://github.com/mnikn/react-dynamic-material-form)

[2][npm搜索结果](https://www.npmjs.com/search?q=mui%20dynamic)

Mui 动态表单

8 React-router ScrollTop

[1][How to Scroll to Top on Route Change With React Router Dom v6](https://www.matthewhoelter.com/2022/04/02/how-to-scroll-to-top-on-route-change-with-react-router-dom-v6.html)

9 节流更改状态

import throttle from 'lodash/throttle';
const setValueListRef = useRef(throttle((newValue) => setActualValueList(newValue), 1000));
const setValueList = setValueListRef.current;

10 scrollbar(导航栏)

https://stackoverflow.com/questions/59485741/how-to-display-scrollbar-on-hover

11 Mui 如何更改下拉框的样式

// 通过 MenuProps
<Select
fullWidth
value={age}
onChange={handleChange}
MenuProps={{
PaperProps: {
sx: {
bgcolor: 'pink',
'& .MuiMenuItem-root': {
padding: 2,
},
},
},
}}
>

[1][Style the dropdown element of MUI Select](https://stackoverflow.com/questions/50353676/style-the-dropdown-element-of-mui-select)

12 监听账户和链变化

应用:用户断连,强制登出

import { useEffect } from 'react';
import { useWeb3React } from '@web3-react/core';
import { useLoginState } from '@/store/login';

export function useAccountChangeListen() {
// 当用户断连,logout
const { provider } = useWeb3React();
const { logout } = useLoginState();

useEffect(() => {
const { ethereum } = window;

if (!provider || !ethereum) {
return;
}

const handleChainChanged = () => {
logout();
};
const handleAccountsChanged = () => {
logout();
};

ethereum.on('chainChanged', handleChainChanged);
ethereum.on('accountsChanged', handleAccountsChanged);

return () => {
if (ethereum.removeListener) {
ethereum.removeListener('chainChanged', handleChainChanged);
ethereum.removeListener('accountsChanged', handleAccountsChanged);
}
};
}, [logout, provider]);
}

[1][Listening to the MetaMask account network changes in React.js app](https://stackoverflow.com/questions/71320938/listening-to-the-metamask-account-network-changes-in-react-js-app)

13 Suspend & ErrorBoundary & Recoil

这三者结合,非常得丝滑

看一个示例:

<ErrorBoundary key={userState.account} fallbackRender={() => <UnknownErrorFallback />}>
<Suspense fallback={<Loading />}>
<HomeHistory />
</Suspense>
</ErrorBoundary>

关键:1.ErrorBoundary 的key 2.HomeHistory中用 Recoil 进行数据展示

  • ErrorBoundary 的key的作用是重置,随着key变化,会重新触发一次悬念

14 设置备用图片

background设置多背景图片

background-image:url("images/logo.png"),url("images/errorLogo.png");

[1][图片加载失败如何用默认图片代替](https://juejin.cn/post/6844903833294880781)

15 Mac创建跨域Chrome快捷启动方式

[1][Mac创建跨域Chrome快捷启动方式](https://juejin.cn/post/6844903929520586766)

16 前端上传文件Demo

<IconButton component="label">
<input
ref={inputFileRef}
hidden
accept="image/*"
id="contained-button-file"
type="file"
onChange={handleOnChange}
/>
<Avatar
alt="Avatar"
src={image ?? profile?.avatar}
sx={{ width: '72px', height: '72px' }}
/>
</IconButton>
const [file, setFile] = useState<File>();

const handleOnChange = useCallback((event: any) => {
const newImage: File | undefined = event.target?.files?.[0];

// 储存File实体
setFile(newImage);
}, []);

// 本地图片路径,可以直接给img标签
const imageUrl = URL.createObjectURL(file)

<img src={imageUrl} />
// 请求
await userUploadAvatar(url, file);

[1][Upload button 上传按钮](https://mui.com/zh/material-ui/react-button/#upload-button)

[2][AvatarUpload.js](https://gist.github.com/Pacheco95/aa5c28b7a61dacba5b8f55f84d1fa591)

17 SocketIo封装

import {io} from "socket.io-client";

const ENDPOINT = 'ws://localhost:5000/';

export default class SocketService {
private socket: any = {};

constructor(private emisor_id?: string) {
this.socket = io(ENDPOINT);
}

public send = (message: string) => {
this.socket.emit('postMessage', message)
}

// disconnect - used when unmounting
public disconnect (): void {
this.socket.disconnect();
}
}
useEffect(() => {
const socketService = new SocketService();

console.log('mount it!');

return function cleanup() {
socketService.disconnect();
}
}, []);

[1][Socket.io disconnect on React client is not triggered on page refresh](https://stackoverflow.com/questions/64343078/socket-io-disconnect-on-react-client-is-not-triggered-on-page-refresh)

18 React-router-dom 的 matchPath方法

const match: boolean = matchPath("/users/123", {
path: "/users/:id",
exact: true,
strict: false
});

[1][React-router v6. How is matchPath supposed to work now](https://stackoverflow.com/questions/70259883/react-router-v6-how-is-matchpath-supposed-to-work-now)

[2] https://v5.reactrouter.com/web/api/matchPath/props

19 清除 Mui button 的点击样式

<Button disableRipple>
Text
</Button>

[1][How do i change/customize style when MUI button is clicked in reactjs?](https://stackoverflow.com/questions/73045225/how-do-i-change-customize-style-when-mui-button-is-clicked-in-reactjs)

20 Input框内加图标

[1][Font Awesome icon inside text input element](https://stackoverflow.com/questions/19285640/font-awesome-icon-inside-text-input-element)

image-20220914083359406

两种方式:1.绝对定位 2.伪元素 3.当背景

.input {
position: 'relative';
}
.input::after {
position: 'absolute';
content: '💎';
right: 0;
}

21 计算两时间的差值-小时分钟秒

[1][Javascript return number of days,hours,minutes,seconds between two dates](https://stackoverflow.com/questions/13903897/javascript-return-number-of-days-hours-minutes-seconds-between-two-dates)

import dayjs from 'dayjs';

function computeCountdownProp(now: number | undefined, startTime: number | undefined) {
const nowD = dayjs(now);
const targetD = dayjs(startTime);

if (!nowD || !targetD) {
return {};
}

let delta = targetD.diff(nowD, 'seconds');

const hours = Math.floor(delta / 3600);
delta -= hours * 3600; // 把小时减掉,剩余分+秒

const minutes = Math.floor(delta / 60);
delta -= minutes * 60; // 把分钟减掉,剩余秒

const seconds = delta;

return {
hours: hours < 0 ? 0 : hours,
minutes: minutes < 0 ? 0 : minutes,
seconds: seconds < 0 ? 0 : seconds,
};
}

export { computeCountdownProp };

❌ 错误示例

这样会计算重复

const hours = targetD.diff(nowD, 'hours');
const minutes = targetD.diff(nowD, 'minutes');
const seconds = targetD.diff(nowD, 'seconds');

return {
hours: hours < 0 ? 0 : hours,
minutes: minutes < 0 ? 0 : minutes,
seconds: seconds < 0 ? 0 : seconds,
};

22 React与防抖

[1][How to use throttle or debounce with React Hook?](https://stackoverflow.com/questions/54666401/how-to-use-throttle-or-debounce-with-react-hook)

✅ 常规做法

import { throttle } from 'lodash'

const App = () => {
const [value, setValue] = useState(0)
const throttled = useRef(throttle((newValue) => console.log(newValue), 1000))

useEffect(() => throttled.current(value), [value])

return (
<button onClick={() => setValue(value + 1)}>{value}</button>
)
}

23 React中如何标注事件类型

1.键盘事件

[1][Fix TypeScript error: Property ‘keyCode’ does not exist on type ‘FormEvent’.](https://www.garysieling.com/blog/fix-typescript-error-property-keycode-not-exist-type-formevent/)

onKeyUp(event: React.KeyboardEvent<object>) {
event.preventDefault();

if (event.keyCode === 13) {
}
}

2.input变化事件

[1][Typescript input onchange event.target.value](https://stackoverflow.com/questions/40676343/typescript-input-onchange-event-target-value)

onChange = (e: React.ChangeEvent<HTMLInputElement>)=> {
const newValue = e.target.value;
}

24 聊天框自动滚动到底部

[1][Automatically scroll down chat div](https://stackoverflow.com/questions/25505778/automatically-scroll-down-chat-div)

if (firstTime) {
container.scrollTop = container.scrollHeight;
firstTime = false;
} else if (container.scrollTop + container.clientHeight === container.scrollHeight) {
container.scrollTop = container.scrollHeight;
}

25 如何关闭即销毁Mui模态框

[1][How to handle "outside" click on Dialog (Modal)?](https://stackoverflow.com/questions/57329278/how-to-handle-outside-click-on-dialog-modal)

  • 如何关闭即销毁Mui模态框?

  • 如何关闭点击外部关闭模态框的特性?

    const handleClose = (event, reason) => {
    if (reason && reason == "backdropClick")
    return;
    myCloseModal();
    }

    <Dialog onClose={handleClose} />
  • Mui stack的 rowSpacingcolumnSpacing 区别

    • rowSpacing代表行间距
    • columnSpacing列间距

TODO

26 在React中使用socketio.client的最佳实践

[1][Cannot close the socket when logout](https://stackoverflow.com/questions/61906608/cannot-close-the-socket-when-logout)

TODO

27 如何获取一个交易的确认块

思想:通过getTransactionReceipt轮询查交易的收据,收据中带有confirmations字段

import { useCallback, useEffect, useMemo, useState } from 'react';

import { TransactionReceipt } from '@ethersproject/abstract-provider';
import { useWeb3React } from '@web3-react/core';
import { ethers } from 'ethers';

import { SupportedChainId } from '@/constants/chains';
import { RetryOptions, RetryableError, retry } from '@/utils/retry';

const RETRY_OPTIONS_BY_CHAIN_ID: { [chainId: number]: RetryOptions } = {
[SupportedChainId.POLYGON]: { n: 50, minWait: 1000, maxWait: 2500 },
[SupportedChainId.POLYGON_MUMBAI]: { n: 50, minWait: 1000, maxWait: 2500 },
};
const DEFAULT_RETRY_OPTIONS: RetryOptions = { n: 1, minWait: 0, maxWait: 0 };

function useBlockConfirm(tx: ethers.ContractTransaction | undefined) {
const { chainId, provider } = useWeb3React();
const [blockConfirmed, setBlockConfirmed] = useState<number>();

const { hash } = useMemo(() => tx, [tx]) || {};

const onReceipt = useCallback(
(tx: { chainId: number; hash: string; receipt: TransactionReceipt }) => {
setBlockConfirmed(tx.receipt.confirmations);

console.log('[tx]:', tx);
},
[],
);

const getReceipt = useCallback(
(hash: string) => {
if (!provider || !chainId) throw new Error('No provider or chainId');
const retryOptions = RETRY_OPTIONS_BY_CHAIN_ID[chainId] ?? DEFAULT_RETRY_OPTIONS;
return retry(
() =>
provider.getTransactionReceipt(hash).then((receipt) => {
if (receipt === null) {
console.debug(`Retrying tranasaction receipt for ${hash}`);
throw new RetryableError();
}

if (receipt.confirmations < 3) {
onReceipt({ chainId, hash, receipt });

console.debug(`Retrying tranasaction receipt for ${hash}`);
throw new RetryableError();
}
return receipt;
}),
retryOptions,
);
},
[chainId, onReceipt, provider],
);

useEffect(() => {
if (!chainId || !hash) {
return;
}

const { promise, cancel } = getReceipt(hash);
promise
.then((receipt) => {
if (receipt) {
onReceipt({ chainId, hash, receipt });
} else {
console.log('[no receipt]:');
}
})
.catch((error) => {
if (!error.isCancelledError) {
console.warn(`Failed to get transaction receipt for ${hash}`, error);
}
});

return () => {
cancel();
};
}, [chainId, getReceipt, hash, onReceipt]);

useEffect(() => {
if (!tx) {
setBlockConfirmed(undefined);
}
}, [tx]);

return useMemo(
() => ({
blockConfirmed,
}),
[blockConfirmed],
);
}

export { useBlockConfirm };