前端框架 React 学习总结
(1)创建store仓库,reducers分开管理reducers:数据开发中type字符串应放入一个常量管理default:}}default:}}count,user}//1、引入redux//引入日志//导入reducers//2、创建仓库//合并多个reducer})(2)导入到src/index.js文件,全局配置(3)组件中的使用return (
目录
25、useLayoutEffect和useInsertionEffect
1、新建src/redux/index.js用于存放redux的文件
一、React在HTML里的运用
React在HTML里的使用核心就是导入3个依赖:
<!--React核心库-->
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<!--React-dom用于支持react操作DOM-->
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<!--引入babel用于将jsx转为js-->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
案例代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>test</title>
</head>
<body>
<div id="root"></div>
</body>
<!--React核心库-->
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<!--React-dom用于支持react操作DOM-->
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<!--引入babel用于将jsx转为js-->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<!--这里一定要用这个type,让babel翻译script标签中的代码-->
<script type="text/babel">
//1、创建虚拟Dom
// 方式一 JSX
const VDOM = (
<h1 id="title">
<span>Hello,React</span>
</h1>
)
//方式二 js语法创建DOM
const VDOM2 = React.createElement("h1", {id: "title"}, React.createElement("span", {}, "Hello,React"))
//2、将虚拟DOM转成真实DOM,并插入页面 React17
// ReactDOM.render(VDOM, document.getElementById("root"))
//3、React18后,推荐使用这种
//获取根元素
const root = ReactDOM.createRoot(document.getElementById("root"))
//将元素在根元素中显示
/**
* root.render()
* 根元素里说有的内容都会被删除,替换为react元素
* 当重复调用render时,react会将两次渲染的结果进行比较,
* 它确保只修改那些发生变化的元素,对DOM做最小的修改
*/
root.render(VDOM2)
// root.render(VDOM)
</script>
</html>
运行效果:

执行流程如下:

React通过虚拟DOM,将React元素和原生DOM,进行映射,虽然操作的是虚拟DOM,但这些操作都会在真实DOM中体现。
虚拟DOM的好处:
1、降低API复杂度
2、解决兼容性问题
3、提升性能(减少DOM的不必要操作)
每当我们调用root.render()时,页面就会发生重新渲染:
React通过diffing算法,将新元素和旧元素进行比较,通过比较找到发生变化的元素,并且只对发生变化的元素进行修改,没发生变化的不予处理。
二、React框架的常用操作
脚手架创建:
使用详情查看官网,这里只记录我的笔记:React 官方中文文档 – 用于构建用户界面的 JavaScript 库
npx create-react-app projectName
项目打包


打包时为了路径对应需要到package.json中添加"homepage":"./"这一属性。
1、JSX基础语法规则

案例代码:object对象不能执行渲染,arr可以通过map函数进行遍历输出。
import React from 'react';
function App () {
const num=16
const bool=true
const name="string"
const arr=[1,2,3,4,"Jack",true]
const obj={name:"Mary",age:12}
return(
<div style={{marginLeft:"20px"}}>
<div>num:{num}</div>
<div>bool:{bool}</div>
<div>name:{name}</div>
<div>arr:{arr}</div>
<div>arr遍历: {
arr.map((item,index)=>(
<p key={index}><span color={"red"}>{item}</span></p>
))
}</div>
{/*对象不能直接打印*/}
{/*<div>obj:{obj}</div>*/}
<div>obj.name:{obj["name"]}</div>
</div>
)
}
export default App;
效果:

