Skip to main content

React使用记录

使用vite搭建react+typescript工程

  1. pnpm create vite 选择React、TypeScript(SWC熟练的话可以选择TypeScript+SWC)
  2. 生成的tsconfig.jsontsconfig.node.json配置文件如下
tsconfig.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" }]
}
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"并且需要配置noEmitemitDeclarationOnlyRequires '--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'来解决

  1. 设置打包后的目录
vite.config.ts
  build: {
outDir: 'afterSales',
},
  1. vite本身支持scss pnpm add -D sass后就可以使用scss
  2. 引入react router pnpm add react-router-dom
  3. 设置react页面的title可以用useEffect
useEffect(() => {
document.title = '页面title'
})
  1. vite配置项目别名,vite配置文件 和 ts配置文件都需要设置

    vite中alias别名配置

  • 使用到path__dirname__filename等,需要pnpm add -D @types/node
vite.config.ts
import path from "path";

resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
"@pages": path.resolve(__dirname, "src/pages"),
},
},
tsconfig.json
"compilerOptions": {
"baseUrl": "./",
"paths":{
"@/*": ["src/*"],
"@pages/*": ["src/pages/*"],
},
}
  1. 如果项目在生产环境想共用已有域名,则可以配置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
vite.config.ts
base: '/b/',
  • react router 配置basename
src/main.tsx
const router = createBrowserRouter([
{
path: "/",
element: <Manage />,
},
], {
basename: '/b',
});

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>,
)
  1. vite环境变量 环境变量和模式
  • 可以新建配置文件.env.staging.env.production
.env.staging
# staging(预发布)模式
VITE_NODE_ENV=prepare
.env.production
# production(生产)模式
VITE_NODE_ENV=production
  • 创建相应的script命令
package.json
"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
  1. vite开发配置开启https 如下配置,浏览器访问时提示“使用了不受支持的协议”,这是因为https协议需要一个合法可用的证书。
vite.config.ts
server: {
host: 'test.com',
port: 443,
strictPort: true,
https: true,
},

本地开发时,可以添加 @vitejs/plugin-basic-ssl 到项目插件中,它会自动创建和缓存一个自签名的证书。

pnpm add -D @vitejs/plugin-basic-ssl
vite.config.ts
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.FunctionComponentReact.FC 显示标明返回的是一个函数组件

MutableRefObject

MutableRefObjectuseRef 钩子返回的对象类型,用于在 React 组件中创建和管理可变的引用对象。它的 current 属性可以存储任何可变的值,并在组件的整个生命周期内保持不变。useRef 常用于访问和操作 DOM 元素,但也可以用于存储其他可变值。通过使用 useRefMutableRefObject,你可以在 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

react-activation

基本使用

  1. 安装:pnpm add react-activation
  2. 在不会被销毁的位置(一般为应用入口处)放置 <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> 内部
src/main.tsx
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>,
)

注意事项

  1. 跳回被 <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')}`);
}
}
  1. 被缓存的组件会缓存路由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>
</>
}