本文以react18与其hooks举例,并不会过多叙述类写法
小坑篇
- 在使用
React.StrictMode下,组件的默认渲染全部会执行两次,详情见 官方解释
暂不理解篇
- [Render Props](https://zh-hans.reactjs.org/docs/render-props.html#be-careful-when-using-render-props-with-reactpurecomponent)
组件篇
比较普通的变化
- 对于函数式组件来说,这个函数本身就是render函数,所以每次涉及到数据更新,都会导致函数重新创建,内部的变量也随之重新创建,这个需要自己通过hooks的使用进行优化
- jsx替换template,具体自行了解
- 组件标签上的class被className替代,这是jsx的原因,避免了class关键字冲突;组件的style属性需要手动定义并手动绑定你需要的节点
- 没有所谓的@与v-,只有jsx,具体自行了解
- 组件导入默认被注册,如果需异步注册使用需使用lazy
- 样式强制独立,抽离为单独文件,导入即可使用,也有css in js的各种写法,如emotion
- react-create-app脚手架内置了scss
- ref概念偏差。类组件会返回其实例,函数组件默认啥都不返回(因为他们没有实例,返回需使用forwardRef与useImperativeHandle配合),forwardRef还有转发节点的功能
- 提供了性能测试组件Profiler
- 提供了节点挂载接口Portals
- 空节点使用Fragment或代替
附加内容
React 顶层API
| 名称 | 类型 | 作用 |
|---|---|---|
| React.Component | class | 类组件的基类 |
| React.PureComponent | class | 和楼上的相似,但多了一个props 和 state浅比较更新的shouldComponentUpdate函数 |
| React.memo | 组件 | 缓存、高阶组件。props变化(浅比较)时才会更新,但组件内部使用 useState,useReducer 或 useContext 创建的内容会正常更新 |
| React.Children | 接口 | 对于子集(props.children)的一系列辅助函数,无需考虑子集是否为单一还是列表, 如 map count |
| React.Fragment | 组件 | 空标签组件 |
| React.createRef/React.forwardRef | 接口 | ref系操作接口 |
| React.lazy | 函数 | 组件动态引入加载函数 |
| React.Suspense | 组件 | 异步加载组件 |
| React.startTransition | 函数 | 非紧急更新内容 |
DOM 整改
- className代替class
- 新增dangerouslySetInnerHTML属性,相当于innerHTML,但对xss进行了处理
- style 值只能是对象,不能是字符串;涉及到px单位的属性会自动增加px字符串,如
<div style={{ height: 10 }}>相当于<div style={{ height: '10px' }}> - 整合了响应事件,如onCopy、onKeyDown等,具体查看 合成事件 – React (reactjs.org)
TS类型
路由篇v6
路由嵌套/路由跳转/路由传参
App.jsx
import { Routes, Route, Navigate, useNavigate } from 'react-router-dom';
export default function App() {
const navigate = useNavigate(); // 注意,此方法必须声明在根部
// 函数式路由跳转
const toMain = () => {
navigate(`/home/main/${132}`, { // 携带param参数
replace: true // 是否覆盖当前路由
});
};
// 路由回退
const back = () => navigate(-1)
return (
<div className="App">
<Routes>
{/* 默认空路由重定向 */}
<Route path="" element={<Navigate to="/study" />} />
{/* 有嵌套的路由 */}
<Route path="/home/*" element={<Study />} />
{/* 无嵌套的路由 */}
<Route path="/comic" element={<Comic />} />
</Routes>
</div>
);
}Home.jsx
export default function Home() {
return (
<div className="home">
<Routes>
{/* 带有param参数的子路由定义,path写法和vue类似 */}
<Route path="main/:id" element={<HomeMain />} />
</Routes>
</div>
);
}HomeMain.jsx
import { useParams } from 'react-router-dom';
export default function HomeMain() {
const { id = -1 } = useParams(); // 路由param参数获取
return (
<div className="home-main">
</div>
);
}路由传参
上面已经介绍了param传参,接下来介绍state与search传参(对应vue-router中的params和query);值得一提的是,这三种传参都不会因为页面刷新而消失
state/search
import { useNavigate, useLocation } from 'react-router-dom';
// ....
const navigate = useNavigate();
navigate('/A?name=lucy', {
state: { // state参数设置
name: 'adic'
}
});
// ....
const { state, search } = useLocation(); // 路由state参数获取
// 注意,state为一开始跳转时传入的对象,但search为原生字符串,如?name=lucy,需要自己拆分处理(这个我不确定有没有其他办法,v6文档有点emmm)样式篇
未完待续
全局状态管理篇
redux与ts。实现我们需要@reduxjs/toolkit与react-redux两个依赖,请自行安装
Redux 中文官网 - JavaScript 应用的状态容器,提供可预测的状态管理。 | Redux 中文官网
基础配置
// store.ts 根仓库
import { configureStore } from '@reduxjs/toolkit';
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import user from './useUser.store'; // 实际仓库
// 从 store 本身推断出 `RootState` 和 `AppDispatch` 类型
export type RootState = ReturnType<typeof store.getState>;
// 推断出类型: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch;
export const store = configureStore({
reducer: {
user
}
});
// 用于触发仓库的函数
export const useAppDispatch: () => AppDispatch = useDispatch;
// 用于获取仓库的值
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;// useUser.store.ts user仓库
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
const slice = createSlice({ // 仓库实例
name: 'user', // 仓库名称
initialState: { // 仓库值
islogged: false,
token: ''
},
reducers: { // 仓库函数,内部仅支持同步操作
setToken: (state, { payload }: PayloadAction<string>) => {
if (!payload) return;
state.islogged = true;
state.token = payload;
}
}
});
export const userStoreActions = slice.actions; // 供使用的函数(reducers中定义的)集合
export default slice.reducer; // 根仓库需要的依赖// index.tsx 项目入口组件
import { store } from './store';
import { Provider } from 'react-redux';
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<Provider store={store}> // 仓库注入
<App />
</Provider>
);
reportWebVitals();// xxx.tsx 某个使用到仓库的组件
import { useAppSelector, useAppDispatch } from './store';
import { userStoreActions } from './useUser.store';
export default function AppLogin() {
const dispatch = useAppDispatch();
// 此时isLogin是响应式的状态
const isLogin = useAppSelector((state) => state.user.islogged);
const login = () => {
// 使用仓库函数时 需要使用dispatch包裹一次此函数
dispatch(userStoreActions.setToken(data.jwt));
}
return <></>
}// xxx.ts 某个使用到仓库的ts文件
import { userStoreActions } from './useUser.store';
import { store } from './store';
function xx() {
// 此操作为Thunk理念,详情请看一下文档
// https://cn.redux.js.org/tutorials/essentials/part-5-async-logic#thunks-%E4%B8%8E%E5%BC%82%E6%AD%A5%E9%80%BB%E8%BE%91
store.dispatch((dispatch, getState) => {
const token = getState().user.islogged
dispatch(userStoreActions.setToken(''));
});
}hooks篇
通过hooks创建的内容都会被特殊处理(收集),无需担心组件重新渲染时对于变量的改变
useState
创建响应式变量;注意,在修改值时并不会立刻修改值,是一种异步的修改概念和this.setState相同;建议配合纯函数使用
const [a, setA] = useState(0) // 确定的初始值
const [b] = useState(()=> Math.random()) // 不确定但会在初始化生成的初始值
const [c] = useState(Math.random()) // 不确定但会在每次视图更新是生成的初始值
setA(1) // 普通的赋值
setA(a => a + 1) // 基于当前值的赋值useReducer
复杂变量管理
- 配合useContext可实现redux功能
const [list, listDispatch] = useReducer<
React.Reducer<
// list的数据类型
number[],
// listDispatch函数的参数类型。请使用联合类型,这样更有利于type对应的payload类型验证
| {
type: 'add';
payload: number;
}
| {
type: 'clear';
}
>,
// ->a3回调函数的参数类型
number[]
>(
// a1 第一个参数为list当前值 第二个参数为listDispatch调用时的传入参数
(state, action) => {
// 下面根据不同的类型做不同的处理
switch (action.type) {
case 'add':
return [...state, action.item];
case 'clear':
return [];
default:
throw new Error();
}
},
// a2 list初始化的值
[],
// a3 可选 对->a2值的重写操作,常用于类型重定义
(state) => state
);useRef\useImperativeHandle
实例获取(默认情况下获取的是没有如何值的)\实例暴露值
- useRef不会去监听绑定内容的变化,如果需要监听,请使用useCallback
- useRef同样可以用于声明变量,但变量改变时并不会想useState那样触发视图更新,所以更适合节点或计时器id的记录
useEffect\useLayoutEffect
相当于监听属性\视图更新后触发的监听属性。
- 此hooks生成的结果会被自动收集,无需进行useCallback等hooks的手动收集
- 如果 Reducer Hook 的返回值与当前 state 相同,React 将跳过子组件的渲染及副作用的执行
useContext
组件多级通讯
useCallback
用以避免render函数执行时多次创建同一个函数,需要手动传入依赖值来确定是否重新创建;也可以用来监测dom
监测dom
export function useDom<T extends Element>(cb: (e: T | null) => void) {
// eslint-disable-next-line
return useCallback(cb, []);
}useMemo
类似于计算属性,但需要手动传入其依赖值;也可以用于避免创建相同的内容
useDeferredValue
类似于防抖节流的值副本。可以配合memo来进行优化渲染
- 注意,值副本是在正常变化的,减缓的是渲染
export default function Add() {
const [a, c] = useState('');
const v = useDeferredValue(a);
return (
<div className="add">
<input type="text" onInput={(e: any) => c(e.target.value)} />
{useMemo(
() =>
Array(10000)
.fill(0)
.map((_, i) => <p key={i}>{v}</p>),
[v] // 如果将 v 替换为默认的 a,则会导致input持续输入时页面极其卡顿
)}
</div>
);
}useTransition
类似于防抖节流的函数执行。他会将传入内容认为是 非紧急渲染(默认其他都是紧急渲染)
- 注意,值副本是在正常变化的,减缓的是渲染
export default function Add() {
const [a, c] = useState('');
const [isPending, startTransition] = useTransition();
const sets = (e: string) => {
startTransition(() => {
c(e);
});
};
return (
<>
<input type="text" onInput={(e: any) => sets(e.target.value)} />
{
// 在不断输入时,会发现h1标签在闪烁,因为延迟执行的原因
isPending && <h1>{a}</h1>
}
</>
);
}