9-3
1 Vscode 本地安装插件
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 | set | db.defaults({ posts: [], user: {}, count: 0 }).write() |
删除 | remove | db.unset('user.name').write() |
更新 | update | db.update('count', n => n + 1).write() db.get('posts').insert({ title: 'xxx', content: 'xxxx' }).write() db.set('user.name', 'typicode').write() |
查询 | get | db.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)
两种方式: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的
rowSpacing
和columnSpacing
区别- 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 };