React基础大全

news2024/12/24 8:33:55

文章目录

  • 一、React基本介绍
    • 1.虚拟DOM优化
      • 1.1 原生JS渲染页面
      • 1.2 React渲染页面
    • 2.需要提前掌握的JS知识
  • 二、入门
    • 1.React基本使用
    • 2.创建DOM的两种方式
      • 2.1 使用js创建(一般不用)
      • 2.2 使用jsx创建
    • 3.React JSX
      • 3.1 JSX常见语法规则
      • 3.2 for循环渲染数据
    • 4.模块与组件、模块化与组件化
      • 4.1 模块
      • 4.2 组件
      • 4.3 模块化
      • 4.4 组件化
  • 三、面向组件编程
    • 1.安装React开发者调试工具
    • 2.自定义组件
      • 2.1 函数式组件
      • 2.2 类式组件
    • 3.组件实例(class)三大核心属性
      • 3.1 状态-state
        • 3.1.1 基础使用(不适用)
        • 3.1.2 代码优化
      • 3.2 属性-props
        • 3.2.1 基础用法
        • 3.2.2 校验props属性值
        • 3.2.3 函数式组件实现
      • 3.3 refs和事件处理
        • 3.3.1 字符串形式(不推荐)
        • 3.3.2 回调函数形式
        • 3.3.3 refs容器形式
        • 3.3.4 事件处理
    • 4.收集表单数据
      • 4.1 非受控组件案例
      • 4.2 受控组件案例
      • 4.3 函数柯里化
      • 4.4 不使用函数柯里化实现方式
    • 5.组件的生命周期
      • 5.1 生命周期-案例
      • 5.2 生命周期(旧)
        • 5.2.1 流程图
        • 5.2.2 示例代码
      • 5.3 生命周期(新)
        • 5.3.1 流程图
        • 5.3.2 代码案例
        • 5.3.2 getSnapshotBeforeUpdate-案例
      • 5.4 DOM的diffing算法
  • 四、react应用(基于react脚手架)
    • 1.创建react应用
      • 1.1 react脚手架
      • 1.2 创建项目并启动
      • 1.3 项目结构
    • 2.组件目录定义和导入
    • 3.样式模块化
    • 4.vscode安装react插件
    • 5.组件化编码流程
    • 6.组件组合使用-TodoList案例
      • 6.1 拆组件
      • 6.2 依赖安装
      • 6.3 总结
  • 五、react-axios
    • 1.安装axios
    • 2.代理配置
      • 2.1 方法一(不使用)
      • 2.2 方法二(常用)
    • 3.github搜索案例
  • 六、react-router
    • 1.相关理解
      • 1.1 SPA理解
      • 1.2 路由的理解
      • 1.3 react-router-dom的理解
    • 2.react-router-dom
      • 2.1 安装
      • 2.2 路由组件使用
      • 2.3 路由组件和一般组件的区别
      • 2.4 NavLink
      • 2.5 封装NavLink组件
      • 2.6 Switch使用
      • 2.7 样式丢失
      • 2.8 路由模糊匹配
      • 2.9 Redirect重定向
      • 2.10 嵌套(多级)路由
      • 2.11 向路由组件传递参数数据
        • 2.11.1 传递params参数
        • 2.11.2 传递search参数
        • 2.11.3 传递state参数
      • 2.12 路由跳转的两个模式
        • 2.12.1 push模式
        • 2.12.2 replace模式
      • 2.13 编程式路由导航
      • 2.14 让一般组件也有路由组件的参数-withRouter
      • 2.15 BrowserRouter与HashRouter的区别
  • 七、组件库-Ant Design
    • 1.安装
    • 2.入门体验
    • 3.按需引入样式
    • 4.自定义主题
      • 4.1 安装包
      • 4.2 修改package.json
      • 4.3 创建配置文件
  • 八、redux
    • 1.理解
      • 1.1 redux是什么
      • 1.2 使用场景
    • 2.redux的核心API
      • 2.1 安装redux
      • 2.2 redux工作流程
      • 2.3 使用案例
        • 2.3.1 求和案例-纯react版本
        • 2.3.2 求和案例-redux精简版
        • 2.3.3 求和案例-redux完整版
      • 2.4 异步action
        • 2.4.1 安装
        • 2.4.2 注册中间件
        • 2.4.3 异步aciton定义
  • 九、react-redux
    • 1.模型图
    • 2.安装
    • 3.相关概念
    • 4.基础操作(单组件)
      • 4.1 index.js
      • 4.2 redux相关
      • 4.3 容器
      • 4.4 UI组件
    • 5.基础操作(代码优化)
      • 5.1 容器优化
      • 5.2 redux状态变化机制
      • 5.3 容器传入store优化-Provider
      • 5.4 整合UI组件和容器组件
    • 6.基础操作(多组件)数据共享
      • 6.1 实现效果
      • 6.2 redux目录结构
      • 6.3 相关代码
    • 7.纯函数
      • 7.1 reducer注意事项
      • 7.2 纯函数
    • 8.redux开发者工具
      • 8.1 安装扩展程序
      • 8.2 安装库
      • 8.3 store.js注册插件
  • 十、react打包
  • 十一、react扩展
    • 1.setState
      • 1.1 对象式
      • 1.2 函数式
    • 2.路由懒加载(lazyLoad)
      • 2.1 无懒加载
      • 2.2 懒加载
    • 3.Hooks
      • 3.1 是什么?
      • 3.2 三个常用的Hook
      • 3.3 State Hook
      • 3.4 Effect Hook
      • 3.5 Ref Hook
    • 4.Fragment
    • 5.Context
      • 5.1 使用
      • 5.2 类组件代码示例
      • 5.3 函数组件代码示例
    • 6.组件优化
      • 6.1 解决办法1
      • 6.2 解决办法2(推荐)
    • 7.render props
      • 7.1 如何向组件内部动态传入带内容的结构(标签)
      • 7.2 children props
      • 7.3 render props
    • 8.错误边界
    • 9.组件通信方式总结
  • 十二、React Router6
    • 1.概述
    • 2.component
      • 2.1 BrowerRouter
      • 2.2 HashRouter
      • 2.3 Routes与Route
      • 2.4 Link
      • 2.5 NavLink
      • 2.6 Navigate
      • 2.7 Outlet
    • 3.Hooks
      • 3.1 useRoutes()
      • 3.2 useNavigate()
      • 3.3 useParams()
      • 3.4 useSearchParams()
      • 3.5 useLocation()
      • 3.6 useMatch()
      • 3.7 useInRouterContext()
      • 3.8 useNavigationType()
      • 3.9 useOutlet()
      • 3.10 useResolvedPath()
    • 4.综合案例
      • 4.1 useRouters路由表
      • 4.2 嵌套路由
      • 4.3 路由传参
      • 4.4 编程式路由导航

一、React基本介绍

  • React是用于构建用户界面的JavaScript的库,将数据渲染为HTML视图的开源库。
  • 三部曲:
    • 1.发送请求获取数据
    • 2.处理数据(过滤、整理格式等)
    • 3.操作DOM呈现页面
  • 为什么要学?
    • 1.原生JavaScript操作DOM繁琐、效率低(DOM-API操作UI)。
    • 2.原生JavaScript直接操作DOM,浏览器会进行大量的重绘重排
    • 3.原生JavaScript没有组件化编码方案,代码复用率低。
  • React特点:
    • 1.采用组件化模式、声明式编码,提高开发效率及组件复用率。
    • 2.在React Native中可以使用React语法进行移动端开发。
    • 3.使用虚拟DOM+优秀的Diffing算法,尽量减少与真实DOM的交互。
  • 官网
    • 英文官网:https://react.dev/
    • 中文官网:
      • https://zh-hans.react.dev/
      • https://react.docschina.org

1.虚拟DOM优化

1.1 原生JS渲染页面

在这里插入图片描述

1.2 React渲染页面

在这里插入图片描述

2.需要提前掌握的JS知识

  • 判断this的指向
  • class(类)
  • ES6语法规范
  • npm包管理器
  • 原型、原型链
  • 数组常用方法
  • 模块化

二、入门

  • 相关react练习的js,在本文的资源绑定里面

1.React基本使用

  • babel的作用:
    • 1.将ES6的语法转化为ES5
    • 2.将JSX语法转为JS
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>hello_react</title>
</head>
<body>
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <!-- 引入react核心库 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->    
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 -->
    <script type="text/babel">
        // 1.创建虚拟DOM(一定不要写引号,不需要写引号)
        const VDOM = <h1>Hello, React</h1>
        // 2.渲染虚拟DOM到页面
        ReactDOM.render(VDOM, document.getElementById('test'));
    </script>
</body>
</html>

在这里插入图片描述

2.创建DOM的两种方式

  • 为什么React使用JSX语法而不是JS
    • JSX写法更适用于复杂的页面渲染场景
  • 关于虚拟DOM:
    • 1.本质是Object类型的对象(一般对象)
    • 2.虚拟DOM绑定的函数很少,真实DOM比较多,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性。
    • 3.虚拟DOM最终会被React转化为真实DOM,呈现在页面上。

2.1 使用js创建(一般不用)

...省略
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <script type="text/babel">
        // 1.创建虚拟DOM
        const VDOM = React.createElement('h1', {id: 'title'}, React.createElement('span', {}, 'Hello,React'))
        // 2.渲染虚拟DOM到页面
        ReactDOM.render(VDOM, document.getElementById('test'));
    </script>
...省略

2.2 使用jsx创建

...省略
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <script type="text/babel">
        // 1.创建虚拟DOM(一定不要写引号,不需要写引号)
        const VDOM = <h1 id="title"><span>Hello, React</span></h1>
        // 2.渲染虚拟DOM到页面
        ReactDOM.render(VDOM, document.getElementById('test'));
    </script>
...省略

3.React JSX

  • 全称:JavaScript XML
  • react定义的一种类似于XML的JS扩展语法:JS +XML↵
  • 本质是React.createElement(component,props,.….children)方法的语法糖
  • 作用:用来简化创建虚拟DOM操作
    • 写法:var ele = <h1>Hello JSX</hw>
    • 注意:它不是字符串,也不是HTML/XML标签,最终产生的是一个JS对象

3.1 JSX常见语法规则

  • JS语法规则:
    • 1.定义虚拟DOM时,不要写引号
    • 2.标签中混入JS表达式时要用{}
      • 表达式就是能被变量接收的:var a = 表达式;
      • 表达式和代码的区别
        • 表达式:
          • a
          • a+b
          • demo(1)
          • arr.map()
          • function test () {}
        • 代码:
          • if(){}
          • for(){}
          • switch(){case:xxxx}
    • 3.样式的类名指定不要用class,要用className。
    • 4.内联样式,要用style={{key:value}}的形式去写。
      • 如果是需要定义font-size属性,那么需要改成驼峰命名:fontSize
    • 5.虚拟DOM里面只能有一个根标签
    • 6.标签必须闭合:
      • 例如input标签:两种写法
        • <input type="text"/>
        • <input type="text"/></input>
    • 7.标签首字母
      • (1)若小写字母开头,则将改标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。
      • (2)若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>hello_react</title>
    <style>
        .title{
            background-color:blueviolet;
        }
    </style>
</head>
<body>
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <!-- 引入react核心库 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->    
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 -->
    <script type="text/babel">
        const a = "A";
        const b = "B";
        // 1.创建虚拟DOM(一定不要写引号,不需要写引号)
        const VDOM = (
            <div>
                <h1 className="title">
                    <span style={{color: 'white', fontSize: '29px'}}>{a.toLocaleLowerCase()}</span>
                </h1>
                <h1>
                    <span style={{color: 'blue', fontSize: '29px'}}>{b.toLocaleLowerCase()}</span>
                </h1>
            </div>
        )
        // 2.渲染虚拟DOM到页面
        ReactDOM.render(VDOM, document.getElementById('test'));
    </script>
</body>
</html>

3.2 for循环渲染数据

  • 由于React的虚拟DOM的原因,如果遍历数据,需要有个key,DOM优化算法就是根据key进行判断是否存在。
<body>
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <!-- 引入react核心库 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->    
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 -->
    <script type="text/babel">
        const title = "前端js框架列表";
        const framework_list = [
            {"value": 1, "label": "Angular"}, 
            {"value": 2, "label": "React"}, 
            {"value": 3, "label": "Vue"}, 
        ];
        // 1.创建虚拟DOM(一定不要写引号,不需要写引号)
        const VDOM = (
            <div>
                <h1 className="title">
                    <span>{title}</span>
                </h1>
                <ul>
                    {
                        framework_list.map((item)=>{
                            return <li key={item.value}>{item.label}</li>
                        })
                    }
                </ul>
            </div>
        )
        // 2.渲染虚拟DOM到页面
        ReactDOM.render(VDOM, document.getElementById('test'));
    </script>
</body>

4.模块与组件、模块化与组件化

4.1 模块

  • 理解:向外提供特定功能的 js 程序,一般就是一个 js 文件↵
  • 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。
  • 作用:复用 js,简化js的编写,提高js 运行效率↵

4.2 组件

  • 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)
  • 为什么:一个界面的功能更复杂
  • 作用:复用编码,简化项目编码,提高运行效率

4.3 模块化

  • 当应用的js都以模块来编写的,这个应用就是一个模块化的应

4.4 组件化

  • 当应用是以多组件的方式实现,这个应用就是一个组件化的应用

三、面向组件编程

1.安装React开发者调试工具

  • 需要使用chrome浏览器
    在这里插入图片描述

2.自定义组件

2.1 函数式组件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>hello_react</title>
    <style>
        .title{
            background-color:blueviolet;
        }
    </style>
</head>
<body>
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <!-- 引入react核心库 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->    
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 -->
    <script type="text/babel">
        // 1.创建函数式组件
        function Demo(){
            // 组件里面的this,会经过babel,但是babel会开启严格模式,不允许this指向window,所以会打印undefined
            console.log(this)
            return <h2>我是组件</h2>
        }
        // 2.渲染组件到页面
        ReactDOM.render(<Demo/>, document.getElementById('test'))
        /*
            执行了ReactDOM.render(<Demo/>...…………之后,发生了什么?
                1.React解析组件标签,找到了Demo组件。
                2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
        */
    </script>
</body>
</html>

2.2 类式组件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>类组件</title>
</head>
<body>
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <!-- 引入react核心库 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->    
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 -->
    <script type="text/babel">
        // 1.创建类式组件
        class MyComponent extends React.Component {
            render(){
                //render是放在哪里的?—MyComponent的原型对象上,供实例使用。
                //render中的this是谁?-MyComponent的实例对象。
                return <h2>我是类定义的组件</h2>
            }
        }
        // 2.渲染组件到页面
        ReactDOM.render(<MyComponent/>, document.getElementById('test'))
        /*
            执行了ReactDOM.render(<MyComponent/>...…………之后,发生了什么?
                1.React解析组件标签,找到了MyComponent组件。
                2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法
                3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
        */
    </script>
</body>
</html>

3.组件实例(class)三大核心属性

3.1 状态-state

  • 理解:状态其实就是数据,驱动页面的显示内容
    • state 是组件对象最重要的属性,值是对象(可以包含多个 key-value 的组合)
    • 组件被称为"状态机",通过更新组件的 state 来更新对应的页面显示(重新渲染组件)

注意事项:
1.组件中 render方法中的this为组件实例对象组件自定义的方法中this为undefined,如何解决?
        a.强制绑定this:通过函数对象的bind()
        b.箭头函数
2.状态的数据不可以直接更新,需要使用setState进行更改,React才会同步更新到页面显示。
3.更新是一种合并,不是替换。只会替换setState传入的key和value,没有传入的,不会进行删除。

