一篇带你了解React

216 阅读18分钟

Day01: react初识和jsx语法

一、react

用于构建用户界面的 JavaScript 库
react官网: https://zh-hans.reactjs.org/
创建react项目: npm create vite@latest 项目名 -- --template react

二、jsx语法

1. jsx是什么
jsx 就是js和html混合的写法,可以理解为js中新增了模板片段的数据类型
// 指定挂载节点  render函数指定渲染内容
ReactDOM.createRoot(document.getElementById('root')).render(<App />)

react中的组件就是一个函数  return 组件模板
(1) 定义不同的jsx组件
(2) 在APP.jsx中导入不同组件并暴露出去
        import Btn from "./components/Btn";
        import Address from "./components/Address";
        export default () => {
            return (
                <div>
                    <h1>App</h1>
                    <Btn />
                    <hr />
                    <Address />
                </div>
            );
        };
(3) 在main.jsx中导入APP.jsx
        import React from "react";
        import ReactDOM from "react-dom/client";
        import App from "./App";
        // render函数指定渲染内容
        ReactDOM.createRoot(document.getElementById("root")).render(<App />);
2. jsx插值
插值语法: {  }, 在{}内写数据说明要使用js
**遇到{ }按照js解析, 遇到<标签>按照html解析
注意: 
    ① 标签必须是闭合标签,但标签一定要有 / 结尾
    ② jsx插值不会解析html标签字符串
        let str = "<em>hello</em>";
        <span>{str}</span>  不会解析str的em标签
    ③ 解析html标签字符串
        <span dangerouslySetInnerHTML={{ __html: str }}></span>
    ④ jsx中类名不再使用class,而是使用className
        <h1 className="title">类名用className</h1>
    ⑤ jsx不允许重复的属性,所以不能直接使用多个className
        若要对一个元素定义多个类名,可使用字符串拼接或模板字符串
        字符串拼接: 
            className={style.btn + "" + (res == item ? style.active : "")} 
        模板字符串:
            className={`${style.btn} ${res == item ? style.active : ""}`}
    ⑥ label标签的for属性别名
        <label htmlFor="title"></label>
    ⑦ JSX中style的值是一个对象,若要动态绑定style,一定是双大括号 {{}}
        <div style={{ width:"200px",height:"200px",background:"green" }}></div>
例子1:
        let title = "react"
        let id = "title"
        let desc = <p>先学习jsx语法</p>
        let list = ["apple", "banana", "orange"]
        // 注意: 单独的布尔值不会渲染,需要配合三元运算符或条件判断
        let flag = true
        return (
            <div>
                /* 属性插值:id={id}  内容插值: { title } */
                <h1 id={id}>{ title }</h1>
                { desc }
                {/* 遇到大括号,说明插值或者有js语法 */}
                {
                    list.map(item => (
                        {/* 
                            style 属性插值
                            style的两个大括号,最外层大括号代表使用了jsx的插值语法
                            注意: 若在jsx中遇到两个大括号,外层大括号一定是jsx插值语法
                            内层大括号表示属性为对象形式
                         */}
                        <li style={{color: flag ? "red": ""}}>
                            {item}
                        </li>
                    ))
                }
            </div>
        )
例子2:
        // 对象插值使用
        let obj = {
            src: "https://ts2.cn.mm.bing.net/th?id=ORMS.7adf490cf1c86731419dfb43608528ab&pid=Wdp&w=612&h=304&qlt=90&c=1&rs=1&dpr=1.5&p=0",
            title: "留给AMD的时间不多了",
        }
        // 插值其他点
        let str = "<em>hello</em>";
        
        return (
            <div>
                // 可以把对象单独使用每个属性
                <img src={obj.src} title={obj.title} />
                // 可以使用展开运算符,对象的key值会直接作为属性名
                <img {...obj} />

                <span>{str}</span>  // 不会解析html标签字符串
                <span dangerouslySetInnerHTML={{ __html: str }}></span>
                <h1 className="title">类名用className</h1>
                <label htmlFor="title">label的for属性用htmlFor代替</label>
            </div>
        )
3. 条件渲染
条件渲染的方式:
    ① 使用if条件
    ② 使用三目运算符
        三目运算符 : 后为null,则结果为false时什么都不会渲染
            const flag=true
            return {flag ?<div>hello</div> :null}
    ③ && 与运算符
        逻辑与&& 第一个操作数为true或能隐式转换为true,结果为第二个操作数
                第二个参数作数为false或能隐式转换为false,直接不会渲染
主要应用:
    ① 根据不同条件做出不同选择渲染到页面
    ② 渲染数据时,若数据不存在,可以用条件渲染判断
        let show = false;
        let isLogin = true;
        let userTemp;
        if (isLogin) {
            userTemp = <h1>欢迎张三</h1>;
        } else {
            userTemp = <a href="">登录</a>;
        }
        return (
            <div>
                {userTemp}
                {/* 行内样式  控制显示隐藏 */}
                <h2 style={{color: "red", display: show ? "block" : "none"}}>hello</h2>
                {/* 条件渲染 */}
                {isLogin ? <h1>欢迎张三</h1> : <a href="">登录</a>}
                {/* 符合条件 渲染指定内容,否则什么都不渲染 */}
                {
                    isLogin ? (
                        <div>
                            <h1>hello</h1>
                        </div>
                    ) : null
                }
                {/* 逻辑与,&&前为true,渲染&&后面内容; 否则不渲染 */}
                {
                    isLogin && (
                        <div>逻辑与渲染</div>
                    )
                }
            </div>
        )
4. 列表渲染
(1) 渲染列表数据--数组
        const data = [
            {
                title: "24款套手工麦克杯",
                content: "马克杯的意思是大柄杯子"
            },
            {
                title: "24款套手工麦克杯",
                content: "马克杯的意思是大柄杯子"
            },
            {
                title: "24款套手工麦克杯",
                content: "马克杯的意思是大柄杯子"
            },
            {
                title: "24款套手工麦克杯",
                content: "马克杯的意思是大柄杯子"
            }
        ]
        return(
            <div>
                <ul>
                    {
                        data.map((item,index) => {
                            // 注意 箭头函数内若有大括号包裹,需要再加return关键字
                            // 渲染数据大于一行,要用小括号包裹
                            return(
                                // key关键字 key={唯一标识} 每条数据都需要一个唯一索引
                                <li key={index}>
                                    <h4>{item.title}</h4>
                                    <p>{item.content}</p>
                                </li>
                            )
                        })
                    }
                </ul>
            </div>
        )
(2) 渲染列表数据--对象
        let cityObj = {
            A: "安徽",
            B: "北京",
            C: "成都",
        };
        return (
            <div>
                {/* Object.keys(cityObj)获取对象的键的集合 */}
                <ol>
                    {Object.keys(cityObj).map((item) => (
                        <li key={item}>
                            {/* item就是对象键, 对象名[键名]取到键名对应的键值 */}
                            {item}---{cityObj[item]}
                        </li>
                    ))}
                </ol>
            </div>
        )
5. 事件处理
React元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:
    ① React事件的命名采用小驼峰式(onClick),而不是纯小写
    ② 使用 JSX 语法时需要传入一个函数作为事件处理函数,而不是一个字符串
