React

# useState 遍历渲染 绑定事件
## 一：变量和方法的定义,写法
``` java
const message = "this is message"
function App() {
  return(
   <div>
     <h1>this is title</h1>
     {message}
   </div>
  )
}
```
#### 1.变量
变量被定义在组件之外：变量可以在整个文件里使用
变量被定义在组件之内：变量只可以在组件内部使用
``` java
const message = "this is message"
```
#### 2.方法
一般方法写在组件内部
getMessage 是一个方法，返回一段文本，并在 return 语句中调用它。
``` java
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语句，变量声明属于语句，不是表达式，不能出现在{}中
``` java
// ①使用引号传递字符串
{"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：表示遍历出来的每一项
``` java
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.两个元素
三元运算符
``` java
const isLogin = true
// 单个元素
{isLogin && <span>this is span</span>}
// 两个元素
{isLogin?<span>jack</span>:<span>loading</span>}
```
#### 3.复杂条件渲染
自定义函数＋if判断语句
``` java
// 定义文章类型
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}
``` java
function App(){
  const clickHandler = ()=>{
     console.log("button按钮点击了")
  }
  return(
    <button onClick={clickHandler}></button>
  )
}
```
#### 2.事件参数e
``` java
function App(){
  const clickHandler = (e)=>{
     console.log("button按钮点击了",e)
  }
  return <button onClick={clickHandler}>click me</button>  
}
```
#### 3.传递自定义参数
事件绑定的位置改造成箭头函数的写法，在执行clickHandler实际处理业务函数的时候传递实参
onClick 事件本身并不会自动传递参数。所以，通过箭头函数，能够将自定义的参数 "jack" 传递给事件处理函数。
``` java
function App(){
  const clickHandler = (name)=>{
     console.log("button按钮点击了",name)
  }
  return <button onClick={() => clickHandler("jack")}>click me</button>  
}
```
#### 4.既要传递自定义参数，而且还要事件对象e
``` java
function App(){
  const clickHandler = (name,e)=>{
     console.log("button按钮点击了",name,e)
  }
  return <button onClick={(e) => clickHandler("jack",e)}>click me</button>  
}
```
## 五：组件基础使用
一个组件就是首字母大写的函数，内部存放了组件的逻辑和视图ui,渲染组件只需要把组件当成标签书写即可
``` java
// 1.定义组件
function Button(){
   // 组件内部逻辑
   return <button>click me</button>
}

// 2.使用组件
function App(){
  return (
     <div>
     // 自闭和
     <Button />
     </div>
  )
}
```
## 六：useState
#### 1.概念
useState是用来给组件添加状态变量的，状态变量一旦发生变化组件的视图ui也会跟着变化
``` java
// 导入
import {useState} from "react"
// 1.useState是一个函数，返回值是一个数组
// 2.数组中的第一个参数是状态变量，第二个参数是set函数用来修改状态变量
// 3.useState的参数将作为count的初始值
const [count, setCount] = useState(0)
```
``` java
// 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 对象中的其他属性，避免覆盖整个对象。
``` java
const [form, setForm] = useState({
  name:"jack",
})

const handleChangeName = ()=>{
  setForm({
    ...form,
    name:"john",
  })
}
```
## 七：基础样式控制-class类名控制
className="foo"
#### ①index.css文件
``` java
.foo{
   color:blue;
}
```
#### ②App.js文件
``` java
// 导入样式
import "./index.css"