2、state数据的使用
state的描述:
普通数据在组件中发生改变,页面是无法感知的,也就是说普通数据在js里虽然改变了,但在页面上是没有重新更新渲染的功能的。
state只有当前组件可以访问。state是可变的,当state里的数据发生变化时,对应页面显示的变量也会随之重新渲染刷新。
state相当于一个变量,React会监控这个变量的变化,当state发生变化时,会触发组件的重新渲染,使得我们的修改可以在页面中呈现。
函数组件使用钩子函数useState设置,类组件通过this.state进行设置。
修改state时,state的值相同,React在一些情况下也会继续执行当前组件的渲染,但这次渲染不会触发其他子组件的渲染。(通常发生在值第一次相同时,这种情况基本不会对性能有什么影响)
修改state时,state的值相同,非第一次比较相同值,都不会进行渲染组件。
修改state时,若state的值不相同,则会发生重新渲染。
useState():
useState({your obj})返回值是一个数组,第一个参数是你设置的初始值,第二个元素是一个函数(通常命名为setXxx,这个函数用来修改state,调用其修改state后会触发组件的重新渲染)。
注意:直接修改state的值,虽然变量值改变了,但组件并不会重新渲染。所以需要通过setXxx的函数来进行设置,并重新渲染。
useState本质就是在通过deff算法比对后,判断发生变化后,再调用了root.render()来进行更新渲染。(若先后设置的值都相同,则不会调用render来渲染更新)
注意事项:
1、只有state的值发生改变时,才会发生重新渲染。
2、当state的值是一个对象时,修改时使用新对象去替换已有对象
(如果直接修改state对象,由于对象还是那个对象【对象地址没发生改变】,所以不会生效【更新渲染】)
解决方案:
1)通过Object.assign来创建浅拷贝的新对象
user={name:"孙悟空",age:12}
const newUser=Object.assign({},user) //浅拷贝 将user的所有属性赋值到新对象里
newUser.name="猪八戒"
setUser(newUser)
2)通过解构重构来实现
user={name:"孙悟空",age:12}
setUser({...user,name:"猪八戒"}) //解构再重构成新对象
这种两种情况下,每次生成的newUser都是新对象,地址都不同,所以每次都会重新渲染。
3、setState修改的不是当前的state,而是它修改的组件下次渲染的state;
4、setState修改state数据是异步执行的。
(所以,当调用setState去修改一个state时,一定要注意,有可能出现计算错误的情况。
为了避免这种错误,我们可以通过给setState传递回调函数的形式来修改state。
解决方案: setState中,回调函数的返回值将成为新的state值,回调函数执行时,React会将最新的state值作为参数传递
核心:
setCounter((prevCounter)=>{ //preCounter为counter之前的最新值,由react管理返回
return prevCounter + 1
})
简写:
setCounter(prevCounter=> prevCounter + 1)
const [counter, setCounter] = useState(1)
const addHandler = () => {
setTimeout(() => {
setCounter((prevCounter) => {
//setState中,回调函数的返回值将成为新的state值,
//回调函数执行时,React会将最新的state值作为参数传递
return prevCounter + 1
})
}, 1000)
}
)
5、多次调用重复调用setState,只会执行最后一次
参考文档:用 State 响应输入 – React 中文文档
在类组件中的state:
使用this.state获取state,使用this.setState修改state
上面聊到的异步问题,在类组件中这样写:
this.setState(prevState=>{
return {
count: prevState.count+1
}
})
注意:当state里有多个属性时,通过setState修改其中一个属性,且该属性是state的直接属性,仅会修改该属性,其他属性不会被修改。
state={
count:0,
test:"哈哈"
}
clickHandler=()=>{
setState({count: 1}) //这里只会修改count属性,不会修改test属性
}
这段代码修改后的state是{count:1,test:"哈哈“}而不是{count:1}
非直接属性修改时:
state = {
count: 0,
test: "哈哈",
obj: {
name: "孙悟空",
age: 18
}
}
clickHandler = () => {
setState({
obj: {name: "沙和尚"}
})
}
修改后,state里的值是
state = {
count: 0,
test: "哈哈",
obj: {
name: "沙和尚"
}
}
此时obj的age属性并没有被保留,而是直接被{name: "沙和尚"}对象替换了。

3、生命周期
发送网络请求一般在componentDidMount里执行,
销毁组件开销一般在componentWillUnmount里执行。




4、数据的双向绑定与Ref
方法一:
class Test extends Component {
constructor (props, context) {
super(props, context)
this.state={
val: "233"
}
}
changeVal=(e)=>{
console.log(e)
this.setState({
val: e.target.value
})
console.log(e.target.value)
}
render () {
return (
<div>
<input type="text" onChange={this.changeVal} value={this.state.val} />
</div>
)
}
}
方法二:通过ref实现
class Test extends Component {
constructor (props, context) {
super(props, context)
this.state={
val: "233"
}
//1、在构造函数里创建ref的语法
this.myRef=React.createRef()
}
search=()=>{
//3、获取DOM元素
console.log(this.myRef.current.value)
}
render () {
return (
<div>
{/*2、绑定到元素上去*/}
<input type="text" ref={this.myRef} onChange={this.search}/>
</div>
)
}
}
通过ref获取并操作原生dom:
在原生js中,获取对象时我们常用document.getElementById这些方法来获取并操作Dom原生,document.getElement之类的方法会对页面进行查询再获取,在React里使用时,有时并非最优性能的解。这里我们可以使用ref来实现。
函数组件中的使用:
一般使用useRef()这个钩子函数, 使用方法查看官方文档:
使用React获取DOM对象有以下几个步骤:
1、创建一个存储DOM的容器;
const myRef=useRef() //创建一个存储DOM对象的容器
2、在DOM标签中设置ref属性与useRef()容器绑定;
<div id="dom" ref={myRef}>xxx</div>
React会自动将当前元素的DOM对象,设置为容器的current属性。
<div id="dom" ref={myRef}>xxx</div>
const myRef=useRef() //创建一个存储DOM对象的容器
const log=()=>{
const dom=document.getElementById("dom")
console.log(myRef.current) //DOM元素存在ref的current属性里
console.log(myRef.current===dom) //true 证明React获取的DOM和document获取的是同一个DOM对象
myRef.current.innerText="嘻嘻" //修改成功
}
- useRef() 返回的就是一个JS对象
- {current: undefined}
- 甚至你直接把useRef() 改为 {current: undefined} 都能正常运行

- 直接创建对象取接受和使用useRef取接受的区别:
1)我们创建的对象,每次渲染都会重新创建一个新对象;
2)useRef创建对象,可以确保每次渲染获取到的都是同一个对象。
- 当你需要一个对象不会因为组件的重新渲染而改变时,你可以使用useRef()
类组件中的使用:
1、创建DOM容器对象
myRef=React.createRef()
2、获取DOM元素ref
<div id="dom" ref={this.myRef}>xxx</div>
其他特性和函数组件里的描述基本一致。
类组件的refs的操作参考:
5、PropTypes验证规则
参考:
6、React里的插槽
参考:
7、错误边界
避免一错全不渲染的情况
参考:错误边界 – React
8、直接操作refs元素
函数组件里使用:


9、高阶组件的运用案例
参考:高阶组件 – React
实现组件加载时间的复用
import React from "react"
export default function showTime (Comp) {
return class extends React.Component {
constructor (props, context) {
super(props, context)
this.state = {
startTime: new Date().getTime(),
loadingTime: 0
}
}
componentDidMount () {
let endTime = new Date().getTime()
this.setState({
loadingTime: endTime - this.state.startTime
})
}
render () {
return (
<Comp loadingTime={this.state.loadingTime}/>
)
}
}
}
这样便实现了高阶组件的复用,需要使用这个功能时,只需要调用该函数对象进行功能追加即可。
10、性能优化
参考:性能优化 – React
1、新版本以后Component用PureComponent,
PureComponent自带state和props的检查,但其中有变化时才重写渲染,否则不重新渲染,提升性能。
函数组件中的优化:相当于pureComonent
React.memo()生命周期钩子,相当于pureCompnent会在props和state变化时才发生更新。
案例代码:
import React from "react"
const componentTwo = React.memo((props) => {
return (
<div>
</div>
)
})
export default componentTwo
2、组件用完后资源记得释放
11、Hook生命周期钩子的使用
钩子函数只能用于函数组件和自定义钩子。不能直接放到其他个别方法里使用,会直接报错。
使用函数组件的效率一般会比类组件效率高一些,但在函数组件(无状态组件)中又没有state等属性,所以这里诞生了Hook为函数组件添加state和生命周期等元素。
案例一: useState 修改状态
import React,{useState} from "react"
function FunComponentOne () {
//定义一个变量,初始值为0,可通过setCount方法修改值
//第一个位置变量名,第二个位置方法名:修改变量的方法
const [count,setCount]=useState(0)
return (
<div>
<h2>{count}</h2>
<button onClick={()=>setCount(count+1)}>+</button>
</div>
)
}
export default FunComponentOne
案例二:useEffect 生命周期钩子

import React,{useState,useEffect} from "react"
function FunComponentOne () {
//定义一个变量,初始值为0,可通过setCount方法修改值
//第一个位置变量名,第二个位置方法名
const [count,setCount]=useState(0)
//第二个参数为空,相当于生命周期钩子:componentDidMount+componentDidUpdate
//加载完成和数据修改都会走该函数
useEffect(() => {
console.log("=======================")
})
//第二个参数为空数组[],相当于componentDidMount,可以用于网络请求
useEffect(() => {
console.log("++++++++++++++++++++++++++++")
}, [])
//第二个参数可以传入有值的数组,当数组里的变量修改,会调用该函数
useEffect(() => {
console.log("!!!!!!!!!!!!!!!!!!!!!!!!!")
}, [count])
//当第二个参数为空数组[],且有返回函数时相当于componentWillUnMount
//一般用于销毁主键
useEffect(() => {
return function clearUp(){
console.log("clearUp")
}
},[])
return (
<div>
<h2>{count}</h2>
<button onClick={()=>setCount(count+1)}>+</button>
</div>
)
}
export default FunComponentOne
案例3: Hook reducer类似于升级版的useState
案例4:自定义Hook 降低耦合度
可以将频繁调用的hook定义到自己的hook里,注意要用use开头

