useState 遍历渲染 绑定事件
一:变量和方法的定义,写法
const message = "this is message"
function App() {
return(
<div>
<h1>this is title</h1>
{message}
</div>
)
}
1.变量
变量被定义在组件之外:变量可以在整个文件里使用
变量被定义在组件之内:变量只可以在组件内部使用
const message = "this is message"
2.方法
一般方法写在组件内部
getMessage 是一个方法,返回一段文本,并在 return 语句中调用它。
function App() {
// 方法的声明
const getMessage = () => {
return "this is a function message";
};
return (
<div>
<h1>this is title</h1>
{getMessage()} // 方法的调用
</div>
);
}
3.return语句
return语句中写的是HTML,渲染到页面上的内容
4.顺序
一般来说,React 组件内部的代码组织顺序是:变量 方法 return语句(HTML 代码)
5.{}作用
通过{}识别表达式,比如变量,函数调用,方法调用等
注意:if switch语句,变量声明属于语句,不是表达式,不能出现在{}中
// ①使用引号传递字符串
{"abc"}
// ②使用js变量
const count = 100
{count}
// ③函数调用和方法调用
function getName(){
return "jack"
}
{getName()}
{new Date().getDate()}
// ④使用js对象
<div style={{color:"red"}}>this is div</div>
二:遍历列表 map
※:加上key(id) item:表示遍历出来的每一项
function App() {
const list = [
{ id: 1001, name: "Vue" },
{ id: 1002, name: "React" },
{ id: 1003, name: "Angular" }
];
return (
<ul>
{list.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
export default App;
三:条件渲染
1.单个元素
通过&&控制单个元素的显示与隐藏
2.两个元素
三元运算符
const isLogin = true
// 单个元素
{isLogin && <span>this is span</span>}
// 两个元素
{isLogin?<span>jack</span>:<span>loading</span>}
3.复杂条件渲染
自定义函数+if判断语句
// 定义文章类型
const articleType = 3 // 有三种情况 0 1 3
// 定义核心函数:根据文章类型返回不同的jsx模板
function getArticleTem(){
if(articleType === 0){
return <div>无图文章</div>
}else if(articleType === 1){
return <div>单图文章</div>
}else{
return <div>三图文章</div>
}
}
function App(){
return (
<div className="App">
// 调用函数渲染不同的模板
{getArticleTem()}
</div>
)
}
四:事件绑定
1.基础绑定
on+事件名称={事件处理程序} 遵循驼峰命名
绑定点击事件:onClick={clickHandler}
function App(){
const clickHandler = ()=>{
console.log("button按钮点击了")
}
return(
<button onClick={clickHandler}></button>
)
}
2.事件参数e
function App(){
const clickHandler = (e)=>{
console.log("button按钮点击了",e)
}
return <button onClick={clickHandler}>click me</button>
}
3.传递自定义参数
事件绑定的位置改造成箭头函数的写法,在执行clickHandler实际处理业务函数的时候传递实参
onClick 事件本身并不会自动传递参数。所以,通过箭头函数,能够将自定义的参数 "jack" 传递给事件处理函数。
function App(){
const clickHandler = (name)=>{
console.log("button按钮点击了",name)
}
return <button onClick={() => clickHandler("jack")}>click me</button>
}
4.既要传递自定义参数,而且还要事件对象e
function App(){
const clickHandler = (name,e)=>{
console.log("button按钮点击了",name,e)
}
return <button onClick={(e) => clickHandler("jack",e)}>click me</button>
}
五:组件基础使用
一个组件就是首字母大写的函数,内部存放了组件的逻辑和视图ui,渲染组件只需要把组件当成标签书写即可
// 1.定义组件
function Button(){
// 组件内部逻辑
return <button>click me</button>
}
// 2.使用组件
function App(){
return (
<div>
// 自闭和
<Button />
</div>
)
}
六:useState
1.概念
useState是用来给组件添加状态变量的,状态变量一旦发生变化组件的视图ui也会跟着变化
// 导入
import {useState} from "react"
// 1.useState是一个函数,返回值是一个数组
// 2.数组中的第一个参数是状态变量,第二个参数是set函数用来修改状态变量
// 3.useState的参数将作为count的初始值
const [count, setCount] = useState(0)
// useState实现一个计数器按钮
import {useState} from "react"
function App(){
// 1.调用useState添加一个状态变量
// count:状态变量 setCount:修改状态变量的方法
const [count, setCount] = useState(0)
// 2.点击事件回调
const handleClick =()=>{
// 作用:用传入的新值修改count,重新使用新的count去渲染ui
setCount(count+1)
}
return (
<div className="App">
// 使用组件
<button onClick={handleClick}>{count}</button>
</div>
)
}
2.useState修改状态的规则
状态是只读的,只能替换不能修改
使用新值替换老值,使用新对象替换老对象
修改对象状态:传给set方法一个全新的对象来进行修改
使用展开运算符 ...form 来保留 form 对象中的其他属性,避免覆盖整个对象。
const [form, setForm] = useState({
name:"jack",
})
const handleChangeName = ()=>{
setForm({
...form,
name:"john",
})
}
七:基础样式控制-class类名控制
className="foo"
①index.css文件
.foo{
color:blue;
}
②App.js文件
// 导入样式
import "./index.css"
function App(){
return (
<div className="App">
// 自闭和
<span className="foo">this is class foo</span>
</div>
)
}
八:classnames优化类名控制
classnames用来根据条件动态地合并类名。
// 安装方式
npm install classnames
// 使用了classnames库来动态设置React组件中的className
import classNames from "classnames"
className={classNames("nav-item",{active:type === item.type})}
九:案例-评论列表
1.渲染评论列表--map方法遍历
※:只要数据会影响到视图的变化,都应该用useState去做维护
※:状态变量必须有一个初始值。commentList的初始值useState(list)
// 1.使用useState维护评论列表list
const[commentList,setCommentList]=useState(list)
// 2.遍历渲染评论项
{commentList.map(item=> (
<div key={item.rpid}>评论项</div>
))}
// 3.修饰细节 头像 用户名 评论内容 评论时间 评论数量
{item.user.avatar}
{item.user.uname}
{item.content}
{item.ctime}
{item.like}
2.删除自己的评论
只有自己的评论才显示删除按钮,点击删除按钮,删除评论,列表中不再显示
思路:①条件渲染判断是否是自己的评论
②通过id去做匹配,过滤(用户id和评论列表中id)
⭐filter函数返回一个新数组,不更改老数组。类似于一个漏斗,满足条件的留下,不满足条件的漏下去。比如:一共有五条评论,id分别为12345,用户id和评论区id都为3,那么就把3漏下去,把1245作为一个新的数组返回。保留所有rpid !== id 的评论(即删除 rpid === id 的评论)
⭐所有id都是评论ID(rpid),并不是用户 ID(uid)。
⭐className="delete-btn" 用于为这个 元素添加 CSS 样式
// 条件:user.id=== item.user.id
// 删除功能
const handleDel = (id)=>{
console.log(id)
// 对commentList做过滤处理
setCommentList(commentList.filter(item=> item.rpid !== id ))
}
{user.uid=== item.user.uid &&
<span className="delete-btn" onClick={()=> handleDel(item.rpid)}>
删除
</span>}
3.实现渲染导航Tab和高亮
点击哪个tab项,哪个做高亮处理
点击谁就把谁的标识记录下来,然后和遍历时的每一项标识做匹配,谁匹配到就设置负责高亮的类名
const [type,setType] = useState("hot")
const handleTabChange =(type) =>{
console.log(type)
setType(type)
}
const tabs = [
{type:"hot",text:"最热"},
{type:"time",text:"最新"},
]
// 高亮类名 active
{tabs.map(item=>
<span
key={item.type}
onClick={()=>handleTabChange(item.type)}
className={"nav-item ${type === item.type && "active"}"}>
{item.text}
</span>)}
父子通信 useEffect
一:受控表单绑定
通过react状态去控制Input框的状态
监听输入框内容变化,每当用户输入时,e.target.value(输入框的值)会更新 value,从而实现输入框内容与状态同步。
import {useState} from "react"
// 1.准备一个React状态值
const [value,setValue] = useState("")
// 2.通过value属性绑定状态,通过onChange属性绑定状态同步的函数
// 通过事件参数e拿到输入框的最新的值,反向修改到react状态身上
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
二:react中获取dom(使用useRef)
使用useRef获取input输入框的DOM对象
①1.useRef生成ref对象,并与JSX绑定
②2.dom可用时,通过inputRef.current拿到dom对象
import {useRef} from "react";
function App(){
// 1. 创建一个 ref 对象,并初始化为 null
const inputRef = useRef(null)
// 2. 定义一个函数,点击按钮时获取 input 的 DOM 对象
const showDom =()=>{
console.log(inputRef.current) // 输出 input 的 DOM 对象
}
return(
<div>
{/* 3. 绑定 ref 到 input 元素 */}
<input type="text" ref={inputRef} />
{/* 4. 点击按钮时执行 showDom 函数 */}
<button onClick={showDom}>获取dom</button>
</div>
)
}
三:案例-发表评论
1.核心功能实现
①用数控绑定方式,可以把用户输入的东西,收集到content里面
②点击时发布评论,替换content字段,收集到的数据是什么就绑定什么
③延续数据修改不可变的方式,原本的所有数据都展开,增加一条自己的数据
※需求:获取评论内容,点击发布按钮发布评论
⭐setCommentList([...commentList, newComment]):
...commentList 复制已有的评论列表。
{...} 创建一个新评论对象,并添加到列表中。
setCommentList() 更新 commentList 状态,让页面重新渲染,显示新评论。
// 发表评论
const [content,setContent] = useState("")
const handPublish =()=>{
setCommentList([
...commentList,
{
rpid:100,
user:{
uid:"30009257",
avatar,
uname:"黑马前端",
},
content:content,
ctime:"10-19 09:00",
like:66,
}
])
}
// 评论框
<textarea
className="reply-box-textarea"
placeholder="发一条友善的评论"
value={content}
onChange={(e)=>setContent(e.target.value)}
/>
// 发布按钮
<div className="reply-box-send">
<div className="send-text" onClick={handPublish}>发布</div>
</div>
2.id和时间处理
需求:rpid要求一个唯一的随机数id(uuid)
ctime要求以当前时间为标准,生成固定格式(dayjs)
// 安装uuid
npm install uuid
// 使用方式
import {v4 as uuidv4} from "uuid";
uuidv4();
// 安装Day.js
npm install dayjs
// 使用方式
import dayjs from "dayjs"
Format
{
import {v4 as uuidV4} from "uuid"
import dayjs from "dayjs"
rpid:uuidV4(), //随机id
user:{
uid:"30009257",
avatar,
uname:"黑马前端",
},
content:content,
ctime:dayjs(new Date()).format("MM-DD hh:mm"),// 格式化 月-日 时:分
like:66,
}
3.清空内容和聚焦实现
需求:清空内容-把控制input框的value状态设置为空串
重新聚焦-拿到input的dom元素,调用focus方法
inputRef.current.focus() 让输入框自动获取焦点
// 1.清空输入框内容
setContent("")
// 2.重新聚焦 dom(useRef) -focus
const inputRef = useRef(null)
inputRef.current.focus()
// 评论框
<textarea
className="reply-box-textarea"
placeholder="发一条友善的评论"
ref={inputRef}
value={content}
onChange={(e)=>setContent(e.target.value)}
/>
四:父子组件通信
组件之间的数据传递
1.父传子基础实现
步骤:①父组件传递数据-在子组件标签上绑定属性
②子组件接收数据-子组件通过props参数接收数据
props:包含着父组件传递过来的所有数据
父组件App通过props向子组件Son传递数据,然后子组件Son在渲染时接收并显示这个数据。
function Son(props){
console.log(props)
return <div> this is son,{props.name}</div>
}
function App(){
const name = "this is app name"
return(
<div>
<Son name = {name}/> {/* 传递name变量给Son组件 */}
</div>
)
}
2.props说明
可以传递任意的数据:数字,字符串,布尔值,数组,对象,函数,jsx
props是只读对象,子组件只能读取props中的数据,不能直接进行修改,父组件的数据只能有父组件修改
3.父传子children说明
父组件通过children传递子元素给Son组件,然后Son组件可以通过 props.children渲染这个子元素。
props.children:Son组件的子元素
function Son(props){
console.log(props)
// props.children在这里是<span> this is span </span>
return <div> this is son,{props.children}</div>
}
function App(){
return(
<div>
<Son>
<span> this is span</span>
</Son>
</div>
)
}
4.子传父实现
在子组件中调用父组件中的函数并传递实参
父组件通过props传递一个函数getMsg给子组件,然后子组件在点击按钮时调用这个函数,并把自己的数据sonMsg传回给父组件。
function Son({onGetSonMsg}){
// Son组件中的数据
const sonMsg = "this is son msg"
return(
<div>
this is Son
<button onClick ={()=>onGetSonMsg(sonMsg)}>sendMsg</button>
</div>
)
}
function App(){
const [msg,setMsg] = useState("")
const getMsg = (msg)=>{
console.log(msg)
setMsg(msg)
}
return(
<div>
this is App,{msg}
<Son onGetSonMsg = {getMsg}/>
</div>
)
}
五:useEffect
1.概念理解和基础使用
用来执行一些“自动触发的操作”,而不是等到用户点击按钮、输入内容等事件才执行。可以想象成一个“自动运行的代码块”:当组件加载(渲染)时,就会自动执useEffect里的代码,当某些数据变化时(比如 state 发生变化),useEffect也可以自动执行。
useEffect 不需要用户手动触发(比如点击按钮),它会在页面加载或状态变化时自动执行。比如:页面一打开就执行代码,或某个状态变化后自动执行代码
你只管写在useEffect里,React会帮你找合适的时机自动执行!
useEffect(() =>{},[])
// 参数1是一个函数,在函数内部可以放置要执行的操作
// 参数2是一个数组,在数组里放置依赖项,不同依赖项会影响第一个参数函数的执行,
// 当是一个空数组的时候,只会在组件组件首次渲染时执行一次
⭐作用
①首次进入页面后,初始化操作
②监视某个值的变化
③退出页面后,执行收尾操作
2.不同依赖项说明
①空数组依赖:只在初始渲染时执行一次
import { useEffect, useState } from "react";
function App() {
// 2.传入空数组依赖 初始执行一次
useEffect(()=>{
console.log("副作用函数执行了")
},[])
return (
<div>
this is App
<button onClick ={()=>setCount(count +1)}>+{count}</button>
</div>
)
}
export default App;
②添加特定依赖项:组件初始渲染+特性依赖项变化时执行
import { useEffect, useState } from "react";
function App() {
const [count,setCount] = useState(0)
// 3.传入特定依赖项 初始 + 依赖项变化时执行
useEffect(()=>{
console.log("副作用函数执行了")
},[count])
return (
<div>
this is App
<button onClick ={()=>setCount(count +1)}>+{count}</button>
</div>
)
}
export default App;
3.清除副作用
在useEffect里,“副作用”指的是组件渲染时需要执行的额外操作,比如:
发送网络请求,操作 DOM,订阅事件,定时器。这些操作不是组件渲染UI所必需的,但又是程序运行时需要的,所以叫“副作用”。
React提供了清理副作用的方法,就是在useEffect里返回一个函数,这个函数会在组件卸载或useEffect重新执行时运行,负责清除不需要的副作用。
useEffect是“开灯”,但如果灯一直亮着没人关,就会浪费电。所以React允许你在组件卸载时“关灯”,就是清除副作用!
useEffect(() =>{
// 实现副作用操作逻辑
return()=>{
// 清除副作用逻辑
}
},[])
在Son组件渲染时开启一个定时器,卸载时清除这个定时器
①Son 组件在渲染时启动一个定时器,每隔 1 秒输出 "定时器执行中"。
②App 组件包含一个 show 状态,控制 Son 组件是否显示。
③点击按钮 setShow(false) 会卸载 Son 组件,同时 useEffect 清理定时器,防止定时器继续运行。
import { useEffect, useState } from "react";
function Son(){
// 1.渲染时开启一个定时器
useEffect(()=>{
const timer = setInterval(()=>{
console.log("定时器执行中")
},1000)
return () =>{
// 清除副作用 组件卸载时
clearInterval(timer)
}
},[])
return <div>this is son</div>
}
function App() {
// 通过条件渲染模拟组件卸载
const [show,setShow] = useState(true)
return (
<div>
{show && <Son />}
<button onClick ={()=>setShow(false)}>卸载Son组件</button>
</div>
)
}
export default App;
六:自定义hook实现
useToggle是一个自定义Hook,用于管理 true/false 状态。
好处是让toggle逻辑可以复用,多个组件可以直接使用。
按钮点击时,toggle() 切换 value,从而控制
import { useEffect, useState } from "react";
// 封装自定义hook
// 1.声明一个以use打头的函数
// 2.在函数体内封装可复用的逻辑 只要是可复用的逻辑
// 3.把组件中用到的状态或者回调return出去 以对象或者数组
// 4.在哪个组件中要用到这个逻辑,就执行这个函数,解构出来状态和回调进行使用
function useToggle(){
// 可复用的逻辑代码
const [value,setValue] = useState(true)
const toggle = ()=>setValue(!value)
// 哪些状态和回调函数需要在其他组件中使用 return
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;
七:reacthooks使用规则说明
1.只能在组件中或者其他自定义hook函数中调用
2.只能在组件的顶层调用,不能嵌套在if for其他函数中
Redux
一:Redux
在React里,组件之间传递数据通常要一层层用props传递,会变得很麻烦。Redux通过 集中管理数据,让所有组件都可以直接从Store获取数据,避免了繁琐的传递。
Store(存储):存储整个应用的状态数据(state),并且只能通过dispatch(action) 来修改。
使用步骤
①定义一个reducer函数
reducer是一个函数,用于接收旧的state和action,并返回新的state
它根据action生成新的state,不能直接修改原来的state,必须返回一个新对象
在Redux中,所有的状态更新都必须通过Reducer进行,而不能直接修改state
组件触发dispatch(action),让Reducer更新state
reducer函数的参数必须有两个,分别是:
state:当前的状态(需要它来存储和管理数据)
action:要执行的操作(需要通过它决定如何修改数据)包含type(操作类型和 payload(数据)
// 计数器(加1、减1)
function reducer(state = { count: 0 }, action) {
if (action.type === "INCREMENT") {
return { count: state.count + 1 };
}
if (action.type === "DECREMENT") {
return { count: state.count - 1 };
}
return state; // 没有匹配的action,返回原来的state
}
②创建store实例
store就是Redux的数据中心,它存储所有的状态。
createStore(reducer) 用来创建 store
import { createStore } from "redux";
const store = createStore(reducer);
③订阅store数据变化(subscribe)
subscribe作用是:当Redux里的数据变化时,就会自动触发这个函数。
每次store里的数据有变化,就会执行subscribe里的回调函数
store.getState() 获取最新的数据并打印出来
store.subscribe(() => {
console.log("状态更新了:", store.getState());
});
④ 提交action修改数据(dispatch)
dispatch(action)触发reducer 进行数据修改
dispatch需要传一个action对象,type表示要执行的操作。
这时reducer会修改状态,然后store更新数据,触发subscribe里的回调
store.dispatch({ type: "INCREMENT" }); // 让 count +1
store.dispatch({ type: "INCREMENT" }); // 再加 1
store.dispatch({ type: "DECREMENT" }); // 让 count -1
⑤获取最新的state
store.getState()可以获取当前store里的最新数据
每次dispatch执行后,可以用getState()查看数据是否正确更新了
console.log("最终 count:", store.getState().count);
⭐完整代码
import { createStore } from "redux";
// 1. 定义 reducer(管理状态)
function reducer(state = { count: 0 }, action) {
if (action.type === "INCREMENT") {
return { count: state.count + 1 };
}
if (action.type === "DECREMENT") {
return { count: state.count - 1 };
}
return state;
}
// 2. 创建 store
const store = createStore(reducer);
// 3. 订阅 store(数据变化时触发)
store.subscribe(() => {
console.log("状态更新了:", store.getState());
});
// 4. 触发数据修改(dispatch)
store.dispatch({ type: "INCREMENT" });
store.dispatch({ type: "INCREMENT" });
store.dispatch({ type: "DECREMENT" });
// 5. 获取最新状态
console.log("最终 count:", store.getState().count);
二:Redux与React-环境准备
在React中使用redux,官方要求安装两个其他插件
1.Redux Toolkit
2.react-redux
// 1.使用CRA快速创建React项目
npx create-react-app react_redux
// 2.安装配套工具
npm i @reduxjs/toolkit react-redux
// 3.启动项目
npm run start
3.store目录结构
✅ store/ 目录用来集中管理Redux的状态。
✅ modules/ 目录拆分不同的业务模块(user.js、cart.js)。
✅ index.js 负责整合modules/ 里的reducer并创建store。
✅ React组件可以用useSelector获取store里的数据,用useDispatch修改数据。
三:Redux与React-实现计数器counter
1.Redux store配置
①配置counterStore模块
counterStore.js负责定义计数器的状态(state),并提供修改state的方法(reducer)
createSlice():定义state状态,可以在initialState中定义该模块的初始状态。
创建reducer函数,可以在reducers中定义如何修改state的方法,这些方法会自动变成Redux的reducer。
会自动生成action对象,你可以直接使用它们来触发state的变化
createSlice()把原本需要手写action、reducer和action创建函数的步骤合并成了一个更简洁的方法。
import { createSlice } from "@reduxjs/toolkit";
const counterStore = createSlice({
name: "counter", // 这个 store 的名字
initialState: { count: 0 }, // 初始状态,count 设为 0
reducers: {
increment(state) { state.count++ }, // 增加 count
decrement(state) { state.count-- }, // 减少 count
}
});
// 导出 action 创建函数
// 提取increment和decrement,作为action创建函数
const { increment, decrement } = counterStore.actions;
// 获取 reducer
// 提取counterReducer,作为Redux store需要的reducer
const counterReducer = counterStore.reducer;
// 导出increment和decrement,供React组件调用。
// 导出counterReducer,让store认识这个reducer。
export { increment, decrement };
export default counterReducer;
②配置根store并组合counterStore模块
配置store/index.js
configureStore创建store方法,比createStore()更简单
通过configureStore创建全局store
把counterStore里的reducer组合进store
import {configureStore} from "@reduxjs/toolkit"
import counterReducer from "./modules/counterStore"
// 创建根store组合子模块
// configureStore() 是Redux Toolkit提供的简化版 createStore()
const store = configureStore({
reducer:{
counter:counterReducer // 把counterStore加入store
}
})
export default store
2.React组件
①在React里注入store
Provider用于将Redux store提供给React应用的所有组件,让它们可以访问store里的状态
App组件(和所有子组件都可以通过useSelector获取store里的数据,并通过useDispatch修改数据。
import store from "./store";
import { Provider } from "react-redux";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}> // 让React组件都能访问Redux里的state
<App />
</Provider>
);
②在React组件里使用store里的数据
用useSelector获取store里的count并在组件中显示。
import { useSelector } from "react-redux";
// useSelector(state => state.counter)
// state.counter → 获取counterStore里的state
// { count } → 取出count并赋值给变量
const { count } = useSelector(state => state.counter);
③在React组件里修改store里的数据
用useDispatch触发increment和decrement方法,从而修改store里的count
import { useDispatch, useSelector } from "react-redux";
// 导入 action 创建函数
import { increment, decrement } from "./store/modules/counterStore";
function App() {
const { count } = useSelector(state => state.counter); // 获取 count
const dispatch = useDispatch(); // 获取 dispatch 用来提交action,触发reducer里的方法
return (
<div className="App">
<button onClick={() => dispatch(decrement())}>-</button>
<span>{count}</span>
<button onClick={() => dispatch(increment())}>+</button>
</div>
);
}
⭐React组件使用Redux
useSelector获取state
useDispatch触发action修改state
Provider让store可在所有组件使用
四:Redux与React-提交action传参
组件中有两个按钮add to 10 和 add to 20可以直接把count值修改到对应的数字,目标count
值是在组件中传递过去的,需要在提交action的时候传递参数
在reducers的同步修改方法中添加action对象参数,在调用actionCreater的时候传递参数,参数会被传递到action对象payload属性上
store/modules/counterStore.js
定义Redux的状态和修改方法
import {createSlice} from "@reduxjs/toolkit"
const counterStore = createSlice({
name:"counter", // 这个模块的名字
initialState:{
count:0 // count初始值为0
},
// 修改数据的同步方法
reducers:{
increment(state){
state.count++
},
decrement(state){
state.count--
},
// 直接把count设为某个值(使用action传参)
addToNum(state,action){
state.count = action.payload // action.payload里存着传过来的参数
}
}
})
// 解构出创建action对象的函数 {actionCreater}
const {increment,decrement,addToNum} = counterStore.actions
// 获取reducer函数
const counterReducer = counterStore.reducer
// 导出创建action对象的函数和reducer函数
export{increment,decrement}
export default counterReducer
store/index.js
创建 Redux Store
configureStore()创建Redux Store
counterReducer作为counter模块的reducer
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./modules/counterStore"; // 引入counter模块
// 创建Redux Store,管理counter模块
const store = configureStore({
reducer: {
counter: counterReducer, // 这里的key "counter" 影响useSelector的使用
},
});
export default store;
App.js
React组件,使用Redux管理状态
import {useDispatch,useSelector} from "react-redux"
// 导入创建action对象的方法actionCreater
import {increment,decrement,addToNum } from "./store/modules/counterStore"
function App(){
// 获取Redux里的count状态
const {count} = useSelector(state => state.counter)
// 得到dispatch函数
const dispatch = useDispatch()
return(
<div className="App">
// 调用dispatch提交action对象
<button onClick={() => dispatch(decrement())}>-</button>
<span>{count}</span>
<button onClick={() => dispatch(increment())}>+</button>
// 传递参数,直接修改count
<button onClick={() => dispatch(addToNum(10))}>add to 10</button>
<button onClick={() => dispatch(addToNum(20))}>add to 20</button>
</div>
)
}
点击 "add to 10" 按钮时,发生的流程:
①dispatch(addToNum(10))派发(dispatch)一个 action
{ type: "counter/addToNum", payload: 10 }
②这个action被counterReducer处理
addToNum(state, action) {
state.count = action.payload; // action.payload 里存的是 10
}
③count变成10,Redux状态更新
④eact组件 App.js自动重新渲染,显示最新的count
action.payload:
{
type: "counter/addToNum",
payload: 10 // 这个 payload 就是传递过来的参数
}
参数传递的完整流程
①dispatch(addToNum(10)) 触发addToNum
②createSlice()生成的addToNum处理action
③action.payload里保存着10
④state.count = action.payload,所以count变成10
五:Redux与React-异步状态操作
1.创建store的写法保持不变,配置好同步修改状态的方法
2.单独封装一个函数,在函数内部return一个新函数,在新函数中
①封装异步请求获取数据
②调用同步actionCreater传入异步数据生成一个action对象,并使用dispatch提交
3.组件中dispatch的写法保持不变
⭐创建Redux模块(Slice)
createSlice用来创建一个Redux模块
reducers里定义了setChannels(state, action),用于同步更新Redux状态,即把 action.payload 里的频道数据存入 channelList。
const channelStore = createSlice({
name:"channel",
initialState:{
channelList:[]
},
reducers:{
setChannels(state,action){
state.channelList = action.payload
}
}
})
⭐创建异步请求的Action
封装一个异步Action(fetchChannelList),用来获取频道数据并存入Redux。
fetchChannelList是一个返回函数的函数,它的内部:
异步请求 axios.get(url) 获取数据
调用setChannels并dispatch(setChannels(数据)),将获取的数据提交到Redux,更新channelList。
const {setChannels} = channelStore.actions
const url = "http://geek.itheima.net/v1_0/channels"
const fetchChannelList = ()=>{
return async(dispatch)=>{
const res = await axios.get(url)
dispatch(setChannels(res.data.data.channels))
}
}
export {fetchChannelList}
⭐在组件中触发异步请求
在组件挂载时(useEffect),自动发送异步请求,获取频道列表并更新Redux状态。
useDispatch():获取Redux的dispatch方法,组件通过它触发Redux的Action。
useEffect(() => {...}, [dispatch]):组件渲染后自动执行fetchChannelList()
fetchChannelList()会执行异步请求,然后dispatch(setChannels(数据)),最终 Redux状态会更新。
const dispatch = useDispatch()
useEffect(() =>{
dispatch(fetchChannelList())
},[dispatch]
})
代码的整体流程
①Redux createSlice定义状态和同步更新方法
②fetchChannelList发送异步请求,并dispatch更新Redux
③组件useEffect触发 fetchChannelList,使Redux自动存储异步数据
④最终组件可以从Redux读取 channelList并显示频道信息
fetchChannelList 就是一个封装好的异步操作
👉 组件dispatch(fetchChannelList()) 触发请求并更新Redux
👉 Redux状态更新后,组件就能拿到最新的数据
ReactRouter
一:ReactRouter
1.概念
一个路径path对应一个组件component,在浏览器中访问一个path的时候,path对应的组件会在页面中进行渲染
const routes = [
{
path:'/about',
component:About,
},
{
path:'/article',
component:Article,
},
]
2.基础环境配置
// 1.创建项目并安装所有依赖
npx create-react-app react-router-pro
npm i
// 2.安装最新的ReactRouter包
npm i react-router-dom
// 3.启动项目
npm run start
3.小案例
创建一个可以切换登录页和文章页的路由系统
/login 登录
/article 文章
在index.js文件中
createBrowserRouter:用于创建浏览器路由对象,基于URL进行页面切换。
RouterProvider:用于提供路由,让应用知道如何根据URL显示不同的页面。
import React from "react";
import ReactDOM from "react-dom/client";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
// 1.创建router实例对象并且配置路由对应关系
const router = createBrowserRouter([
{
path:"/login",
element:<div>我是登录页</div>
},
{
path:"/article",
element:<div>我是文章页</div>
}
])
// ReactDom.createRoot:获取HTML里的 <div id="root"></div> 作为React渲染的根节点。
// RouterProvider:绑定router,让应用可以根据URL切换页面。
const root = ReactDom.createRoot(document.getElementById("root"))
root.render(
<React.StrictMode>
// 2.路由绑定
<RouterProvider router = {router}></RouterProvider>
</React.StrictMode>
)
二:抽象路由模块
1.src-page
①Login文件夹
Login-index.js
const Article = ()=>{
return <div>我是文章页</div>
export default Article
}
②Article文件夹
Article-index.js
const Login = ()=>{
return <div>我是登录页</div>
export default Login
}
2.src-router-index.js
import Login from "../page/Login"
import Article from "../page/Article"
import {createBrowserRouter} from "react-router-dom"
const router = createBrowserRouter([
{
path:"/login",
element:<Login/>
},
{
path:"/article",
element:<Article/>
}
])
export default router
3.index.js
// 导入路由router
import router from "./router"
<RouterProvider router = {router}></RouterProvider>
三:路由导航
路由系统中的多个路由之间需要进行路由跳转,并且在跳转的同时有可能需要传递参数进行通信
1.声明式导航
在模块中通过'Link'组件描述要跳转到哪里去
比如后台管理系统的左侧菜单
<Link to = "/article">文章</Link>
2.编程式导航
通过'useNavigate'钩子得到导航方法,然后通过方法以命令式的形式进行路由跳转
通过调用navigate方法传入地址path实现跳转
登录请求完毕之后跳转就可以选择这种方式
import {useNavigate} from "react-router-dom"
const Login = ()=>{
const navigate = useNavigate()
return(
<div>
我是登录页
<button onClick={() => navigate("/article")}>跳转到文章页</button>
</div>
)
}
export default Login
四:导航跳转传参
1.searchParams传参
<button onClick={() => navigate("/article?id=1001&name=jack")}>searchParams传参</button>
Article-index.js
import {useSearchParams} from "react-router-dom"
const Article =()=>{
const [params] = useSearchParams()
const id = params.get("id")
const id = params.get("name")
return <div>我是文章项{id}-{name}</div>
}
2.params传参
<button onClick={() => navigate("/article/1001/jack")}>params传参</button>
index.js
import { createBrowserRouter} from "react-router-dom";
const router = createBrowserRouter([
{
path:'/article/:id/:name',
element:<Article/>
}
])
Article-index
import {useSearchParams} from "react-router-dom"
const Article =()=>{
const params = useParams()
const id = params.id
const name = params.name
return <div>我是文章项{id}-{name}</div>
}