3.1.1 基础使用(不适用)
  • 添加按钮绑定事件流程:
    • 1.定义一个类组件
    • 2.编写render方法
    • 3.编写点击事件函数
    • 4.在构造器中绑定点击函数
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>类组件</title>
</head>
<body>
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <!-- 引入react核心库 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->    
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 -->
    <script type="text/babel">
        // 1.创建类式组件
        class Weather extends React.Component {
            // 构造器只会调用一次,调用几次组件,才会调用几次。
            constructor(props){
                super(props)
                // 初始化状态
                this.state = {isHot: true, wind: "微风"}
                //解决changeWeather中this指向问题
                this.changeWeather = this.changeWeather.bind(this)
            }
            // render调几次? 1+n次 1是初始化的那次 n是状态更新的次数
            render(){
                // 读取状态
                const {isHot, wind} = this.state
                return <h2 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}{wind}</h2>
            }
            // changeWeather调用几次?——点几次调几次
            changeWeather(){
                //changeweather放在哪里?  Weather的原型对象上,供实例使用
                //由于changeWeather是作为onclick的回调,所以不是通过实例调用的,是直接调用
                //类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined
                //   解决办法:this.changeWeather = this.changeWeather.bind(this)
                //console.log(this)

                // 注意:状态(state)不可直接更改,下面这行就是直接更改!!!
                // this.state.isHot = ! this.state.isHot
                
                //  注意:状态必须通过setState进行更新,且更新是一种合并,不是替换。
                this.setState({isHot: ! this.state.isHot})
            }
        }
        // 2.渲染组件到页面
        ReactDOM.render(<Weather/>, document.getElementById('test'))
    </script>
</body>
</html>
3.1.2 代码优化
  • 优化内容:
    • 1.避免在构造器中,写大量的绑定代码:需要改成箭头函数,箭头函数会寻找外层的this
    • 2.避免在构造器中,赋值state:而是直接在类里面命名
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>类组件</title>
</head>
<body>
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <!-- 引入react核心库 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->    
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 -->
    <script type="text/babel">
        // 1.创建类式组件
        class Weather extends React.Component {
            state = {isHot: true, wind: "微风"}
            render(){
                const {isHot, wind} = this.state
                return <h2 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}{wind}</h2>
            }
            //自定义方法——要用赋值语句的形式+箭头函数
            changeWeather = ()=>{
                this.setState({isHot: ! this.state.isHot})
            }
        }
        // 2.渲染组件到页面
        ReactDOM.render(<Weather/>, document.getElementById('test'))
    </script>
</body>
</html>

3.2 属性-props

  • 作用:通过标签属性从组件外向组件内传递变化的数据
  • 注意:
    • 1.props是只读的,无法修改
    • 2.函数式组件有props,但是没有state和refs,如果需要实现,需要用到新特性hooks
3.2.1 基础用法
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>props基本使用</title>
</head>
<body>
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <!-- 引入react核心库 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->    
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 -->
    <script type="text/babel">
        // 1.创建类式组件
        class Person extends React.Component {
            render(){
                const {name,sex,age} = this.props
                return (
                    <ul>
                        <li>姓名:{name}</li>
                        <li>性别:{sex}</li>
                        <li>年龄:{age}</li>
                    </ul>
                )
            }
        }
        // 2.渲染组件到页面
        ReactDOM.render(<Person name="tom" sex="女" age="18"/>, document.getElementById('test'))
        // 下面使用解包进行传递,但是ES语法是不允许是解包一个对象的,下面是因为babel识别到了React标签用了这个语法,特殊进行了转换,才可以使用
        const p = {name:"tom", sex:"女", age:"18"}
        ReactDOM.render(<Person {...p}/>, document.getElementById('test'))
    </script>
</body>
</html>
3.2.2 校验props属性值
  • 从React16开始,需要引入prop-types.js才可以使用
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>props基本使用</title>
</head>
<body>
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <!-- 引入react核心库,全局会多了一个对象React -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->    
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes -->
    <script type="text/javascript" src="../js/prop-types.js"></script>

    <!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 -->
    <script type="text/babel">
        // 1.创建类式组件
        class Person extends React.Component {
            // 属性值校验
            static propTypes = {
                // name必传的字符串类型
                name: PropTypes.string.isRequired,
                // sex非必填的字符串类型
                sex: PropTypes.string,
                // 非必传的数字类型
                age: PropTypes.number,
                // 传入一个函数
                speak: PropTypes.func
            }
            // 属性值不传的默认值
            static defaultProps = {
                sex: "不知道男女",
                age: 18
            }
            render(){
                const {name,sex,age} = this.props
                return (
                    <ul>
                        <li>姓名:{name}</li>
                        <li>性别:{sex}</li>
                        <li>年龄:{age}</li>
                    </ul>
                )
            }
        }
        
        // 2.渲染组件到页面
        const p = {"name": "tom", sex:"女", age:18, speak: speak}
        ReactDOM.render(<Person {...p}/>, document.getElementById('test'))
        function speak () {
            console.log("说话")
        }
    </script>
</body>
</html>
3.2.3 函数式组件实现
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>props基本使用</title>
</head>
<body>
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <!-- 引入react核心库,全局会多了一个对象React -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->    
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes -->
    <script type="text/javascript" src="../js/prop-types.js"></script>

    <!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 -->
    <script type="text/babel">
        // 1.创建函数式组件
        function Person (props) {
            const {name,sex,age} = props
                return (
                    <ul>
                        <li>姓名:{name}</li>
                        <li>性别:{sex}</li>
                        <li>年龄:{age}</li>
                    </ul>
                )
        }
        // 属性值校验
        Person.propTypes = {
                // name必传的字符串类型
                name: PropTypes.string.isRequired,
                // sex非必填的字符串类型
                sex: PropTypes.string,
                // 非必传的数字类型
                age: PropTypes.number,
            }
        // 属性值不传的默认值
        Person.defaultProps = {
            sex: "不知道男女",
            age: 18
        }
        // 2.渲染组件到页面
        const p = {"name": "tom", sex:"女", age:18}
        ReactDOM.render(<Person {...p}/>, document.getElementById('test'))
   
    </script>
</body>
</html>

3.3 refs和事件处理

  • refs:组件内的标签可以定义 ref 属性来标识自己,类似组件的里面的id
  • 平时开发只需要使用内联函数形式即可
  • 注意:ref不要过度使用(发生事件的DOM元素和操作的DOM元素,这个时候就可以不写ref,而是使用事件处理)
3.3.1 字符串形式(不推荐)
  • 官方不推荐,因为存在效率问题
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ref和事件处理</title>
</head>
<body>
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <!-- 引入react核心库,全局会多了一个对象React -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->    
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes -->
    <script type="text/javascript" src="../js/prop-types.js"></script>

    <!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 -->
    <script type="text/babel">
        // 1.创建类式组件
        class Demo extends React.Component {
            showData = ()=>{
                const {input1} = this.refs
                alert(input1.value)
            }
            render(){
                const {name,sex,age} = this.props
                return (
                    <div>
                        <input type="text" ref="input1" placeholder="点击按钮提示数据"/>
                        <button onClick={this.showData}>点我提示左侧的数据</button>
                    </div>
                )
            }
        }
        // 2.渲染组件到页面
        ReactDOM.render(<Demo/>, document.getElementById('test'))
   
    </script>
</body>
</html>
3.3.2 回调函数形式

更新数据的时候,重新render会调用两次(常用)*

  • 如果ref回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数null,然后第二次会传入参数DOM元素。这是因为在每次渲染时会创建一个新的函数实例,所以React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成class的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的
  • 下面是内联函数的写法,更新的时候会调用两次
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ref和事件处理</title>
</head>
<body>
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <!-- 引入react核心库,全局会多了一个对象React -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->    
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes -->
    <script type="text/javascript" src="../js/prop-types.js"></script>

    <!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 -->
    <script type="text/babel">
        // 1.创建类式组件
        class Demo extends React.Component {
            showData = ()=>{
                console.log(this)
                alert(this.input1.value)
                this.setState({})
            }
            render(){
                const {name,sex,age} = this.props
                return (
                    <div>
                        <input type="text" ref={(currentNode) =>{this.input1 = currentNode;console.log("@@", currentNode);}} placeholder="点击按钮提示数据"/>
                        <button onClick={this.showData}>点我提示左侧的数据</button>
                    </div>
                )
            }
        }
        // 2.渲染组件到页面
        ReactDOM.render(<Demo/>, document.getElementById('test'))
   
    </script>
</body>
</html>

在这里插入图片描述
通过将 ref 的回调函数定义成class的绑定函数的方式可以避免上述问题(不常用)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ref和事件处理</title>
</head>
<body>
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <!-- 引入react核心库,全局会多了一个对象React -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->    
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes -->
    <script type="text/javascript" src="../js/prop-types.js"></script>

    <!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 -->
    <script type="text/babel">
        // 1.创建类式组件
        class Demo extends React.Component {
            showData = ()=>{
                console.log(this)
                alert(this.input1.value)
                this.setState({})
            }
            saveInput = (currentNode)=>{
                this.input1 = currentNode
            }
            render(){
                const {name,sex,age} = this.props
                return (
                    <div>
                        <input type="text" ref={this.saveInput} placeholder="点击按钮提示数据"/>
                        <button onClick={this.showData}>点我提示左侧的数据</button>
                    </div>
                )
            }
        }
        // 2.渲染组件到页面
        ReactDOM.render(<Demo/>, document.getElementById('test'))
   
    </script>
</body>
</html>
3.3.3 refs容器形式
  • React.createRef
  • 该容器只能存储一个ref,而且current是当前节点
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ref和事件处理</title>
</head>
<body>
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <!-- 引入react核心库,全局会多了一个对象React -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->    
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes -->
    <script type="text/javascript" src="../js/prop-types.js"></script>

    <!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 -->
    <script type="text/babel">
        // 1.创建类式组件
        class Demo extends React.Component {
            /*
                React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点
            */
           myRef = React.createRef()
            showData = ()=>{
                alert(this.myRef.current.value)
            }
            render(){
                const {name,sex,age} = this.props
                return (
                    <div>
                        <input type="text" ref={this.myRef} placeholder="点击按钮提示数据"/>
                        <button onClick={this.showData}>点我提示左侧的数据</button>
                    </div>
                )
            }
        }
        // 2.渲染组件到页面
        ReactDOM.render(<Demo/>, document.getElementById('test'))
   
    </script>
</body>
</html>
3.3.4 事件处理
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ref和事件处理</title>
</head>
<body>
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <!-- 引入react核心库,全局会多了一个对象React -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->    
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes -->
    <script type="text/javascript" src="../js/prop-types.js"></script>

    <!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 -->
    <script type="text/babel">
        // 1.创建类式组件
        class Demo extends React.Component {
            /*
                (1).通过onXxx属性指定事件处理函数(注意大小写)
                    a.React使用的是自定义(合成)事件,而不是使用的原生DOM事件 --- 为了更好的兼容性
                    b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) --- 为了的高效
                (2)通过event.target得到发生事件的DOM元素对象
            */
           myRef = React.createRef()
            showData = (event)=>{
                alert(event.target.value)
            }
            render(){
                const {name,sex,age} = this.props
                return (
                    <div>
                        <input onBlur={this.showData} type="text" placeholder="失去焦点提示数据"/>
                    </div>
                )
            }
        }
        // 2.渲染组件到页面
        ReactDOM.render(<Demo/>, document.getElementById('test'))
   
    </script>
</body>
</html>

4.收集表单数据

  • 表单的组件分类:
    • 受控组件:好处是可以省略ref
    • 非受控组件:现用现取

4.1 非受控组件案例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ref和事件处理</title>
</head>
<body>
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <!-- 引入react核心库,全局会多了一个对象React -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->    
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes -->
    <script type="text/javascript" src="../js/prop-types.js"></script>

    <!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 -->
    <script type="text/babel">
        // 1.创建类式组件
        class Demo extends React.Component {
            handleSubmit = (event)=>{
                // 阻止表单提交
                event.preventDefault()
                const {username, password} = this
                alert(`你的用户名是:${username.value},你的密码是:${password.value}`)
            }
            render(){
                return (
                    <form action="http://127.0.0.1" onSubmit={this.handleSubmit}>
                        用户名:<input ref={c => this.username = c} type="text" name="username"/>
                        密码:<input ref={c => this.password = c} type="password" name="password"/>
                        <button>登录</button>
                    </form>
                )
            }
        }
        // 2.渲染组件到页面
        ReactDOM.render(<Demo/>, document.getElementById('test'))
   
    </script>
</body>
</html>

4.2 受控组件案例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ref和事件处理</title>
</head>
<body>
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <!-- 引入react核心库,全局会多了一个对象React -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->    
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes -->
    <script type="text/javascript" src="../js/prop-types.js"></script>

    <!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 -->
    <script type="text/babel">
        // 1.创建类式组件
        class Demo extends React.Component {
            //初始化状态,表单信息
            state = {
                username: '', //用户名
                password: '' // 密码
            }
            // 保存用户名到状态中
            saveUsername = (event)=>{
                this.setState({username: event.target.value})
            }
            // 保存密码到状态中
            savePassword = (event)=>{
                this.setState({password: event.target.value})
            }
            handleSubmit = (event)=>{
                // 阻止表单提交
                event.preventDefault()
                const {username, password} = this.state
                alert(`你的用户名是:${username},你的密码是:${password}`)
            }
            render(){
                return (
                    <form action="http://127.0.0.1" onSubmit={this.handleSubmit}>
                        用户名:<input onChange={this.saveUsername} type="text" name="username"/>
                        密码:<input onChange={this.savePassword} type="password" name="password"/>
                        <button>登录</button>
                    </form>
                )
            }
        }
        // 2.渲染组件到页面
        ReactDOM.render(<Demo/>, document.getElementById('test'))
   
    </script>
</body>
</html>

4.3 函数柯里化

  • 解决的问题:由于4.2中,每个字段都需要写一个onChange函数,会造成很多的重复工作。
  • 函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
  • 常见的高阶函数有:Promise、setTimeout、arr.map()等等
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>函数柯里化</title>
</head>
<body>
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <!-- 引入react核心库,全局会多了一个对象React -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->    
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes -->
    <script type="text/javascript" src="../js/prop-types.js"></script>

    <!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 -->
    <script type="text/babel">
        /*
            高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
                1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
                2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
            函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
        */
        // 1.创建类式组件
        class Demo extends React.Component {
            //初始化状态,表单信息
            state = {
                username: '', //用户名
                password: '' // 密码
            }
            // 保存表单数据到状态中
            saveFormData = (datType)=>{
                return (event)=>{
                    this.setState({[datType]: event.target.value})
                }
            }
            handleSubmit = (event)=>{
                // 阻止表单提交
                event.preventDefault()
                const {username, password} = this.state
                alert(`你的用户名是:${username},你的密码是:${password}`)
            }
            render(){
                return (
                    <form action="http://127.0.0.1" onSubmit={this.handleSubmit}>
                        用户名:<input onChange={this.saveFormData('username')} type="text" name="username"/>
                        密码:<input onChange={this.saveFormData('password')} type="password" name="password"/>
                        <button>登录</button>
                    </form>
                )
            }
        }
        // 2.渲染组件到页面
        ReactDOM.render(<Demo/>, document.getElementById('test'))
   
    </script>
</body>
</html>

4.4 不使用函数柯里化实现方式

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ref和事件处理</title>
</head>
<body>
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <!-- 引入react核心库,全局会多了一个对象React -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->    
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes -->
    <script type="text/javascript" src="../js/prop-types.js"></script>

    <!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 -->
    <script type="text/babel">
        /*
            高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
                1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
                2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
            函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
        */
        // 1.创建类式组件
        class Demo extends React.Component {
            //初始化状态,表单信息
            state = {
                username: '', //用户名
                password: '' // 密码
            }
            // 保存表单数据到状态中
            saveFormData = (datType, event)=>{
                this.setState({[datType]: event.target.value})
            }
            handleSubmit = (event)=>{
                // 阻止表单提交
                event.preventDefault()
                const {username, password} = this.state
                alert(`你的用户名是:${username},你的密码是:${password}`)
            }
            render(){
                return (
                    <form action="http://127.0.0.1" onSubmit={this.handleSubmit}>
                        用户名:<input onChange={(event) => {this.saveFormData('username', event)}} type="text" name="username"/>
                        密码:<input onChange={(event) => {this.saveFormData('password', event)}} type="password" name="password"/>
                        <button>登录</button>
                    </form>
                )
            }
        }
        // 2.渲染组件到页面
        ReactDOM.render(<Demo/>, document.getElementById('test'))
   
    </script>
</body>
</html>

5.组件的生命周期

  • 组件从创建到死亡它会经历一些特定的阶段。
  • React 组件中包含一系列勾子函数(生命周期回调函数),会在特定的时刻调用。
  • 我们在定义组件时,会在特定的生命周期回调函数,中做特定的工作。

