React使用记录
使用vite搭建react+typescript工程
pnpm create vite
选择React、TypeScript(SWC熟练的话可以选择TypeScript+SWC)- 生成的
tsconfig.json
和tsconfig.node.json
配置文件如下
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
其中
"moduleResolution": "bundler"
会导致使用<div>
等标签时ts报错:JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
,改为"moduleResolution": "node"
。(注意:tsconfig.node.json
中的"moduleResolution": "bundler"
也需要改为"moduleResolution": "node"
)由于
allowImportingTsExtensions
配置项需要"moduleResolution": "bundler"
并且需要配置noEmit
或emitDeclarationOnly
:Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set.
,所以去掉"allowImportingTsExtensions": true,
import React from 'react';import ReactDOM from 'react-dom/client';
这种import引入会ts报错:This module is declared with 'export =', and can only be used with a default import when using the 'allowSyntheticDefaultImports' flag.
,在tsconfig.json
中设置"allowSyntheticDefaultImports": true
来解决import App from './App.tsx'
ts报错:An import path cannot end with a '.tsx' extension. Consider importing './App.js' instead.
,改为import App from './App'
来解决
- 设置打包后的目录
build: {
outDir: 'afterSales',
},
- vite本身支持scss
pnpm add -D sass
后就可以使用scss - 引入react router
pnpm add react-router-dom
- 设置react页面的title可以用useEffect
useEffect(() => {
document.title = '页面title'
})
- vite配置项目别名,vite配置文件 和 ts配置文件都需要设置
- 使用到
path
、__dirname
、__filename
等,需要pnpm add -D @types/node
import path from "path";
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
"@pages": path.resolve(__dirname, "src/pages"),
},
},
"compilerOptions": {
"baseUrl": "./",
"paths":{
"@/*": ["src/*"],
"@pages/*": ["src/pages/*"],
},
}
- 如果项目在生产环境想共用已有域名,则可以配置nginx,并相应设置vite打包的
base
及react router的basename
比如已有域名aaa.com,项目在生产环境想使用aaa.com/b
- nginx配置
server {
listen 80;
server_name aaa.com;
root /export/App/dist;
index index.html;
location / {
root /export/App/dist;
if ($request_filename ~* .*\.(?:htm|html)$)
{
add_header Cache-Control "no-store";
}
add_header Cache-Control "no-cache";
}
location /b/ {
root /export/App/dist/;
try_files $uri $uri/ /b/index.html;
if ($request_filename ~* .*\.(?:htm|html)$)
{
add_header Cache-Control "no-store";
}
add_header Cache-Control "no-cache";
}
}
- vite配置打包的
base
base: '/b/',
- react router 配置
basename
const router = createBrowserRouter([
{
path: "/",
element: <Manage />,
},
], {
basename: '/b',
});
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>,
)
- vite环境变量 环境变量和模式
- 可以新建配置文件
.env.staging
、.env.production
# staging(预发布)模式
VITE_NODE_ENV=prepare
# production(生产)模式
VITE_NODE_ENV=production
- 创建相应的script命令
"scripts": {
"dev": "vite",
"pre": "vite --mode staging",
"prod": "vite --mode production",
"build": "tsc && vite build",
"buildPre": "tsc && vite build --mode staging",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
- 文件中使用:
import.meta.env.VITE_NODE_ENV
- vite开发配置开启https 如下配置,浏览器访问时提示“使用了不受支持的协议”,这是因为https协议需要一个合法可用的证书。
server: {
host: 'test.com',
port: 443,
strictPort: true,
https: true,
},
本地开发时,可以添加 @vitejs/plugin-basic-ssl
到项目插件中,它会自动创建和缓存一个自签名的证书。
pnpm add -D @vitejs/plugin-basic-ssl
import basicSsl from '@vitejs/plugin-basic-ssl'
export default {
plugins: [
basicSsl()
],
server: {
host: 'test.com',
port: 443,
strictPort: true,
https: true, // 不设置也行,@vitejs/plugin-basic-ssl 插件会强制开启https
},
}
在React项目中使用TypeScript
使用useState声明state时声明类型
How to use React useState hook with Typescript
React.FC
React.FunctionComponent
或 React.FC
显示标明返回的是一个函数组件
MutableRefObject
MutableRefObject
是 useRef
钩子返回的对象类型,用于在 React 组件中创建和管理可变的引用对象。它的 current
属性可以存储任何可变的值,并在组件的整个生命周期内保持不变。useRef
常用于访问和操作 DOM 元素,但也可以用于存储其他可变值。通过使用 useRef
和 MutableRefObject
,你可以在 React 组件中更方便地管理和操作可变的引用对象。
在 TypeScript 中,MutableRefObject
的类型定义如下:
interface MutableRefObject<T> {
current: T;
}
useRef
常用于访问和操作 DOM 元素。例如:
import React, { useRef, useEffect } from 'react';
const MyComponent = () => {
// 创建一个 MutableRefObject,初始值为 null
const divRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (divRef.current) {
// 访问和操作 DOM 元素
divRef.current.style.backgroundColor = 'lightblue';
}
}, []);
return (
<div ref={divRef}>
This div has a light blue background.
</div>
);
};
export default MyComponent;
除了处理 DOM 元素,useRef
还可以用于存储其他可变值,例如计数器、定时器 ID 等:
import React, { useRef, useEffect } from 'react';
const TimerComponent = () => {
const timerId = useRef<number | null>(null);
useEffect(() => {
timerId.current = window.setTimeout(() => {
console.log('Timer expired');
}, 1000);
return () => {
if (timerId.current !== null) {
clearTimeout(timerId.current);
}
};
}, []);
return (
<div>
<p>Check the console for the timer message.</p>
</div>
);
};
export default TimerComponent;
key是数组index或者key重复时删除数组渲染会有问题
可以使用随机数
setDetailList([{id: Math.random()}]);
react项目keep-alive
基本使用
- 安装:
pnpm add react-activation
- 在不会被销毁的位置(一般为应用入口处)放置
<AliveScope>
外层,用<KeepAlive>
包裹需要保持状态的组件
tip
- 建议使用 babel 配置:在 babel 配置文件
.babelrc
中增加react-activation/babel
插件babel.config.js{
"plugins": [
"react-activation/babel"
]
} - 如果不使用 babel 配置,建议给每个
<KeepAlive>
声明全局唯一且不变的 cacheKey 属性,以确保缓存的稳定性 - 与 react-router 或 react-redux 配合使用时,建议将
<AliveScope>
放置在<Router>
或<Provider>
内部
import KeepAlive, { AliveScope } from 'react-activation';
ReactDOM.createRoot(document.getElementById('root')!).render(
<BrowserRouter basename='/aa'>
<AliveScope>
<Routes>
<Route path='/' element={<KeepAlive cacheKey="MANAGE" name="MANAGE"><Manage /></KeepAlive>}></Route>
<Route path='/detail' element={<Detail />}></Route>
<Route path='/update' element={<Edit />}></Route>
<Route path='/create' element={<Edit />}></Route>
<Route path='/done' element={<Done />}></Route>
</Routes>
</AliveScope>
</BrowserRouter>,
)
注意事项
- 跳回被
<KeepAlive>
包裹保持状态的组件时,不会执行该组件的useEffect(() => {}, []);
,还会执行useEffect(() => {});
。要想还执行该组件的useEffect(() => {}, []);
,则需要跳回该组件前,手动刷新缓存:
import { useAliveController } from 'react-activation';
export default function Demo() {
const { refreshScope } = useAliveController();
function handleLink() {
refreshScope('MANAGE');
navigate(`//?aa=${searchParams.get('aa')}`);
}
}
- 被缓存的组件会缓存路由param,如下的
aaValue
:
import { useSearchParams } from 'react-router-dom';
const [searchParams] = useSearchParams();
const aaValue = searchParams.get('aa');
JSX中使用&&
的注意点
Stop using && in React Conditional Rendering
遇到的报错
1. A component is changing a controlled input to be uncontrolled.
export default function AddressBook() {
const [detailAddress, setDetailAddress] = useState<string | undefined>(undefined);
return <>
<input type="text" value={detailAddress} onChange={(e) => setDetailAddress(e.target.value)}></input>
</>
}
如上写法会报错:Warning: A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components
报错原因:首次渲染出的input是这样的<input type="text" value=undefined/>
,因此react就判定这是一个非受控组件。而一旦开始输入,触发了onChange
回调,就会给detailAddress
赋值,这时input就成为了受控的input。
解决方案:定义时初始值赋值为空字符串 const [detailAddress, setDetailAddress] = useState<string>('');
或者 给input的value属性一个初始值
export default function AddressBook() {
const [detailAddress, setDetailAddress] = useState<string | undefined>(undefined);
return <>
<input type="text" value={detailAddress ?? ''} onChange={(e) => setDetailAddress(e.target.value)}></input>
</>
}