事件可以定义在行内,也可以单独提取
(1) 不带参数事件(或默认带一个事件对象e)
        let temp = (
            <button
                onClick = {
                    (e) => {
                        alert("hello");
                        console.log(e); //阻止冒泡、阻止浏览器默认行为、鼠标位置等
                        e.preventDefault();  // 阻止默认事件
                        e.stopPropagation();  // 阻止事件冒泡
                        console.log(e.target); 
                    }
                }
            >
                按钮(事件1)
            </button>
        );
        let f = (e) => {
            console.log(e); //阻止冒泡、阻止浏览器默认行为、鼠标位置、键盘按键
        };
        return (
            {temp}
            // 注意具体事件不要忘记加{}包裹,且这是无参事件
            <button onClick={f}>触发f事件</button>
        )
(2) 带普通参数的事件
        let del = (index) => {
            console.log("删除" + index + "元素");
        };
        return (
            {/* 带参事件 */}
            {/* 错误写法 这会在页面加载完毕直接调用方法 */}
            <button onClick={del(0)}>商品0</button> 
            {/* 箭头函数是事件处理函数 */}
            <button onClick={() => del(0)}>商品0</button>
            /* 
            注意:
                若是事件直接写在行内,正常写法即可
                    <button onClick={() => del(0)}>商品0</button>
                若事件携带普通参数,元素调用事件时候需要加上一层箭头函数,否则会立即执行事件
            */
        )
(3) 表单事件
    js中: 单选、复选、选择框 change事件; 文本输入框、文本域 input事件
    jsx中表单事件: 单选、复选、选择框、文本输入框、文本域 都是change事件    
        let temp = (
            <div>
                <input
                    type="file"
                    onChange={(e) => {
                        console.log(e.target.files);
                    }}
                />
            </div>
        );
        return (
            {temp}
        )

三、jsx生成的react元素

jsx最终会生成一个React元素。 定义变量:const element=

hello,baizhan

jsx被编译后变成这样一段js代码: const element = React.createElement( 'h1', {className: 'con'}, 'hello,baizhan' ); React.createElement返回的对象对象用来描述界面应该长什么样子,我们把这个对象称为React元素 //简化版 const element = { type: 'h1', props: { className: 'con', children: 'hello,baizhan' } }; 之后React DOM会根据这个对象描述的信息生成真正的DOM元素

四、私有样式

1. 全局样式
import "./index.css";
2. 私有样式(less scss同理)
    ① 在定义样式时,加上 .module
        eg: index.module.css  header.module.less ...
    ② import引入私有样式
        import style from "./index.module.scss";
        style: 会把样式中的类名全部替换,绑定css样式需要用替换后的类名绑定到元素上
    ③ 绑定到元素上
    let temp = (
        // 注意是className,且要用替换后的类名
        <div className={style.box}>
            <p>hello</p>
        </div>
    );
    在css中正常使用 .box 定义即可

Day02: react组件

一、react组件

1. 模板复用
将模板以函数的形式定义,传入参数,在调用模板函数时,在传入实参,即可实现模板复用
        //提升模板复用度
        let artical = (title, content) => (
            <div>
                <h4>{title}</h4>
                <p>{content}</p>
            </div>
        );

        return (
            <div>
                <h2>模板复用</h2>
                {artical("西游记", "师徒四人 西天取经")}
                {artical("红楼梦", "一个男人和很多女人的故事")}
            </div>
        )
2. 函数式组件
react中的组件是以函数形式定义的,不同于普通函数的是,首字母大写
每个函数组件都有一个形参props,用于接收调用组件时传递过来的数据
        // Artical组件  首字母大写 为了区分组件和普通函数
        // props对象 包含调用组件时传入的属性
        let Artical = (props) => (
            <div>
                <h4>{props.title}</h4>
                <p>{props.content}</p>
            </div>
        );
        // 解构
        Artical = ({ title, content }) => (
            <div>
                <h4>{title}</h4>
                <p>{content}</p>
            </div>
        );

        return (
            <div>
                <h2>函数式组件</h2>
                {/* 以组件形式调用 */}
                <Artical title="西游记" content="师徒四人 西天取经" />
                <Artical title="红楼梦" content="一个男人和很多女人的故事" />
            </div>
        )
3. 组件插槽
在函数组件中,props对象默认有一个children属性,指代插槽内容
        let Artical = ({ title, children }) => (
            <div>
                <h4>{title}</h4>
                <div>{children}</div>
            </div>
        );
        let slot = <em>我是插槽内容1</em>

        return (
            <div>
                <h2>组件插槽</h2>
                <Artical title="西游记">
                    {slot}
                </Artical>
                <Artical title="红楼梦">
                    <em><strong>我是插槽内容2</strong></em>
                </Artical>
            </div>
        )
4. props传入模板片段
let content = <strong>师徒四人</strong>
<!-- 传入模板片段,注意属性值加 { } -->
<Artical title="西游记" content={content} />
5. 响应数据
若要使用可以跟着视图更新不断变化的数据,则需要用到响应数据
useState用于创建响应数据
    ① 引入useState: import { useState } from "react";
    ② useState创建的数据有两种状态,一种是读取状态,一种是修改状态,两者缺一不可
        let [num, setNum] = useState(100);
            num可以直接在模板读取 {num}
            setNum是修改数据的方法,注意调用时这个方法一定有参数,所以不要忘了箭头函数包裹
        // 形参props对象接收外部数据
        let Count = (props) => {
            // 组件状态(响应数据)
            let [num, setNum] = useState(100)

            // 组件模板  点击修改num的值为500
            return <button onClick={() => setNum(500)}>按钮{num}</button>
        }

        return (
            <div>
                <h2>组件状态(响应数据)</h2>
                <Count />
            </div>
        )

二、单文件组件与组件通信

react组件通信都是靠props完成的
父传子: 同上 父组件定义属性,子组件通过函数组件的props形参接收,并可以把props解构
子传父: 在父组件上定义方法,把父组件的方法通过props传递给子组件调用,并把结果反馈给父组件
1. 父组件
    <Address 
        citys={["北京","上海","广州","深圳","郑州"]} 
        // 子到父: ① 父组件上定义方法
        change={(val) => {
          // 形参val就是子组件的数据
          alert("您选择了" + val);
        }}
    />
2. 子组件
import { useState } from "react";
// 引入私有化样式
import style from "./Address.module.css";
// 父子通信 都是靠props
// 子到父: ② 把父组件的方法通过props传递给子组件调用
export default ({citys, change}) => {
    let [res, setRes] = useState(""); 
    console.log(citys);  
    
    return (
        <div>
        <h2>父子组件通信</h2>
        {
            citys.map((item) => {
            return (
                <button
                    // 注意: jsx不允许重复的属性所以不能直接使用多个className
                    // 若要对一个元素定义多个类名,可使用字符串拼接或模板字符串
                    // 字符串拼接: className={style.btn + "" + (res == item ? style.active : "")} 
                    className={`${style.btn} ${res == item ? style.active : ""}`}
                    key={item} 
                    onClick={() => {
                        setRes(item) // 将新的结果传给res
                        // 子到父: ③ 在子组件中把反馈结果返回给父组件
                        change(item)
                    }}
                >
                    {item}
                </button>
            )
            } )
        }
        </div>
    )
    };

[ 以下笔记代码在react-component02 项目中 ]

三、响应数据