12、useMemo
相当于Vue里的Computed
与useCallback十分相似,useCallback是用来缓存函数对象,useMemo是用来缓存函数的执行结果。
不使用useMemo的执行效果:
const sum = (a, b) => {
console.log("求和执行了")
return a + b
}
function App() {
console.log(sum(1,2))
console.log(sum(1,2))
return (
<>
<div></div>
</>
);
}
效果:每次调用都会重新执行。

使用了useMemo:
const sum = (a, b) => {
console.log("求和执行了")
return a + b
}
function App() {
let a=1
let b=2
// 每次组件渲染时,都会执行
// useMemo用于缓存函数的执行结果
const result = React.useMemo(() => {
return sum(a, b)
},[])
console.log(result)
console.log(result)
return (
<>
<div />
</>
);
}
效果:函数只执行了一次,对于开销很大的函数,使用useMemo可以很好地改善性能。

当useMemo里传入变量时:
const sum = (a, b) => {
console.log("求和执行了")
return a + b
}
function App() {
const [count, setCount] = useState(1)
let b = 2
// 每次组件渲染时,都会执行
// useMemo用于缓存函数的执行结果
const result = React.useMemo(() => {
return sum(count, b)
}, [])
useEffect(() => {
console.log("count:", count)
console.log(result)
console.log(result)
}, [count])
return (
<>
<div>
<button onClick={() => setCount(prev => prev + 1)}>a加1</button>
</div>
</>
);
}
打印:可以看到每次执行都是第一次的结果,变量改变也没有重新缓存result。

解决上面问题的方法:
const result = React.useMemo(() => {
return sum(count, b)
}, [count]) // 第二个参数数组中传入对应变量,当变量发生变化时,会重新调用useMemo进行结果缓存更新。
对于上述情况,如果修改过于频繁,基本使用不到缓存效果,就不推荐这种情况使用了。
另外,useMemo也可以像React.memo一样返回组件缓存:
function App() {
const el = useMemo(() => {
return <div><p>hello</p></div>
}, [])
return (
<>
<div>
{el}
</div>
</>
);
}


13、React函数里的事件
在React中,函数接收到DOM元素触发的事件参数event不再是DOM对象,而是由react已封装好的事件对象。
像原生JS里阻止默认行为的return false写法不会再生效。
React里事件的常用阻止方法:
event.preventDefault(); //取消默认行为 类似原生js里的return false的操作效果,如阻止a标签跳转
event.stopPropagation(); //取消事件冒泡 取消alter操作的弹出
14、Portal在body中显示组件
通过portal可以将组件渲染到页面中的指定位置。
使用方法:
1、在index.html中添加一个新元素
2、修改组件的渲染方式:
1)通过ReactDOM.createPortal来作为返回值创建元素;
2)参数:1. JSX (修改前return后的代码);2.目标位置(DOM元素)
使用场景:一些需要层级较高的组件的显示,如对话框。

实现参考官方文档:
15、WebStorm里的快捷创建命令
rsi 函数组件(带props)
rsc 函数组件(不带props)
rcc 类组件
16、CSS模块化使用
1)创建一个 xxx.module.css文件;

2)在对应组件中引入css;
import styles from "../styles/test.module.css";
3)通过引入的参数来设置类型
![]()

class模块可动态地设置唯一的class值,避免不同组件类名重复的问题。

17、useEffect
useEffect为函数组件里的生命周期钩子,在组件渲染完毕后执行。用于处理一些具有副作用影响的代码和生命周期里的执行,如:
以下代码在执行时,会报too many render的错误,原因是setState函数是一个有副作用的函数,当调用setState时,由于值不相同,页面会进行重新渲染,每次重新渲染又会重新执行该逻辑,从而陷入死循环。
const ChildTwo=()=>{
const [data,setData]=useState(1)
setData(233)
return <div><p>hello</p></div>
}
不用useEffect的解决思路,可以通过异步函数来解决:
const ChildTwo = () => {
const [data, setData] = useState(1)
setTimeout(() => {
setData(233)
}, 0)
return <div><p>hello</p></div>
}
但这样并不规范,通过useEffect可以轻松解决。
const ChildTwo = () => {
const [data, setData] = useState(1)
useEffect(() => {
setData(233)
})
return <div><p>hello</p></div>
}
Effect调用的时机:
1、默认,每次组件重新渲染都会调用,并且是每次渲染完成后调用;
2、useEffect(()=>{})在只有第一个函数参数时,会在页面完成渲染后和页面更新渲染完成后执行,相当于Compent组件里:componentDidMount 和 componentDidUpdate 一起的生命周期;
//1
useEffect(()=>{
console.log(props.number)
setNumber(props.number)
}) //所有更新都执行
3、useEffect(()=>{},[])可以传入第二个参数数组为空数组,此时只会在初始化页面渲染完毕时执行,相当于componentDidMount 的生命周期,只在第一次 render 的时候执行func;
//2
useEffect(()=>{
console.log(props)
},[]) //仅在挂载时候执行
4、useEffect(()=>{},[a,b,c])可以在第二个参数数组中传入需要监听的参数,当监听的参数发生变化时,就会重新调用该func,即在初始化渲染完毕和参数更新发生重新渲染完毕后执行,相当于Compent组件里:componentDidMount 和 componentDidUpdate 一起的生命周期,但只监听对应数组里的参数,setState等方法不要放如监听数组,设不设置都一样;
//3
useEffect(()=>{
console.log(count)
},[count]) //count更新时执行//4
const Asynchronous : React.FC=({number})=>{
const [number2,setNumber2] = useState(number);
useEffect(()=>{
console.log(number)
setNumber2(number)
},[number,setNumber2]) //监听props对象number的更改
//setNumber2是useState返回的setter,所以不会在每次渲染时重新创建它,因此effect只会运行一次//所以,尽量不要把stateState设置到变化的依赖里,浪费代码空间
}
5、useEffect里可以数组return func()来作为组件卸载时的执行函数:类似于Compent组件的componentWillUnmount生命周期
const timer = setInterval(() => {
setCount(count + 1)
}, 1000)
// useEffect方法的第一个参数是一个函数,函数可以return一个方法,这个方法就是在组件销毁的时候会被调用
useEffect(() => {
return () => {
clearInterval(timer)
}
}, [])
18、通过useEffect实现防抖案例
const ChildTwo = () => {
const [data, setData] = useState("")
console.log("渲染了Child")
useEffect(() => {
console.log(data)
return () => {
console.log("清理的函数执行了")
}
}, [data])
const onChangeInput = e => {
setData(e.target.value)
}
return <div>
<input type="text" value={data} onChange={onChangeInput}/>
</div>
}
打印:通过下图你可以发现不同区域的执行顺序

添加防抖操作:
const ChildTwo = () => {
const [data, setData] = useState("")
console.log("渲染了Child")
useEffect(() => {
const timer=setTimeout(()=>{
console.log("effect触发了")
console.log(data)
},1000)
return () => {
clearTimeout(timer)
}
}, [data])
const onChangeInput = e => {
setData(e.target.value)
}
return <div>
<input type="text" value={data} onChange={onChangeInput}/>
</div>
}
执行结果:输入了多次setTimeout里的操作只执行了一次。