5.1 生命周期-案例

  • 需求:
    • 1.标题需要2秒中从透明度1渐变到0再到1
    • 2.点击按钮,组件消失
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>组件生命周期</title>
</head>
<body>
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <!-- 引入react核心库,全局会多了一个对象React -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->    
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes -->
    <script type="text/javascript" src="../js/prop-types.js"></script>

    <!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 -->
    <script type="text/babel">
        // 1.创建类式组件
        class Life extends React.Component {
            state = {opacity: 1}

            death = () => {
                // 卸载组件
                ReactDOM.unmountComponentAtNode(document.getElementById('test'))
            }
            
            // 组件被挂载之后调用
            componentDidMount(){
                this.timer = setInterval(() => {
                    // 获取原状态
                    let {opacity} = this.state
                    // 减小0.1
                    opacity -= 0.1
                    if(opacity <= 0) opacity = 1
                    // 设置新的透明度
                    this.setState({opacity})
                }, 200);
            }

            // 组件将要卸载
            componentWillUnmount(){
                // 清除定时器
                 clearInterval(this.timer)
            }

            // 初始化渲染,和状态更新的时候会被调用
            render(){
                return (
                    <div>
                        <h2 style={{opacity:this.state.opacity}}>React学不会怎么办?</h2>
                        <button onClick={this.death}>不活了</button>
                    </div>
                )
            }
        }
        // 2.渲染组件到页面
        ReactDOM.render(<Life/>, document.getElementById('test'))
   
    </script>
</body>
</html>

5.2 生命周期(旧)

5.2.1 流程图
  • 新版本:提出了两个新的钩子,废弃了两个钩子
  • 总共有四种方式会触发钩子:
    • 组件挂载时
    • 父组件render
    • setState
    • forceUpdate:不想让状态更新,但是想让页面刷新

componentWillReceiveProps:组件第一次render的时候不会被调用,而在第二次开始才会被调用

在这里插入图片描述

5.2.2 示例代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>组件生命周期</title>
</head>
<body>
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <!-- 引入react核心库,全局会多了一个对象React -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes -->
    <script type="text/javascript" src="../js/prop-types.js"></script>

    <!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 -->
    <script type="text/babel">
        // 1.创建类式组件
        class Count extends React.Component {

            constructor(props){
                console.log("render - constructor")
                super(props)

                // 初始化状态
                this.state = {count: 0, content: "content"}
            }

            add = () => {
                this.setState({count: this.state.count + 1})
            }

            // 卸载组件按钮回调
            death = () => {
                ReactDOM.unmountComponentAtNode(document.getElementById('test'))
            }

            // 强制更新的按钮回调
            force = () => {
                this.forceUpdate()
            }

            // 修改子组件的内容
            changeContent = () => {
                this.setState({"content": "content1"})
            }

            // 组件将要挂载的钩子
            componentWillMount(){
                console.log("render - componentWillMount")
            }

            // 组件挂载完毕的钩子
            componentDidMount(){
                console.log("render - componentDidMount")
            }

            // 组件将要卸载的钩子
            componentWillUnmount(){
                console.log("render - componentWillUnmount")
            }

            // 控制组件是否更新的钩子(返回的布尔类型,如果是false,那么不会继续往下走)
            shouldComponentUpdate(){
                console.log("render - shouldComponentUpdate")
                return true
            }

            // 组件将要更新的钩子
            componentWillUpdate(){
                console.log("render - ComponentWillUpdate")
            }

            // 组件完成更新的钩子
            componentDidUpdate(){
                console.log("render - componentDidUpdate")
            }

            // 初始化渲染,和状态更新的时候会被调用
            render(){
                console.log("render")
                return (
                    <div>
                        <h2>当前求和为:{this.state.count}</h2>
                        <button onClick={this.add}>点我+1</button>
                        <button onClick={this.death}>卸载组件</button>
                        <button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
                        <button onClick={this.changeContent}>修改传入子组件的参数内容</button>
                        <Child content={this.state.content} />
                    </div>
                )
            }
        }

        class Child extends React.Component{
            // 第二次开始接收父组件参数之前的钩子
            componentWillReceiveProps(props){
                console.log("render - componentWillReceiveProps", {props})
            }

            render(){
                return <h1>子组件的内容:{this.props.content}</h1>
            }
        }

        // 2.渲染组件到页面
        ReactDOM.render(<Count/>, document.getElementById('test'))

    </script>
</body>
</html>

5.3 生命周期(新)

  • 废弃三种钩子:
    • componentWillMount
    • componentWillUpdate
    • componentWillReceiveProps
  • 新增两种钩子,但是不常用:
    • getDerivedStateFromProps:如果state的值在任何时候都取决于props,那么可以用该钩子。如果使用了该组件,会导致代码难维护。
    • getSnapshotBeforeUpdate:在组件渲染准备渲染前,可以拿到渲染前的组件的props和state,用于和渲染后的props和state做一些组件高度等对比,实现某些场景功能。
5.3.1 流程图

从React18开始,有三个钩子的函数名称需要加UNSAFE_前缀(除了最后的销毁的钩子,其他名称有WILL的名称的钩子,那么都需要加上UNSAFE_)
   1.UNSAFE_componentWillMount
   2.UNSAFE_componentWillUpdate
   3.UNSAFE_componentWillReceiveProps
加上UNSAFE_的原因:由于后续需要推出异步渲染,如果使用上述三种,很有可能会出现其他的问题

在这里插入图片描述

5.3.2 代码案例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>组件生命周期</title>
</head>
<body>
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <!-- 引入react核心库,全局会多了一个对象React -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes -->
    <script type="text/javascript" src="../js/prop-types.js"></script>

    <!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 -->
    <script type="text/babel">
        // 1.创建类式组件
        class Count extends React.Component {

            constructor(props){
                console.log("render - constructor")
                super(props)

                // 初始化状态
                this.state = {count: 0}
            }

            add = () => {
                this.setState({count: this.state.count + 1})
            }

            // 卸载组件按钮回调
            death = () => {
                ReactDOM.unmountComponentAtNode(document.getElementById('test'))
            }

            // 强制更新的按钮回调
            force = () => {
                this.forceUpdate()
            }

            // 修改子组件的内容
            changeContent = () => {
                this.setState({"content": "content1"})
            }

            // 组件挂载完毕的钩子
            componentDidMount(){
                console.log("render - componentDidMount")
            }

            // 组件将要卸载的钩子
            componentWillUnmount(){
                console.log("render - componentWillUnmount")
            }

            // 控制组件是否更新的钩子(返回的布尔类型,如果是false,那么不会继续往下走)
            shouldComponentUpdate(){
                console.log("render - shouldComponentUpdate")
                return true
            }

            // 组件完成更新的钩子
            componentDidUpdate(preProps, preState, snapshotValue){
                console.log("render - componentDidUpdate")
            }

            static getDerivedStateFromProps(props, state){
                console.log("render - getDerivedStateFromProps")
                return props
            }

            getSnapshotBeforeUpdate(){
                console.log("render - getSnapshotBeforeUpdate")
                return "该值会传入给 componentDidUpdate钩子"
            }

            // 初始化渲染,和状态更新的时候会被调用
            render(){
                console.log("render")
                return (
                    <div>
                        <h2>当前求和为:{this.state.count}</h2>
                        <button onClick={this.add}>点我+1</button>
                        <button onClick={this.death}>卸载组件</button>
                    </div>
                )
            }
        }
        // 2.渲染组件到页面
        ReactDOM.render(<Count/>, document.getElementById('test'))

    </script>
</body>
</html>
5.3.2 getSnapshotBeforeUpdate-案例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>组件生命周期</title>
    <style>
        .list{
            width: 200px;
            height: 150px;
            background-color: skyblue;
            overflow: auto;
        }
        .news{
            height: 30px;
        }
    </style>
</head>
<body>
    <!-- 创建一个容器 -->
    <div id="test"></div>

    <!-- 引入react核心库,全局会多了一个对象React -->
    <script type="text/javascript" src="../js/react17/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM。全局会多了一个对象ReactDOM -->
    <script type="text/javascript" src="../js/react17/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转化为js -->
    <script type="text/javascript" src="../js/react17/babel.min.js"></script>
    <!-- 引入prop-types,用于对组件标签属性进行限制。全局会多了一个对象PropTypes -->
    <script type="text/javascript" src="../js/react17/prop-types.js"></script>

    <!-- type需要写text.babel,因为里面写的是jsx,需要babel转化 -->
    <script type="text/babel">
        // 1.创建类式组件
        class NewsList extends React.Component {

            state = {newsArr:[]}

            componentDidMount(){
                setInterval(() => {
                    const newsArr = this.state.newsArr
                    // 模拟一条新闻
                    const news = '新闻' + (newsArr.length + 1)
                    // 更新状态
                    this.setState({newsArr:[news, ...newsArr]})
                }, 1000)
            }

            getSnapshotBeforeUpdate(){
                return this.refs.list.scrollHeight
            }

            componentDidUpdate(preProp, preState, height){
                this.refs.list.scrollTop += this.refs.list.scrollHeight - height
            }

            // 初始化渲染,和状态更新的时候会被调用
            render(){
                return (
                    <div className="list" ref="list">
                        {
                            this.state.newsArr.map((n, index) => {
                                return <div className="news" key={index}>{n}</div>
                            })
                        }
                    </div>
                )
            }
        }

        // 2.渲染组件到页面
        ReactDOM.render(<NewsList/>, document.getElementById('test'))

    </script>
</body>
</html>

5.4 DOM的diffing算法

  • 虚拟DOM的key的作用:
    • 简单的说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用。
    • 详细的说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
      • a.旧虚拟DOM中找到了与新虚拟DOM相同的key:
        • (1)若虚拟DOM中内容没变,直接使用之前的真实DOM
        • (2)若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
      • b.旧虚拟DOM中未找到与新虚拟DOM相同的key。根据数据创建新的真实DOM,随后渲染到到页面。
  • 用index作为key可能会引发的问题:
    • 1.若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新,界面效果没问题,但效率低。
    • 2.如果结构中还包含输入类的DOM:会产生错误DOM更新==>界面有问题。
    • 3.注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
使用index索引值做为key导致的问题:导致key会频繁的变化,导致页面不断地重新渲染

初始数据:
	{id:1,name:'小1',age:18},
	{id:2,name:'小2',age:19},
初始的虚拟DOM:
	<li key=0>小1 --- 18</li>
	<li key=1>小2 --- 19</li>

更新后的数据:
	{id:3,name:'小3',age:20},
	{id:1,name:'小1',age:18},
	{id:2,name:'小2',age:19},
更新后的的虚拟DOM:
	<li key=0>小3 --- 20</li>
	<li key=1>小1 --- 18</li>
	<li key=2>小2 --- 19</li>
  • 开发中如何选择key?:
    • 1.最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
    • 2.如果确定只是简单的展示数据,用index也是可以的。

四、react应用(基于react脚手架)

包安装

# 安装具体大版本react:npm add react@16
# 安装最新版本:npm add react

1.创建react应用

  • 使用create-react-app模块创建应用

1.1 react脚手架

  1. xxx脚手架:用来帮助程序员快速创建一个基于xxx库的模板项目
    • a.包含了所有需要的配置(语法检查、jsx编译、devServer…)
    • b.下载好了所有相关的依赖
    • c.可以直接运行一个简单效果
  2. react 提供了一个用于创建react 项目的脚手架库:create-react-app
  3. 项目的整体技术架构为:react +webpack+ es6+ eslint
  4. 使用脚手架开发的项目的特点:模块化,组件化,工程化

1.2 创建项目并启动

注意:使用npm需要安装node,我安装的版本是v20.18.0

  • 第一步:全局安装:npm i -g create-react-app
  • 第二步:切换到想创建项目的目录,使用命令:create-react-app hello_react
  • 第三步:进入项目文件夹:cd hello_react
  • 第四步:启动项目:npm start

如果需要降级react版本:
npm i react-dom@16.x --legacy-peer-deps
npm i react@16.x --legacy-peer-deps

1.3 项目结构

在这里插入图片描述

  • public:静态资源文件夹
    • favicon.icon:网站页签标题
    • index.html:主页面(核心文件,有个id=root的div标签)
    • logo192.png:192x192的logo图
    • logo512.png:512x512的logo图
    • mainfest.json:应用加壳的配置文件(不使用加壳,可以删除)
    • robots.txt:爬虫协议文件
  • src:源码文件夹
    • App.css:App组件的样式
    • Apps.js:App组件:是一个主组件,会被index.js挂载到index.html的id=root的div节点里面
    • App.test.js:用于给App做测试(几乎不用,可以删除)
    • index.css:样式
    • index.js:入口文件(引入了React相关库,会把App.js的主组件放到index.html的id=root的div节点里面)
    • logo.svg:logo图

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <!-- %PUBLIC_URL%代表public文件夹路径 -->
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <!-- 开启理想视口,用于做移动端网页的适配 -->
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <!-- 用于配置浏览器页签+地址栏的颜色(仅支持安卓手机浏览器),兼容性不太好 -->
    <meta name="theme-color" content="#000000" />
    <!-- 描述信息,搜索引擎收录我们网页信息的时候有用 -->
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <!-- 用于指定网页添加到手机主屏幕快捷方式的图标(苹果手机专用) -->
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <!-- 应用加壳时的配置文件 -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
  </head>
  <body>
    <!-- 若浏览器不支持js,则展示标签中的内容 -->
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <!-- 总入口容器 -->
    <div id="root"></div>
  </body>
</html>

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
  // 检查<App/>的所有子组件里面是否写的合理(例如某些语法是过期了)
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

reportWebVitals();

2.组件目录定义和导入

在这里插入图片描述

3.样式模块化

  • 为什么需要模块化?防止不同组件间,定义了相同的标签的样式,会互相影响。
    错误案例
.title{
	background-color: orange;
}

第一种方法,使用组件名称(常用)

.component_name{
	.title{
		background-color: orange;
	}
}

第二种方法,导包传入(不常用)

  • 步骤:
    • index和css中间加module命名
    • 导入css样式,需要拿变量接收,接完了会变成一个对象
    • 将对象写入到对应的标签
      在这里插入图片描述

4.vscode安装react插件

在这里插入图片描述

快捷键:
	rcc:类组件模板
	rfc:函数式组件模板

5.组件化编码流程

  • 编码流程:
    1. 拆分组件:拆分界面,抽取组件
    2. 实现静态组件:使用组件实现静态页面效果
    3. 实现动态组件:
      • 动态显示初始化数据
        • 数据类型
        • 数据名称
        • 保存在哪个组件?
      • 交互(从绑定时间监听开始)

6.组件组合使用-TodoList案例

  • 需求:
    • 显示所有todo列表
    • 输入文本,点击按钮显示到列表的首位,并清除输入的文本
      在这里插入图片描述

6.1 拆组件

在这里插入图片描述

6.2 依赖安装

# 安装uuid(比uuid库更小)
npm add nanoid
# 安装类型验证库
npm add prop-types

6.3 总结

  • 代码包在资源里面:1.TODO-LIST案例.zip
  • 相关知识点:
    • 1.拆分组件、实现静态组件,注意:className、style的写法
    • 2.动态初始化列表,如何确定将数据放在哪个组件的state中?
      • 某个组件使用:放在其自身的state中
      • 某些组件使用:放在他们共同的父组件state中(官方称此操作为:状态提升)
    • 3.关于父子之间通信
      • 1.【父组件】给【子组件】传递数据:通过props传递
      • 2.【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数
    • 4.注意defaultChecked 和 checked的区别,类似的还有:defaultValue和 value
    • 5.状态在哪里,操作状态的方法就在哪里

五、react-axios

  • 说明:
    • React 本身只关注于界面,并不包含发送ajax请求的代码
    • 前端应用需要通过ajax请求与后台进行交互(json数据)
    • react 应用中需要集成第三方 ajax 库(或自己封装)
  • 常用的ajax请求库
    • jQuery:比较重,如果需要另外引入不建议使用
    • axios:轻量级,建议使用
      • 封装XmlHttpRequest对象的ajax
      • promise风格
      • 可以用在浏览器端和node服务器端

1.安装axios

npm add axios

2.代理配置

  • 解决开发的时候,跨域问题

2.1 方法一(不使用)

  • 只有react服务器没有的资源,才会通过代理转发。会造成问题:
    • 如果访问:/index.html 会访问到react的public的index.html文件,而不是服务端的资源
      在这里插入图片描述
  • 优点:配置简单,前端请求资源时可以不加任何前缀。
  • 缺点:不能配置多个代理。
  • 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000(优先匹配前端资源)