若要使用可以跟着视图更新不断变化的数据,则需要用到响应数据
1. 基本用法
useState用于创建响应数据
    ① 引入useState: import { useState } from "react";
    ② 创建响应数据: let [res, setRes] = useState("")
2. 引用类型的响应数据
例子:
    export default () => {
        // 引用类型的数据
        let [list, setList] = useState(["苹果"]);
        // 往数组中添加新元素
        let add = () => {
            /* 
                这个操作虽然已经向数组内添加了新元素,但是视图不会更新
                原因是react为了提升效率 减少无用更新 在调用修改数据的方法时,新值会等于旧值(引用类型数据,判断的不是值,而是引用地址)
            */
            // list.push("西瓜");
            // setList(list);

            // 正确操作 ① push  ② 使用一个新数组包裹,并对原数组元素展开
            list.push("西瓜");
            setList([...list]);
        };

        return (
            <div>
                <p>{list.join("、")}</p>
                <button onClick={add}>添加</button>
            </div>
        );
    };

四、表单交互

双向数据绑定,在vue中可以直接使用v-model,数据更新,视图变化
但是在react中,不存在v-model,所以对于双向数据绑定需要用到:
    ① useState 响应数据 让数据更新
    ② onChange事件搭配event对象 让视图变化
受控组件: 组件是受useState控制的,反之就是非受控组件
1. 基本表单交互
文本框,选择框,下拉菜单,文件选择框 对应交互规则如下:
export default () => {
    // 文本框
    let [inp, setInp] = useState("")
    // 复选框
    let [flag, setFlag] = useState(true);
    // 下拉框
    let [address, setAddress] = useState("");

    return (
    <div>
        <h2>表单交互</h2>
        {/* 1. 文本框主要是靠 value属性 和 e.tatget.value 进行交互 */}
        <input type="text" value={inp} onChange={(e) => setInp(e.target.value)} />
        <span>{inp}</span>
        <hr />
        {/* 2.单选复选框主要是靠 checked属性 和 e.target.checked 进行交互 */}
        <input type="checkbox" checked={flag} onChange={(e) => setFlag(e.target.checked)}/>
        <span>{ flag ? "选中了" : "没选中" }</span>
        <hr />
        {/* 3. 下拉框主要是靠select的 value属性 和 e.target.value 进行交互 */}
        <select value={address} onChange={(e) => setAddress(e.target.value)}>
            {/* 注意 下拉框的值 option有value属性就显示的value属性值,没有value属性就显示文本值 */}
            <option>请选择</option>
            <option>上海</option>
            <option>北京</option>
            <option>杭州</option>
        </select>
        <span>{address}</span>
        <hr />
        {/* 4. 非受控元素 文件选择框不需要useState交互,只需要通过 e.target.files[0]获取 */}
        <input
            type="file"
            onChange={(e) => {
            // 监听选择事件 获取选择的文件对象
            console.log(e.target.files[0]);
            }}
        />
    </div>
    )
};
2. 单选按钮组

(1) 基本用法

    export default () => {
        let [sex, setSex] = useState("男");

        return (
        <div>
            <label>
                <input type="radio" name="sex" checked={sex == "男"} 
                    // 逻辑与&& 第一个操作数为true,结果为第二个操作数,用于简化三目运算
                    onChange={(e) => e.target.checked && setSex("男")}
                />男
            </label>
            <label>
                <input type="radio" name="sex" checked={sex == "女"}
                    onChange={(e) => e.target.checked && setSex("女")}
                />女
            </label>
            <p>{sex}</p>
        </div>
        );
    };

(2) 封装使用

    export default () => {
        let [sex, setSex] = useState("男");
        // 选项集合  生成多个单选按钮
        let options = ["男", "女", "未知"];

        return (
        <>
            {options.map((item) => (
                <label key={item}>
                    <input type="radio" name="sex" checked={sex == item}
                        onChange={(e) => e.target.checked && setSex(item)}
                    />
                    {item}
                </label>
            ))}
            <p>{sex}</p>
        </>
        );
    };
3. 复选按钮组(封装使用)
export default () => {
    let [hobbys, setHobbys] = useState(["篮球", "乒乓球"]);
    // 多选选项集合
    let options = ["篮球", "足球", "乒乓球", "排球", "羽毛球"];

    return (
    <>
        {options.map((item) => (
            <label>
                <input type="checkbox" checked={hobbys.includes(item)}
                    onChange={(e) => {
                    // 勾选 添加至hobbys  取消 从hobbys中删除
                    // 注意数组是引用类型,添加元素需要先push再展开到新数组中
                    if (e.target.checked) {
                        hobbys.push(item);
                    } else {
                        // 取消勾选,找到对应取消勾选数据的索引,再删除
                        let index = hobbys.indexOf(item);
                        hobbys.splice(index, 1);
                    }
                    setHobbys([...hobbys]);
                    }}
                />{" "}
                {item}
            </label>
        ))}
        <p>{hobbys.join("、")}</p>
    </>
    );
};

Day03: ReactHook

一、antd组件库

安装: npm i antd --save
antd组件没有全局引入,都是按需导入
1. 国际化设置
(1) 按需导入
    如有特殊需求(仅修改单一组件的语言)可使用 locale 参数
    import 'dayjs/locale/zh-cn';
    import locale from 'antd/es/date-picker/locale/zh_CN';
    <DatePicker locale={locale} />;
(2) 全局配置
        import zhCN from 'antd/locale/zh_CN';
        import {ConfigProvider} from 'antd'
        <React.StrictMode>
            <ConfigProvider locale={zhCN}>
                <App/>
            </ConfigProv    ider>
        </React.StrictMode>
2. 引入重置样式
//main.jsx
import 'antd/dist/reset.css';
3. 提示组件
(1) 引入: import { message } from "antd";
(2) 定义提示:
    let [messageApi, contextHolder] = message.useMessage();
(3) 渲染到页面中:
    在 return 中任意位置渲染 {contextHolder}
(4) 使用组件:
    messageApi.warning();  messageApi.success()  ...
4. 数字输入框
    import { useState } from "react";
    import { Input } from "antd";

    export default () => {
        let [num, setNum] = useState("");
        return (
            <div>
            <h1>App</h1>
            <Input
                value={num}
                {/* 限定只能输入数字 且最多三位 */}
                onChange={(e) => setNum(e.target.value.replace(/\D/g, "").slice(0, 3))}
            />
            </div>
        );
    };

二、ReactHook

1. useState
创建状态,将数据存储
在react中,每次模板更新,组件函数都会被重新调用
eg:
        import { useState } from "react";
        export default () => {
            // 每次模板更新 组件函数 都会被调用一次
            console.log("hello");
            let [num, setNum] = useState(100); //创建状态 存储数据
            let timer = setInterval(() => {
                console.log(1);
            }, 5000);
            return (
                <div>
                    <button onClick={() => setNum(num + 1)}>{num}</button>
                </div>
            );
        };
2. useEffect
使用前需要先引入useEffect: import { useState, useEffect } from "react";
用法(1): 节点挂载完毕时触发,相当于vue中的mounted
     useEffect(() => {
        console.log("节点挂载完毕,可访问DOM", 
            document.getElementById("root").innerHTML);
    }, []);
    注意: 第二个参数不能省略,且为空数组时,表示挂载完毕