function App(){
  return (
     <div className="App">
     // 自闭和
     <span className="foo">this is class foo</span>
     </div>
  )
}
```
## 八：classnames优化类名控制
classnames用来根据条件动态地合并类名。
```java
// 安装方式
npm install classnames
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3899383/e406da4a-faab-4354-9ae7-a02df29b2f60.png)
```java
// 使用了classnames库来动态设置React组件中的className
import classNames from "classnames"
 className={classNames("nav-item",{active:type === item.type})}
```
## 九：案例-评论列表
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3899383/11774d7e-ee16-4cc2-95d2-7653accd82dc.png)
#### 1.渲染评论列表--map方法遍历
※：只要数据会影响到视图的变化，都应该用useState去做维护
※：状态变量必须有一个初始值。commentList的初始值useState(list)
```java
// 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" 用于为这个 <span> 元素添加 CSS 样式
```java
// 条件：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项，哪个做高亮处理
点击谁就把谁的标识记录下来，然后和遍历时的每一项标识做匹配，谁匹配到就设置负责高亮的类名
```java
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框的状态
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3899383/174c287c-a7ab-4891-856f-7bd6e6432b43.png)
监听输入框内容变化，每当用户输入时，e.target.value（输入框的值）会更新 value，从而实现输入框内容与状态同步。
```java
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对象 
```java
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 状态，让页面重新渲染，显示新评论。
```java
 // 发表评论
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）
```java
// 安装uuid
npm install uuid
// 使用方式
 import {v4 as uuidv4} from "uuid";
 uuidv4();

 // 安装Day.js
 npm install dayjs
 // 使用方式
 import dayjs from "dayjs"
 Format
```  
```java
{
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.清空内容和聚焦实现
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3899383/3d12200b-4888-4089-8f80-1084deb34acf.png)
需求：清空内容-把控制input框的value状态设置为空串
      重新聚焦-拿到input的dom元素，调用focus方法
      inputRef.current.focus() 让输入框自动获取焦点
```java
// 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在渲染时接收并显示这个数据。
```java
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组件的子元素
```java
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传回给父组件。
```java
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会帮你找合适的时机自动执行！
```java
useEffect(() =>{},[])
// 参数1是一个函数，在函数内部可以放置要执行的操作
// 参数2是一个数组，在数组里放置依赖项，不同依赖项会影响第一个参数函数的执行，
//                 当是一个空数组的时候，只会在组件组件首次渲染时执行一次
    
```
⭐作用
①首次进入页面后，初始化操作
②监视某个值的变化
③退出页面后，执行收尾操作
#### 2.不同依赖项说明
①空数组依赖：只在初始渲染时执行一次
```java
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; 
```
②添加特定依赖项：组件初始渲染＋特性依赖项变化时执行
```java
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允许你在组件卸载时“关灯”，就是清除副作用！
```java
 useEffect(() =>{
    // 实现副作用操作逻辑
    return()=>{
    // 清除副作用逻辑
    }
 },[])  
```
在Son组件渲染时开启一个定时器，卸载时清除这个定时器
①Son 组件在渲染时启动一个定时器，每隔 1 秒输出 "定时器执行中"。
②App 组件包含一个 show 状态，控制 Son 组件是否显示。
③点击按钮 setShow(false) 会卸载 Son 组件，同时 useEffect 清理定时器，防止定时器继续运行。
```java
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，从而控制 <div> 显示/隐藏。
```java
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（数据）
```java
// 计数器（加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
```java
import { createStore } from "redux";