19、useReducer
useReducer(reducer, initialArg, init?)
const [state, dispatch] = useReducer(reducer, initialArg, init?)
reducer:需要一个整合函数,对当前state里的所有操作都应该在该函数中定义。该函数的返回值会成为state的新值。reducer执行时会收到两个参数,第一个是当前最新的state,第二个参数为dispatch被调用时传入的参数。
initialArg:state的初始值,作用和useState()里的值是一样的。
返回值:返回的是一个数组,第一个参数是state用于获取state里的值;第二个参数是state修改的派发器,具体修改行为由另一个函数(reducer)执行。
案例一:
const ChildTwo = () => {
const [count,countDispatch]=useReducer(()=>{
console.log("reducer执行了!")
},1)
return <div>
<button onClick={()=>countDispatch()}>点击</button>
</div>
}
打印:点击触发dispatch派发器,派发器调用reducer函数里的逻辑。

案例二:
在reducer里传入参数:
const ChildTwo = () => {
const [count, countDispatch] = useReducer((state, action) => {
if (action && action.num) {
switch (action.type) {
case "increase":
return state + action.num
case "decrease":
return state - action.num
}
}
return state
}, 1)
useEffect(() => {
console.log(count)
}, [count])
return <div>
<p>{count}</p>
<button onClick={() => countDispatch({type: "increase", num: 10})}>加10</button>
<button onClick={() => countDispatch({type: "decrease", num: 1})}>减1</button>
</div>
}
实现效果:reducer第一个参数为最新的state数据。也可以在dispatch中传入参数action,reducer可以通过第二个参数接收对应传值的action来进行相关逻辑执行。

注意:为了避免Reducer在组件里声明会重复创建,所以尽量在组件外创建Reducer。
案例二更规范的写法:
const countReducer = (state, action) => {
if (action && action.num) {
switch (action.type) {
case "increase":
return state + action.num
case "decrease":
return state - action.num
}
}
return state
}
const ChildTwo = () => {
const [count, countDispatch] = useReducer(countReducer, 1)
useEffect(() => {
console.log(count)
}, [count])
return <div>
<p>{count}</p>
<button onClick={() => countDispatch({type: "increase", num: 10})}>加10</button>
<button onClick={() => countDispatch({type: "decrease", num: 1})}>减1</button>
</div>
}
20、React.memo
官方文档:https://zh-hans.react.dev/reference/react/memo
React.memo是一个高阶组件,它接收另一个组件作为参数,会返回一个包装过的新组件,包装后的新组件就会具有缓存作用。
包装后,只有组件的props发生变化,才会触发组件的重新渲染,否则总是返回缓存中的结果。
案例一:
function App() {
console.log("App发生渲染")
const [count, setCount] = useState(1)
return (
<>
<div className="App">
<Son/>
<p>{count}</p>
<button onClick={() => setCount(count => ++count)}>点击加1</button>
</div>
</>
);
}
const Son = () => {
console.log("Son发生渲染")
return <div>
<p>son</p>
<GrandSon/>
</div>
}
const GrandSon = () => {
console.log("GrandSon发生渲染")
return <div><p>GrandSon</p></div>
}
实现效果:每次修改App里的数据,子孙组件都发生了改变

案例二:
对案例一进行优化:
function App() {
console.log("App发生渲染")
const [count, setCount] = useState(1)
return (
<>
<div className="App">
<Son/>
<p>{count}</p>
<button onClick={() => setCount(count => ++count)}>点击加1</button>
</div>
</>
);
}
const Son = React.memo(() => {
console.log("Son发生渲染")
return <div>
<p>son</p>
<GrandSon/>
</div>
})
const GrandSon = () => {
console.log("GrandSon发生渲染")
return <div><p>GrandSon</p></div>
}
实现效果:可以看到,当Son组件被memo包裹时,Son一系列数下的组件都被缓存优化到了。每次修改父组件,子孙组件都未重新渲染。

案例三:
在App组件中,像子组件Son传入修改参数的setCount函数。
function App() {
console.log("App发生渲染")
const [count, setCount] = useState(1)
const addOne = () => {
setCount(count => ++count)
}
return (
<>
<div className="App">
<Son addOne={addOne}/>
<p>{count}</p>
<button onClick={addOne}>点击加1</button>
</div>
</>
);
}
const Son = React.memo(({addOne}) => {
console.log("Son发生渲染")
return <div>
<p>son</p>
<button onClick={addOne}>Son点击加1</button>
<GrandSon/>
</div>
})
const GrandSon = () => {
console.log("GrandSon发生渲染")
return <div><p>GrandSon</p></div>
}
实现效果:当Son组件中调用了父组件中的函数时,你会发现React.memo失效了,此时,子孙组件仍然会被重新渲染。因为App组件发生改变重新渲染,addOne函数也会重新定义,此时传入Son组件里的函数就相当于更新了,会使得Son进行重新渲染。但问题是Son里的内容并没有改变,有什么方法可以解决这种情况的问题呢?这就要用到下面介绍的useCallback钩子了。

21、useCallback
useCallback是一个钩子函数,用来创建React中的回调函数。创建的回调函数不会总在组件重新渲染时重新创建。简单来说,就是对回调函数做了一层缓存。
const cachedFn = useCallback(fn, dependencies)
useCallback的第一个参数是一个回调函数,第二个参数是依赖数组(当依赖数组中的变量发生变化时,回调函数才会重新创建;如果不指定依赖数组,回调函数每次都会重新创建,失去意义)。
注:如果使用时,不传第二个参数,函数仍然会在每次渲染时重新创建,和没使用没什么区别。
案例四:
function App() {
console.log("App发生渲染")
const [count, setCount] = useState(1)
const addOne = useCallback(() => {
setCount(count => ++count)
},[]) // 这里的函数只会在组件初始化时创建,更新时不会再次创建
return (
<>
<div className="App">
<Son addOne={addOne}/>
<p>{count}</p>
<button onClick={addOne}>点击加1</button>
</div>
</>
);
}
const Son = React.memo(({addOne}) => {
console.log("Son发生渲染")
return <div>
<p>son</p>
<button onClick={addOne}>Son点击加1</button>
<GrandSon/>
</div>
})
const GrandSon = () => {
console.log("GrandSon发生渲染")
return <div><p>GrandSon</p></div>
}
实现效果:React.memo案例三中的问题解决了