用法(2): 节点挂载完毕+指定数据更新时触发,相当于vue中的mounted+watch
    useEffect(() => {
        console.log(num);  // 初始会打印一次num
    }, [num]);  // [num] 表示num改变,也会打印
用法(3): 卸载 销毁时触发
    useEffect(
        // 函数内第一个参数又是一个函数,表示卸载销毁
        () => () => {
            console.log("销毁");
        },
        []  // 注意:[]不能省略
    );
结合用法:
    useEffect(() => {
        console.log("组件挂载");
        // 只要函数内返回一个函数,就代表卸载
        return () => {
            console.log("组件销毁");
        };
    }, []);
3. useRef
useRef用于访问DOM,且访问DOM要在节点挂载完毕后才能访问
注意使用前先引入
    import { useState, useEffect, useRef } from "react"
(1) 使用变量定义ref,注意变量名要与绑定的标签ref属性同名
    let btn = useRef(null)
(2) ref属性绑定到要访问的元素节点上,注意属性值是动态的,加{}
    <button ref={btn}></button>
(3) 在useEffect中访问DOM,且需要一个current属性
    useEffect(() => {
        // btn.current.DOM属性方法
        console.log(btn.current.innerHTML);
    }, []);
    import { useState, useEffect, useRef } from "react";
    export default () => {
        let [num, setNum] = useState(100); //创建状态 会被存储下来
        // 访问DOM 第一步
        let btn = useRef(null); //与标签ref属性同名

        useEffect(() => {
            // 访问DOM 第三步
            console.log(btn.current.innerHTML);
        }, [num]);

        return (
            <div>
                {/* 访问DOM 第二步 */}
                <button ref={btn} onClick={() => setNum(num + 1)}>
                    {num}
                </button>
            </div>
        );
    };

Day04: ReactHook

一、React Hook

Hook简介:
    Hook 是 React 16.8 的新增特性。
    Hook都是函数,这些函数能让我们不使用class的情况下还能"钩入"React state及生命周期等特性
1. State Hook
(1) useState:
    useState是一个Hook,它本质上是一个函数,这个函数可以用来定义组件的state变量。
    useState() 方法里面唯一的参数就是初始 state。
(2) 更改state:
    useState返回值为当前state以及更新state的函数。
    要更改state,则调用更新state的函数就可以
(3) 引用类型的state数据:
    参考day02 => 响应数据 部分
        import { useState } from 'react'
        export default () => {
            // 声明一个叫 count 的 state 变量。useState返回一个数组
            const [count, setCount] = useState(0)
            //声明叫做color的state变量
            const [color, setColor] = useState('red')
            return (
                <div>
                    我是使用hook的组件
                    <br />
                    <button onClick={()=> setCount(count+1)}>递增</button>
                    <button onClick={()=> setColor('blue')}>变蓝</button>
                    <h3 style={{color}}>数量:{count}</h3>
                </div>
            )
        }
(4) State 闭包问题
    import { useState } from "react";
    /*
    闭包 是定义在一个函数内部的函数 并且被外层函数外部所引用
    let res2;
    function f(){
        let x=10;
        function f2(){
        console.log(x)
        }
        res2=f2;
        return f2;
        setTimeout(f2,1000)  // 隐蔽 产生闭包(定时器)
        document.onclick=f2;  // 隐蔽 产生闭包(事件)
    }
    let res=f();
    */
    export default () => {
        //闭包内部 读取不到最新状态值
        let [num, setNum] = useState(100);
        // 记录定时器
        let [timer, setTimer] = useState(null);

        let start = () => {
            // 绑定定时器
            let t = setInterval(() => {
                // 闭包 一直读到的是num起始值
                // setNum(num + 1);// 传入新值
                /* 解决state闭包问题: 使用一个函数接收,函数内部形参就是新的值 */
                setNum((val) => val + 1); //形参是当前值 return要设置的新值
            }, 1000);
            setTimer(t);
        };
        return (
            <div>
                <h2>state闭包问题--{num}</h2>
                <button onClick={start}>开始</button>
            </div>
        );
    };

2. Effect Hook
Effect Hook 可以让我们在函数组件中执行副作用操作。
比如:数据获取(网络请求),设置订阅(添加事件)以及操作DOM
useEffect:
    可以接收一个参数,是一个回调函数,此函数就是Effect Hook
    默认情况下,它在页面第一次渲染之后(挂载之后)和每次更新之后都会执行。
(1) 初始触发
    节点挂载完毕时触发,相当于vue中的mounted
    useEffect(() => {
        console.log("节点挂载完毕,可访问DOM", 
            document.getElementById("root").innerHTML);
    }, []);
    注意: 
        ① 第二个参数为数组,且数组为空时,表示挂载完毕会执行一次
        ② 若省略了第二个参数[],则除了挂载完毕会调用,每次数据更新也会被调用
(2) 初始 + 指定数据更新触发
    节点挂载完毕+指定数据更新时触发,相当于vue中的mounted+watch
    传递数组作为 useEffect 的第二个可选参数,数组中可以设置要监听发生变化的数据,可以是一个也可以是多个,只要有一个数据发生变化,React 就会执行 effect
    语法: useEffect(()=>{},[被监听的数据1,被监听的数据2])
        useEffect(() => {
            console.log(num);  // 初始会打印一次num
        }, [num]);  // [num] 表示num改变,也会打印
(3) 初始 + 有数据更新就触发
    节点挂载完毕+页面内有数据变化就触发
    若省略了第二个参数[],则除了挂载完毕会调用,只要页面内有数据更新也都会被调用
    useEffect(() => {
        console.log(挂载完毕+有数据更新,我都会执行);
    });
(4) 卸载、销毁时触发
    useEffect(
        // 函数内第一个参数又是一个函数,表示卸载销毁
        () => () => {
            console.log("销毁");
        },
        []  // 注意:[]不能省略
    );
(5) 使用一个Effect结合多种操作
    useEffect(() => {
        console.log("组件挂载");
        // 只要函数内返回一个函数,就代表卸载
        return () => {
            console.log("组件销毁");
        };
    }, []);  // 不写[],每次页面内数据有更新也会被调用
    注意: 对于复杂逻辑,还是建议使用Effect实现关注点分离,一个Effect只定义一个功能
        import { useState, useEffect, useRef } from 'react'
        export default function Counter() {
            const [count, setCount] = useState(0)
            const [color, setColor] = useState('red')
            const ipt = useRef(null)
            //接收一个函数
            useEffect(() => {
                ipt.current.value = `点击了${count}次`
                // 不写第二个参数,除了初始会调用,每次数据更新也会调用
            })
            return (
                <div>
                    <h2>State/Effect/Ref Hook</h2>
                    <button onClick={() => setCount(count + 1)}>递增</button>
                    <button onClick={() => setColor('blue')}>变蓝</button>
                    <h3 style={{ color }}>数量:{count}</h3>
                    <input ref={ipt} />
                </div>
            )
        }
3. Ref Hook
useRef 返回一个可变的 ref 对象
参数为其 .current 属性的初始值。
返回的 ref 对象在组件的整个生命周期内保持不变。
useRef用于访问DOM,且访问DOM要在节点挂载完毕后才能访问
import { useState, useEffect, useRef } from "react"
(1) 使用变量定义ref,注意变量名要与绑定的标签ref属性同名
    let btn = useRef(null)
