目录
一、开发环境搭建
1、创建React项目
① 使用 create-react-app 官方脚手架创建项目
执行cmd命令:npx create-react-app xxx
xxx为项目名称(英文,小写)
create-react-app 底层由Webpack构建
② 使用 vite 创建: npm create vite@latest 选择react即可
③ 其他创建方式:启动一个新的 React 项目 – React 中文文档
2、项目目录及文件简介
使用 create-react-app创建:
│ .gitignore
│ package-lock.json
│ package.json
│ README.md
│
├─node_modules // 存放项目所依赖的一些第三方包文件
├─public // 静态资源文件夹
│ favicon.ico // 导航图标
│ index.html // 项目首页的html模版
│ logo192.png // logo图片
│ logo512.png // logo图片
│ manifest.json // 应用加壳配置文件,在手机上描述我们应用程序的json文件
│ robots.txt // 爬虫协议文件
│
└─src // 源码文件夹
App.css //App组件的样式
App.js // App组件
App.test.js // 自动化测试文件,用于给App做测试
index.css // 全局样式
index.js // 入口文件,所用组件都会通过index.js载入
logo.svg // logo
reportWebVitals.js // 导入了一个web-vitals的第三方库,用来检查应用程序的性能
setupTests.js // 从Jest-dom导入匹配器,在进行测试时使用
3、安装React Developer Tools
React Developer Tools用于react项目的调试
二、核心语法
2.1 JSX
概念:
JSX是JavaScript和XML(HTML)的缩写,表示在JS代码中编写HTML模版结构,它是React中编写UI模版的方式;
JSX并不是标准的JS语法,它是JS的语法扩展,浏览器本身不能识别,需要通过解析工具(babel)做解析之后才能在浏览器中运行。
示例:
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
JSX规则:
① 只能返回一个根元素,可以用 <>
</>
元素来代替根元素;
② 标签必须闭合;
③ 使用驼峰式命名法给 大部分属性命名;由于 class
是一个保留字,所以在 React 中需要用 className
来代替;
④ 在JSX中可以通过 大括号语法{} 识别 JavaScript中的表达式,比如常见的变量、函数调用、方法调用等等。
注意:if语句、switch语句、变量声明属于语句,不是表达式,不能出现在{}中;标签的属性值绑定直接使用{},如src={....},错误写法src=“{....}”。
2.2 条件渲染
① 与运算符(&&)及 三元表达式(? :)
function App() {
const flag = false;
return (
<>
{/* 与运算符&& */}
{flag && <div>flag为true时显示</div>}
{/* 三元表达式(?:) */}
{flag ? <div>flag为true时显示</div> : <div>flag为false时显示</div>}
</>
);
}
export default App;
② if语句:
function App() {
const id = 1;
function hobby() {
if (id === 0) {
return <div>唱</div>;
} else if (id === 1) {
return <div>跳</div>;
} else {
return <div>rap</div>;
}
}
return (
<>
{hobby()}
</>
);
}
export default App;
2.3 列表渲染
① map:
import { Fragment } from "react";
function App() {
const hobbys = [
{ id: 0, hobby: "唱" },
{ id: 1, hobby: "跳" },
{ id: 2, hobby: "rap" },
{ id: 3, hobby: "篮球" }
];
const listHobbys = hobbys.map( item => (
<Fragment key={item.id}>
<li>{item.hobby}</li>
<li>--分割线--</li>
</Fragment>
));
return (
<>
<ul>{listHobbys}</ul>
</>
);
}
export default App;
Tip:Fragment 标签相当于 <>
</>
标签,与之不同的是Fragment标签支持能接受键值或属性,可以遍历循环渲染元素
② filter:筛选数组,使用方法与map类似
2.4 事件绑定
语法:on + 事件名称 = { 事件处理程序 },整体上遵循驼峰命名法
以点击事件为例:
① 基础回调:
function App() {
// -----基础回调
const handlerClick = (e) => {
console.log("button被点击了");
// 获取事件参数e
console.log(e);
};
return (
<>
<button onClick={handlerClick}>click me</button>
</>
);
}
export default App;
② 传递自定义参数
function App() {
//------传递自定义参数
const handlerClick = (name) => {
console.log(name);
};
return (
<>
<button onClick={() => handlerClick("jack")}>click me</button>
</>
);
}
export default App;
③ 传递自定义参数+事件参数e
function App() {
//------传递自定义参数+事件参数e
const handlerClick = (name, e) => {
console.log(name, e);
};
return (
<>
<button onClick={(e) => handlerClick("jack", e)}>click me</button>
</>
);
}
export default App;
注意:传递自定义参数必须使用箭头函数的形式。
2.5 样式控制
① 行内样式控制及类名样式控制:
import "./index.css"; // 导入外部样式
function App() {
return (
<div>
{/* 行内样式控制,style属性传入对象 */}
<span style={{ color: "red", fontSize: "50px" }}>this is span</span>
{/* 通过class类名控制 */}
<span className="foo">this is span</span>
</div>
);
}
export default App;
② 设置属性集中对象
function App() {
// 属性集中在一个对象中
const imagePropsData = {
className: "foo",
style: {
width: "200px",
height: "200px"
},
src: "http//......"
};
return (
<div>
<image {...imagePropsData} title=''/>
</div>
);
}
export default App;
2.6 常用 React Hook
2.6.1 useState
useState 是一个 React Hook(函数),它允许我们向组件添加一个状态变量, 状态变量一旦发生变化组件的视图UI也会跟着变化。
语法:const [state, setState] = useState( initialState )
- state:状态变量
- setState:修改状态变量值的函数(异步)
- initialState:状态变量初始值
使用方法:
① 状态变量为 简单数据 类型
import { useState } from "react";
function App() {
// 调用 useState 添加一个状态变量
const [count, setCount] = useState(0);
const handlerClick = () => {
// 使用传入的新值修改count;重新使用新的count渲染ui
setCount(count + 1);
};
return (
<div>
<button onClick={handlerClick}>{count}</button>
</div>
);
}
export default App;
② 状态变量为 对象 类型
import { useState } from "react";
function App() {
const [form, setForm] = useState({ name: "jack" });
const changeForm = () => {
setForm({
...form,
name: "john"
// 变更的属性后写
});
};
return (
<div>
<button onClick={changeForm}>{form.name}</button>
</div>
);
}
export default App;
提示:在React中,状态被认为是只读的,我们应该始终替换它而不是修改它,对于对象类型的状态变量,应该始终传给set方法一个全新的对象来进行修改。
案例:绑定受控表单:
核心绑定流程:
① 声明一个react状态-useState
② 通过value属性值绑定react状态
③ 绑定onChange事件 通过事件参数e拿到输入框最新的值 反向修改到react状态
import { useState } from "react";
function App() {
const [value, setValue] = useState("");
return (
<>
<input type="text" value={value} onChange={(e) => setValue(e.target.value)} />
</>
);
}
export default App;
2.6.2 useRef
作用:获取dom
① useRef生成ref对象,绑定到dom标签上
② dom可用时,ref.current获取dom
③ 渲染完毕之后dom生成之后才可用
import { useRef } from "react";
function App() {
const inputRef = useRef(null);
const showDom = () => {
console.dir(inputRef.current);
inputRef.current.focus(); // 获取焦点
};
return (
<>
<input type="text" ref={inputRef} />
<button onClick={showDom}>获取dom</button>
</>
);
}
export default App;
使用场景:父组件调用子组件的方法
import { useRef, forwardRef, useImperativeHandle } from "react";
// 子组件
const Child = forwardRef((props, ref) => {
const childFn = () => {
console.log("子组件childFn方法");
};
// 暴露给父组件的方法,属性等
useImperativeHandle(ref, () => ({ childFn }));
return (
<>
<div>子组件</div>
</>
);
});
// 父组件
function App() {
// 获取子组件
const childRef = useRef();
const handleClick = () => {
// 使用子组件暴露的方法
childRef.current.childFn();
};
return (
<div>
<Child ref={childRef} />
<button onClick={handleClick}>点击执行子组件方法</button>
</div>
);
}
export default App;
2.6.3 useEffect
概念:useEffect是一个React Hook函数,用于在React组件中创建不是由事件引起而是由渲染本身引起的操作(副作用), 即执行时机为渲染时触发;使用场景如发送AJAX请求,更改DOM等等。
语法:useEffect( setup, dependencies? )
参数 setup 是一个函数,可以把它叫做副作用函数,在函数内部可以放置要执行的操作;
参数 dependencies 是一个数组(可选参),在数组里放置依赖项,不同依赖项会影响第一个参数函数的执行,当是一个空数组的时候,副作用函数只会在组件渲染完毕之后执行一次。
依赖项
|
副作用函数执行时机
|
---|---|
没有依赖项
|
组件初始渲染 + 组件更新时执行
|
空数组依赖
|
只在初始渲染时执行一次
|
添加特定依赖项
|
组件初始渲染 + 特性依赖项变化时执行
|
import { useEffect, useState } from "react";
// useEffect 依赖项参数说明
function App() {
// 1、无依赖项 副作用函数执行时机:初始渲染+组件更新
const [count, setCount] = useState(0);
useEffect(() => {
console.log("无依赖项:副作用函数执行了");
});
// 2、空数组依赖 副作用函数执行时机:初始渲染
useEffect(() => {
console.log("空数组依赖:副作用函数执行了");
}, []);
// 3、添加特定依赖项 副作用函数执行时机:初始渲染+依赖项变化执行
useEffect(() => {
// count发生变化时执行
console.log("添加特定依赖项:副作用函数执行了");
}, [count]);
return (
<>
<div>this is app</div>
<button onClick={() => setCount(count + 1)}>点击组件更新:{count}</button>
</>
);
}
export default App;
清除副作用:
场景:在useEffect中开启了一个定时器,我们想在组件卸载时把这个定时器再清理掉。
语法:
useEffect(() => {
// 实现操作副作用逻辑
return () => {
// 清除副作用逻辑
};
}, []);
案例:需求:在Son组件渲染时开启一个定制器,卸载时清除这个定时器
import { useEffect, useState } from "react";
function Son() {
//渲染时开启一个定时器
useEffect(() => {
const timer = setInterval(() => console.log("定时器执行中..."), 1000);
return () => {
// 清除副作用函数
clearInterval(timer);
};
}, []);
return <>this is son</>;
}
function App() {
// 通过条件渲染模仿组件卸载
const [show, setShow] = useState(true);
return (
<>
{show && <Son />}
<button onClick={() => setShow(true)}>加载son组件</button>
<button onClick={() => setShow(false)}>卸载son组件</button>
</>
);
}
export default App;
2.7 性能优化
2.7.1 useReducer
作用:和useState的作用类似,用来管理相对复杂的状态数据
基础用法:
- 定义一个reducer函数(根据不同的action返回不同的新状态)
-
在组件中调用useReducer,并传入reducer函数和状态的初始值
-
事件发生时,通过dispatch函数分派一个action对象(通知reducer要返回哪个新状态并渲染UI)
import { useReducer } from "react"
// 1. 定义reducer函数 根据不同的action 返回不同的状态
function reducer (state, action) {
switch (action.type) {
case 'INC':
return state + 1
case 'DEC':
return state - 1
case 'SET':
return action.payload
default:
return state
}
}
function App() {
// 2. 组件中调用useReducer(reducer, 0) => [state, dispatch]
const [state, dispatch] = useReducer(reducer, 0)
return (
<div className="App">
this is app
{/* 3. 调用dispatch({type:'INC'}) => 通知reducer产生一个新的状态 使用这个新状态更新UI */}
<button onClick={() => dispatch({ type: 'DEC' })}>-</button>
{state}
<button onClick={() => dispatch({ type: 'INC' })}>+</button>
<button onClick={() => dispatch({ type: 'SET', payload: 100 })}>update</button>
</div>
)
}
export default App
2.7.2 useMemo
作用:在组件每次重新渲染的时候缓存计算的结果,类似vue的计算属性,适用场景:消耗非常大的计算。
与普通计算区别:普通计算会随着UI刷新重新计算,useMemo只会随着依赖项的改变而变化。
语法:useMemo(() => { 根据依赖项a返回的计算结果 },[ 依赖项a ]}
说明:使用useMemo做缓存之后可以保证只有依赖项发生变化时才会重新计算。
import { useMemo, useState } from "react"
// 计算斐波那契数列之和
function fib (n) {
console.log('计算函数执行了')
if (n < 3)
return 1
return fib(n - 2) + fib(n - 1)
}
function App () {
const [count1, setCount1] = useState(0)
const result = useMemo(() => {
// 返回计算得到的结果
return fib(count1)
}, [count1])
const [count2, setCount2] = useState(0)
console.log('组件重新渲染了')
return (
<div className="App">
this is app
<button onClick={() => setCount1(count1 + 1)}>change count1: {count1}</button>
<button onClick={() => setCount2(count2 + 1)}>change count2: {count2}</button>
{result}
</div>
)
}
2.7.3 React.memo
作用:允许组件在Props没有改变的情况下跳过渲染。
React组件默认的渲染机制:只要父组件重新渲染子组件就会重新渲染。
memo进行缓存:只有props发生变化的时候才会重新渲染 (不考虑context)
语法:使用 memo 将 子组件 包裹,并用变量接收,用此变量作为 子组件 名字。
const MemoComponent = memo(function SomeComponent (props) {
// ...
})
props的比较机制:在使用memo缓存组件之后,React会对每一个 prop 使用 Object.is 比较新值和老值,返回true,表示没有变化。
prop是简单类型:Object.is(3, 3) => true 没有变化
prop是引用类型(对象 / 数组):Object([], []) => false 有变化,React只关心引用是否变化
使用方法:
import { memo, useState } from "react"
const MemoSon = memo(function Son () {
console.log('我是子组件,我重新渲染了')
return <div>this is son</div>
})
function App () {
const [count, setCount] = useState(0)
return (
<div className="App">
// 点击按钮父组件会刷新,子组件不刷新,
// 也就是说`console.log('我是子组件,我重新渲染了')` 不会再次执行
<button onClick={() => setCount(count + 1)}>+{count}</button>
<MemoSon />
</div>
)
}
export default App
2.7.4 useCallback
作用:在组件多次重新渲染的时候缓存函数,跳过组件的重新渲染
语法:useCallback(fn,dependence)
与useMemo区别:useMemo缓存的是数据,useCallback缓存的是函数
使用方法:
① 首先将子组件变成一个记忆组件:参考 2.7.3 React.memo
② 将需要缓存的函数使用useCallback包裹
import { memo, useCallback, useState } from "react"
const Input = memo(function Input ({ onChange }) {
console.log('子组件重新渲染了')
return <input type="text" onChange={(e) => onChange(e.target.value)} />
})
function App () {
// 传给子组件的函数
const changeHandler = useCallback((value) => console.log(value), [])
// 触发父组件重新渲染的函数
const [count, setCount] = useState(0)
return (
<div className="App">
{/* 把函数作为prop传给子组件 */}
<Input onChange={changeHandler} />
<button onClick={() => setCount(count + 1)}>{count}</button>
</div>
)
}
export default App
2.8 封装自定义Hook
概念:自定义Hook是以 use 打头的函数,通过自定义Hook函数可以用来实现逻辑的封装和复用(组件间共享逻辑)。
使用规则:只能在组件中或者其他自定义Hook函数中调用;只能在组件的顶层调用,不能嵌套在 if、for、其他函数中。
案例:封装组件切换的Hook
import { useState } from "react";
// 封装自定义hook
function useToggle() {
const [value, setValue] = useState(true);
const toggle = () => setValue(!value);
return { value, toggle };
}
// 在组件中使用
function App() {
const { value, toggle } = useToggle();
return (
<div>
{value && <div>this is div</div>}
<button onClick={toggle}>toggle</button>
</div>
);
}
export default App;
三、组件
3.1 概念
React组件分为函数式组件与类组件,官方主推函数式组件。
① 定义组件:
// 定义组件并导出 Button.js
export default function Button() {
// 编写组件逻辑
return (
<>
<button>我是自定义组件</button>
</>
);
}
② 使用组件:
// 导入并使用组件 App.js
import Button from "./Button";
function App() {
return (
<div className="App">
<Button />
</div>
);
}
export default App;
注意:组件名字以大写字母开头。
3.2 父子通信
props可传递任意的数据:数字、字符串、布尔值、数组、对象、函数、JSX;
props是只读对象,子组件只能读取props中的数据,不能直接进行修改, 父组件的数据只能由父组件修改。
// 子组件
function Son(props) {
return (
<>
<button>{props.name}</button>
{props.jsx}
</>
);
}
// 父子件
function App() {
const msg = {
name: "son",
// 传递 JSX
jsx: <div>666</div>
};
return (
<div>
<Son {...msg} />
</div>
);
}
export default App;
插槽:children特殊的prop
// 子组件
function Son({ children }) {
return <div>{children}</div>;
}
// 父组件
function App() {
return (
<>
<Son>
<span>this is son2 span</span>
</Son>
</>
);
}
export default App;
3.3 子父通信
import { useState } from "react";
// 子组件
function Son({ onGetSonMsg }) {
// 子组件传给父组件的数据
const sonMsg = "this is son msg";
return (
<div>
<button onClick={() => onGetSonMsg(sonMsg)}>sendMsg</button>
</div>
);
}
// 父组件
function App() {
const [msg, setMsg] = useState("");
// 传递给子组件的函数
const getMsg = (msg) => {
setMsg(msg);
};
return (
<>
<Son onGetSonMsg={getMsg} />
{msg}
</>
);
}
export default App;
3.4 跨层级组件通信
实现步骤:
-
使用 createContext 方法创建一个上下文对象Ctx
-
在顶层组件(App)中通过 Ctx.Provider 组件 提供数据
-
在底层组件(B)中通过 useContext 钩子函数获取消费数据
eg:组件层级:App → A → B
import { createContext, useContext } from "react";
const MsgContext = createContext(); // 步骤1
// 顶层组件
function App() {
const msg = "this is App mag";
return (
<>
{/* 步骤2 */}
<MsgContext.Provider value={msg}>
<A />
</MsgContext.Provider>
</>
);
}
// 底层组件
function B() {
// 步骤3
const msg = useContext(MsgContext);
return <div>this is B{msg}</div>;
}
// 中间组件
function A({ onGetAMsg }) {
return (
<div>
this is A
<B />
</div>
);
}
export default App;
3.5 Class组件
现开发不推荐使用class组件。
3.5.1 class组件的基本结构
import { Component } from "react"
class Counter extends Component {
// 1. 通过类属性state定义状态数据
state = {
count: 0
}
// 2. 定义事件回调修改状态数据
setCount = () => {
// 通过setState方法来修改状态数据
this.setState({
count: this.state.count + 1
})
}
// 3. 通过render来写UI模版
render () {
return <button onClick={this.setCount}>{this.state.count}</button>
}
}
function App () {
return (
<>
<Counter />
</>
)
}
export default App
3.5.2 class组件的生命周期
componentDidMount:组件挂载完毕自动执行
componentWillUnmount:组件卸载时自动执行
import { Component, useState } from "react"
class Son extends Component {
// 声明周期函数
// 组件渲染完毕执行一次 发送网络请求
componentDidMount () {
console.log('组件渲染完毕了,请求发送起来')
// 开启定时器
this.timer = setInterval(() => {
console.log('定时器运行中')
}, 1000)
}
// 组件卸载的时候自动执行 副作用清理的工作 清除定时器 清除事件绑定
componentWillUnmount () {
console.log('组件son被卸载了')
// 清除定时器
clearInterval(this.timer)
}
render () {
return <div>i am Son</div>
}
}
function App () {
const [show, setShow] = useState(true)
return (
<>
{show && <Son />}
<button onClick={() => setShow(false)}>unmount</button>
</>
)
}
export default App
3.5.3 class组件通信
import { Component } from "react"
// 1. 父传子 直接通过prop子组件标签身上绑定父组件中的数据即可
// 2. 子传父 在子组件标签身上绑定父组件中的函数,子组件中调用这个函数传递参数
// 子组件
class Son extends Component {
render () {
// 使用this.props.msg
return <>
<div>我是子组件 {this.props.msg}</div>
<button onClick={() => this.props.onGetSonMsg('我是son组件中的数据')}>sendMsgToParent</button>
</>
}
}
// 父组件
class Parent extends Component {
state = {
msg: 'this is parent msg'
}
getSonMsg = (sonMsg) => {
console.log(sonMsg)
}
render () {
return <div>我是父组件<Son msg={this.state.msg} onGetSonMsg={this.getSonMsg} /></div>
}
}
function App () {
return (
<>
<Parent />
</>
)
}
export default App
四、集中状态管理
4.1 Redux
Redux 是React最常用的集中状态管理工具,类似于Vue中的Pinia(Vuex),可以独立于框架运行,中文官网:Redux 中文官网 ,Redux调试工具:Redux DevTools
在React中使用redux,需安装插件 - Redux Toolkit 和 react-redux
Redux Toolkit(RTK),是编写Redux逻辑的方式一套工具的集合集。
react-redux - 用来 链接 Redux 和 React组件 的中间件
使用方法:
① 在项目中配置 Redux Toolkit 和 react-redux
npm i @reduxjs/toolkit react-redux
② store目录结构设计
③ 定义store并注入react
- 定义counterStore.js子模块,实现加减功能。
// counterStore.js
import { createSlice } from "@reduxjs/toolkit";
const counterStore = createSlice({
name: "counter",
// 初始化state
initialState: {
count: 0
},
// 修改状态的方法 同步方法 支持直接修改
reducers: {
inscrement(state) {
//加
state.count++;
},
decrement(state) {
// 减
state.count--;
}
}
});
// 解构并按需导出actionCreater函数
export const { inscrement, decrement } = counterStore.actions;
// 以默认导出的方式导出reducer
export default counterStore.reducer;
- 将子模块导入至入口文件index.js
// src/store/index.js
import { configureStore } from "@reduxjs/toolkit";
// 导入子模块reducer
import counterReducer from "./modules/channelStore";
// configureStore创建一个redux数据
const store = configureStore({
reducer: {
counter: counterReducer
}
});
export default store;
- 为React注入store
// 项目入口文件index.js
/*
react-redux负责把Redux和React 链接 起来,
内置 Provider组件 通过 store 参数把创建好的store实例注入到应用中,
链接正式建立
*/
import store from './store'
import { Provider } from 'react-redux'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<Provider store={store}>
<App />
</Provider>
)
④ 在React组件使用store中的数据
- 获取store中的数据 - useSelector
// react组件
import { useSelector } from 'react-redux'
function App () {
// 获取store中的count
const { count } = useSelector(state => state.counter)
return (
<div className="App">
{count}
</div>
)
}
export default App
- 修改store中的数据 - useDispatch
// react组件
import { useDispatch, useSelector } from 'react-redux'
// 导入子模块中修改数据的加减方法
import { inscrement, decrement } from './store/modules/counterStore'
function App () {
const { count } = useSelector(state => state.counter)
// 通过useDispatch派发事件
const dispatch = useDispatch()
return (
<div className="App">
{/* 调用dispatch提交action对象 */}
<button onClick={() => dispatch(decrement())}>-</button>
{count}
<button onClick={() => dispatch(inscrement())}>+</button>
</div>
)
}
export default App
- 向store中的数据传递参数
a.在子模块定义一个reducer
import { createSlice } from "@reduxjs/toolkit"
const counterStore = createSlice({
name: 'counter',
// 初始化state
initialState: {
count: 0
},
// 修改状态的方法 同步方法 支持直接修改
reducers: {
addToNum (state, action) {
/**
* action 里面有 type 和 payload 两个属性,所有的传参都在payload里面
*/
state.count = action.payload
}
}
})
// 解构并按需导出actionCreater函数
export const { addToNum } = counterStore.actions;
// 以默认导出的方式导出reducer
export default counterStore.reducer;
b.组件中使用
// react组件
import { useDispatch, useSelector } from 'react-redux'
// 导入actionCreater
import { addToNum } from './store/modules/counterStore'
function App () {
const { count } = useSelector(state => state.counter)
const dispatch = useDispatch()
return (
<div className="App">
{count}
{/* 调用dispatch提交action对象并传入参数 */}
<button onClick={() => dispatch(addToNum(10))}>add To 10</button>
</div>
)
}
export default App
⑤ 持久化方案 - Redux-Persist
- 装包
npm i redux-persist
- 在store入口文件中配置
// src/store/index.js
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./modules/counterStore";
// 导入两个相关包
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage"; // defaults to localStorage for web
// 定义持久化配置
const persistConfig = {
key: "root", // 本地存储的键名
storage, // 使用的存储引擎,默认为 localStorage
whitelist: ["counter"] // 白名单,需要持久化的 reducer 的名字,不设置则全部缓存
};
// 将 reducer 与持久化配置整合
const persistedReducer = persistReducer(persistConfig, counterReducer);
// 创建store
const store = configureStore({
reducer: persistedReducer,
devTools: true, // 是否开启开发者工具,默认true
// 配置中间件:如果使用redux-persist,则需要设置为false,否则控制台报错(非序列化数据)
middleware: (getDefaultMiddleware) => getDefaultMiddleware({
serializableCheck: false,
})
});
// 创建持久化后的store
const persistor = persistStore(store);
// 导出store和持久化后的store
export {
store,
persistor
}
- 在项目入口文件中注入
// index.js 项目入口
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { Provider } from "react-redux";
// 导入redux-persist提供PersistGate组件
import { PersistGate } from "redux-persist/integration/react";
import { store, persistor } from "./store";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
{/* 开启持久化 */}
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>
);
reportWebVitals();
4.2 zustand
zustand - 极简的状态管理工具,可维护多个状态,异步操作无需特殊处理,官网:Zustand Documentation,国内翻译的中文教程:[翻译] zustand 文档(readme)
使用方法:
// zustand
import { create } from "zustand";
// 1. 创建store
const useStore = create((set) => {
return {
// 状态数据
count: 0,
// 修改状态数据的方法(异步方法与之相同,建议使用async与await)
inc: () => {
// 语法1:参数是函数 需要用到老数据的场景
set((state) => ({ count: state.count + 1 }));
// 语法2:参数直接是一个对象
// set({ count: 100 })
},
// 传递参数
update: (newCount) => set({ count: newCount })
};
});
// 2.组件中使用
function App() {
const { count, inc, update } = useStore();
return (
<>
<button onClick={inc}>{count}</button>
<button onClick={() => update(10)}>传值count</button>
</>
);
}
export default App;
切片模式: 当单个store比较大时,可以采用切片模式进行模块拆分组合,类似与模块化
① 创建store目录进行模块化:
② 定义 channelStore
export const createChannelStore = (set) => {
return {
channelList: [],
// 异步操作
fetchGetList: async () => {
const res = await fetch("http://xxx");
const jsonRes = await res.json();
set({
channelList: jsonRes.data.channels
});
}
};
};
② 定义 counterStore
export const createCounterStore = (set) => {
return {
count: 0,
inc: () => {
set((state) => ({ count: state.count + 1 }));
}
};
};
③ 在入口文件(store/index.js)组合
import { create } from "zustand";
// 导入子模块
import { createCounterStore } from "./module/counterStore";
import { createChannelStore } from "./module/channelStore";
// 创建store
const useStore = create((...a) => {
return {
...createCounterStore(...a),
...createChannelStore(...a)
};
});
export default useStore;
④ 在组件中使用
import { useEffect } from "react"
// 导入store
import useStore from "./store"
function App () {
// 组件使用
const { count, inc, fetchGetList, channelList } = useStore()
useEffect(() => {
fetchGetList()
}, [fetchGetList])
return (
<>
<button onClick={inc}>{count}</button>
<ul>
{
channelList.map(item => <li key={item.id}>{item.name}</li>)
}
</ul>
</>
)
}
export default App
五、Router
5.1 基本使用
1、搭建开发环境 react-router-dom版本为6
npm i react-router-dom
2、创建并注入路由实例
① 在 src
目录下新建目录router,创建 index.js
文件(初始化路由)
// src/router/index.js
// 导入页面组件
import Login from '../page/Login'
import NotFound from '../page/NotFound'
// 导入创建路由的函数
import { createBrowserRouter } from 'react-router-dom';
// 创建router路由实例对象,并配置路由对应关系(路由数组)
const router = createBrowserRouter([
{
// 需要访问的路径
path: '/',
// 和路径对应的组件
element: <Login />
},
{
// 404路由配置
path: '*',
element: <NotFound />
}
]);
export default router;
② 注入路由实例
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
// 注入
import { RouterProvider } from 'react-router-dom';
import router from './router';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
{/* 使用 redux 的时候,需要用 Provider 包裹 RouterProvider */}
{/* 路由绑定,注入路由实例对象 */}
<RouterProvider router={router} />
</React.StrictMode>
);
5.2 嵌套路由
初始化路由:使用 children 属性配置二级路由,配置默认二级路由:只需要在二级路由的位置将path替换为index 属性为true
即可。
// src/router/index.js
const router = createBrowserRouter([
{
path: '/',
element: <Layout />,
children: [
{
index: true, // 设置为默认二级路由 一级路由访问的时候,它也能得到渲染
element: <Board />
},
{
path: 'about',
element: <About />
}
]
}
])
二级路由出口:使用 <Outlet />
组件 配置 二级路由 渲染位置
import { Outlet } from 'react-router-dom';
// Layout 组件
const Layout = () => {
return (
<div>
<div>一级路由</div>
{/* 二级路由出口 */}
<Outlet />
</div>
);
};
export default Layout;
5.3 路由导航
1、声明式导航
通过 <Link /> 组件的 to 属性 指定 要跳转的 路由path,组件会被渲染为浏览器支持的a链接,如果需要 传参 直接通过 字符串拼接 的方式 拼接参数 即可。
语法:
import { Link } from 'react-router-dom';
<Link to={'path路径'}>XXX</Link>
2、编程式导航
基本使用:
// 导入
import { useNavigate } from 'react-router-dom'
// 获取useNavigate
const navigate = useNavigate()
// 使用
<button onClick={() => navigate('/article')}>跳转到文章页</button>
<button onClick={() => navigate(-1)}>返回上一级</button>
useNavigate 配置参数:
// 导入
import { useNavigate } from 'react-router-dom'
const navigate = useNavigate()
interface NavigateOptions {
replace?: boolean; // 是否替换当前导航
state?: any; // 包含一个可选值以存储在历史记录状态中,然后可以通过 useLocation 在目标路由上访问该值
// preventScrollReset?: boolean;
// relative?: RelativeRoutingType;
// unstable_flushSync?: boolean;
// unstable_viewTransition?: boolean;
}
navigate(to: string, options?: NavigateOptions)
// 常见用法
navigate("/new-route", { replace: true });
3、路由导航传参
以 编程式导航传参 为例,声明式导航与之相同;
① searchParams 传参(字符串拼接)
- 传参
import { useNavigate } from 'react-router-dom';
const navigate = useNavigate();
navigate('/article?userId=1001&articleId=abcd');
- 接收参数
// 调用 useSearchParams 钩子函数,
// 从返回结果(数组)中解构出params对象,
// 调用该对象的get方法,
// 将需要的 参数名字 传递给get方法即可
import { useSearchParams } from 'react-router-dom';
const [params] = useSearchParams();
const userId = params.get('userId')
② params 传参
- 改造路由:需要对路由规则数组进行改造(用
/:参数名
进行占位)
// src/router/index.js
const router = createBrowserRouter([
{
// path改造
path: '/login/:userId/:likeId',
element: <Login />
}
]);
- 传参:
import { useNavigate } from 'react-router-dom';
const navgiate = useNavigate();
navigate('/article/1001/abcd');
- 接收参数:
// 导入并调用 useParams 钩子函数,
// 该钩子函数的返回值是个对象,
// 传递的参数在这个对象中
import { useParams } from 'react-router-dom';
const params = useParams();
const userId = params.userId
const likeId = params.likeId
5.4 路由模式
创建 history 路由 - createBrowserRouter:
// src/router/index.js
import { createBrowserRouter } from 'react-router-dom'
const router = createBrowserRouter([...])
export default router
创建 hash 路由 - createHashRouter:
// src/router/index.js
import { createHashRouter } from 'react-router-dom'
const router = createHashRouter([...])
export default router
两种路由模式区别:
路由模式 | url表现 | 底层原理 | 是否需要后端支持 |
---|---|---|---|
history | url/login | history对象 + pushState事件 | 是 |
hash | url/#/login | 监听hashChange事件 | 否 |
5.5 路由懒加载
使用方法 :
1、把路由修改为由React提供的 lazy 函数进行动态导入
2、使用React内置的 Suspense组件 包裹路由中element选项对应的组件
// src/router/index.js
import { createBrowserRouter } from "react-router-dom";
import { Suspense, lazy } from "react";
// 1、lazy函数对组件进行导入
const Login = lazy(() => import("../page/Login"));
const router = createBrowserRouter([
{
path: "/",
// 2、Suspense组件 包裹 路由组件,fallbacb(可选属性)可以设置加载中的效果
element: (
<Suspense fallback={"加载中..."}>
<Login />
</Suspense>
)
}
]);
export default router;
5.6 路由权限控制
路由权限控制常见方案是通过定义高阶组件来控制,以下场景模拟登录时的权限控制:
1、新建 src/components/AuthRoute.js 文件定义高阶组件:
// src/components/AuthRoute.j
// 核心逻辑: 有token 正常跳转 无token 去登录
import { getToken } from '@/utils' // 模拟获取token
import { Navigate } from 'react-router-dom'
export function AuthRoute ({ children }) {
const token = getToken()
if (token) {
return <>{children}</>
} else {
return <Navigate to={'/login'} replace />
}
}
2、在路由初始中配置:用定义的高阶组件将需要权限的路由将其包裹
// src/router/index.js
import ... // 导入路由组件
import { createBrowserRouter } from 'react-router-dom'
// 导入高阶组件
import { AuthRoute } from '@/components/AuthRoute'
const router = createBrowserRouter([
{
path: "/",
// 需要进行权限验证的路由使用<AuthRoute>组件包裹
element: <AuthRoute> <Layout /></AuthRoute>,
children: [
{
index: true,
element: <Home />
},
{
path: 'article',
element:<Article />
},
{
path: 'publish',
element: <Publish />
}
]
},
{
path: "/login",
element: <Login />
}
])
export default router
六、React+TypeScript
6.1 useState
1、自动推导
通常React会根据传入useState的默认值来自动推导类型,不需要显式标注类型
// 自动推导value为boolean类型
const [value, toogle] = useState(false)
2、传递泛型参数
① 限制useState函数参数的初始值必须满足类型为: User | ()=> User
定义类型User
interface User {
name: string
age: number
}
User
const [user, setUser] = useState<User>({
name: 'jack',
age: 18,
})
( )=> User
const [user, setUser] = useState<User>(() => {
return {
name: 'jack',
age: 18,
}
})
② 限制setUser函数的参数必须满足类型为:User | ()=> User | undefined
const [user, setUser] = useState<User>({
name: 'jack',
age: 18,
})
const changeUser = () => {
// User
setUser({
name: 'john',
age: 28,
})
// ()=> User
setUser(() => ({
name: 'john',
age: 28,
}))
}
3、初始值为null
当我们不知道状态的初始值是什么,将useState的初始值为null是一个常见的做法,可以通过具体类型联合null来做显式注解
function App() {
const [user, setUser] = useState<User | null>(null)
const changeUser = () => {
// 限制setUser函数的参数类型可以是 User | null
setUser(null)
setUser({
name: 'jack',
age: 18,
})
}
// 为了类型安全 可选链做类型守卫
// 只有user不为null(不为空值)的时候才进行点运算
return <>this is app {user?.age}</>
}
6.2 props
为组件prop添加类型,本质是给函数的参数做类型注解
1、基础用法
interface Props {
// 必选参数
className: string
// 可选参数
title?: string
}
// 子组件
function Button(props: Props) {
const { className } = props
return <button className={className}>click me </button>
}
// 父组件
function App() {
return (
<>
<Button className="test" title="this is title" />
</>
)
}
2、为children添加类型
children是一个比较特殊的prop, 支持多种不同类型数据的传入,需要通过一个内置的ReactNode类型来做注解,注解之后,children可以是多种类型,包括:React.ReactElement 、string、number、React.ReactFragment 、React.ReactPortal 、boolean、 null 、undefined
interface Props {
children: React.ReactNode
}
// 子组件
function Button(props: Props) {
const { children } = props
return <button>{children} </button>
}
// 父组件
function App() {
return (
<>
<Button>
<span>this is span</span>
</Button>
</>
)
}
3、为事件prop添加类型
场景:子传父
interface Props {
onGetMsg?: (msg: string) => void
}
function Son(props: Props) {
const { onGetMsg } = props
const clickHandler = () => {
onGetMsg?.('this is msg')
}
return <button onClick={clickHandler}>sendMsg</button>
}
function App() {
const getMsgHandler = (msg: string) => {
console.log(msg)
}
return (
<>
<Son onGetMsg={getMsgHandler} />
</>
)
}
七、免责声明
- 本博客中的文章摘自网上的众多博客,仅作为自己知识的补充和整理,并分享给其他需要的 coder,不会用于商用;
- 因为很多博客的地址已经记不清楚了,所以不会在这里标明出处;
-
React中文官网:React 官方中文文档
-
Redux中文官网:Redux 中文官网
-
React Redux中文官网:React Redux 中文文档 | React Redux 中文文档
-
React-Router官网:Home v6.22.3 | React Router
-
TypeScript学习网站:TypeScript 教程 - 网道
-
Ant-Design官网:Ant Design - 一套企业级 UI 设计语言和 React 组件库
-
Ant-Design Mobile 官网:Ant Design Mobile - Ant Design Mobile
-
vite官网:开始 | Vite 官方中文文档
-
React 生态清单:【前端】一个”哇塞“前端需要知道的 React 生态清单 ~ - 掘金