案例五:
function App() {
console.log("App发生渲染")
const [count, setCount] = useState(1)
const [num, setNum] = useState(1)
const addOne = useCallback(() => {
setCount(count => count + num)
setNum(num => num + 1)
}, [num])
return (
<>
<div className="App">
<Son addOne={addOne}/>
<p>{count}</p>
<button onClick={addOne}>点击加1</button>
</div>
</>
);
}
const Son = React.memo(({addOne}) => {
console.log("Son发生渲染")
return <div>
<p>son</p>
<button onClick={addOne}>Son点击加1</button>
<GrandSon/>
</div>
})
const GrandSon = () => {
console.log("GrandSon发生渲染")
return <div><p>GrandSon</p></div>
}
实现效果:在依赖数组中数组callback函数中使用到的变化,每当数组中的变量发生改变时,回调函数都会重新定义,执行时机和useEffect依赖数组里是类似的。但案例中这种情况已经失去了优化的初衷。所以,案例五中的情况,尽量不要用useCallback了,使用和不使用效果都一样,还多了一层缓存的耗时操作。

22、自定义钩子函数
官方文档:使用自定义 Hook 复用逻辑 – React 中文文档
官方案例:
使用use开头,后面为自定义名称,可以将需要集中处理的部分写到自定义钩子函数里,并且可以通过返回对象给调用自定义钩子的组件。
import { useState, useEffect } from 'react';
export function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
function handleOnline() {
setIsOnline(true);
}
function handleOffline() {
setIsOnline(false);
}
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}
23、React.forwardRef
用来指定React组件向外暴露的ref
无法直接获取react组件的DOM对象,因为一个React组件中可能有多个Dom对象,React也不知道要给你谁。
案例一:
function App() {
const sonRef=useRef()
useEffect(()=>{
console.log(sonRef)
})
return (
<>
<div>
{/* 无法直接获取react组件的DOM对象
因为一个React组件中可能有多个Dom对象
React也不知道要给你谁
*/}
<Son ref={sonRef}/>
</div>
</>
);
}
function Son(){
const inputRef=useRef()
const clickHandler=()=>{
// 点击按钮获取文本
console.log(inputRef.current.value);
}
return (
<div>
<h1>Son</h1>
<input ref={inputRef} type="text"/>
<button onClick={clickHandler}>点击</button>
</div>
)
}
效果:普通DOM对象可以被获取到,而React组件的DOM对象不能被直接获取到。

案例二:
对子组件进行改造:
const Son = React.forwardRef((props, ref) => { // 通过forwardRef包装的函数会多一个ref参数,这个ref用于指明这个组件中需要返回的ref是谁。即当父组件调用这个组件的ref时,该组件就返回该ref。
const inputRef = useRef()const clickHandler = () => {
// 点击按钮获取文本
console.log(inputRef.current.value);
}return (
<div>
<h1 ref={ref}>Son</h1>
<input ref={inputRef} type="text"/>
<button onClick={clickHandler}>点击</button>
</div>
)
})
效果:父组件拿到了子组件返回的ref,且ref为子组件自定义指定的DOM元素的ref

24、useImperativeHandle
单纯使用forwardRef暴露组件内部的ref会存在一定的安全隐患,可以通过useImperativeHandle来实现自定义保留ref。
function App() {
const sonRef = useRef()
useEffect(() => {
console.log(sonRef)
})
return (
<>
<div>
{/* 无法直接获取react组件的DOM对象
因为一个React组件中可能有多个Dom对象
React也不知道要给你谁
*/}
<Son ref={sonRef}/>
</div>
</>
);
}
const Son = React.forwardRef((props, ref) => {
const inputRef = useRef()
// useImperativeHandle 可以用来指定useRef返回的值
useImperativeHandle(ref,()=>{
// 回调函数的返回值,会成为ref的值
return {name:"孙悟空"}
})
const clickHandler = () => {
// 点击按钮获取文本
console.log(inputRef.current.value);
}
return (
<div>
<h1>Son</h1>
<input ref={inputRef} type="text"/>
<button onClick={clickHandler}>点击</button>
</div>
)
})
效果:useImperativeHandle的回调函数的返回值是什么,回调的ref就是什么。

也可以这样返回DOM对象:
// useImperativeHandle 可以用来指定useRef返回的值
useImperativeHandle(ref,()=>{
// 回调函数的返回值,会成为ref的值
return inputRef.current
})
但为了更好的管控DOM对象,一遍这样写:
// useImperativeHandle 可以用来指定useRef返回的值
useImperativeHandle(ref, () => {
// 回调函数的返回值,会成为ref的值
return {
changeInputValue(val) {
inputRef.current.value = val
}
}
})
25、useLayoutEffect和useInsertionEffect
使用场景:
1、useInsertionEffect一般用来在DOM加载前在原DOM上插入一些原生DOM元素之类的操作,这种操作一般不太合理,所以使用频率不高。(加载前,插入DOM元素首选)
2、uselayoutEffect在样式渲染前执行,可以在里面加一些样式改变的操作,并且页面只会渲染一次,若使用useEffect去设置样式,页面在渲染完成时会执行原有的一次,在加载到useEffect里的样式时又会渲染一次。这种样式渲染情况,推荐使用uselayoutEffect来处理。(渲染前,添加新样式首选)
3、在18版本以后useEffect做了一些调整,适应大多数情况的执行。样式出现闪烁情况时,可以考虑uselayoutEffect。(18以后尽量用useEffect,大多数情况首选)
先从一张图区别这两者和useEffect的执行时机差异:

useLayoutEffect 是 useEffect 的一个版本,在浏览器重新绘制屏幕之前触发。
官网文档:useLayoutEffect – React 中文文档
useInsertionEffect 可以在布局副作用触发之前将元素插入到 DOM 中。
官网文档:useInsertionEffect – React 中文文档
简单地讲,useInsertionEffect会在Dom改变前执行,useLayoutEffect会在Dom改变后执行,前两种都是在DOM渲染前执行的,而useEffect是在DOM渲染完毕后执行的。
案例一:
查看三个effect的执行先后:
function App() {
const [count, setCount] = useState(1)
const h3Ref = useRef()
useEffect(() => {
console.log("useEffect", h3Ref)
})
useLayoutEffect(() => {
console.log("useLayoutEffect", h3Ref)
})
useInsertionEffect(() => {
console.log("useInsertionEffect", h3Ref)
})
return (
<div>
<h3 ref={h3Ref}>{count}</h3>
</div>
);
}
打印:可以看到useInsertionEffect执行时DOM还没改变,此时DOM元素还未改变。

26、useDebugValue
这个钩子用得不多,一般是在开发中区别自定义钩子的标识。
const useMyHook=()=>{
useDebugValue("哈哈")
useEffect(()=>{
console.log("gooooo")
})
}
function App() {
useMyHook()
return (
<div>
</div>
);
}
在React开发者插件工具中才能看到备注的hook:控制台直接是看不到的,一般在自定义钩子特别多时,开发区分时用到。


三、组件之间的传值