2.2 方法二(常用)

  • src/setupProxy.js:是一个React脚手架默认加载的文件,不能使用ES6语法去写,需要用CJS语法。React会将这个配置文件加载webpack里面。

第一步:创建代理配置文件

在src下创建配置文件:src/setupProxy.js

第二步:编写setupProxy.js配置具体代理规则

const proxy = require('http-proxy-middleware')

module.exports = function(app){
    app.use(
        // 要把哪些前缀的api代理转发到目标服务器
        // http-proxy-middleware 2以下版本写法:proxy.createProxyMiddleware('/api1',{})
        // 下面是http-proxy-middleware 2以上版本写法
        proxy.createProxyMiddleware('/api1',{
            target:'http://localhost:5000', // 请求转发给谁
            changeOrigin:true, // 控制服务器收到的请求头中Host的值(建议必须)
            /*
                changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
                changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
                changeOrigin默认值为false,但我们一般将changeOrigin值设为true
            */
            pathRewrite:{'^/api1': ''} // 重写请求路径(必须)
        }),
        proxy.createProxyMiddleware('/api2',{
            target:'http://localhost:5001', 
            changeOrigin:true, 
            pathRewrite:{'^/api2': ''}
        })
    )
}

3.github搜索案例

  • 代码包在资源里面:2.github搜索案例.zip
    在这里插入图片描述

axios使用案例

import React, { Component } from 'react'
import axios from 'axios'

export default class Search extends Component {

    search = ()=>{
        const {keyWordNode:{value:keyWord}} = this
        console.log(keyWord)
        // 发送网络请求
        axios.get(`https://api.github.com/search/users?q=${keyWord}`).then(
            response => {console.log('成功了', this.props.loadUserList(response.data.items))},
            error => {console.log('失败了', error)}
        )
    }

    render() {
        return (
            <section className='jumbotron'>
                <h3 className='jumbotron-heading'>搜索Github用户</h3>
                <div>
                    <input ref={c => this.keyWordNode = c} type='text' placeholder='请输入关键词点击搜索' />&nbsp;
                    <button onClick={this.search}>搜索</button>
                </div>
            </section>
        )
    }
}

六、react-router

1.相关理解

1.1 SPA理解

  • 单页Web应用(single page web application, SPA)
  • 整个应用只有一个完整的页面。
  • 点击页面中的链接不会刷新页面,只会做页面的局部更新。
  • 数据都需要通过ajax请求获取,并在前端异步展现。

1.2 路由的理解

  • 什么是路由?
    • 一个路由就是一个映射关系(key: value)
    • key为路径,value可能是function或component
  • 路由分类
    • 后端路由:
      • value是function,用来处理客户端提交的请求。
      • 注册路由:router.get(path,function(req,res))
      • 工作过程:当node接收到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据
    • 前端路由
      • 浏览器端路由,value 是component,用于展示页面内容。↵
      • 注册路由: <Route path="/test"component={Test}>↵
      • 工作过程:当浏览器的 path 变为/test 时,当前路由组件就会变为Test 组件

1.3 react-router-dom的理解

  • react的一个插件库。
  • 专门用来实现一个SPA 应用。
  • 基于react的项目基本都会用到此库。

2.react-router-dom

  • 官网:http://react-router.docschina.org/
  • 总共有三个类别:
    • WEB:web开发使用
    • NATIVE:移动端开发使用
    • ANYWHERE:web开发和移动端都可以使用,但是最好不使用这个,API会比较复杂。

2.1 安装

# 由于react-router-dom在2021年11月份升级到了6版本,如果安装5版本需要使用如下命令
npm add react-router-dom@5

2.2 路由组件使用

  • 路由器两种:
    • BrowserRouter:使用的是H5的history API,不兼容IE9及以下版本。路径中没有#,例如:localhost:3000/demo/test
    • HashRouter:路径包含#,例如:localhost:3000/#/demo/test
  • 注意:路由的切换和注册,需要在同一个路由器的标签内。
    • 一般是在<App>的最外侧包裹了一个<BrowserRouter>或<HashRouter>
  • 路由跳转:<Link to “/xxxx” component={Demo}>
  • 路由注册(页面展示的地方):<Route path=‘/xxxx’ component={Demo} >
import React, { Component } from 'react'
import {Link,BrowserRouter,Route} from 'react-router-dom'
import Home from './components/Home'
import About from './components/About'

export default class App extends Component {

  loadUserList = (userList)=>{
    this.setState({userList: userList})
  }

  render() {
    return (
      <div>
        <div className='row'>
          <div className='col-xs-offset-2 col-xs-8'>
            <div className='page-header'><h2>React Router Demo</h2></div>
          </div>
        </div>
        <BrowserRouter>
          <div className='row'>
            <div className='col-xs-2 col-xs-offset-2'>
              <div className='list-group'>
                {/* 在原生html中,靠<a>标签跳转不同的页面 */}
                {/* <a className='list-group-item' href='./about.html'>About</a>
                <a className='list-group-item active' href='./home.html'>Home</a> */}

                {/* 在React中靠路由连接实现切换组件 */}
                <Link className='list-group-item' to='/about'>About</Link>
                <Link className='list-group-item' to='/home'>Home</Link>
              </div>
            </div>
            <div className='col-xs-6'>
              <div className='panel'>
                <div className='panel-body'>
                  {/* 注册路由 */}
                  <Route path="/about" component={About}></Route>
                  <Route path="/home" component={Home}></Route>
                </div>
              </div>
            </div>
          </div>
        </BrowserRouter>
      </div>
    )
  }
}

2.3 路由组件和一般组件的区别

  • 写法不同:
    • 一般组件:<Demo/>
    • 路由组件:<Route path=‘/demo’ component={Demo}/ >
  • 存放位置不同:
    • 一般组件:components
    • 路由组件:pages
  • 接收到的props不同:
    • 一般组件:写组件标签时传递了什么,就能收到什么
    • 路由组件:会收到三个参数:history、location、match
      • history:
        • go(n):回退n步:
          • n > 0:前进n步
          • n < 0:回退n步
        • goBack():回退1步
        • goForward():前进1步
        • push(path,state):跳转(留痕)
        • replace(path,state):跳转(不留痕)
      • location:
        • pathname: 路由地址
        • search: 路由组件传入的search参数
        • state:路由组件传入的state参数
      • math:
        • params:params参数
        • path:路由地址
        • url:路由地址
  • 借助this.prosp.history对象上的API对操作路由跳转、前进、后退
    • this.prosp.history.push()-
    • his.prosp.history.replace()
    • this.prosp.history.goBack()
    • this.prosp.history.goForward()
    • this.prosp.history.go()

2.4 NavLink

  • 解决点击了路由,对应页面的按钮需要高亮
    • 原理是给当前的按钮的class加了一个active类名
  • 如果不是active类名,而是其他的,可以通过参数activeClassName控制
    在这里插入图片描述
<NavLink activeClassName='active' className='list-group-item' to='/about'>About</NavLink>

2.5 封装NavLink组件

  • 需求:解决一些NavLink的属性重复编写的问题
  • 总结:
    • NavLink可以实现路由链接的高亮,通过activeClassName指定样式名
    • 标签体内容是一个特殊的标签属性,通过this.props.children可以获取标签体内容
      封装
import React, { Component } from 'react'
import { NavLink } from 'react-router-dom'

export default class MyNavLink extends Component {
  render() {
    return (
      <NavLink activeClassName='active' className='list-group-item' {...this.props}/>
    )
  }
}

使用

/*下面两个效果一样*/
<MyNavLink to='/home'>Home</MyNavLink>
<MyNavLink to='/home' chilren='Home'/>

细节:MyNavLink标签中间的Home标题,他会作为一个组件的children参数传入到this.props参数里面

2.6 Switch使用

  • 通常情况下,path和component是一一对应的关系。
  • 如果出现了两个路由的path是相同的,但是component是不同的,那么默认会都展示出来两个组件
<Route path="/about" component={About}></Route>
<Route path="/home" component={Test}></Route>
<Route path="/home" component={Home}></Route>
  • Switch组件:如果从上到下,匹配到了一个path,那么就不会继续往下进行匹配
    • 使用场景:一个路由组件以上使用
import {Switch,BrowserRouter,Route} from 'react-router-dom'
<Switch>
    <Route path="/about" component={About}></Route>
    <Route path="/home" component={Test}></Route>
    <Route path="/home" component={Home}></Route>
</Switch>

2.7 样式丢失

  • 问题:如果path加了前缀的时候,刷新,请求资源的时候,也会加上前缀
    在这里插入图片描述
  • 原因:引入css的时候,不能使用:./css/xxxx.css
    在这里插入图片描述
  • 解决办法:
    • 1.去掉点:/css/bootstrap.css
    • 2.换成PUBLIC_URL:%PUBLIC_URL%/css/bootstrap.css

2.8 路由模糊匹配

  • 默认是开启前缀模糊匹配
    • 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
  • 开启严格匹配:<Route exact={true} path=“/about” component={About}/ >
  • 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
<MyNavLink to='/home/a/b/c'>Home</MyNavLink>
// 上面可以匹配到下面的路由
<Route path="/home" component={Home}></Route>
  • 开启精准匹配
<MyNavLink to='/home'>Home</MyNavLink>
<Route exact={true} path="/home" component={Home}></Route>

2.9 Redirect重定向

  • 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
<Switch>
	<Route path="/about" component={About}></Route>
	<Route exact={true} path="/home" component={Home}></Route>
	<Redirect to="/about"/>
</Switch>

2.10 嵌套(多级)路由

在这里插入图片描述

  • 注册子路由时要写上父路由的path值
  • 路由的匹配是按照注册路由的顺序进行的
    一级路由
<MyNavLink to='/home'>Home</MyNavLink>
<Switch>
	<Route path="/home" component={Home}></Route>
	<Redirect to="/about"/>
</Switch>

二级路由

<MyNavLink to="/home/news">News</MyNavLink>
<Switch>
	<Route path="/home/news" component={News}></Route>
</Switch>

2.11 向路由组件传递参数数据

  • 使用频率(高->低):params参数、search参数(需要解析参数)、state参数(敏感信息传递可以用这种)
    在这里插入图片描述一级路由
<MyNavLink to='/home'>Home</MyNavLink>
<Switch>
	<Route path="/home" component={Home}></Route>
	<Redirect to="/about"/>
</Switch>

二级路由

<MyNavLink to="/home/news">News</MyNavLink>
<Switch>
	<Route path="/home/news" component={News}></Route>
</Switch>
2.11.1 传递params参数
  • 路由链接(携带参数):<Link to=‘/demo/test/tom/18’}>详情</Link>
  • 注册路由(声明接收):<Route path="/demo/test/:name/:age"component={Test}/>
  • 接收参数:const{id,title}=this.props.match.params
    Message列表
import React, { Component } from 'react'
import { Link,Route } from 'react-router-dom'
import Detail from './Detail'

export default class Message extends Component {
  state = {
    messageArr:[
      {id:'01',title:'消息1'},
      {id:'02',title:'消息2'},
      {id:'03',title:'消息3'},
    ]
  }
  render() {
    const {messageArr} = this.state
    return (
      <div>
        <ul>
          {
            messageArr.map((msgObj) => {
              return (
                <li key={msgObj.id}>
                  {/* 向路由组件传递params参数 */}
                  <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
                </li>
              )
            })
          }
        </ul>
        <hr/>
        {/* 声明接收params参数 */}
        <Route path="/home/message/detail/:id/:title" component={Detail}/>
      </div>
    )
  }
}

Message详情

import React, { Component } from 'react'


export default class Detail extends Component {
  render() {
    const {id,title} = this.props.match.params
    return (
      <ul>
        <li>ID:{id}</li>
        <li>TITLE:{title}</li>
      </ul>
    )
  }
}

2.11.2 传递search参数
  • 路由链接(携带参数):<Link to=‘/demo/test?name=tom&age=18’}>详情</Link>
  • 注册路由(无需声明,正常注册即可):<Route path="/demo/test”component={Test}/>
  • 接收参数:const{search}=this.props.location
  • 备注:获取到的search是一个参数字符串,需要自己另外处理转化为对象
    Message列表
import React, { Component } from 'react'
import { Link,Route } from 'react-router-dom'
import Detail from './Detail'

export default class Message extends Component {
  state = {
    messageArr:[
      {id:'01',title:'消息1'},
      {id:'02',title:'消息2'},
      {id:'03',title:'消息3'},
    ]
  }
  render() {
    const {messageArr} = this.state
    return (
      <div>
        <ul>
          {
            messageArr.map((msgObj) => {
              return (
                <li key={msgObj.id}>
                  {/* 向路由组件传递search参数 */}
                  <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
                </li>
              )
            })
          }
        </ul>
        <hr/>
        {/* 无需声明接收search参数 */}
        <Route path="/home/message/detail" component={Detail}/>
      </div>
    )
  }
}

Message详情

import React, { Component } from 'react'

function queryUrl (search) {
    let obj = {}
    let url = search.slice(1)
    let arr = url.split('&') //[name=jack,age=18]
    arr.forEach(item => {
      let newArr = item.split('=') //[name,jack]  [age,18]
      obj[newArr[0]] = newArr[1]
    })
    return obj
  }

export default class Detail extends Component {
  render() {
    const {search} = this.props.location
    const {id,title} = queryUrl(search)

    return (
      <ul>
        <li>ID:{id}</li>
        <li>TITLE:{title}</li>
      </ul>
    )
  }
}

2.11.3 传递state参数
  • 路由链接(携带参数):<Link to={{path:‘/demo/test’,state:{name:‘tom’,age:18}}}>详情</Link>
  • 注册路由(无需声明,正常注册即可):<Route path="/demo/test"component={Test}/>
  • 接收参数:this.props.location.state
  • 备注:刷新也可以保留住参数
    Message列表
import React, { Component } from 'react'
import { Link,Route } from 'react-router-dom'
import Detail from './Detail'

export default class Message extends Component {
  state = {
    messageArr:[
      {id:'01',title:'消息1'},
      {id:'02',title:'消息2'},
      {id:'03',title:'消息3'},
    ]
  }
  render() {
    const {messageArr} = this.state
    return (
      <div>
        <ul>
          {
            messageArr.map((msgObj) => {
              return (
                <li key={msgObj.id}>
                  {/* 向路由组件传递state参数 */}
                  <Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
                </li>
              )
            })
          }
        </ul>
        <hr/>
        {/* 无需声明接收state参数 */}
        <Route path="/home/message/detail" component={Detail}/>
      </div>
    )
  }
}

Message详情

import React, { Component } from 'react'

export default class Detail extends Component {
  render() {
    // 接收state参数
    const {id,title} = this.props.location.state

    return (
      <ul>
        <li>ID:{id}</li>
        <li>TITLE:{title}</li>
      </ul>
    )
  }
}

2.12 路由跳转的两个模式

  • 两个模式的区别:
    • push模式是默认的模式,路由跳转会留下痕迹
    • replace模式跳转不会留下痕迹
2.12.1 push模式
  • 默认的路由组件,就是push模式
2.12.2 replace模式
<Link replace to="/home/message/detail">{msgObj.title}</Link>

2.13 编程式路由导航

  • 需求:
    • A组件展示后等三秒钟,跳转到B组件跳转
    • 跳转可以点击按钮是push模式 还是replace模式
    • 实现:回退、前进
  • 路由组件:
    • history:
      • go(n):回退n步:
        • n > 0:前进n步
        • n < 0:回退n步
      • goBack():回退1步
      • goForward():前进1步
      • push(path,state):跳转(留痕)
      • replace(path,state):跳转(不留痕)
  • 借助this.prosp.history对象上的API对操作路由跳转、前进、后退
    • this.prosp.history.push()-
    • his.prosp.history.replace()
    • this.prosp.history.goBack()
    • this.prosp.history.goForward()
    • this.prosp.history.go()
import React, { Component } from 'react'
import { Link,Route } from 'react-router-dom'
import Detail from './Detail'

export default class Message extends Component {
  state = {
    messageArr:[
      {id:'01',title:'消息1'},
      {id:'02',title:'消息2'},
      {id:'03',title:'消息3'},
    ]
  }

  replaceShow = (id,title)=>{
    // replace跳转+params参数
    this.props.history.replace(`/home/message/detail/${id}/${title}`)

    // replace跳转+携带search参数
    // this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)

    // replace跳转+携带state参数
    // this.props.history.replace(`/home/message/detail`, {id,title})
  }