(2) ref属性绑定到要访问的元素节点上,注意属性值是动态的,加{}
    <button ref={btn}></button>
(3) 在useEffect中访问DOM,且需要一个current属性
    useEffect(() => {
        // btn.current.DOM属性方法
        console.log(btn.current.innerHTML);
    }, []);
        import React, { useRef, useState } from 'react'
        export default () => {
            //使用ref保存的数据,即使组件重新渲染,数据也不会被重置
            const count = useRef(0)
            const [time,setTime] = useState()
            const changeCount=()=>{
                count.current++
                // 获取当前时间
                setTime(new Date().toLocaleTimeString())
            }
            const showCount = () => {
                console.log(count)
            }

            return (
                <div>
                    <button onClick={changeCount}>更改count</button>
                    <button onClick={showCount}>查看count</button>
                    <h3>当前时间是:{time}</h3>
                </div>
            )
        }
4. Hook使用规则
只在组件最顶层调用Hook
不要在循环,条件或嵌套函数中调用 Hook, 确保总是在 React 函数的最顶层调用Hook
这么做的目的是保证组件在初次渲染或者重新渲染的时候调用Hook的顺序保持一致
5. 自定义Hook
什么情况下自定义Hook:
    当我们想在两个或者多个函数之间共享逻辑时,我们会把它提取到第三个函数中。
    这实现共享逻辑的第三个函数就是Hook
自定义Hook使用规则:
    ① 自定义 Hook 是一个函数
    ② 其名称以 use 开头
    ③ 函数内部可以调用其他的 Hook
    ④ 在两个组件中使用相同的 Hook 不会共享 state 
例子: 获取用户信息的hook
        //自定义的hook
        import { useEffect, useState } from "react";
        export default () => {
            const [userInfo,setUserInfo]=useState({})
            useEffect(() => {
                const info=JSON.parse(localStorage.getItem('userInfo')) 
                setUserInfo(info)
            },[])
            return [userInfo,setUserInfo]
        }

        // 使用自定义hook
        //User.js
        import React from 'react';
        import useUserInfo from '../hooks/useUserInfo'
        export default () => {
            const [userInfo,setUserInfo] = useUserInfo()
            return (
                <div>
                    <h3>我是第一个使用自定义Hook的组件</h3>
                    {userInfo.name}
                    <button onClick={()=>setUserInfo({name:'zs111'})}>
                        更改用户信息
                    </button>
                </div>
            );
        }
6. Memo Hook
useMemo(fn, deps)  返回一个缓存值
可以理解为 类似于vue的计算属性
第一个参数为函数,用来返回需要缓存的值,第二个参数为依赖项数组。
它仅会在某个依赖项改变时(第二个参数改变时)才重新调用函数进行计算,改变缓存值的状态
如果没有提供依赖项数组(没有第二个参数),useMemo 在每次渲染和任何数据变化时都会计算新的值。
如果第二个参数为空数组,useMemo只会在每次渲染时调用一次
这种优化有助于避免在每次渲染时都进行高开销的计算。
useMemo跟useEfect区别:
    ① useMemo用于更改缓存数据,诸如副作用(网络请求等)这类的操作属于useEffect的适用范畴
    ② useMemo存在返回值,可以使用变量接收返回值; useEffect没有返回值
        import { useMemo, useState } from "react"
        export default () => {
            const [num1, setNum1] = useState(0)
            const [num2, setNum2] = useState(0)
            // useMemo有返回值,可以使用变量接收返回值
            const num = useMemo(() => {
                // 当依赖项中的num1或者num2发生变化时,重新执行该函数,不受其他state改变的影响
                console.log("num1 + num2");
                return num1 + num2
            },[num1, num2])
            return (
                <div>
                    <h2>Memo Hook</h2>
                    <button onClick={() => setNum1(num1+1)}>更改num1--{num1}</button>
                    <button onClick={() => setNum2(num2+1)}>更改num2--{num2}</button>
                    <h5>num1 + num2 = {num}</h5>
                </div>
            )
        }
7. Callback Hook
useMemo(fn, deps)  返回一个缓存函数
缓存函数的目的就是为了不让组件每次重新渲染时都重新定义这个函数
第一个参数为函数,是需要被缓存的函数; 第二个参数为依赖项数组(用法同useMemo)
它仅会在某个依赖项改变时才重新定义被缓存函数
useCallback会产生闭包 方法内部需要访问到组件最新数据,需要在第二个参数位置指定
    import { useMemo, useState, useCallback } from "react"
    export default () => {
        const [num1, setNum1] = useState(0)
        const [num2, setNum2] = useState(0)
        // useMemo 缓存值
        const num = useMemo(() => {
            // 当依赖项中的num1或者num2发生变化时,重新执行该函数,不受其他state改变的影响
            console.log("num1 + num2");
            return num1 + num2
        }, [num1, num2])

        // useCallback 缓存函数  (监听当num1发生变化时,才去执行setNum1函数)
        const changeNum1 = useCallback(() => {
            setNum1(num1 + 1)
        },[num1])

        return (
            <div>
                <h2>Callback Hook</h2>
                {/* 调用缓存函数 注意调用时不加括号 */}
                <button onClick={changeNum1}>更改num1--{num1}</button>
                <button onClick={() => setNum2(num2 + 1)}>更改num2--{num2}</button>
                <h5>num1 + num2 = {num}</h5>
            </div>
        )
    }

Day05: Hook&Redux

一、React Hook