1、Props父子组件之间传值
通过props可以实现父组件传值给子组件。
在函数组件中定义了一个形参props,它指向的是一个对象,它包含了父组件中包含的所有参数。在类组件中同过this.props获取(类组件的props存储到类的实例对象中)。
官网文档介绍:
通过组件传参的方式:
<ChildOne num={12}/>
<ChildTwo num={24}/>
子组件接收参数:
class ChildTwo extends Component {
render () {
return (
<div>
<h2>我是子组件ChildTwo: {this.props.name}</h2>
</div>
)
}
}
function ChildOne (prop) {
return(
<div>
<h2>ChildOne子组件:{prop.num}</h2>
</div>
)
}
2、子向父传值
通过父组件传递方法对象给子组件,子组件再调用该方法并传入对应参数和处理给父组件。
父组件:
function ParentOne () {
function getSonData (data) {
console.log(data)
}
return (
<div>
<ChildThree getSonData={getSonData} />
</div>
)
}
子组件:
function ChildThree (prop) {
function sendData(){
prop.getSonData("我是子组件的数据")
}
return(
<div>
<button onClick={sendData}>点击向父组件传值</button>
</div>
)
}
这里父组件将getSonData方法对象先传递给子组件,子组件拿到方法对象后可以通过prop进行调用并传入子组件的参数到方法里,此时会调用父组件里的方法以拿到子组件的数据。
3、context实现跨层级通信
(1)首先,创建一个MyContext.js用来管理context环境变量
import React from "react"
//创建中间仓库
const MyContext=React.createContext(undefined)
export default MyContext
(2)案例
第一层父组件,提供数据
class LayerOne extends Component {
constructor (props, context) {
super(props, context)
this.state={
name: "cute Tom"
}
}
render () {
return (
<div>
<h1>我是one</h1>
<MyContext.Provider value={this.state}>
<LayerTwo/>
</MyContext.Provider>
</div>
)
}
}
第二层:包含第三层
class LayerTwo extends Component {
render () {
return (
<div>
<h2>我是two</h2>
<LayerThree/>
</div>
)
}
}
第三层可以直接消费数据,注意这里需要声明一下是哪个MyContext
class LayerThree extends Component {
render () {
return (
<div>
<h3>我是three</h3>
<MyContext.Consumer>
{value => <div><h2>{value.name}</h2></div>}
</MyContext.Consumer>
</div>
)
}
}
LayerThree.contextType=MyContext
效果:




context hook案例:
函数组件中的使用:Context Hook
4、createContext和useContext的使用
方法一利用Consumer标签获取:
1)创建Context共享仓库, React.createContext();
2)在需要Context的组件中引入Context;
3) 通过<Context名.Consumer>标签里的一个回调函数获取Context值,回调函数的第一个参数对应了Context的值。
const ShareBox = React.createContext({
foods: ["vegetable", "fruit", "rice"],
name: "大仓库",
count: 100
})
const Test = () => {
return (
<div className={styles.main+" App"}>
<ChildOne/>
<p>Hello world</p>
</div>
);
};
const ChildOne = () => {
return <div>
<ShareBox.Consumer>
{
(ctx) => {
return <div>
{ctx.name} - {ctx.age}
{
ctx.foods.map((item, index) => {
return <div key={index}>{item}</div>
})
}
</div>
}
}
</ShareBox.Consumer>
</div>
}
这里首先通过createContext创建仓库对象,对象内容传入函数:
const ShareBox = React.createContext({
foods: ["vegetable", "fruit", "rice"],
name: "大仓库",
count: 100
})
在跨层级的子孙组件中,通过<仓库名.Consumer>标签包裹的方式,标签内必须是一个回调函数,其中参数对应了仓库里放的值。这种操作在每个组件中都可以用到。
<ShareBox.Consumer>
{
(ctx) => {
return <div>
{ctx.name} - {ctx.age}
{
ctx.foods.map((item, index) => {
return <div key={index}>{item}</div>
})
}
</div>
}
}
</ShareBox.Consumer>
打印:

方法二利用useContext钩子获取:
1)创建Context共享仓库, React.createContext();
2)在需要Context的组件中引入Context;
3) 通过<Context名.Consumer>标签里的一个回调函数获取Context值,回调函数的第一个参数对应了Context的值。
1和2参考方法一里的代码,第三步如下:
const ChildOne = () => {
const ctx = useContext(ShareBox)
return <div>
{ctx.name} - {ctx.age}
{
ctx.foods.map((item, index) => {
return <div key={index}>{item}</div>
})
}
</div>
}
useContext(以Context作为参数) ,会将Context中的数据获取并作为返回值。
Provider的使用:
Provider表示数据的生产者,可以使用它来指定Context中的数据;通过value属性来指定Context中存储的数据,这样一来,在该组件的所以子组件中,都可以通过Context来指定它所指定的数据。
可以通过Context.Provider来修改Context的值。
当组件读取Context中数据时,它会读取离它最近的Provider中的value作为Context的数据。没有Provider则读取的是Context中的默认数据。
const ShareBox = React.createContext({
foods: ["vegetable", "fruit", "rice"],
name: "大仓库",
count: 100
})
const Test = () => {
return (
<div className={styles.main + " App"}>
<ShareBox.Provider value={
{
foods: ["1", "2", "3"],
name: "小仓库",
count: 50
}
}>
<ChildOne/>
</ShareBox.Provider>
<p>Hello world</p>
</div>
);
};
const ChildOne = () => {
const ctx = useContext(ShareBox)
return <div>
{ctx.name} - {ctx.age}
{
ctx.foods.map((item, index) => {
return <div key={index}>{item}</div>
})
}
</div>
}
效果:父组件修改了Context里的值,子孙组件接收到的是<Xxx.Provider>的value属性里的值。

结合state一起使用:
context可以结合state一起使用来实现动态渲染:
const ShareBox = React.createContext({
foods: ["vegetable", "fruit", "rice"],
name: "大仓库",
count: 100,
addFood: () => {
},
removeFood: () => {
}
})
const Test = () => {
const [data, setData] = useState({
foods: ["vegetable", "fruit", "rice"],
name: "大仓库",
count: 100
})
const addFood = () => {
setData({...data, foods: ["vegetable", "fruit", "rice", "chicken"]})
}
const removeFood = () => {
setData({...data, foods: ["vegetable", "fruit"]})
}
return (
<div className={styles.main + " App"}>
<ShareBox.Provider value={
{
...data,
addFood,
removeFood
}
}>
<ChildOne/>
</ShareBox.Provider>
<p>Hello world</p>
</div>
);
};
const ChildOne = () => {
const ctx = useContext(ShareBox)
return <div>
{ctx.name} - {ctx.age}
{
ctx.foods.map((item, index) => {
return <div key={index}>{item}</div>
})
}
<button onClick={ctx.addFood}>添加</button>
<button onClick={ctx.removeFood}>删除</button>
</div>
}
四、网络请求框架使用
见我博客:
前端框架 网络请求 Fetch Axios_Dragon Wu的博客-CSDN博客
五、React路由的使用
常用API
官方文档:React Router: Declarative Routing for React.js
React Router可以将url地址和组件进行映射,当用户访问某个地址时,与其对应的组件会自动的挂载。
使用React Router的步骤:
1、安装依赖
2、在入口文件中引入BrowserRouter组件
3、将BrowserRouter设置为根组件
Route标签:
将路由与组件进行映射。
<Route path={"/home"} element={<Home />} />
// 有两个参数,path的值为映射的URL地址,element为要挂载的组件(版本5里叫component)
NavLink标签:
在版本5里是Link标签。
设置点击后的样式格式:

<NavLink to={"/about"}>关于</NavLink>
// 类似于a标签,点击后会发生页面跳转,不同在于a标签每次跳转会重新加载页面,而NavLink不会重新加载页面,Nav是局部跳转(速度比a标签快,网络开销小)。
<NavLink/> 与<Link/>类似,但增加了一个点击之后导航高亮的效果 ,它有一个默认的类名active ,通过这个类名可以修改高亮的颜色
<Link to> : 尾随/ 的问题
在v5中,如果当前 url 是/home, 那么<Link to="news"> 会渲染成 <a href="/news">; 而如果当前 url 是 /home/,那么又会渲染成 <a href="/home/news">在v6中,无论当前 url 是/home还是/home/,<Link to="news">都会渲染成<a href='/home/news'>。
useParams:
通过useParams钩子函数可以获取URL里传入的参数。
function Detail () {
console.log(useParams())
return (
<div>
<h2>详情</h2>
<p>id: {useParams().id}</p>
</div>
)
}
useLocation:
通过useLocation钩子函数可以获取URL的基本信息。
console.log(useLocation())
打印:

useMatch:
useMatch用于检查当前url是否匹配某个路由。
useMatch(路由字符串)
console.log(useMatch("/detail/123"))
// 不匹配就返回null,匹配返回一个对象
useMatch("/user/:id")
// 也支持动态参数匹配

useNavigate:
useNavigate获取一个跳转页面的函数。相当于通过代码来执行跳转。默认是push方法,会产生历史记录;可以设置replace为true来实现redirect的效果(不参数历史记录)
使用方法:
function Detail() {
const route = useNavigate()
const go = () => {
// route("/about")
route("/about", {replace: true})
}
return (
<div>
<h2>详情</h2>
<button onClick={go}>跳转</button>
</div>
)
}
Navigate标签:
组件用来跳转到对应的位置,默认使用push,会产生历史记录。可设置replace属性,变成redirect模式,将不会产生历史记录。
<Navigate to={"/about"} replace />
//当我们将这标签放到某个组件中是,组件在加载到这个标签的第一时间会产生跳转。
参考:
React Router 6 (React路由) 最详细教程-阿里云开发者社区
6.v新特性:
首先安装依赖:
yarn add react-router-dom@6
声明式导航
(推荐使用编程式导航效率更高)
案例代码:一般使用BrowseRouter有history记录
import React, { Component } from "react"
import { BrowserRouter, Routes, Route, Navigate, NavLink, useParams,Link,Outlet } from "react-router-dom"
class Home extends Component {
render () {
return (
<div>
<h1>Home</h1>
</div>
)
}
}
class About extends Component {
render () {
return (
<div>
<h1>About</h1>
</div>
)
}
}
function Detail () {
console.log(useParams())
return (
<div>
<h2>详情</h2>
<p>id: {useParams().id}</p>
</div>
)
}
function Main(){
return (
<div>
<h1>文档</h1>
<Link to={"/doc/one"}>文档1</Link>
<Link to={"/doc/two"}>文档2</Link>
<Link to={"/doc/three"}>文档3</Link>
{/*嵌套路由时注意书写这个标签*/}
<Outlet/> {/* 指定路由组件呈现的位置 */}
</div>
)
}
function App () {
return (
<div>
<BrowserRouter>
<div>
<NavLink to={"/home"}>首页</NavLink>|
<NavLink to={"/about"}>关于</NavLink>|
<NavLink to={"/detail/123"}>详情</NavLink>|
<NavLink to={"/doc"}>文档</NavLink>
</div>
<Routes>
<Route path={"/home"} element={<Home />} />
<Route path={"/about"} element={<About />} />
{/*嵌套路由*/}
<Route path={"doc"} element={<Main/>}>
<Route path={"one"} element={<h3>one</h3>} />
<Route path={"two"} element={<h3>two</h3>} />
<Route path={"three"} element={<h3>three</h3>} />
</Route>
{/*动态路由*/}
<Route path={"/detail/:id"} element={<Detail />} />
{/*路由重定向,也可用于404处理*/}
<Route path="/*" element={<Navigate to="/home" replace />} />
{/*404处理*/}
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
</div>
)
}
export default App
// 用来作为 404 页面的组件
const NotFound = () => {
return <div>你来到了没有知识的荒原</div>
}
编程式导航
(1)新建src/router/index.js文件
import { Navigate } from "react-router-dom"
import Home from "../page/home/Home"
import HomeLeft from "../page/home/HomeLeft"
import HomeRight from "../page/home/HomeRight"
import About from "../page/About"
import Detail from "../page/Detail"
const routes=[
{
path: "/",
element: <Navigate to="/home" />
},
{
path: "home",
element: <Home />,
children: [
{
index: true,
element: <HomeLeft />
},
{
path: "right",
element: <HomeRight />
}]
},
{
path: "/about",
element: <About />
},
{
path: "/detail",
element: <Detail />
},
{ path: "*", element: <p>404页面不存在</p> }
]
export default routes
(2)为src/index.js添加BrowserRouter容器
注意: BrowserRouter必须在App标签的外层

(3)App.js如下:
import React from 'react'
import routes from "./router/index.js"
import {useRoutes,NavLink } from "react-router-dom"
function App () {
// useRoutes可以用路由表生成<Routes>...</Routes>结构
// 根据路由表生成对应的路由规则
const element = useRoutes(routes)
return(
<div id="App">
<div>
<NavLink to={"/home"} >首页</NavLink>|
<NavLink to={"/about"}>关于</NavLink>|
<NavLink to={"/detail"}>详情</NavLink>
</div>
<div>
{element}
</div>
</div>
)
}
export default App
(4)若有嵌套路由要使用<Outlet/>标签标明子组件插入的位置
class Home extends Component {
render () {
return (
<div>
<h1>Home</h1>
<Link to={"/home/right"}>homeRight</Link>
<Link to={"/home"}>homeLeft</Link>
{/*嵌套路由时注意书写这个标签*/}
<Outlet/> {/* 指定路由组件呈现的位置 */}
</div>
)
}
}
编程式跳转
默认是push 模式
export default function HomeNews() {
const navigate = useNavigate();
const jump = ()=>{
navigate('/home')
}
return (
<News>
<button onClick={jump}>点击跳转</button>
</News>
)
}
使用{replace:true} 就会变为replace模式
navigate('/home', { replace: true });
也可以使用 navigate(-1) 传入一个数字来进行跳转
navigate(1)//传入数字
六、Anti Desgin的使用实现UI界面
官方文档:Ant Design - A UI Design Language
1、添加到项目:
yarn add antd
2、样式引入
全局映入样式:在src/index.js里引入样式,不推荐,会导入很多无用的样式
import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'
按需映入,推荐
下面两种方式都可以只加载用到的组件。
-
使用 babel-plugin-import(推荐)。
-