  pushShow = (id,title)=>{
    // push跳转+params参数
    this.props.history.push(`/home/message/detail/${id}/${title}`)

    // push跳转+携带search参数
    // this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)
    
    // push跳转+携带state参数
    // this.props.history.replace(`/home/message/detail`, {id,title})
  }

  back = () => {
    this.props.history.goBack()
  }
  forward = () => {
    this.props.history.goForward()
  }
  render() {
    const {messageArr} = this.state
    return (
      <div>
        <ul>
          {
            messageArr.map((msgObj) => {
              return (
                <li key={msgObj.id}>
                  {/* 向路由组件传递参数 */}
                  <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
                  &nbsp;<button onClick={()=>{this.pushShow(msgObj.id,msgObj.title)}}>push查看</button>
                  &nbsp;<button onClick={()=>{this.replaceShow(msgObj.id,msgObj.title)}}>replace查看</button>
                </li>
              )
            })
          }
        </ul>
        <hr/>
        {/* 需声明接收params参数 */}
        <Route path="/home/message/detail/:id/:title" component={Detail}/>

        {/* search参数无需声明接收,正常注册路由即可*/}
        {/* <Route path="/home/message/detail" component={Detail}/> */}
          
        {/* state参数无需声明接收,正常注册路由即可*/}
        {/* <Route path="/home/message/detail" component={Detail}/> */}

        <button onClick={this.back}>回退</button>&nbsp;
        <button onClick={this.forward}>前进</button>
      </div>
    )
  }
}

2.14 让一般组件也有路由组件的参数-withRouter

import React, { Component } from 'react'
import {withRouter} from 'react-router-dom'


class Header extends Component {

  back = () => {
    this.props.history.goBack()
  }

  render() {
    return (
      <div>
        <div>
        <h2>React Router Demo</h2>
        </div>
      </div>
    )
  }
}

// 让一般组件,也有路由组件的三个props属性
export default withRouter(Header)

2.15 BrowserRouter与HashRouter的区别

  • 1.底层原理不一样:
    • BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
    • HashRouter使用的是URL的哈希值。
  • 2.ur1表现形式不一样
    • BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
    • HashRouter的路径包含#,例如:localhost:3000/#/demo/test
  • 3.刷新后对路由state参数的影响
    • (1).BrowserRouter没有任何影响,因为state保存在history对象中。
    • (2).HashRouter刷新后会导致路由state参数的丢失。
  • 4.备注:HashRouter可以用于解决一些路径错误相关的问题。
  • 5.BrowerRouter用得比较多

七、组件库-Ant Design

  • web开发推荐Ant Design:
    • 官网地址:https://ant-design.antgroup.com/index-cn
  • 移动端开发推荐有赞团队的Vant:
    • 官网地址:https://vant-ui.github.io/vant/#/zh-CN

1.安装

# 如果要安装4版本的主要执行:npm install antd@4
npm install antd

2.入门体验

  • 注意:如果是4版本的,需要引入css样式,如果是5版本的antd,只需要导入组件,就有对应的样式。下面是用antd4版本案例
import React, { Component } from 'react'
import { Button} from 'antd'
import {
  WechatOutlined
} from '@ant-design/icons';
import 'antd/dist/antd.css'

export default class App extends Component {

  render() {
    return (
      <div>
        <Button type="primary" icon={<WechatOutlined />}>点我</Button>
      </div>
    )
  }
}

3.按需引入样式

  • import ‘antd/dist/antd.css’ 代表将所有antd的css样式都引入来了。
  • 3.x版本的教程,会更加详细,4.x省略了一些
    • 4.x按需引入样式教程:https://3x.ant.design/docs/react/use-with-create-react-app-cn
  • 在5.x版本,不需要再手工引入样式,框架自己处理了。

4.自定义主题

  • antd默认主题色是支付宝的蓝色,下面是修改默认主题色
  • 注意:
    • 4.x版本使用了less和css变量,需要安装包
    • 5.x版本使用了CSS-in-JS,不需要另外安装包
  • 下面是4.x版本的教程

4.1 安装包

  • npm add react-app-rewired customize-cra
  • npm add craco-less
  • npm add babel-plugin-import
    • 教程来源于3.x

4.2 修改package.json

  • 需改里面scripts
    在这里插入图片描述

4.3 创建配置文件

  • 然后在项目根目录创建一个 config-overrides.js 用于修改默认配置。
const { override, fixBabelImports, addLessLoader } = require('customize-cra');

module.exports = override(
  fixBabelImports('import', {
    libraryName: 'antd',
    libraryDirectory: 'es',
   style: true,
  }),
 addLessLoader({
  lessOptions:{
    javascriptEnabled: true,
    modifyVars: { '@primary-color': 'orange' },
  }
 }),
);

八、redux

  • 文档:
    • 英文:https://redux.js.org/
    • 中文文档:https://redux.org.cn

1.理解

1.1 redux是什么

  • redux是一个专门用于做状态管理的JS库(不是react插件库)。
  • 它可以用在react,angular,vue等项目中,但基本与react配合使用。
  • 作用:集中式管理 react 应用中多个组件共享的状态。

1.2 使用场景

  • 某个组件的状态,需要让其他组件可以随时拿到(共享)。
  • 一个组件需要改变另一个组件的状态(通信)。
  • 总体原则:能不用就不用,如果不用比较吃力才考虑使用。

2.redux的核心API

  • 注意:在reducer中,不要使用在某种情况下,不会更改state的逻辑代码。例如,某个数为偶数的时候,才加1。
    • 逻辑代码需要在业务层控制好,reducer只负责加工state数据。

2.1 安装redux

  • 下面案例都是使用redux4进行使用
# 如果需要安装redux4版本:npm add redux@4
npm add redux

2.2 redux工作流程

  • 流程:
    • React Components需要修改state,通过Action对象告诉Store,Action对象里面有type和data。
      在这里插入图片描述
  • action:一个动作一个
    • 动作的对象
    • 它可以是两种类型:
      • Object:同步action
      • function:异步action
    • 包含两个属性:
      • type:标识属性,值为字符串,唯一,必要属性
      • data:数据属性,值类型任意,可选属性
    • 例子:{type:‘ADD_STUDENT’,data:{name:‘tom’,age:18}}
  • reducer:每个组件都有一个
    • 用于初始化状态,加工状态
    • 加工时,根据旧的state和action,产生新的state的纯函数
  • store:一个项目就一个
    • 存放公共state的地方

2.3 使用案例

2.3.1 求和案例-纯react版本
import React, { Component } from 'react'

export default class Count extends Component {

    state = {count:0}

    // 加法
    increment = ()=>{
        const {value} = this.selectNumber
        const {count} = this.state
        this.setState({"count":count+value*1})
    }
    // 减法
    decrement = ()=>{
        const {value} = this.selectNumber
        const {count} = this.state
        this.setState({"count":count-value*1})
    }
    // 奇数再加
    incrementIfOdd = ()=>{
        const {value} = this.selectNumber
        const {count} = this.state
        if(count%2 !== 0){
            this.setState({"count":count+value*1})
        }
    }
    // 异步加
    incrementAsync = ()=>{
        const {value} = this.selectNumber
        const {count} = this.state
        setTimeout(()=>{
            this.setState({"count":count+value*1})
        },500)
    }

  render() {
    return (
      <div>
        <h1>当前求和为:{this.state.count}</h1>
        <select ref={c => this.selectNumber = c}>
            <option value="1">1</option>
            <option value="2">2</option>
            <option value="3">3</option>
        </select>&nbsp;
        <button onClick={this.increment}>+</button>&nbsp;
        <button onClick={this.decrement}>-</button>&nbsp;
        <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>&nbsp;
        <button onClick={this.incrementAsync}>异步加</button>
      </div>
    )
  }
}

2.3.2 求和案例-redux精简版
  • 将组件组件注册到store,数据更改了会被通知(主要是重新调用组件render)。两种方式
    • 1.在每个组件的componentDidMount函数中注册
    • 2.在index.js中注册
  • 步骤:
    • 1.编写reducer
    • 2.编写store,将reducer注册
    • 3.在代码中使用store.getState获取状态
    • 4.在代码中使用store.dispatch({type:‘increment’,data:value*1}) action让store告诉reducer加工数据。
    • 5.在组件订阅store。(如果在index.js订阅了,就需要这一步骤)
      在这里插入图片描述
      count_reducer.js
/* 
    1.该文件是用于创建一个为count组件服务的reducer,reducer的本质就是一个函数
    2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
*/

// 如果没有传入值,或者传入的preState是undefined,那么默认为0
export default function countReducer(preState=111,action){
    //从action对象中获取:type、data
    const {type,data} = action
    //根据type决定如何加工数据
    switch (type){
        case 'increment':
            // 加法
            return preState + data
        case 'decrement':
            // 减法
            return preState - data
        default:
            return preState
    }
}

store.js

/* 
    该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import { createStore } from "redux"
//引入为Count组件服务的reducer
import countReducer from './count_reducer'
//暴露store
export default createStore(countReducer)

index.js

import ReactDOM from 'react-dom';
import App from './App';
import store from './redux/store';

ReactDOM.render(<App/>,document.getElementById('root'))

store.subscribe(()=>{
  ReactDOM.render(<App/>,document.getElementById('root'))
})

coponents/Count/index.jsx

import React, { Component } from 'react'
//引入store,用户获取redux获取的状态
import store from '../../redux/store'
import {createIncrementAction,createDecrementAction} from '../../redux/count_action'

export default class Count extends Component {

    // componentDidMount(){
    //     // 检测redux中状态的变化,只要变化,就调用render
    //     store.subscribe(()=>{
    //         // 只要调用了setState就调用render
    //         this.setState({})
    //     })
    // }

    // 加法
    increment = ()=>{
        const {value} = this.selectNumber
        store.dispatch(createIncrementAction(value*1))
    }
    // 减法
    decrement = ()=>{
        const {value} = this.selectNumber
        store.dispatch(createDecrementAction(value*1))
    }
    // 奇数再加
    incrementIfOdd = ()=>{
        const {value} = this.selectNumber
        const count = store.getState()
        if(count%2 !== 0){
            store.dispatch(createIncrementAction(value*1))
        }
    }
    // 异步加
    incrementAsync = ()=>{
        const {value} = this.selectNumber
        const count = store.getState()
        setTimeout(()=>{
            store.dispatch(createDecrementAction(value*1))
        },500)
    }

  render() {
    return (
      <div>
        <h1>当前求和为:{store.getState()}</h1>
        <select ref={c => this.selectNumber = c}>
            <option value="1">1</option>
            <option value="2">2</option>
            <option value="3">3</option>
        </select>&nbsp;
        <button onClick={this.increment}>+</button>&nbsp;
        <button onClick={this.decrement}>-</button>&nbsp;
        <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>&nbsp;
        <button onClick={this.incrementAsync}>异步加</button>
      </div>
    )
  }
}

2.3.3 求和案例-redux完整版
  • 比精简版多了以下内容:
    • 新增文件:
      • 1.count_action.js:专门用于创建action对象
      • 2.constant.js:放置容易写错的type值
        constant.js
/*
 该模块是用于定义action对象中type类型的常量值 , 是为了:便于管理的同时,防止程序员写错单词
 */
export const INCREAMENT = 'increment'
export const DECREAMENT = 'increment'

count_action.js

/* 
    该文件专门为Count组件生成action对象
*/
import { INCREAMENT,DECREAMENT } from "./constant"
export const createIncrementAction = data => ({type:INCREAMENT,data})
export const createDecrementAction = data => ({type:DECREAMENT,data})

count_reducer.js

/* 
    1.该文件是用于创建一个为count组件服务的reducer,reducer的本质就是一个函数
    2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
*/
import { INCREAMENT,DECREAMENT } from "./constant"
// 如果没有传入值,或者传入的preState是undefined,那么默认为0
export default function countReducer(preState=111,action){
    //从action对象中获取:type、data
    const {type,data} = action
    //根据type决定如何加工数据
    switch (type){
        case INCREAMENT:
            // 加法
            return preState + data
        case DECREAMENT:
            // 减法
            return preState - data
        default:
            return preState
    }
}

store.js

/* 
    该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import { createStore } from "redux"
//引入为Count组件服务的reducer
import countReducer from './count_reducer'
//暴露store
export default createStore(countReducer)

index.js

import ReactDOM from 'react-dom';
import App from './App';
import store from './redux/store';

ReactDOM.render(<App/>,document.getElementById('root'))

store.subscribe(()=>{
  ReactDOM.render(<App/>,document.getElementById('root'))
})

coponents/Count/index.jsx

  • 2.3.2一样

2.4 异步action

  • 异步action不是一定要用的东西,在组件里面,通过定时器实现,也是可以
  • action返回的是Object,那么就是同步action。返回的是一个函数,那么就是异步action。
    • 因为函数里面可以开启异步任务
2.4.1 安装
# 如果是rect16,安装命令:npm add redux-thunk@2.3.0
# 如果是react最新版本,安装命令:npm add redux-thunk
2.4.2 注册中间件

store.js

/* 
    该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import { createStore,applyMiddleware } from "redux"
//引入为Count组件服务的reducer
import countReducer from './count_reducer'

// 引入redux-thunk,用于支持异步action
import thunk from "redux-thunk"

//暴露store,注册异步action的异步中间件
export default createStore(countReducer,applyMiddleware(thunk))

2.4.3 异步aciton定义
/* 
    该文件专门为Count组件生成action对象
*/
import { INCREAMENT,DECREAMENT } from "./constant"
import store from "./store"

//同步action,就是指action的值为object类型的一般对象
export const createIncrementAction = data => ({type:INCREAMENT,data})
export const createDecrementAction = data => ({type:DECREAMENT,data})

//异步action,就是指action的值为函数;异步action中一般都会调用同步action
export const createIncrementAsyncAction = (data,time) => {
    return (dispatch)=>{
        setTimeout(()=>{
            // 通知redux加上data
            dispatch(createIncrementAction(data))
        },time)
    }
}

九、react-redux

  • 目的:不让开发者随意的在UI组件里面随意和redux进行交互,而是通过UI组件外面那一层容器进行交互。

1.模型图

  • 1.所有的UI组件都应该包裹一个容器组件,他们是父子关系。
  • 2.容器组件是真正和redux打交道的,里面可以随意的使用redux的api。
  • 3.Ul组件中不能使用任何redux的api。
  • 4.容器组件会传给UI组件:(1).redux中所保存的状态。(2).用于操作状态的方法
  • 5.备注:容器给Ul传递:状态、操作状态的方法,均通过props传递。
    在这里插入图片描述

2.安装

# react16安装:npm add react-redux@7.2.2
# 下面是react最新版安装命令
npm add react-redux

3.相关概念

  • (1)明确两个概念:
    • 1)UI组件:不能使用任何redux的api,只负责页面的呈现、交互等。
    • 2)容器组件:负责和redux通信,将结果交给UI组件。
  • (2)如何创建一个容器组件-—靠react-redux 的connect函数
    • connect(mapStateToProps,mapDispatchToProps)(UI组件)
      • mapStateToProps:映射状态,返回值是一个对象
      • mapDispatchToProps:映射操作状态的方法,返回值是一个对象
  • (3)备注1:容器组件中的store是靠props传进去的,而不是在容器组件中直接引入
  • (4)备注2:mapDispatchToProps,也可以是一个对象

4.基础操作(单组件)

在这里插入图片描述

4.1 index.js

  • 监控redux状态变化
import ReactDOM from 'react-dom';
import App from './App';
import store from './redux/store';

ReactDOM.render(<App/>,document.getElementById('root'))

// 监测redux中状态的改变,如redux的状态发生了改变,那么重新渲染App组件
store.subscribe(()=>{
  ReactDOM.render(<App/>,document.getElementById('root'))
})

4.2 redux相关

store.js

/* 
    该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import { createStore,applyMiddleware } from "redux"
//引入为Count组件服务的reducer
import countReducer from './count_reducer'

// 引入redux-thunk,用于支持异步action
import thunk from "redux-thunk"

//暴露store,注册异步action的异步中间件
export default createStore(countReducer,applyMiddleware(thunk))

constant.js

/*
 该模块是用于定义action对象中type类型的常量值 , 是为了:便于管理的同时,防止程序员写错单词
 */
export const INCREAMENT = 'increment'
export const DECREAMENT = 'decrement'

