一、学习资料
官网;
二、快速配置开发环境
三、React基础
react的生命周期
挂载阶段
组件在这个阶段只会执行一次,以下的api是按时间顺序执行的
constructor
- 用户初始化内部状态
- 将类方法的 this 绑定到类实例上
getDerivedStateFromProps
- 有可能造成组件内部的 state 被意外覆盖,根据 React 官方的建议,应谨慎使用这个方法。
render
- 使用jsx渲染dom
componentDidMount
- 获取渲染后的dom
更新阶段
当state、props、context这三个值任一一个改变都会触发这个阶段,以下的api是按时间顺序执行的
getDerivedStateFromProps
- 有可能造成组件内部的 state 被意外覆盖,根据 React 官方的建议,应谨慎使用这个方法。
shouldComponentUpdate
- 当返回值为false的时候,后面的api都不会执行了,也就是不会重新渲染
- 常用于性能优化
- 最容易出现bug的api
render
- 使用jsx渲染dom
getSnapshotBeforeUpdate
- 在本次更新真实 DOM 之前,你有一次访问原始 DOM 树的机会,就是这个生命周期方法
componentDidUpdate
- 组件完成更新时会调用这个方法
卸载阶段
组件在这个阶段只会执行一次
componentWillUnmount
- 当组件即将被从虚拟 DOM 中移除时触发
错误处理阶段
getDerivedStateFromError
componentDidCatch
这两个api当组件错误的时候会执行捕获,层层上递,知道遇到有组件定义这两个api,如果没有,则会报错。
context Api
hook
react16.8以上的版本才支持
Hooks是一套为函数组件
设计的,用于访问 React 内部状态或执行副作用
操作,以函数形式存在的 React API,常见的内部状态有 state、context、memo、ref
不编写 class 的情况下使用 state 以及其他的 React 特性。
hook的生命周期
componentDidMount
,componentDidUpdate
,componentWillUnmount
三个生命周期合成一个
挂载阶段
useEffect(()=>{},[]) // `不返回函数`则是componentDidMount
useEffect(()=>{}) // `不返回函数`则是componentDidMount
更新阶段
useEffect(()=>{}) // `不返回函数`等于componentDidUpdate
useEffect(()=>{},[state1]) // `不返回函数`等于componentDidUpdate,只有state1更新才会执行,跟vue的watch类似。
卸载阶段
useEffect(()=>{return ()=>{}}) // `返回函数`则是componentWillUnmount这个生命周期
常见的hooks
注意
:
- 第一,只能在 React 的函数组件中调用 Hooks
- 第二,只能在组件函数的最顶层调用 Hooks。
useState
基本使用
// 定义
const [data, setData] = useState({a:1,b:2})
// 修改
setData(preData => ({...preData, b:3}))
作用
- 让视图跟随数据变化
- 存储数据
- 让useEffect钩子能够监听其数据变化
注意点
- 自动批处理,指的是多个useState在react合成一个执行,避免重复render,在react18之后,异步函数才会自动批处理,之前是没有的。
// 同步 ,调用了setCount和setCount1,只触发1次render,因为react内部自动批处理了
function syncState(){
setCount(1)
setCount1(2)
}
// 异步 ,调用了setCount和setCount1,会触发两次render
async function asyncState1(){
await Promise.resolve(()=>{1})
setCount(1)
setCount1(2)
}
useEffect
基本使用
useEffect(()=>{},[data])
作用
- 监听组件的生命周期
useEffect(()=>{
// componentDidMount
return ()=>{
// componentWillUnmount
}
},[])
useEffect(()=>{
// componentDidMount + componentDidUpdate
return (preData)=>{
// componentWillUnmount || data变化两次中的第一次preData
}
},[data])
- 监听useState定义的数据变化
注意点
useRef
基本使用
const refEle = useRef()
作用
- 存储不需要数据驱动视图改变的数据,比如sdk实例、dom元素、普通数据
注意点
- 当数据的变化不需要作用于视图,则需要考虑用useRef。这是为了避免useEffect监听数据导致出现额外的函数执行。
useMemo[缓存值]
作用
- 常用于计算大量的数据时,缓存使用。跟vue的computed类似。
// 记忆化(Memoization),对于计算量大的函数,通过缓存它的返回值来节省计算时间,提升程序执行速度。
// 当a||b发生变化的时候,它才会执行
const memoized = useMemo(() => createByHeavyComputing(a, b), [a, b]);
useCallback[缓存函数]
作用
跟useMemo相同的作用,但是它返回的是一个函数。
const memoizedFunc = useCallback(() => {/*省略*/}, [a, b]);
等价于
// 当a||b发生变化的时候,它才会执行
const memoizedFunc = useMemo(() => () => {/*省略*/}, [a, b]);
// 只有当a||b的值发生变化的时候,它们的函数引用地址||值才会发生变化
useImperativeHandle
作用
- 父组件能够直接使用子组件的方法
// 子组件
import React, { useState, useImperativeHandle, forwardRef } from 'react'
const IM = forwardRef((props, ref) => {
const [msgList, setMsgList] = useState([])
function onAddMsgItem() {
setMsgList(preData => [...preData, 'a'])
}
useImperativeHandle(ref, () => ({
onAddMsgItem,
}));
return (
<div className={cs.live_im}>
{
msgList.map((item, index) => {
return <div key={index}/>
})
}
</div>
)
})
export default IM
// 父组件
import React, { useState, useRef } from 'react'
import IM from './componenst/IM'
const Father = ()=>{
const imEle = useRef();
return (
<div className={cs.wrapper}>
<IM ref={imEle}></IM>
<div onClick={() => imEle.current.onAddMsgItem()}>开始</div>
</div>
)
}
useLayoutEffect
作用
- 与useEffect作用类似,但是里面的方法是同步的,它的执行顺序比useEffec优先
- 确保所有数据的更新在渲染页面之前已经完成,避免页面闪烁(重绘)
特点
- useEffect里面的方法的执行是异步的,useLayoutEffect是同步的。
- useLayoutEffect里面的执行会阻碍dom元素的渲染,但是它能确保dom的渲染是在其方法执行之后。不像useEffect会导致页面闪烁(重绘)。
import * as React from "react";
import "@/component/TheMain.less";
export function TheMain() {
const [val, setVal] = React.useState('0')
const [val1, setVal1] = React.useState('10')
React.useLayoutEffect(() => {
setVal('1')
setVal1('2')
}, []);
// React.useEffect(() => {
// setVal('1')
// setVal1('2')
// }, []);
return <div className="TheMain">{val} {val1} </div>;
}
通过上面的代码:
注释useEffect,使用useLayoutEffect,页面直接渲染 1 2
注释useLayoutEffect,使用useEffect,页面渲染 0 10 再变成 1 2
useContext
作用
- 孙子组件直接读取爷爷组件定义的响应式数据,避免组件层层透传的麻烦。
// 大体思路:
// 爷爷组件声明需要透传的数据obj,
// 使用导出声明与使用React.createContext(obj),并被它包裹。
// 孙子组件导入ObjContext并使用React.useContext(ObjContext)获取数据obj。
// 爷爷组件
import * as React from "react";
import * as ReactDOM from "react-dom";
import { App } from "./App";
const obj = {
value: 111111,
};
export const ObjContext = React.createContext(obj);
ReactDOM.render(
<ObjContext.Provider value={obj}>
<App />
</ObjContext.Provider>,
document.getElementById("root")
);
// 孙子组件
import * as React from "react";
import "@/component/TheMain.less";
import { ObjContext } from "@/main";
export function TheMain() {
const obj = React.useContext(ObjContext)
return <div className="TheMain">{obj.value}</div>;
}
自定义hooks
作用
- 当一个函数方法,频繁使用+使用到了
hooks
的相关方法。
import { useContext, useCallback } from 'react'
import { useNavigate } from 'react-router-dom'
import { Context, accountLogin } from '../store'
const useLogin = () => {
const navigate = useNavigate()
const { state, dispatch } = useContext(Context)
const { loginInfo } = state
const checkLogin = useCallback(() => {
console.log('checkLogin', loginInfo)
if (!loginInfo?.uid || !loginInfo?.sid) {
navigate('/login')
return false
}
return true
}, [navigate, loginInfo])
return { checkLogin }
}
export default useLogin
能触发 React 渲染(函数组件)的方法
当props
、state
、context
。只要这三种数据之一发生了变化,React 就会对当前组件触发协调过程,最终根据Diffing算法更改页面。
Diffing算法
协调
使用diff算法,找出变更的元素,渲染ui的过程被称为协调
setState
父组件setState,子组件也会重新渲染
React 的处理 render 的基本思维模式是每次一有变动就会去重新渲染整个应用
以最小的代价去更新 DOM,但是当dom树很大,
顶层的一个state微小变动也会出现vn比较性能的损耗
合成事件
合成事件是原生 DOM 事件的一种包装,它与原生事件的接口相同,根据 W3c 规范,React 内部规范化(Normalize)了这些接口在不同浏览器之间的行为,开发者不用再担心事件处理的浏览器兼容性问题。
合成事件与原生dom事件的区别
注册事件方式不同
// 原生的三种注册方式
<button id="btn" onclick="handleClick()">按钮</button>
document.getElementById('btn').onclick = handleClick;
document.getElementById('btn').addEventListener('click', handleClick);
// react注册方式
<button onClick={handleClick}>按钮</button>
特定事件的行为不同
React 合成事件规范化了一些在各个浏览器间行为不一致,甚至是在不同元素上行为不一致的事件,
其中有代表性的是 onChange 。
实际注册的目标 DOM 元素不同
//已知通过 evt.nativeEvent 属性,可以得到这个合成事件所包装的原生事件。
evt.target === evt.natveEvnet.target
evt.currentTarget != evt.natveEvnet.currentTarget
原因是因为React 使用了事件代理模式。React 在创建根( createRoot )的时候,
会在容器上监听所有自己支持的原生 DOM 事件。
当原生事件被触发时,React 会根据事件的类型和目标元素,找到对应的 FiberNode 和事件处理函数,
创建相应的合成事件并调用事件处理函数。
组件通信
数据流只能是单向的。
父传子
props
父传孙(透穿),兄弟组件互相通信
context
性能优化
纯展示的组件
没有用到state、props、context的组件可以使用React.memo进行封装,它在父组件的state更新时,不会重新渲染。
const PureComponent = React.memo(()=>{
useEffect(()=>{
console.log('纯组件,不更改')
})
return (
<div>这是纯组件,不会改变的哦</div>
)
})
缓存数据
useMemo
常用于计算大量的数据时,缓存使用。跟vue的computed类似。
// 记忆化(Memoization),对于计算量大的函数,通过缓存它的返回值来节省计算时间,提升程序执行速度。
const memoized = useMemo(() => createByHeavyComputing(a, b), [a, b]);
useCallback,跟useMemo相同的作用,但是它返回的是一个函数。
const memoizedFunc = useCallback(() => {/*省略*/}, [a, b]);
等价于
const memoizedFunc = useMemo(() => () => {/*省略*/}, [a, b]);
异步组件加载
如果需要加载一个异步组件,切记需要由Suspense包裹
import React, { Suspense, lazy } from 'react'
const Home = lazy(() => import('../views/Home/index'))
function ReactHoom(){
<Suspense fallback="loading">
<Home/>
</Suspense>
}
代码复用
自定义hooks
- 当一个函数方法,频繁使用+使用到了hooks的相关方法。
import { useContext, useCallback } from 'react'
import { useNavigate } from 'react-router-dom'
import { Context, accountLogin } from '../store'
const useLogin = () => {
const navigate = useNavigate()
const { state, dispatch } = useContext(Context)
const { loginInfo } = state
const checkLogin = useCallback(() => {
console.log('checkLogin', loginInfo)
if (!loginInfo?.uid || !loginInfo?.sid) {
navigate('/login')
return false
}
return true
}, [navigate, loginInfo])
return { checkLogin }
}
export default useLogin
组件组合
<father>
<son></son>
<father>
高阶组件
一个函数接受一个组件作为参数,经过一系列加工后,最后返回一个新的组件。
const EnhancedComponent = withSomeFeature(WrappedComponent);
// ----------------- --------------- ----------------
// | ---- | |
// | | | |
// V V V V
// 增强组件 (约定前缀) 高阶组件 原组件
const EnhancedComponent = withSomeFeature(args)(WrappedComponent);
// ----------------- --------------- ---- ----------------
// | | | |
// | V V |
// | 高阶函数 参数 |
// | --------------------- |
// | | |
// V V V
// 增强组件 高阶组件 原组件
四、常用的三方工具库
1、react的全局状态管理
react-redux与redux-toolkit
react-redux是一个js状态管理库。 redux-toolkit则将react-redux与react这两个库进行绑定。
使用这些库的原因有两个:
- 响应式管理全局数据
- 内部自动实现了许多性能优化
- redux-toolkit简化了react-redux的很多操作
相关资料
依赖安装
npm install @reduxjs/toolkit react-redux
工作流程
在store文件夹中,通过@reduxjs/toolkit提供的两个方法
`createSlice`:初始化状态树中模块state的初始化数据与修改state的方法
`createSlice.actions`: 导出修改state的方法,方便react组件使用
`configureStore`: 导出状态树
在根组件中,通过react-redux提供的方法
`Provider`:将状态树注入
在业务组件中,通过react-redux提供的方法
`useSelector`: 获取状态树的模块state值
`useDispatch`: 修改状态树的模块state值
基本使用,详情参考官方文档
pnpm i @reduxjs/toolkit react-redux
initStore
src/store/index
import { createSlice, configureStore } from '@reduxjs/toolkit'
// 初始化store树中stateModule的初始数据state与修改state的方法reducers
export const initStateModule = createSlice({
name: 'state',
initialState: {
loading: false,
useInfo: {
username: 'snyc'
},
},
reducers: {
syncActionSetUseInfo(state, action) {
state.useInfo = action.payload
},
setLoading(state, action) {
state.loading = action.payload
},
},
})
// 导出同步方法,方便react组件使用
export const {
syncActionSetUseInfo,
setLoading,
} = initStateModule.actions
// 导出异步方法
export const asyncActionSetUserInfo = (params = {}): any => {
return async (dispatch, getState) => {
dispatch(setLoading(true))
console.log('params', params)
try {
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 3000)
})
dispatch(syncActionSetUseInfo(params))
} catch (error) {
console.error(error)
}
dispatch(setLoading(false))
}
}
// 导出store
export default configureStore({
reducer: {
state: initStateModule.reducer
}
})
将store注入根组件
import * as React from "react";
import * as ReactDOM from "react-dom";
import { App } from "./App";
// store相关
import { Provider } from "react-redux";
import { store } from "./store/index";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
在组件中操作store
import * as React from 'react'
// 引入store相关
import { useSelector, useDispatch } from "react-redux";
import { syncActionSetUseInfo, asyncActionSetUserInfo } from 'src/pages/mobile/store'
interface Props {
}
interface State {
}
export default (props: Props) => {
// 声明store相关
const state = useSelector((states: any) => states.state) as any;
const dispatch = useDispatch();
const handleChangeSync = () => {
dispatch(syncActionSetUseInfo({
username: "hhhh"
}))
}
const handleChangeASync = () => {
dispatch(asyncActionSetUserInfo({
username: "aaaa"
}))
}
React.useEffect(() => {
console.log('state', state)
}, [state])
return <div className={cs.wrapper}>
<div onClick={handleChangeSync}>handleChangeSync</div>
<div onClick={handleChangeASync}>handleChangeASync</div>
</div>
}
2、react路由
react-router-dom
相关资料
基本使用,详情参考官方文档
路由的配置与渲染
/router/index.jsx
import { createBrowserRouter, RouterProvider, BrowserRouter, Routes, Route } from "react-router-dom";
export const router = createBrowserRouter([
{
path: "/",
element: <Root />, // 当路由为“/ 渲染Root组件
errorElement: <ErrorPage />, // 当路由从为“/”跳往其他没有配置的路由时,渲染ErrorPage组件
children: [
{
index: true, // 默认父路由渲染此子路由
element: <Index />
},
{
path: "/home",
loader: asyncData,
element: <Home />, // 当路由为“/home" 渲染Home组件
children: [
{
path: "contacts/:contactId",
loader: asyncDataViaParmas,
element: <Contact />, // 路由为"/“的子路由为”contacts/:contactId“时,渲染 Contact 注意其父路由的组件 需要配置 <Outlet>来渲染子路由
},
],
},
{
path: "/login",
element: <Login />,
},
],
},
]);
// RouterProvider,用来渲染已经配置完成的路由,通过组件的形式进行导出。
export const RouterRender = () => {
return (
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
};
======等价于下面这个=====
<BrowserRouter basename="/">
<Routes>
<Route
key={'home'}
path={'home'}
element={<Home />}
replace />
<Route
key={'contacts/:contactId'}
path={'contacts/:contactId'}
element={<Contact/>}
></Route>
</Route>
</Routes>
</BrowserRouter>
/main.jsx
页面上的路由跳转
Link
import {
Link,
} from "react-router-dom";
// Link 进行路由跳转
<Link to="login">go to Login</Link>
NavLink
侧边栏导航的跳转,特点是ClassName为回调函数,可以通过它的参数获取跳转到下一个路由的状态显示
import {
NavLink,
} from "react-router-dom";
// Link 进行路由跳转
<NavLink
key={index}
to={item.to}
className={({ isActive, isPending }) =>
isActive ? "active" : isPending ? "pending" : ""
}
>
{item.to}
</NavLink>
子路由的渲染
Outlet
import {
Outlet,
} from "react-router-dom";
export default function Home() {
return (
<div>
<Link to="home/children">home/children</Link>
<Outlet></Outlet>
</div>
);
}
进入路由之前,加载异步数据
- 使用useLoaderData与在路由配置中配置loader
- 获取数据的函数可以通过参数获取进入的url上的参数params
// 可以通过params获取想要跳转的路由参数
export async function asyncData({params}) {
const contacts = await new Promise((res, rej) => {
setTimeout(() => {
res([1, 2, 2, 3, 4, params.contactId]);
}, 3000);
});
return { contacts };
}
export const router = createBrowserRouter([
{
path: "/",
loader: asyncData, // 进入路由前需要加载的异步数据
element: <Home />
}
]);
import {
useLoaderData,
} from "react-router-dom";
export default function Home() {
const { contacts } = useLoaderData() as any;
return (
<div>
{
contacts.toString()
}
</div>
);
}
重定向
从A 跳往 B, B 重定向到 C, 此时路由回退, 回退到 A 而不是 B。
redirect
import { redirect } from "react-router-dom";
redirect('/login')
页面跳转的状态判断
import {
useNavigation,
} from "react-router-dom";
const navigation = useNavigation();
navigation.state // loading 代表页面正在跳转中
3、keepAlive
保留组件活性。