1. Memo Hook
useMemo 相当于Vue中的计算属性 只在依赖变化时 重新求值
具体用法见 day04 => Memo Hook
2. Reducer Hook
useReducer是useState 的替代方案。
它接收一个形如(state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。
① 引入 reducer
    import { useReducer } from "react"
② 在函数之外定义状态(数据更改的状态[数据变化方法]和数据初始状态)
③ 函数内调用reducer
    let [state, dispatch] = useReducer(reducer, initialState);
        dispatch最终会调用 reducer 方法(第二步)  return 参与视图更新的数据
        reducer: 函数外部定义的数据更改状态(方法)
        initialState: 数据初始状态
④ 模板内使用数据和方法
    数据: state.数据名
    方法: onClick={() => dispatch({type: "方法名"})}
    import { useReducer } from "react";
    // ② 函数之外定义状态(state对应state,表示数据; action对象dispatch,表示方法)
    let reducer = (state, action) => {
        // 不同类型的action触发不同的变更数据方法
        switch (action.type) {
            case "numAdd":
                // 返回值: 原state对象+要变更的状态对象
                return { ...state, num: state.num + 1 };
            case "xReduce":
                return { ...state, x: state.x - 1 };
            default:
                return state;
        }
    };
    // ② 函数外部定义初始状态
    let initialState = {
        num: 100,
        x: 500,
    };
    export default () => {
        // ③ 函数内调用reducer
        let [state, dispatch] = useReducer(reducer, initialState);
        return (
            <div>
                <h2>
                    {/* ④ 模板内使用数据和方法 */}
                    useReducer-- {state.num}-- {state.x}
                </h2>
                <button onClick={() => dispatch({ type: "numAdd" })}>num++</button>
                <button onClick={() => dispatch({ type: "xReduce" })}>x--</button>
            </div>
        );
    }
3. Context Hook
进行复杂的组件通信时,我们一般会使用公共状态库
React中的Context Hook 可以解决复杂组件通信(组件关系跨越较大,逐级props不方便)
可在跨多级父子组件中使用Context
(1) 创建 context 上下文对象(在单独的模块内定义)
        import { createContext } from "react";
        export const CountContext = createContext();
(2) 在父辈组件中创建数据,让子辈组件包裹在 Context.Provider中,数据供需要的子辈组件使用
        // ① 引入子辈组件和context对象 
        import Child from "./components/Child"
        import { CountContext } from "./context/CountContext"
        // ② 数据作为对应标签的属性,需要用到该数据的子组件都包裹在里面
        <CountContext.Provider value={给子辈组件访问的数据和方法(多个数据,写成对象形式)}>
            <Child />
        </CountContext.Provider>
(3) 在子辈组件中接收父组件传递的数据
        // ① 引入Context Hook 和context对象
        import { useContext } from "react";
        import { CountContext } from "./context/CountContext";
        export default () => {
            // ② 使用useContext接收数据(数据名与value属性名保持一致)
            let { num, setNum } = useContext(CountContext);
        }
4. ImperativeHandle Hook
useImperativeHandle 让父组件访问子组件方法,像第三方组件库中组件实例的方法(表单验证、重置、提交)
具体见代码

二、Redux状态管理库

1. Redux简介
(1) Redux是什么:
    Redux 是一个使用叫做 action 的事件来管理和更新应用状态的模式和工具库。
    它以集中式 Store(centralized store)的方式对整个应用中使用的状态进行集中管理,其规则确保状态只能以可预测方式更新。
(2) 什么时候使用Redux:
    并非所有应用程序都需要 Redux,如果有以下情况可考虑使用redux:
        应用中有很多 state 在多个组件中需要使用
        应用 state 会随着时间的推移而频繁更新
        更新 state 的逻辑很复杂
        中型和大型代码量的应用,很多人协同开发
2. Redux库和工具
Redux 是一个小型的独立 JS 库。但是,它通常与其他几个包一起使用:
(1) Redux Toolkit
    Redux Toolkit是官方推荐的编写 Redux 逻辑的方法。 
    它包含构建 Redux 应用程序必不可少的包和函数。
(2) React-Redux
    Redux 可以集成到任何的 UI 框架中,其中最常见的是 React
    React-Redux 是我们的官方包,它可以让 React 组件访问 state 片段和 dispatch actions 更新 store,从而同 Redux 集成起来。
(3) Redux DevTools 扩展
    Redux DevTools可以显示Redux存储中状态随时间变化的历史记录,这使我们有效地调试应用程序
#3. Redux数据流
(1) 初始启动:
    ① 使用最顶层的 root reducer 函数创建 Redux store
    ② store 调用一次 root reducer,并将返回值保存为它的初始 state
    ③ 当视图首次渲染时,视图组件访问Redux store的当前state,并使用该数据来决定要呈现的内容

(2) 更新环节:
    ① 应用程序中发生了某些事情,例如用户单击按钮
    ② dispatch 一个 action 到 Redux store
    ③ store用之前的state和当前的action再次运行reducer函数,并将返回值保存为新的 state
    ④ 使用到数据状态的每个组件都使用新数据重新渲染
4. React+Redux基本使用
(1) 安装redux依赖
    npm install @reduxjs/toolkit react-redux
(2) 创建store对象
    创建 store/index.js 文件
        import {configureStore} from '@reduxjs/toolkit'
        import listSlice from './slices/listSlice'

        //创建store对象
        export default configureStore({
            // 传入reducer (reducer就相当于vuex中的modules模块)
            // 注意: 每个store对象至少有一个 reducer
            reducer:{
                // listSlice(列表)模块
                listSlice
            }
        })
(3) 创建 slices/listSlice.js文件,并导出 以供reducer接收
        // 导入slice
        import { createSlice } from "@reduxjs/toolkit";
        // 创建slice
        const listSlice = createSlice({
            // 区域名字
            name: "listSlice",
            // 初始数据(相当于vuex的state 公共状态)
            initialState: {
                list: ["苹果", "西瓜"],
            },
            // reducers 变更数据的方法(相当于vuex的mutations)
            reducers: {
                /* 
                参数:
                    state: 当前数据状态对象
                    payload: 调用方法时携带的参数对象 (直接解构,可以直接得到参数)
                */
                add(state, { payload }) {
                    // 调用方法携带的参数
                    console.log(payload);
                    state.list.push(payload);
                },
                del(state, { payload }) {
                    console.log(payload);
                },
            },
        });
        /* 注意: 向外暴露导出与之前的语法不同
            导出数据,直接 变量名.reducer 即可导出,外部可直接使用
            导出方法,需要 { 方法 } = 变量名.actions 导出,外部使用时还需要先引入
        */
        // 导出reducer
        export default listSlice.reducer;
        // 导出方法
        export const { add, del } = listSlice.actions;
(4) 使用Provider传递Store
    在主入口文件中(main.js)
        // 导入相关依赖
        import {Provider} from 'react-redux'
        import store from './store/index'
        ReactDOM.createRoot(document.getElementById('root')).render(
        <React.StrictMode>  
            {/* 通过 Provider 组件传递store,从而实现组件与redux的交互 */}
            <Provider store={store}>
                <App />
            </Provider>
        </React.StrictMode>,
        )
(5) 使用store的数据
    在需要使用的组件中按规定使用即可
    ① useSelector Hook
        获取store的数据,函数形参是state对
        const xxx = useSelector((state) => state.slice对象.数据名)
    ② useDispatch
        触发actions方法
        let dispatch = useDispatch();
        调用方法: dispatch(具体的方法())
        /* 
            useSelector获取store中的数据
            useDispatch触发actions方法
        */
        import { useSelector, useDispatch } from 'react-redux'
        // 注意: 方法在使用前需要先导入
        import { add, del } from '../slices/userSlice'

        export default () => {
            // 访问数据  
            // useSelector参数为一个函数,函数的形参是state对象 return的数据会被变量接收
            let list = useSelector(state => state.listSlice.list);
            // 使用dispatch触发action
            let dispatch = useDispatch();  // useDispatch 调用方法的工具
        
            return (
                <div className="App">
                    {
                        list.map((item,index) => (
                            <li key={index}>{item}</li>
                        ))
                        <button onClick={() => dispatch(add("香蕉"))}>添加</button>
                    }
                    
                </div>
            );
        }
5. 异步逻辑
如果要让异步逻辑与Store交互,我们需要使用redux middleware(中间件)
Redux Toolkit 的 configureStore 功能默认自动设置 thunk middleware,我们推荐使用 thunk 作为 Redux 开发异步逻辑的标准方式。
Thunk 函数:
    在Thunk函数中我们可以编写异步逻辑的代码(例如 setTimeoutPromiseasync/await),并且可以通过参数获取到dispatch[actions方法],getState()[store数据],从而在异步操作执行后再diapacth action。
注意: Thunk 通常写在当前 slice 文件中
(1) 定义异步方法
    // 导出thunk函数 addAsync为thunk函数的创建函数,它返回一个thunk函数
    // 返回的thunk函数中我们就可以编写异步代码了
    // 形参val: 调用异步函数所携带的参数
        export const AddAsync = (val) => (dispatch, getState) => {
            /* 
                dispatch: 获取reducers中定义的方法  dispatch(方法(参数))
                getState: 获取initialState中的数据
            */
            setTimeout(() => {
                dispatch(add(val));
            }, 2000);
        }
(2) 使用异步方法
    同方法: 
        ① import 导入异步方法
            import {AddAsync} from "./slice/listSlice"const dispatch = useDispatch();
        ③ <button onClick={() => dispatch(AddAsync("异步香蕉"))}>异步添加</button>

Day06: reactRouter

一、react路由

1. router简介
React Router是一个基于 React之上的强大路由库,它可以让你向应用中快速地添加视图和数据流,同时保持页面与URL间的同步
React应用是单页面应用(一般只有一个html文件),路由可以让我们实现页面内容的切换。
安装路由依赖: npm install react-router-dom
2. 路由基本用法
(1) 在main.jsx中 引入BrowserRouter包裹整个应用
    import { BrowserRouter } from "react-router-dom"
    ReactDOM.createRoot(document.getElementById('root')).render(
    <BrowserRouter>
        <App />
    </BrowserRouter>
    )
(2) 使用 Routes和Route 进行对路由的切换
    import { Routes,Route } from "react-router-dom";
    import Index from "./views/Index"
    import List from "./views/list"

    export default () => {
        return (
            <div>
            <Routes>
                <Route path="/" element={<Index />} />
                <Route path="/list" element={<List />} />
            </Routes>
            </div>
        )
    }
3. 控制路由切换
LinkNavLink内置组件的to属性都控制路由切换,在页面中都会渲染成a标签,类似vuerouter-link
LinkNavLink切换的区别:
    Link在页面中渲染成a标签不会有类名
    NavLink在页面中渲染成a标签会有一个 active 的类名,可以实现导航高亮效果
to属性值可以为对象,对象内有一个 pathname 属性,表示要跳转到的路径地址(完整路径)
    <Link to="/">首页</Link>
    <Link to="/user">用户页面</Link>
    <Link to={{pathname:'/user/details'}} >用户详情页</Link>
4. js中路由切换
Navigate Hook是通过js控制页面路由跳转,类似vue中的$router
① 引入 Navigate Hook: 
    import { useNavigate } from "react-router-dom"
② 得到跳转方法: 
    const Navigate = useNavigate();
③ 在js中进行切换:
    Navigate("/list")  跳转到新页面,类似vue的$router.push()
    Navigate("/list",{replace:true})  替换到新页面,类似vue的$router.replace()
    Navigate(-1)  后退一个页面,类似vue的$router.back()
    Navigate(1)  前进一个页面
    import { Routes,Route, Link, NavLink } from "react-router-dom";
    import Index from "./views/Index"
    import List from "./views/list"

    export default () => {
        return (
            <div>
                {/* 控制路由切换 */}
                {/* <Link to="/">首页</Link> | <Link to="/list">列表页</Link> */}
                {/* 导航链接 */}
                <NavLink to="/">首页</NavLink> | <NavLink to="/list">列表页</NavLink>
                <Routes>
                    <Route path="/" element={<Index />} />
                    <Route path="/list" element={<List />} />
                </Routes>
            </div>
        )
    }
5. params传递参数
(1) 传递参数
    <Routes>
        <Route path="/list" element={<List />} />
        <!-- 需要参数的地方直接在路径后加 "/:参数名" 的参数占位 -->
        <Route path="/details/:name" element={<Details />} />
    </Routes>
(2) 接收参数
    import { useParams, useLocation } from "react-router-dom";
    // location 获取路径信息对象
    let location = useLocation();
    // params 获取参数对象  params.name 就是传递的参数
    let params = useParams();
    console.log(location.pathname,params)
        import { Routes, Route, Link, NavLink } from "react-router-dom";
        import Index from "./views/Index"
        import List from "./views/list"
        import Details from "./views/Details"

        export default () => {
            return (
                <div>
                    {/* 导航链接 */}
                    <NavLink to="/">首页</NavLink> | 
                    <NavLink to="/list">列表页</NavLink>
                    <Routes>
                        <Route path="/" element={<Index />} />
                        <Route path="/list" element={<List />} />
                        {/* 注意: 需要参数的路径使用 :参数名 接收 */}
                        <Route path="/details/:name" element={<Details />} />
                    </Routes>
                </div>
            )
        }
    // List.jsx 列表页(列表参数传递给详情页)
        import { useNavigate, Link } from "react-router-dom"
        const List = () => {
            const Navigate = useNavigate();
            let names = ["张三", "李四", "王五", "赵六"]
            // js方式传递参数
            let toDetail = (name) => {
                Navigate("/details/" + name)
            }
            return (
                <div>
                    <h3>列表页</h3>
                    <ul>
                        {
                            names.map(item => 
                                {/* 模板传递参数 */}
                                // <Link to={"/details/" + item}>{item}</Link>
                                {/* js传递参数 */}
                                <li key={item} onClick={toDetail(item)}>{item}</li>
                            )
                        }
                    </ul>
                </div>
            )
        }
        export default List

    // Details.jsx 详情页
        import {useParams,useLocation} from "react-router-dom";
        const Details = () => {
            // location 获取路径信息对象
            let location = useLocation();
            // params 获取参数对象  params.name 就是传递的参数
            let params = useParams();
            console.log(location.pathname, params)
            return (
                <div>
                    <h3>详情页 -- {params.name}</h3>
                </div>
            )
        }
        export default Details
6. 路由懒加载与重定向
(1) 渲染404页面
    element属性可传组件和模板片段
    <Route path="*" element={<p>404</p>} />
(2) 重定向
    使用 Navigate 跳转到另一页面即可
    <Route path="/home" element={<Navigate to="/" />} />
(3) 路由懒加载
    ① 引入和定义
        import { lazy, Suspense } from "react";
        const Index = lazy(() => import("./pages/Index"));
    ② 懒加载
        <Route path="/"
            element={
                // Suspense包裹,fallback属性 指定加载时的占位元素(也可以是模板或组件)
                <Suspense fallback={<div>loading...</div>}>
                    <Index />
                </Suspense>
            }
        />
    import List from "./pages/List";
    // 组件懒加载
    import { lazy, Suspense } from "react";
    let Index = lazy(() => import("./pages/Index"));
    import { Routes, Route, NavLink, Navigate } from "react-router-dom";
    export default () => {
        return (
            <div>
                <NavLink to="/">首页</NavLink> | <NavLink to="/list">列表</NavLink>
                <Routes>
                    {/* 懒加载 */}
                    <Route
                        path="/"
                        element={
                            // fallback 指定加载时的占位元素
                            <Suspense fallback={<div>loading</div>}>
                                <Index />
                            </Suspense>
                        }
                    />
                    <Route path="/list" element={<List />} />
                    {/* 404 */}
                    <Route path="*" element={<p>404</p>} />
                    {/* 重定向   redirect  */}
                    <Route path="/home" element={<Navigate to="/" />} />
                </Routes>
            </div>
        );
    };
7. 嵌套路由
Route 写成双标签,内部就是子路由结构
    import Detail from "./pages/Detail";
    import Home from "./pages/Home";
    // 子路由
    import Index from "./pages/Index";
    import Mine from "./pages/Mine";
    import { Routes, Route, NavLink, Navigate } from "react-router-dom";

    export default () => {
        return (
            <Routes>
                <Route path="/" element={<Home />}>
                    {/* 定义子路由规则 index属性表示与父路由路径保持一致的默认路径  */}
                    <Route index element={<Index />} />
                    <Route path="mine" element={<Mine />} />
                </Route>
                <Route path="/detail" element={<Detail />} />
            </Routes>
        );
    };
    // 要显示子路由的地方,使用 <Outlet></Outlet> 进行占位
8. 路由拦截
    import Index from "./pages/Index";
    import List from "./pages/List";
    import Mine from "./pages/Mine";
    import Login from "./pages/Login";
    import { Routes, Route, Link, NavLink, Navigate } from "react-router-dom";

    // 路由拦截方案
    let Auth = ({ Target, roles }) => {
        let temp = "";

        if (localStorage.getItem("token")) {
            if (roles) {
                // 当前用户角色是否在允许的角色范围内
                if (roles.includes(localStorage.getItem("role"))) {
                    temp = <Target />;
                } else {
                    temp = <p>没有访问权限</p>;
                }
            } else {
                // 所有角色可访问
                temp = <Target />;
            }
        } else {
            // 没有登录 replace属性表示替换路径
            temp = <Navigate to="/login" replace />;
        }
        return temp;
    };

    export default () => {
        return (
            <div>
            <NavLink to="/">首页</NavLink> | 
            <NavLink to="/list">列表</NavLink> |
            <NavLink to="/mine">我的</NavLink>
            {/* 
                每次切换路由 都要执行一次
                先跳入到一个验证组件中 验证组件决定是否渲染目标组件
            */}
            <Routes>
                <Route path="/" element={<Index />} />
                <Route
                path="/list"
                {/* 将Auth以组件形式调用,传入相关属性,表示权限功能 */}
                element={<Auth Target={List} roles={["admin"]} />}
                />
                <Route path="/mine" element={<Auth Target={Mine} />} />
                <Route path="/login" element={<Login />} />
            </Routes>
            </div>
        );
    };
9. 路由拦截配合懒加载
    let Auth = ({ Target, roles }) => {
        let temp = "";

        if (localStorage.getItem("token")) {
            // 懒加载
            let targetTemp = (
                <Suspense fallback={<p>loading...</p>}>
                    <Target />
                </Suspense>
            );

            if (roles) {
                // 当前用户角色是否在允许的角色范围内
                if (roles.includes(localStorage.getItem("role"))) {
                    temp = targetTemp;
                } else {
                    temp = <p>没有访问权限</p>;
                }
            } else {
                // 所有角色可访问
                temp = targetTemp;
            }
        } else {
            // 没有登录
            temp = <Navigate to="/login" replace />;
        }
        return temp;
    };
10. routes与route
(1) Routes
    包裹一组<Route>,每当地址发生变化时,<Routes>都会查看其所有子<Route>元素,以找到最佳路径匹配并呈现对应的UI。
    <Routes>
        <Route path="/" element={<App />}/> 
        <Route path='user' element={<User/>}/>
    </Routes>
(2) Route:
    用来配置URL与UI,如果其路径与当前地址栏中URL匹配,则会呈现其元素。
    属性:
        ① path:设置访问路径
        ② element:设置对应URL要渲染的组件
        ③ caseSensitive:设置路径是否区分大小写,默认为false
11. Outlet组件
父路由元素中应使用<Outlet />来渲染其子路由元素,这允许在呈现子路由时显示嵌套UI。
如果URL跟父路由的路径完全匹配,则会默认渲染设置了index属性的子路由,如果子路由中没有设置index的,则不渲染任何子路由。
    <Routes>
        <Route path="/" element={<App />}>
            <Route index element={<Home />} />
            <Route path="user" element={<User />}/>
        </Route>
    </Routes>

    function App(props) {
        return <>
            <h2>App</h2>
            {/* 将来子路由的UI被渲染到Outlet这个位置 */}
            <Outlet />
        </>
    }
12. Navigate
当渲染一个 <Navigate> 元素的时候,就会执行它指向的路由跳转。
<Navigate to='/user'/> 
一般逻辑发生变化后,想要跳转到其他页面(非手动点击方式)使用。
13. 路由传参
(1) params传参
    在要跳转的路由路径中使用 :params 这种语法传递参数,
    然后在跳转后的页面中使用useParams读取传递过来的参数。
        {/* 设置参数的key值 */}
        <Route path="user/:city" element={<User />} />
        {/* 跳转路由的时候传递参数,参数值为beijing */}
        <NavLink to="user/beijing" style={({ isActive }) => {
            return isActive ? { color: 'red' } : undefined
        }}>用户页面</NavLink>
(2) search传参
    如果to接收一个对象,则可以通过对象的属性search传递参数,通过useSearchParams()获取参数
    此形式传递的参数,是以键值对形式存在的 /xxx?键名=键值
        {/* 通过search属性传递参数,search接收的参数只能是字符串,所以使用模板字符串包裹 */}
        <NavLink to={{pathname:'user',search:`city=${'beijing'}`}} >用户页面</NavLink>

        function User(props) {
            // 通过useSearchParams读取通过search传递的参数
            const [searchParams,setSearchParams]=useSearchParams()
            console.log(searchParams.get('city'))  // beijing
            return <>
                < h2>我是用户页面</h2>
                <Outlet />
            </>
        }

Day07: 组件缓存

一、组件缓存

解决一: 放到公共状态库中 解决二: 插件 react-activation react-activation: 缓存渲染的组件 类似vue中的keep-alive 参考网址: github.com/CJY0208/rea… 使用步骤: (1) 下载模块: npm i react-activation (2) 包裹整个应用 main.jsx

        import {AliveScope} from "react-activation"
        ReactDOM.createRoot(document.getElementById('root')).render(
            <AliveScope>
                <App />
            </AliveScope>
        )
    (3) 哪个组件需要被缓存,就给哪个组件使用 keepAlive包裹
        import KeepAlive from "react-activation";
        <KeepAlive>
            <Ipt />
        </KeepAlive>
    (4) 对于被缓存的组件,不会触发生命周期,activation提供了激活和失活两个钩子函数
        import { useActivate, useUnactivate } from "react-activation";
        useActivate(() => {
            console.log("缓存组件激活");
        });
        useUnactivate(() => {
            console.log("缓存组件失活");
        });
    // 例子: 缓存输入框组件输入的数据
    // App.jsx
    import Ipt from "./components/Ipt";
    import { useState } from "react";
    import KeepAlive from "react-activation";

    const App = () => {
        // 控制Ipt是否渲染
        let [flag, setFlag] = useState(true);

        return (
            <div>
                <h2>组件缓存</h2>
                <button onClick={() => setFlag(!flag)}>切换flag</button>
                <hr />
                {flag && (
                    <KeepAlive>
                    <Ipt />
                    </KeepAlive>
                )}
            </div>
        );
    };
    export default App;

    // Ipt.jsx
    // 激活和失活的钩子
    import { useActivate, useUnactivate } from "react-activation";
    const Ipt = () => {
        useActivate(() => {
            console.log("激活");
        });
        useUnactivate(() => {
            console.log("失活");
        });

        return <input type="text" />;
    };
    export default Ipt;