count_actino.js

/* 
    该文件专门为Count组件生成action对象
*/
import { INCREAMENT,DECREAMENT } from "./constant"

//同步action,就是指action的值为object类型的一般对象
export const createIncrementAction = data => ({type:INCREAMENT,data})
export const createDecrementAction = data => ({type:DECREAMENT,data})

//异步action,就是指action的值为函数
export const createIncrementAsyncAction = (data,time) => {
    return (dispatch)=>{
        setTimeout(()=>{
            // 通知redux加上data
            dispatch(createIncrementAction(data))
        },time)
    }
}

count_reducer.js

/* 
    1.该文件是用于创建一个为count组件服务的reducer,reducer的本质就是一个函数
    2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
*/
import { INCREAMENT,DECREAMENT } from "./constant"
// 如果没有传入值,或者传入的preState是undefined,那么默认为0
export default function countReducer(preState=111,action){
    //从action对象中获取:type、data
    const {type,data} = action
    //根据type决定如何加工数据
    switch (type){
        case INCREAMENT:
            // 加法
            return preState + data
        case DECREAMENT:
            // 减法
            return preState - data
        default:
            return preState
    }
}

4.3 容器

// 引入Count的UI组件
import CountUI from '../../components/Count'
// 引入connect用于连接UI组件与redux
import { connect } from 'react-redux'
// 引入action
import {createIncrementAction,createDecrementAction,createIncrementAsyncAction} from '../../redux/count_action'

/* 映射状态
    1.mapStateToProps函数返回是一个对象
    2.返回得对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
    3.mapStateToProps用于传递状态
*/
function mapStateToProps(state){
    // 等于:<CountUI n={900}/>
    return {count:state}
}

/* 映射操作状态得方法
    1.mapDispatchToProps函数返回是一个对象
    2.返回得对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
    3.mapDispatchToProps用于传递状态
*/
function mapDispatchToProps(dispatch){
    return {
        jia:(number)=>{
            //通知redux执行加法
            dispatch(createIncrementAction(number))
        },
        jian:(number)=>{
            //通知redux执行减法
            dispatch(createDecrementAction(number))
        },
        jiaAsync:(number,time)=>{
            //通知redux执行异步加
            dispatch(createIncrementAsyncAction(number,time))
        },
    }
}

// 创建并暴露一个Count的容器组件
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)

4.4 UI组件

import React, { Component } from 'react'

export default class Count extends Component {

    // 加法
    increment = ()=>{
        const {value} = this.selectNumber
        this.props.jia(value*1)
    }
    // 减法
    decrement = ()=>{
        const {value} = this.selectNumber
        this.props.jian(value*1)
    }
    // 奇数再加
    incrementIfOdd = ()=>{
        const {value} = this.selectNumber
        if(this.props.count % 2 !== 0){
          this.props.jia(value*1)
        }
    }
    // 异步加
    incrementAsync = ()=>{
        const {value} = this.selectNumber
        this.props.jiaAsync(value*1,500)
    }

  render() {
    return (
      <div>
        <h1>当前求和为:{this.props.count}</h1>
        <select ref={c => this.selectNumber = c}>
            <option value="1">1</option>
            <option value="2">2</option>
            <option value="3">3</option>
        </select>&nbsp;
        <button onClick={this.increment}>+</button>&nbsp;
        <button onClick={this.decrement}>-</button>&nbsp;
        <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>&nbsp;
        <button onClick={this.incrementAsync}>异步加</button>
      </div>
    )
  }
}

5.基础操作(代码优化)

  • 优化内容:
    • 容器组件和UI组件整合成一个文件
    • 无需自己给容器组件传递store,给<App/>包裹一个<Provider store={store}>即可。
    • 使用了react-redux后也不用自己检测redux中状态的改变了,容器组件可以自动完成这个工作。
    • mapDispatchToProps也可以简单的写成一个对象
    • 一个组件要和redux“打交道”要经过那几步?
      • 1.定义好UI组件(不暴露)
      • 2.引入connect生成一个容器组件,并暴露。
      • 3.在UI组件中通过this.props.xxx读取和操作状态

5.1 容器优化

在这里插入图片描述

5.2 redux状态变化机制

  • 由于使用了react-redux,当状态变化了,会自动重新渲染组件
    • 原理:由于我们使用connect,react-redux在里面做了状态监听渲染
      在这里插入图片描述

5.3 容器传入store优化-Provider

在这里插入图片描述

  • Provider:会分析所有的容器组件,将store传入所有的store组件
    在这里插入图片描述

5.4 整合UI组件和容器组件

  • 将UI组件和容器组件整合到一个文件里面,整合的内容还是叫容器
import React, { Component } from 'react'
// 引入connect用于连接UI组件与redux
import { connect } from 'react-redux'
// 引入action
import {createIncrementAction,
    createDecrementAction,
    createIncrementAsyncAction} from '../../redux/count_action'

// 定义UI组件
class Count extends Component {

    // 加法
    increment = ()=>{
        const {value} = this.selectNumber
        this.props.jia(value*1)
    }
    // 减法
    decrement = ()=>{
        const {value} = this.selectNumber
        this.props.jian(value*1)
    }
    // 奇数再加
    incrementIfOdd = ()=>{
        const {value} = this.selectNumber
        if(this.props.count % 2 !== 0){
            this.props.jia(value*1)
        }
    }
    // 异步加
    incrementAsync = ()=>{
        const {value} = this.selectNumber
        this.props.jiaAsync(value*1,500)
    }

    render() {
    return (
        <div>
        <h1>当前求和为:{this.props.count}</h1>
        <select ref={c => this.selectNumber = c}>
            <option value="1">1</option>
            <option value="2">2</option>
            <option value="3">3</option>
        </select>&nbsp;
        <button onClick={this.increment}>+</button>&nbsp;
        <button onClick={this.decrement}>-</button>&nbsp;
        <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>&nbsp;
        <button onClick={this.incrementAsync}>异步加</button>
        </div>
    )
    }
}

// 创建并暴露一个Count的容器组件
export default connect(
    state => ({count:state}),
    {
        jia:createIncrementAction,
        jian:createDecrementAction,
        jiaAsync:createIncrementAsyncAction,
    }
)(Count)

6.基础操作(多组件)数据共享

6.1 实现效果

在这里插入图片描述

6.2 redux目录结构

在这里插入图片描述

6.3 相关代码

  • 总结:
    • 定义一个Pserson组件,和Count组件通过redux共享数据。
    • 为Person组件编写: reducer、action,配置constant常量。
    • 重点:Person的reducer和Count的Reducer要使用combineReducers进行合并,合并后的总状态是一个对象!!!
    • 交给store的是总reducer,最后注意在组件中取出状态的时候,记得“取到位”。

App.jsx

import React, { Component } from 'react'
import Count from './containers/Count'
import Person from './containers/Person'

export default class App extends Component {

  render() {
    return (
      <div>
        <Count/>
        <hr/>
        <Person/>
      </div>
    )
  }
}

Person/index.jsx

import React, { Component } from 'react'
// 引入connect用于连接UI组件与redux
import { connect } from 'react-redux'
import { nanoid } from 'nanoid'
import { createAddPersonAction } from '../../redux/action/person'

class Person extends Component {
  addPerson = ()=>{
    const name = this.nameNode.value
    const age = this.ageNode.value
    const personObj = {id:nanoid(),name:name,age:age}
    console.log(personObj)
    this.props.addPerson(personObj)
    this.nameNode.value = ''
    this.ageNode.value = ''
  }
  render() {
    return (
      <div>
        <h2>我是Person组件,上方组件汇总为:{this.props.count}</h2>
        <input ref={c=>this.nameNode = c} type='text' placeholder='输入名字'/>
        <input ref={c=>this.ageNode = c} type='text' placeholder='输入年龄'/>
        <button onClick={this.addPerson}>添加</button>
        <ul>
            {
                this.props.persons.map((p)=>{
                    return <li key={p.id}>{p.name}---{p.age}</li>
                })
            }
        </ul>
      </div>
    )
  }
}

// 创建并暴露一个的容器组件
export default connect(
    state => ({persons:state.person_array,count:state.count}),
    {
        addPerson:createAddPersonAction
    }
)(Person)

Count/index.jsx

import React, { Component } from 'react'
// 引入connect用于连接UI组件与redux
import { connect } from 'react-redux'
// 引入action
import {createIncrementAction,
    createDecrementAction,
    createIncrementAsyncAction} from '../../redux/action/count'

// 定义UI组件
class Count extends Component {

    // 加法
    increment = ()=>{
        const {value} = this.selectNumber
        this.props.jia(value*1)
    }
    // 减法
    decrement = ()=>{
        const {value} = this.selectNumber
        this.props.jian(value*1)
    }
    // 奇数再加
    incrementIfOdd = ()=>{
        const {value} = this.selectNumber
        if(this.props.count % 2 !== 0){
            this.props.jia(value*1)
        }
    }
    // 异步加
    incrementAsync = ()=>{
        const {value} = this.selectNumber
        this.props.jiaAsync(value*1,500)
    }

    render() {
    return (
        <div>
            <h2>我是Count组件</h2>
            <h4>当前求和为:{this.props.count},下方组件总人数为:{this.props.person_count}</h4>
            <select ref={c => this.selectNumber = c}>
                <option value="1">1</option>
                <option value="2">2</option>
                <option value="3">3</option>
            </select>&nbsp;
            <button onClick={this.increment}>+</button>&nbsp;
            <button onClick={this.decrement}>-</button>&nbsp;
            <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>&nbsp;
            <button onClick={this.incrementAsync}>异步加</button>
        </div>
    )
    }
}

// 创建并暴露一个Count的容器组件
export default connect(
    state => ({count:state.count,person_count:state.person_array.length}),
    {
        jia:createIncrementAction,
        jian:createDecrementAction,
        jiaAsync:createIncrementAsyncAction,
    }
)(Count)

redux/store.js

/* 
    该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import { createStore,applyMiddleware,combineReducers } from "redux"
//引入为Count组件服务的reducer
import countReducer from './reducer/count'

// 引入redux-thunk,用于支持异步action
import thunk from "redux-thunk"
import personReducer from "./reducer/person"

//汇总所有的reducer变为一个总的reducer
const allReducer = combineReducers({
    count:countReducer,
    person_array:personReducer,
})

//暴露store,注册异步action的异步中间件
export default createStore(allReducer,applyMiddleware(thunk))

redux/constant.js

/*
 该模块是用于定义action对象中type类型的常量值 , 是为了:便于管理的同时,防止程序员写错单词
 */
export const INCREAMENT = 'increment'
export const DECREAMENT = 'decrement'
export const ADDPPERSON = 'add_person'

redux/action/count.js

/* 
    该文件专门为Count组件生成action对象
*/
import { INCREAMENT,DECREAMENT } from "../constant"

//同步action,就是指action的值为object类型的一般对象
export const createIncrementAction = data => ({type:INCREAMENT,data})
export const createDecrementAction = data => ({type:DECREAMENT,data})

//异步action,就是指action的值为函数
export const createIncrementAsyncAction = (data,time) => {
    return (dispatch)=>{
        setTimeout(()=>{
            // 通知redux加上data
            dispatch(createIncrementAction(data))
        },time)
    }
}

redux/action/person.js

import { ADDPPERSON } from "../constant"

// 创建增加一个人的action动作对象
export const createAddPersonAction = personObj => ({type:ADDPPERSON,data:personObj})

redux/reducer/person.js

import {ADDPPERSON} from '../constant'

// 初始化人的列表
const initState = [{id:'001',name:'tom',age:18}]

export default function personReducer(preState=initState,action){
    const {type,data} = action
    switch(type){
        case ADDPPERSON:
            return [data,...preState]
        default:
            return preState
    }
}

redux/reducer/count.js

/* 
    1.该文件是用于创建一个为count组件服务的reducer,reducer的本质就是一个函数
    2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
*/
import { INCREAMENT,DECREAMENT } from "../constant"
// 如果没有传入值,或者传入的preState是undefined,那么默认为0
export default function countReducer(preState=111,action){
    //从action对象中获取:type、data
    const {type,data} = action
    //根据type决定如何加工数据
    switch (type){
        case INCREAMENT:
            // 加法
            return preState + data
        case DECREAMENT:
            // 减法
            return preState - data
        default:
            return preState
    }
}

7.纯函数

7.1 reducer注意事项

  • 如果使用了unshift:不是纯函数(不得改写参数数据)
import {ADDPPERSON} from '../constant'

// 初始化人的列表
const initState = [{id:'001',name:'tom',age:18}]

export default function personReducer(preState=initState,action){
    const {type,data} = action
    switch(type){
        case ADDPPERSON:
        	// 注意:redux对比的preState的对象内存地址,如果相同,那么就不会做更新。
        	// 错误案例(不会更新):return preState.unshift(data)
            return [data,...preState]
        default:
            return preState
    }
}

7.2 纯函数

  • 一类特别的函数:只要是同样的输入(实参),必定得到同样的输出(返回)
  • 必须遵守以下一些约束
    • 1)不得改写参数数据
    • 2)不会产生任何副作用,例如网络请求,输入和输出设备
    • 3)不能调用 Date.now()或者Math.random()等不纯的方法
  • redux的reducer 函数必须是一个纯函数
// 不是纯函数(只要是同样的输入(实参),必定得到同样的输出(返回))
function demo(a){
	return Math.random() + a
}
// 不是纯函数(不得改写参数数据)
function demo(a){
    a = 9
	return a
}

// 纯函数
function demo(a){
	return a * 2
}

8.redux开发者工具

8.1 安装扩展程序

在这里插入图片描述

8.2 安装库

npm add redux-devtools-extension

8.3 store.js注册插件

  • composeWithDevTools