-
yarn add babel-plugin-import// .babelrc or babel-loader option { "plugins": [ ["import", { "libraryName": "antd", "style": "css" }] // `style: true` 会加载 less 文件 ] }
-
然后只需从 antd 引入模块即可,无需单独引入样式。等同于下面手动引入的方式。
// babel-plugin-import 会帮助你加载 JS 和 CSS import { DatePicker } from 'antd'; -
手动引入
import DatePicker from 'antd/lib/date-picker'; // 加载 JS import 'antd/lib/date-picker/style/css'; // 加载 CSS // import 'antd/lib/date-picker/style'; // 加载 LESS
3、组件里直接调用即可

七、Redux中央仓库的使用
当我们的项目稍微复杂一些时,原生的state可能无法高效的管理和操作数据缓存,通过Redux可以将数据统一管理,并且减低代码耦合。


其工作原理与hook reducer类似
yarn add @reduxjs/toolkit
安装参考:安装 | Redux 中文官网
1、新建src/redux/index.js用于存放redux的文件
//1、引入redux
import { configureStore } from "@reduxjs/toolkit"
//2、创建仓库
const store = configureStore({reducer})
//3、reducer为store服务的执行者, action={type:"",data:5}
function reducer (preState = 10, action) {
switch (action.type) {
case "add":
return preState + action.data
case "sub":
return preState - action.data
default:
return preState
}
}
//4、使用store
console.log(store)
//获取仓库的数据
console.log(store.getState())
//触发action
store.dispatch({
type: "add",
data: 5
})
console.log(store.getState())
2、在src/index.js里引入

运行结果:可以看到数据已经被操作了

3、可以通过logger查看redux日志
yarn add redux-logger
添加logger后:
//1、引入redux
import { configureStore } from "@reduxjs/toolkit"
//引入日志
import { logger } from "redux-logger/src"
//2、创建仓库
// const store = configureStore({reducer})
const store = configureStore({
reducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger)
})
//3、reducer为store服务的执行者, action={type:"",data:5}
function reducer (preState = 10, action) {
switch (action.type) {
case "add":
return preState + action.data
case "sub":
return preState - action.data
default:
return preState
}
}
//4、使用store
console.log(store)
//获取仓库的数据
console.log(store.getState())
//触发action
store.dispatch({
type: "add",
data: 5
})
console.log(store.getState())
可以看到日志的打印:

合并多个reducer
//1、引入redux
import { configureStore} from "@reduxjs/toolkit"
//引入日志
import { logger } from "redux-logger/src"
//2、创建仓库
// const store = configureStore({reducer})
const store = configureStore({
//合并多个reducer
reducer:{
reducer,
reducer2
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger)
})
//3、reducer为store服务的执行者, action={type:"",data:5}
function reducer (preState = 10, action) {
switch (action.type) {
case "add":
return preState + action.data
case "sub":
return preState - action.data
default:
return preState
}
}
function reducer2 (preState = { user: "", num: 5 }, action) {
const { type, data } = action
let newState = { ...preState }
switch (type) {
case "addUser":
newState.user = data.user
return newState
case "delUser":
newState.user = ""
return newState
default:
return newState
}
}
//4、使用store
console.log(store)
//获取仓库的数据
console.log(store.getState())
// //触发action
store.dispatch({
type: "add",
data: 5
})
console.log(store.getState())
store.dispatch({
type: "addUser",
data: {
user: "大猫"
}
})
store.dispatch({
type: "delUser"
})
运行结果:

4、Redux的模块化管理
项目书写时我们需要加上命名空间以避免重复:

另外,我们还需要将常量单独提取到一个文件里管理:

分类管理reducers:

reducer/index.js: 引入所有reducers方便调用
import { count } from "./countReducer"
import {user} from "./userReducer"
export const reducers={
count,
user
}
redux/index.js引入reducer的索引文件即可:
//1、引入redux
import { configureStore} from "@reduxjs/toolkit"
//引入日志
import { logger } from "redux-logger/src"
//引入reducers
import { reducers } from "./reducers"
//2、创建仓库
// const store = configureStore({reducer})
const store = configureStore({
//合并多个reducer
reducer: reducers,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger)
})
5、注意事项
返回值注意:
书写时最好按照这种格式的逻辑:


redux默认不支持异步操作,一般实现思路是等异步任务做完后同步回调时进行redux操作:

6、总结Redux使用的完整步骤
安装依赖:
yarn add @reduxjs/toolkit
yarn add react-redux
日志依赖:
yarn add redux-logger
(1)创建store仓库,reducers分开管理
reducers: 数据开发中type字符串应放入一个常量管理

export function count(preState={num:0},action){
const {type,data}=action
let newState={...preState}
switch (type) {
case "num/add":
newState.num+=data.num
return newState
case "num/sub":
newState.num-=data.num
return newState
default:
return newState
}
}
export function user(preState={user:{name:"",age:1}},action){
const {type,data}=action
let newState={...preState}
switch (type) {
case "user/update":
newState.user=data.user
return newState
case "user/delete":
newState.user={name:"",age:1}
return newState
default:
return newState
}
}
索引所有reducers:
import { count } from "./countReducer"
import { user } from "./userReducer"
export const reducers = {
count,
user
}
创建store仓库并开启日志:
//1、引入redux
import { configureStore } from "@reduxjs/toolkit"
//引入日志
import { logger } from "redux-logger/src"
//导入reducers
import { reducers } from "./reducers"
//2、创建仓库
// const store = configureStore({reducer})
const store = configureStore({
//合并多个reducer
reducer: reducers,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger)
})
export default store
(2)导入到src/index.js文件,全局配置

(3)组件中的使用
import React from "react"
import { connect } from "react-redux"
function ReduxTestComp (props) {
return (
<div>
<h1>count: {props.count.num}</h1>
<h1>user:{props.user.user.age},{props.user.user.name}</h1>
<button onClick={()=>props.updateUser({user:{ name: "Jack", age: 12 }})}>修改用户</button>
<button onClick={()=> props.deleteUser()}>删除用户</button>
<button onClick={()=>props.addNum()}>+</button>
</div>
)
}
export default connect((state) => {//读取仓库中所有state
console.log(state)
return {
count: state.count,
user: state.user
}
}, (dispatch) => {//action操作
console.log(dispatch)
return {
updateUser: (data) => {
return dispatch({ type: "user/update", data: data })
},
deleteUser: ()=>{
return dispatch({type:"user/delete"})
},
addNum:()=>{
return dispatch({type:"num/add",data:{num:12}})
}
}
})(ReduxTestComp)
import React from "react"
import ReduxTestComp from "./component/reduxTest/ReduxTestComp"
function App () {
return (
<div id="App">
<ReduxTestComp />
</div>
)
}
export default App
至此,实现redux的全程应用。
八、Redux Tool使用总结
见我的这篇博客:前端框架 Redux tool RTK 总结-CSDN博客
更多推荐


所有评论(0)