const store = createStore(reducer);
```
③订阅store数据变化（subscribe）
subscribe作用是：当Redux里的数据变化时，就会自动触发这个函数。
每次store里的数据有变化，就会执行subscribe里的回调函数
store.getState() 获取最新的数据并打印出来
```java
store.subscribe(() => {
  console.log("状态更新了:", store.getState());
});
```
④ 提交action修改数据（dispatch）
dispatch(action)触发reducer 进行数据修改
dispatch需要传一个action对象，type表示要执行的操作。
这时reducer会修改状态，然后store更新数据，触发subscribe里的回调
```java
store.dispatch({ type: "INCREMENT" }); // 让 count +1
store.dispatch({ type: "INCREMENT" }); // 再加 1
store.dispatch({ type: "DECREMENT" }); // 让 count -1
```
⑤获取最新的state
store.getState()可以获取当前store里的最新数据
每次dispatch执行后，可以用getState()查看数据是否正确更新了
```java
console.log("最终 count:", store.getState().count);
```
⭐完整代码
```java
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);
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3899383/d9435115-2f8f-44af-9227-0638a72b60a4.png)
## 二：Redux与React-环境准备
在React中使用redux，官方要求安装两个其他插件
#### 1.Redux Toolkit
#### 2.react-redux
```java
// 1.使用CRA快速创建React项目
npx create-react-app react_redux
// 2.安装配套工具
npm i @reduxjs/toolkit react-redux
// 3.启动项目
npm run start
```
#### 3.store目录结构
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3899383/a1ce9766-386f-48a6-809e-95072052de78.png)
✅ 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创建函数的步骤合并成了一个更简洁的方法。
```java
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
```java
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修改数据。
```java
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并在组件中显示。
```java
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
```java
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的状态和修改方法
```java
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
```java
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管理状态
```java
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
```java
{ type: "counter/addToNum", payload: 10 }
```
②这个action被counterReducer处理
```java
addToNum(state, action) {
   state.count = action.payload; // action.payload 里存的是 10
}
```
③count变成10，Redux状态更新
④eact组件 App.js自动重新渲染，显示最新的count
action.payload:
```java
{
  type: "counter/addToNum",
  payload: 10  // 这个 payload 就是传递过来的参数
}
```
参数传递的完整流程
①dispatch(addToNum(10)) 触发addToNum
②createSlice()生成的addToNum处理action
③action.payload里保存着10
④state.count = action.payload，所以count变成10
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3899383/58a127f7-90fe-4b38-887e-1aa86f6a2dfb.png)
## 五：Redux与React-异步状态操作
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3899383/ff044bdc-38a6-4310-9749-bf9f2ed60794.png)
异步操作样板代码
#### 1.创建store的写法保持不变，配置好同步修改状态的方法
#### 2.单独封装一个函数，在函数内部return一个新函数，在新函数中
①封装异步请求获取数据
②调用同步actionCreater传入异步数据生成一个action对象，并使用dispatch提交
#### 3.组件中dispatch的写法保持不变
⭐创建Redux模块（Slice）
createSlice用来创建一个Redux模块
reducers里定义了setChannels(state, action)，用于同步更新Redux状态，即把 action.payload 里的频道数据存入 channelList。
```java
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。
```java
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状态会更新。
```java
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对应的组件会在页面中进行渲染
```java
const routes = [
  {
   path:'/about',
   component:About,
  },
  {
   path:'/article',
   component:Article,
  },
]
```
#### 2.基础环境配置
```java
// 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显示不同的页面。
```java
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>
)
```
## 二：抽象路由模块
router配置
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3899383/41e8a8d1-be8d-4ebf-9236-ae33ba327eea.png)
#### 1.src-page
①Login文件夹
Login-index.js  
```java
const Article = ()=>{
  return <div>我是文章页</div>

  export default Article
}
```
②Article文件夹
Article-index.js
```java
const Login = ()=>{
  return <div>我是登录页</div>

  export default Login
}
```
#### 2.src-router-index.js
```java
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
```java
// 导入路由router
import router from "./router"
 <RouterProvider router = {router}></RouterProvider>
```
## 三：路由导航
路由系统中的多个路由之间需要进行路由跳转，并且在跳转的同时有可能需要传递参数进行通信
#### 1.声明式导航
在模块中通过'Link'组件描述要跳转到哪里去
比如后台管理系统的左侧菜单
```java
<Link to = "/article">文章</Link>
```
#### 2.编程式导航
通过'useNavigate'钩子得到导航方法，然后通过方法以命令式的形式进行路由跳转
通过调用navigate方法传入地址path实现跳转
登录请求完毕之后跳转就可以选择这种方式
```java
import {useNavigate} from "react-router-dom"
const Login = ()=>{
  const navigate = useNavigate()
  return(
     <div>
       我是登录页
       <button onClick={() => navigate("/article")}>跳转到文章页</button>
      </div>
  ) 
}
export default Login
```
## 四：导航跳转传参
#### 1.searchParams传参
```java
<button onClick={() => navigate("/article?id=1001&name=jack")}>searchParams传参</button>
```
Article-index.js
```java
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传参
```java
<button onClick={() => navigate("/article/1001/jack")}>params传参</button>
```
index.js
```java
import { createBrowserRouter} from "react-router-dom";

const router = createBrowserRouter([
 {
  path:'/article/:id/:name',
  element:<Article/>
 }
])
```
Article-index
```java
import {useSearchParams} from "react-router-dom"
const Article =()=>{
 const params = useParams()
 const id = params.id
 const name = params.name
 return <div>我是文章项{id}-{name}</div>
}
```