/* 
    该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import { createStore,applyMiddleware,combineReducers } from "redux"
//引入为Count组件服务的reducer
import countReducer from './reducer/count'

// 引入redux-devtools-extension
import { composeWithDevTools } from "redux-devtools-extension"

// 引入redux-thunk,用于支持异步action
import thunk from "redux-thunk"
import personReducer from "./reducer/person"

//汇总所有的reducer变为一个总的reducer
const allReducer = combineReducers({
    count:countReducer,
    person_array:personReducer,
})

//暴露store,注册异步action的异步中间件
export default createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))

十、react打包

  • 将react代码转换为js代码,可以供浏览器识别
npm run build

十一、react扩展

1.setState

  • setState式异步的,调用之后,不会马上更新值
  • 总结:
    • 1.对象式的setState是函数式的setState的简写方式(语法糖)
    • 2.使用原则:
      • (1).如果新状态不依赖于原状态===>使用对象方式
      • (2).如果新状态依赖于原状态===>使用函数方式
      • (3).如果需要在setState()执行后获取最新的状态数据,要在第二个callback函数中读取

1.1 对象式

  • setState(stateChange, [callback])------对象式的setState
    • 1.statechange为状态改变对象(该对象可以体现出状态的更改)
    • 2.callback是可选的回调函数,它在状态更新完毕、界面也更新后(render调用后)才被调用
import React, { Component } from 'react'

export default class Demo extends Component {
  
  state = {count:0}

  add = () => {
    const {count} = this.state
    this.setState({count:count+1},()=>{
      //改完状态,render之后的值,是加1之后的值
      console.log("callback:" + this.state.count)
    })
    // 输出0
    console.log("输出;" + this.state.count)
  }

  render() {
    return (
      <div>
        <h1>当前求和为:{this.state.count}</h1>
        <button onClick={this.add}>点我+1</button>
      </div>
    )
  }
}

1.2 函数式

  • setState(updater, [callback])------函数式的setState
    • 1.updater为返回statechange对象的函数。
    • 2.updater可以接收到state和props。
    • 3.callback是可选的回调函数,它在状态更新、界面也更新后(render调用后)才被调用。
import React, { Component } from 'react'

export default class Demo extends Component {
  
  state = {count:0}

  add = () => {
    this.setState((state,props)=>{
      return {count:state.count+1}
    },()=>{
      //改完状态,render之后的值,是加1之后的值
      console.log("callback:" + this.state.count)
    })
  }

  render() {
    return (
      <div>
        <h1>当前求和为:{this.state.count}</h1>
        <button onClick={this.add}>点我+1</button>
      </div>
    )
  }
}

2.路由懒加载(lazyLoad)

2.1 无懒加载

import React, { Component } from 'react'
import { NavLink,Route } from 'react-router-dom'
import About from './About'
import Home from './Home'

export default class Demo extends Component {

  render() {
    return (
      <div>
        <div>
          <NavLink to="/home">Home</NavLink>
          <hr/>
          <NavLink to="/about">About</NavLink>
        </div>
        <div>
          <Route path="/about" component={About}/>
          <Route path="/home" component={Home}/>
        </div>
      </div>
    )
  }
}


2.2 懒加载

  • 需要使用Suspense,并在callback中执行正在加载路由资源的时候显示的页面
import React, { Component, lazy,Suspense } from 'react'
import { NavLink,Route } from 'react-router-dom'

const Home = lazy(()=> import('./Home'))
const About = lazy(()=> import('./About'))

export default class Demo extends Component {

  render() {
    return (
      <div>
        <div>
          <NavLink to="/home">Home</NavLink>
          <hr/>
          <NavLink to="/about">About</NavLink>
        </div>
        <div>
          <Suspense fallback={<h1>Loadind....</h1>}>
            <Route path="/about" component={About}/>
            <Route path="/home" component={Home}/>
          </Suspense>

        </div>
      </div>
    )
  }
}

3.Hooks

3.1 是什么?

  • Hook是React 16.8.0版本增加的新特性/新语法
  • 可以让你在函数组件中使用state 以及其他的React 特性

3.2 三个常用的Hook

  • State Hook: React.useState()
    • 状态钩子
  • Effect Hook: React.useEffect()
    • 生命周期钩子
  • Ref Hook: React.useRef()

3.3 State Hook

  • state Hook让函数组件也可以有state状态,并进行状态数据的读写操作
  • 语法:const [xxx,setXxx]=React.useState(initValue)
  • useState()说明:
    • 参数:第一次初始化指定的值在内部作缓存
    • 返回值:包含2个元素的数组,第1个为内部当前状态值,第2个为更新状态值的函数
  • setXxx()2种写法:
    • setxxx(newvalue):参数为非函数值,直接指定新的状态值,内部用其覆盖原来的状态值
    • setxxx(value => newvalue):参数为函数,接收原本的状态值,返回新的状态值,内部用其覆盖原来的状态值
import React from 'react'

// 类式组件
// export default class Demo extends React.Component {

//   state = {count:0}  

//   add = ()=>{
//     this.setState({count:this.state.count+1})
//   }

//   render() {
//     return (
//       <div>
//         <h2>当前求和状态为{this.state.count}</h2>
//         <button onClick={this.add}>点我+1</button>
//       </div>
//     )
//   }
// }

export default function Demo(){
    
    const [count,setCount] = React.useState(0)

    function add(){
        // 第一种写法
        // setCount(count+1)

        // 第二种写法
        setCount((count)=>{return count+1})
    }

    return (
        <div>
        <h2>当前求和状态为{count}</h2>
        <button onClick={add}>点我+1</button>
        </div>
    )
}

3.4 Effect Hook

  • Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
  • React中的副作用操作:
    • 发ajax请求数据获取
    • 设置订阅/启动定时器
    • 手动更改真实DOM
  • 语法和说明:
useEffect(() =>{
	//在此可以执行任何带副作用操作
	return)=>{
		//在组件卸载前执行
		//在此做一些收尾工作,比如清除定时器/取消订阅等
		}
	}[statevalue]//如果指定的是[],回调函数只会在第一次render()后执行
	
  • 可以把 useEffect Hook看做如下三个函数的组合
    • componentDidMount()
    • componentDidUpdate()
    • componentWillUnmount()
import React from 'react'
import ReactDOM from 'react-dom'

// 类式组件
// export default class Demo extends React.Component {

//   state = {count:0}  

//   add = ()=>{
//     this.setState({count:this.state.count+1})
//   }

//   unmount = ()=>{
//     ReactDOM.unmountComponentAtNode(document.getElementById('root'))
//   }

//   componentDidMount(){
//     this.timer = setInterval(()=>{
//         this.setState({count:this.state.count+1})
//     },1000)
//   }

//   componentWillUnmount(){
//     clearInterval(this.timer)
//   }

//   render() {
//     return (
//       <div>
//         <h2>当前求和状态为{this.state.count}</h2>
//         <button onClick={this.add}>点我+1</button>
//         <button onClick={this.unmount}>卸载组件</button>
//       </div>
//     )
//   }
// }

export default function Demo(){
    
    const [count,setCount] = React.useState(0)
    
    React.useEffect(()=>{
        let timer = setInterval(()=>{
            setCount(count => count+1)
        },1000)
        return ()=>{
            clearInterval(timer)
        }
    },[])

    function add(){
        setCount(count+1)
    }

    function unmount(){
        ReactDOM.unmountComponentAtNode(document.getElementById('root'))
    }

    return (
        <div>
            <h2>当前求和状态为{count}</h2>
            <button onClick={add}>点我+1</button>
            <button onClick={unmount}>卸载组件</button>
        </div>
    )
}

3.5 Ref Hook

  • Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
  • 语法:const refcontainer =useRef()
  • 作用:保存标签对象,功能与React.createRef()一样
import React from 'react'
import ReactDOM from 'react-dom'

// 类式组件
// export default class Demo extends React.Component {

//   myRef = React.createRef()

//   show = ()=>{
//     alert(this.myRef.current.value)
//   }

//   render() {
//     return (
//       <div>
//         <input type='text' ref={this.myRef}/>
//         <button onClick={this.show}>点击提示数据</button>
//       </div>
//     )
//   }
// }

export default function Demo(){
    
    const myRef = React.useRef()
    
    function show(){
        alert(myRef.current.value)
    }

    function unmount(){
        ReactDOM.unmountComponentAtNode(document.getElementById('root'))
    }

    return (
        <div>
            <input type='text' ref={myRef}/>
            <button onClick={show}>点击提示数据</button>
        </div>
    )
}

4.Fragment

  • 作用:可以不用必须有一个真实的DOM根标签了

下面两种都可以:

<></>

import React from 'react'
import ReactDOM from 'react-dom'

export default class Demo extends React.Component {

  myRef = React.createRef()

  show = ()=>{
    alert(this.myRef.current.value)
  }

  render() {
    return (
      <>
        <input type='text' ref={this.myRef}/>
        <button onClick={this.show}>点击提示数据</button>
      </>
    )
  }
}


5.Context

  • 一种组件间通信方式,常用于【祖组件】与【后代组件】间通信

5.1 使用

// 1.创建Context容器对象:
const xxxContext = React.createContext()

// 2.渲染子组的时候,外面包裹一层xxxContext.Provider,通过value属性给后代组件传递数据:
<xxxContext.provider value={数据}>
子组件
</xxxContext.Provider>

// 3.后代组件读取数据:
// 3.1 第一种方式:仅适用于类组件
static contextType = xxxContext //声明接收context
this.context // 读取context中value的数据
// 3.2 第二种方式:函数组件与类组件都可以
<xxxContext.Consumer>
{
	value => ( //value就是context中的value数据
		要显示的内容
	)
}
</xxxContext.Consumer>

5.2 类组件代码示例

import React, { Component } from 'react'

// 创建Context对象
const MyContext = React.createContext()

export default class A extends Component {

  state = {username:'tom',age:18}
  

  render() {
    const {username,age} = this.state
    return (
      <div>
        <h3>我是A组件</h3>
        <h4>我的用户名是:{username}</h4>
        <MyContext.Provider value={{username,age}}>
          <B/>
        </MyContext.Provider>
      </div>
    )
  }
}

class B extends Component {

    static contextType = MyContext

    render() {
      return (
        <div>
            <h3>我是B组件</h3>
            <h4>我的从A组件接收到的用户名是:{this.context.username}</h4>
            <C/>
        </div>
      )
    }
  }

 class C extends Component {

    static contextType = MyContext

    render() {
      return (
        <div>
            <h3>我是C组件</h3>
            <h4>我的从A组件接收到的用户名是:{this.context.username},年龄是:{this.context.age}</h4>
        </div>
      )
    }
  }

5.3 函数组件代码示例

import React, { Component } from 'react'

// 创建Context对象
const MyContext = React.createContext()

export default class A extends Component {

  state = {username:'tom',age:18}
  

  render() {
    const {username,age} = this.state
    return (
      <div>
        <h3>我是A组件</h3>
        <h4>我的用户名是:{username}</h4>
        <MyContext.Provider value={{username,age}}>
          <B/>
        </MyContext.Provider>
      </div>
    )
  }
}

class B extends Component {

    static contextType = MyContext

    render() {
      return (
        <div>
            <h3>我是B组件</h3>
            <h4>我的从A组件接收到的用户名是:{this.context.username}</h4>
            <C/>
        </div>
      )
    }
  }

  function C(){
    return (
        <div>
            <h3>我是C组件</h3>
            <h4>我的从A组件接收到的用户名是:
            <MyContext.Consumer>
                {
                    value => {
                        return `${value.username},年龄是:${value.age}`
                    }
                }
            </MyContext.Consumer>
            </h4>
        </div>
    )
}

6.组件优化

  • Component的两个问题:
    • 只要执行setState(),即使不改变状态数据(this.setState({})),组件也会重新render()
    • 只当前组件重新render(),就会自动重新render子组件(尽管没有传任何东西给子组件,子组件也会重新render)==>效率低
  • 效率高的做法:只有当组件的state或props数据发生改变时才重新render()
  • 原因:Component中的shouldComponentUpdate()总是返回true

6.1 解决办法1

  • 重写shouldComponentUpdate()方法
  • 比较新旧state或props数据,如果有变化才返回true,如果没有返回false
import React, { Component } from 'react'

export default class Parent extends Component {

  state = {carName:"奔驰c36"}
  
  changeCar = ()=>{
    this.setState({carName:'迈巴赫'})
  }

  shouldComponentUpdate(nextProps,nextState){
    if(this.state.carName === nextState.carName) return false
    else return true
  }

  render() {
    console.log('Parent')
    const {carName} = this.state
    return (
      <div>
        <h3>我是Parent组件</h3>
        <span>我的车的名字是:{carName}</span>
        <button onClick={this.changeCar}>点击换车</button>
        <Child carName="奥拓"/>
      </div>
      
    )
  }
}

class Child extends Component {

    shouldComponentUpdate(nextProps,nextState){
        if(this.props.carName === nextProps.carName) return false
        else return true
    }

    render() {
      console.log('Child')
      return (
        <div>
            <h3>我是Child组件</h3>
            <span>接收到的车:{this.props.carName}</span>
        </div>
      )
    }
}


6.2 解决办法2(推荐)

  • 使用PureComponent
  • PureComponent重写了shouldcomponentUpdate(),只有state或props数据有变化才返回true
  • 注意:
    • 只是进行state和props数据的浅比较,如果只是数据对象内部数据变了,返回false
    • 不要直接修改state数据,而是要产生新数据
import React, { PureComponent } from 'react'

export default class Parent extends PureComponent {

  state = {carName:"奔驰c36"}
  
  changeCar = ()=>{
    this.setState({carName:'迈巴赫'})
  }

  render() {
    console.log('Parent')
    const {carName} = this.state
    return (
      <div>
        <h3>我是Parent组件</h3>
        <span>我的车的名字是:{carName}</span>
        <button onClick={this.changeCar}>点击换车</button>
        <Child carName="奥拓"/>
      </div>
      
    )
  }
}

class Child extends PureComponent {

    render() {
      console.log('Child')
      return (
        <div>
            <h3>我是Child组件</h3>
            <span>接收到的车:{this.props.carName}</span>
        </div>
      )
    }
}

7.render props

7.1 如何向组件内部动态传入带内容的结构(标签)

Vue中:
	使用slot技术,也就是通过组件标签体传入结构<A><B/></A>
React中:
	使用children props:通过组件标签体传入结构
	使用render props:通过组件标签属性传入结构,一般用render函数属性

7.2 children props

<A>
	<B>xxxx</B>
</A>
问题:如果B组件需要A组件内的数据,==>做不到
import React, { Component } from 'react'

export default class Parent extends Component {
  render() {
    return (
      <div>
        <h3>我是Parent组件</h3>
        <A>
            <B/>
        </A> 
      </div>
    )
  }
}

class A extends Component {
    state = {name:'tom'}
    render() {
      return (
        <div>
            <h3>我是A组件</h3>
            {this.props.children}
        </div>
        )
    }
}

class B extends Component {
    render() {
      return (
        <div>
            <h3>我是B组件</h3>
        </div>
        )
    }
}

7.3 render props

<A render={(data) => <C data={data}></C>}></A>
A组件:{this.props.render(内部state数据)}
C组件:读取A组件传入的数据显示{this.props.data}
import React, { Component } from 'react'

export default class Parent extends Component {
  render() {
    return (
      <div>
        <h3>我是Parent组件</h3>
        {/* 
        // 下面的写法,满足不了A传递参数给B
        <A>
            <B/>
        </A> 
        */}
        <A render={(name)=><B name={name}/>}/>
      </div>
    )
  }
}

class A extends Component {
    state = {name:'tom'}
    render() {
      return (
        <div>
            <h3>我是A组件</h3>
            {this.props.render(this.state.name)}
        </div>
        )
    }
}

class B extends Component {
    render() {
      return (
        <div>
            <h3>我是B组件</h3>
        </div>
        )
    }
}

8.错误边界

  • 理解:错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面。如果前端代码报错了,避免显示以下界面
    在这里插入图片描述
  • 特点:只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
  • 使用方式:getDerivedStateFromError配合componentDidCatch

Parent.jsx

import React, { Component } from 'react'
import Child from './Child'

export default class Parent extends Component {
  
  state = {
    hasError:''
  }

  // 当Parent的子组件出现报错时候,会触发getDerivedStateFromError调用,并携带错误信息
  static getDerivedStateFromError(error){
    console.log(error)
    return {hasError:error}
  }

  // 子组件渲染时出错,就会调用该方法。
  componentDidCatch(){
    // 可以统计此处的错误,然后发送给后台记录,用于通知编码人员进行bug解决
  }

  render() {
    return (
      <div>
        <h2>我是Parent组件</h2>
        {this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child/>}
      </div>
    )
  }
}

Child.jsx

  • 子组件
import React, { Component } from 'react'

export default class Child extends Component {

  state = ''

  render() {
    return (
      <div>
        <h2>我是Child件</h2>
        {
            this.state.users.map((user)=>{
                return <h4 key={user.id}>{user.name}---{user.age}</h4>
            })
        }
      </div>
    )
  }
}

9.组件通信方式总结

  • 组件间关系:
    • 父子组件
    • 兄弟组件(非嵌套组件)
    • 祖孙组件(跨级组件)
  • 通信方式:
    • 1.props:
      • children props
      • render props
    • 2.消息订阅-发布:
      • pubs-sub、event等等
    • 3.集中式管理:
      • redux、dva等等
    • 4.conText:
      • 生产者-消费者模式
  • 比较好的搭配方式:
    • 父子组件:props
    • 兄弟组件:消息订阅-发布、集中式管理
    • 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)

十二、React Router6

1.概述

  • 1.React Router以三个不同的包发布到 npm上,它们分别为:
    • 1.1 react-router:路由的核心库,提供了很多的:组件、钩子。
    • 1.2 react-router-dom:包含react-router所有内容,并添加一些专门用于DOM的组件,例如<BrowserRouter\>等
    • 1.3 react-router-native:包括react-router所有内容,并添加一些专门用于ReactNative的API,例如:<NativeRouter>等。
  • 2.与React Router 5.x版本相比,改变了什么?
    • 2.1 内置组件的变化:移除<switch/>新增<Routes/>等。
    • 2.2 语法的变化: component={About}变为element={<About/>}等。
    • 2.3 新增了多个hook:useParamsuseNavigateuseMatch
    • 2.4 官方推荐函数式组件

2.component

2.1 BrowerRouter

  • 改组件用于包裹整个应用
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";

ReactDOM.render(
  <BrowserRouter>
    {/* 整体结构(通常为App组件) */}
  </BrowserRouter>,root
);

2.2 HashRouter

  • 说明:作用与<BrowserRouter>一样,但<HashRouter>修改的是地址栏的hash值。
  • 备注:6.x版本中<HashRouter>、<BrowserRouter>的用法与5.x相同。

2.3 Routes与Route

  • v6版本中移出了先前的<switch>,引入了新的替代者:<Routes>
  • <Route>和<Routes>要配合使用,且必须要用<Routes>包裹<Route>。
  • <Route>相当于一个if语句,如果其路径与当前URL匹配,则呈现其对应的组件。
  • <Route caseSensitive>属性用于指定:匹配时是否区分大小写(默认为false)。
  • 当URL发生变化时,<Routes>都会查看其所有子<Route>元素以找到最佳匹配并呈现组件。
  • <Route>也可以嵌套使用,且可配合 useRoutes()配置“路由表”,但需要通过<Outlet>组件来渲染其子路由。
<Routes>
    /*path属性用于定义路径,element属性用于定义当前路径所对应的组件*/
    <Route path="/login" element={<Login />}></Route>

		/*用于定义嵌套路由,home是一级路由,对应的路径/home*/
    <Route path="home" element={<Home />}>
       /*test1 和 test2 是二级路由,对应的路径是/home/test1 或 /home/test2*/
      <Route path="test1" element={<Test/>}></Route>
      <Route path="test2" element={<Test2/>}></Route>
		</Route>
	
		//Route也可以不写element属性, 这时就是用于展示嵌套的路由 .所对应的路径是/users/xxx
    <Route path="users">
       <Route path="xxx" element={<Demo />} />
    </Route>
</Routes>

2.4 Link

  • 作用:修改URL,且不发送网络请求(路由链接)。
  • 注意:外侧需要用<BrowserRouter>或<HashRouter>包裹。
import { Link } from "react-router-dom";

function Test() {
  return (
    <div>
    	<Link to="/路径">按钮</Link>
    </div>
  );
}

2.5 NavLink

  • 作用:与<Link>组件类似,且可实现导航的“高亮”效果。
// 注意: NavLink默认类名是active,下面是指定自定义的class

//自定义样式
<NavLink
    to="login"
    className={({ isActive }) => {
        console.log('home', isActive)
        return isActive ? 'base one' : 'base'
    }}
>login</NavLink>

/*
	默认情况下,当Home的子组件匹配成功,Home的导航也会高亮,
	当NavLink上添加了end属性后,若Home的子组件匹配成功,则Home的导航没有高亮效果。
*/
<NavLink to="home" end >home</NavLink>

2.6 Navigate

  • 作用:只要<Navigate>组件被渲染,就会修改路径,切换视图。
  • replace属性用于控制跳转模式(push 或 replace,默认是push)。
import React,{useState} from 'react'
import {Navigate} from 'react-router-dom'

export default function Home() {
	const [sum,setSum] = useState(1)
	return (
		<div>
			<h3>我是Home的内容</h3>
			{/* 根据sum的值决定是否切换视图 */}
			{sum === 1 ? <h4>sum的值为{sum}</h4> : <Navigate to="/about" replace={true}/>}
			<button onClick={()=>setSum(2)}>点我将sum变为2</button>
		</div>
	)
}

2.7 Outlet

  • <Route>产生嵌套时,渲染其对应的后续子路由。
//根据路由表生成对应的路由规则
const element = useRoutes([
  {
    path:'/about',
    element:<About/>
  },
  {
    path:'/home',
    element:<Home/>,
    children:[
      {
        path:'news',
        element:<News/>
      },
      {
        path:'message',
        element:<Message/>,
      }
    ]
  }
])

//Home.js
import React from 'react'
import {NavLink,Outlet} from 'react-router-dom'

export default function Home() {
	return (
		<div>
			<h2>Home组件内容</h2>
			<div>
				<ul className="nav nav-tabs">
					<li>
						<NavLink className="list-group-item" to="news">News</NavLink>
					</li>
					<li>
						<NavLink className="list-group-item" to="message">Message</NavLink>
					</li>
				</ul>
				{/* 指定路由组件呈现的位置 */}
				<Outlet />
			</div>
		</div>
	)
}

3.Hooks

3.1 useRoutes()

  • 作用:根据路由表,动态创建<Routes><Route>
//路由表配置:src/routes/index.js
import About from '../pages/About'
import Home from '../pages/Home'
import {Navigate} from 'react-router-dom'

export default [
	{
		path:'/about',
		element:<About/>
	},
	{
		path:'/home',
		element:<Home/>
	},
	{
		path:'/',
		element:<Navigate to="/about"/>
	}
]

//App.jsx
import React from 'react'
import {NavLink,useRoutes} from 'react-router-dom'
import routes from './routes'

export default function App() {
	//根据路由表生成对应的路由规则
	const element = useRoutes(routes)
	return (
		<div>
			......
      {/* 注册路由 */}
      {element}
		  ......
		</div>
	)
}

3.2 useNavigate()

  • 作用:返回一个函数用来实现编程式导航。
import React from 'react'
import {useNavigate} from 'react-router-dom'

export default function Demo() {
  const navigate = useNavigate()
  const handle = () => {
    //第一种使用方式:指定具体的路径
    navigate('/login', {
      replace: false,
      state: {a:1, b:2}
    }) 
    //第二种使用方式:传入数值进行前进或后退,类似于5.x中的 history.go()方法
    navigate(-1)
  }
  
  return (
    <div>
      <button onClick={handle}>按钮</button>
    </div>
  )
}

3.3 useParams()

  • 作用:回当前匹配路由的params参数,类似于5.x中的match.params
import React from 'react';
import { Routes, Route, useParams } from 'react-router-dom';
import User from './pages/User.jsx'

function ProfilePage() {
  // 获取URL中携带过来的params参数
  let { id } = useParams();
}

function App() {
  return (
    <Routes>
      <Route path="users/:id" element={<User />}/>
    </Routes>
  );
}

3.4 useSearchParams()

  • 作用:用于读取和修改当前位置的 URL 中的查询字符串。
  • 返回一个包含两个值的数组,内容分别为:当前的seaech参数、更新search的函数。
import React from 'react'
import {useSearchParams} from 'react-router-dom'

export default function Detail() {
	const [search,setSearch] = useSearchParams()
	const id = search.get('id')
	const title = search.get('title')
	const content = search.get('content')
	return (
		<ul>
			<li>
				<button onClick={()=>setSearch('id=008&title=哈哈&content=嘻嘻')}>点我更新一下收到的search参数</button>
			</li>
			<li>消息编号:{id}</li>
			<li>消息标题:{title}</li>
			<li>消息内容:{content}</li>
		</ul>
	)
}

3.5 useLocation()

  • 作用:获取当前 location 信息,对标5.x中的路由组件的location属性。
import React from 'react'
import {useLocation} from 'react-router-dom'

export default function Detail() {
	const x = useLocation()
	console.log('@',x)
  // x就是location对象: 
	/*
		{
      hash: "",
      key: "ah9nv6sz",
      pathname: "/login",
      search: "?name=zs&age=18",
      state: {a: 1, b: 2}
    }
	*/
	return (
		<ul>
			<li>消息编号:{id}</li>
			<li>消息标题:{title}</li>
			<li>消息内容:{content}</li>
		</ul>
	)
}

3.6 useMatch()

  • 作用:返回当前匹配信息,对标5.x中的路由组件的match属性。
<Route path="/login/:page/:pageSize" element={<Login />}/>
<NavLink to="/login/1/10">登录</NavLink>

export default function Login() {
  const match = useMatch('/login/:x/:y')
  console.log(match) //输出match对象
  //match对象内容如下:
  /*
  	{
      params: {x: '1', y: '10'}
      pathname: "/LoGin/1/10"  
      pathnameBase: "/LoGin/1/10"
      pattern: {
      	path: '/login/:x/:y', 
      	caseSensitive: false, 
      	end: false
      }
    }
  */
  return (
  	<div>
      <h1>Login</h1>
    </div>
  )
}

3.7 useInRouterContext()

  • 作用:如果组件在 <Router> 的上下文中呈现,则 useInRouterContext 钩子返回 true,否则返回 false。
  • 场景:有判断使用人是不是在路由组件使用了我封装的组件
    在这里插入图片描述

3.8 useNavigationType()

  • 作用:返回当前的导航类型(用户是如何来到当前页面的)。
  • 返回值:POPPUSHREPLACE
  • 备注:POP是指在浏览器中直接打开了这个路由组件(刷新页面)。
    在这里插入图片描述

3.9 useOutlet()

  • 作用:用来呈现当前组件中渲染的嵌套路由。
const result = useOutlet()
console.log(result)
// 如果嵌套路由没有挂载,则result为null
// 如果嵌套路由已经挂载,则展示嵌套的路由对象

3.10 useResolvedPath()

  • 作用:给定一个 URL值,解析其中的:path、search、hash值。
import {useResolvedPath} from 'react-router-dom
const pathObj = useResolvedPath('/user?id=001&name=tom#qwe')
// 解析出来是:{pathname:'/user', search: '?id=001&name=tom', hash: '#qwe'}

4.综合案例

4.1 useRouters路由表

在这里插入图片描述

  • 一般来说,路由都会放到src/routes里面
    在这里插入图片描述

4.2 嵌套路由

路由表
在这里插入图片描述
一级路由
在这里插入图片描述
二级路由在这里插入图片描述

4.3 路由传参

Link参数定义
在这里插入图片描述
在这里插入图片描述
接收参数

  • params传参
    在这里插入图片描述
  • search传参
    在这里插入图片描述
  • state参数
    在这里插入图片描述
    在这里插入图片描述

4.4 编程式路由导航

在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2231477.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

20241102在荣品PRO-RK3566开发板使用荣品预编译的buildroot通过iperf2测试AP6256的WIFI网速

20241102在荣品PRO-RK3566开发板使用荣品预编译的buildroot通过iperf2测试AP6256的WIFI网速 2024/11/2 14:18 客户端&#xff1a;荣耀手机HONOR 70【iPerf2 for Android】 服务器端&#xff1a;荣品PRO-RK3566开发板 预编译固件&#xff1a;update-pro-rk3566-buildroot-hdmi-2…

【启程Golang之旅】并发编程构建简易聊天系统

欢迎来到Golang的世界&#xff01;在当今快节奏的软件开发领域&#xff0c;选择一种高效、简洁的编程语言至关重要。而在这方面&#xff0c;Golang&#xff08;又称Go&#xff09;无疑是一个备受瞩目的选择。在本文中&#xff0c;带领您探索Golang的世界&#xff0c;一步步地了…

【JavaSE】java对象的比较

文章目录 元素的比较基本类型的比较对象的比较 如何进行对象比较重写equals方法基于Comparble.compareTo的比较基于Comparator.compare的比较区分Comparator和Comparable 在PriorityQueue中比较的体现 元素的比较 基本类型的比较 在Java中,基本类型可以直接进行大小的比较 //…

多SpringBoot项目同进程下统一启动

1.背景 使用SpringBoot技术栈进行REST HTTP接口开发服务时&#xff0c;一般来说如果模块较多或者涉及多人协作开发&#xff0c;大家会不自觉的将每个模块独立成一个单独的项目进行开发&#xff0c;部署时则将每个服务进行单独部署和运行。服务间的调用则通过FeignClients&…

lvgl

lvgl 目录 lvgl Lvgl移植到STM32 -- 1、下载LVGL源码 -- 2、将必要文件复制到工程目录 -- 3、修改配置文件 将lvgl与底层屏幕结合到一块 -- lvgl也需要有定时器,专门给自己做了一个函数,告诉lvgl经过了多长时间(ms(毫秒)级别) 编写代码 lvgl的中文教程手册网站…

使用WebAssembly优化Web应用性能

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用WebAssembly优化Web应用性能 引言 WebAssembly 简介 安装工具 创建 WebAssembly 项目 编写 WebAssembly 代码 编译 WebAssem…

【docker】docker 环境配置及安装

本文介绍基于 官方存储库 docker 的环境配置、安装、代理配置、卸载等相关内容。 官方安装文档说明&#xff1a;https://docs.docker.com/engine/install/ubuntu/ 主机环境 宿主机环境 Ubuntu 20.04.6 LTS 安装步骤 添加相关依赖 sudo apt-get update sudo apt-get install…

【Linux】网络编程:初识协议,序列化与反序列化——基于json串实现,网络通信计算器中简单协议的实现、手写序列化与反序列化

目录 一、什么是协议&#xff1f; 二、为什么需要有协议呢&#xff1f; 三、协议的应用 四、序列化与反序列化的引入 什么是序列化和反序列化&#xff1f; 为什么需要序列化和反序列化&#xff1f; 五、序列化推荐格式之一&#xff1a;JSON介绍 六、网络版计算器编程逻…

基于MATLAB的加噪语音信号的滤波

一&#xff0e;滤波器的简述 在MATLAB环境下IIR数字滤波器和FIR数字滤波器的设计方 法即实现方法&#xff0c;并进行图形用户界面设计&#xff0c;以显示所介绍迷你滤波器的设计特性。 在无线脉冲响应&#xff08;IIR&#xff09;数字滤波器设计中&#xff0c;先进行模拟滤波器…

Java项目实战II基于Spring Boot的智能家居系统(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。 一、前言 随着物联网技术的快速发展和普及&#…

基于Spring Boot+Vue的助农销售平台(协同过滤算法、限流算法、支付宝沙盒支付、实时聊天、图形化分析)

&#x1f388;系统亮点&#xff1a;协同过滤算法、节流算法、支付宝沙盒支付、图形化分析、实时聊天&#xff1b; 一.系统开发工具与环境搭建 1.系统设计开发工具 后端使用Java编程语言的Spring boot框架 项目架构&#xff1a;B/S架构 运行环境&#xff1a;win10/win11、jdk1…

【C++笔记】容器适配器及deque和仿函数

【C笔记】容器适配器及deque和仿函数 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C笔记 文章目录 【C笔记】容器适配器及deque和仿函数前言一.容器适配器1.1什么是容器适配器1.2 STL标准库中stack和queue的底层结构 二.stack2.1stack类模…

软考:中间件

中间件 中间件是一类位于操作系统软件与用户应用软件之间的计算机软件&#xff0c;它包括一组服务&#xff0c;以便于运行在一台或多台机器上的多个软件通过网络进行交互。 中间件的主要功能包括通信支持和应用支持。 通信支持为应用软件提供平台化的运行环境&#xff0c;屏蔽…

统信UOS设备驱动开发-常见问题

包含linux设备驱动开发的基础知识及统信UOS设备驱动的总体架构,常用的设备驱动开发调试优化手段及在环境搭建和代码编写过程中常见问题处理等。 文章目录 环境搭建如何编译驱动代码编写如何实现同源异构环境搭建 如何编译内核 下载并解压内核源码包,进入源码根目录,内核的编…

JS 异步 Promise、Async、await详解

目录 一、JS里的同步异步 二、Promise 1、状态 2、all()、race()、any() 3、简单案例 4、异步执行案例 5、解决异步嵌套繁琐的场景 三、async和await 1、async返回类型 2、async与await结合使用的简单案例 3、解决异步嵌套问题 4、批量请求优化 一、JS里的同步异步…

【Vue3】Vue3相比Vue2有哪些新特性?全面解析与应用指南

&#x1f9d1;‍&#x1f4bc; 一名茫茫大海中沉浮的小小程序员&#x1f36c; &#x1f449; 你的一键四连 (关注 点赞收藏评论)是我更新的最大动力❤️&#xff01; &#x1f4d1; 目录 &#x1f53d; 前言1️⃣ 响应式系统的改进2️⃣ Composition API的引入3️⃣ 更好的Type…

Vue 事件阻止 e.preventDefault();click.prevent

Vue 事件阻止 Vue 事件阻止 e.preventDefault(); click.prevent修饰符

基于vue3和elementPlus的el-tree组件,实现树结构穿梭框,支持数据回显和懒加载

一、功能 功能描述 数据双向穿梭&#xff1a;支持从左侧向右侧转移数据&#xff0c;以及从右侧向左侧转移数据。懒加载支持&#xff1a;支持懒加载数据&#xff0c;适用于大数据量的情况。多种展示形式&#xff1a;右侧列表支持以树形结构或列表形式展示。全选与反选&#xf…

leetcode-21-合并两个有序链表

题解&#xff1a; 1、初始化哑节点dum 2、 3、 代码&#xff1a; 参考&#xff1a;leetcode-88-合并两个有序数组

WPF怎么通过RestSharp向后端发请求

1.下载RestSharpNuGet包 2.请求类和响应类 public class ApiRequest {/// <summary>/// 请求地址/// </summary>public string Route { get; set; }/// <summary>/// 请求方式/// </summary>public Method Method { get; set; }/// <summary>//…