Advertisement

React相关知识点

阅读量:

一、什么是 React?

  • 官方解释:用于构建用户界面的 JavaScript 库。是一个将数据渲染为 HTML 视图的开源 JavaScript 库。

  • 1.发送请求获取数据

  • 2.处理数据(过滤、整理格式等)

  • 3.操作 DOM 呈现页面

  • 4.babel.min.js 将 jsx 转化为 js;将ES6转化为ES5;(用于将jsx 转为 js)

  • 5.react.development.js react核心库;(引用时需先引用核心库,再引用其它周边库)

  • 6.react-dom.development.js react扩展库;(用于支持react操作DOM)
    (jQuery封装了很多高级操作DOM的方法)

  • 7.jsx – 使编码人员更加简单的创建虚拟DOM(原始创建虚拟DOM太繁琐);jsx创建虚拟DOM的写法是原始创建虚拟DOM写法的语法糖;

二、为什么要学?

  • 原生 JavaScript 操作 DOM 繁琐,效率低;(DOM-API 操作UI)
  • 使用 JavaScript 直接操作 DOM ,浏览器会进行大量的重绘重排
  • 原生 JavaScript 没有组件化编码方案,代码复用率低;

三、React 的特点:

  • 1.采用组件化 模式、声明式编码 ,提高开发效率及组件复用率;
  • 2.在 React Native 中使用 React 语法进行移动端开发
  • 3.使用虚拟 DOM + 优秀的 Diffing 算法 ,尽量减少与真实 DOM 的交互;
    (虚拟DOM优势:不是体现在首次渲染上,而是体现在后期数据量大的时候修改上,虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多属性)
    关于虚拟DOM :1、本质是Object类型的对象(一般对象);2、虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多属性;3、虚拟DOM最终会被React转化为真实DOM,呈现在页面上。)

四、React高效的原因

  • 1.使用虚拟(virtual)DOM, 不总是直接操作页面真实DOM。
  • 2.DOM Diffing算法, 最小化页面重绘。
关于jsx - 全称(JavaScript XML)react 定义的一种类似于 XML 的 JS 扩展语法:JS + XML
  • XML早期用于存储和传输数据(后来的JSON使用更方便 — parse(将JSON字符串快速的解析成js里对应的数组和对象)、stringfy(将js里对应的数组和对象转化成JSON字符串));
  • 本质是 React.createElement(component,props,…children)方法的语法糖;
  • 作用:用来简化创建虚拟DOM;
  • 写法:在这里插入图片描述
  • 注意:它不是字符串,也不是HTML/XML;
  • 注意:它最终产生的是一个JS对象;
  • 标签名任意:HTML标签或其他标签;
  • js语法用 {} 包裹;
jsx语法规则:
  • 1、定义虚拟DOM时,不要写引号;(toLowerCase() 方法将字符串转换为小写,toUpperCase()方法将字符串转换为大写)

  • 2、标签中混入 js 表达式 时,要用 {};

  • 3、样式的类名指定不要用 class,要用 className;

  • 4、内联样式,要用 style={{ color: 'white', fontSize: '50px' }}的形式去写;

  • 5、只有一个根标签;

  • 6、标签必须闭合;

  • 7、标签首字母:
    (1)、若小写字母开头,则将该标签转为 html 中同名元素;若html中无该标签对应的同名元素,则报错;
    (2)、若大写字母开头,react就去渲染对应的组件;若组件没有定义,则报错;
    一定注意区分【js语句(代码)】与【js表达式】:

    • **表达式:**一个表达式会产生一个值,可以放在任何一个需要值的地方;例如:a、a + b、demo(1)、arr.map()、
      function test() {}、

    • 语句(代码) – 控制代码走向的,没有值: if(){}、for(){}、switch(){case:xxx}
      模块与组件、模块化与组件化:
      模块: 1. 理解:向外提供特定功能的js程序, 一般就是一个js文件;
      2. 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂;
      3. 作用:复用js, 简化js的编写, 提高js运行效率;
      **模块化:**当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
      组件: 1. 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等);
      2. 为什么要用组件: 一个界面的功能更复杂;
      3. 作用:复用编码, 简化项目编码, 提高运行效率;
      组件化: 当应用是以多组件的方式实现, 这个应用就是一个组件化的应用;
      react面向组件化编程:
      (1)、**函数式组件:**用函数定义的组件(适用于【简单组件】的定义)
      在这里插入图片描述
      在这里插入图片描述
      (2)、**类式组件:**用类定义的组件(适用于【复杂组件 — 有状态的组件(state) — 组件的状态里存着数据,数据的改变就会驱动着页面的改变;】的定义)
      在这里插入图片描述
      组件实例的三大核心属性:state 、props、refs * state :state的值是对象,可以包含多个key-value的组合;
      组件被称为“状态机”,通过更新组件的state来更新对应的页面显示(重新渲染组件);
      强烈注意: (1)、组件中 render 方法中的 this 为组件实例对象;
      (2)、组件自定义的方法中 this 为 undefined ,如何解决?
      - 强制绑定 this :通过函数对象的 bind()
      - 箭头函数
      (3)、状态数据,不能直接修改或更新;
      在这里插入图片描述
      在这里插入图片描述
      state的简写:
      在这里插入图片描述
      - props 当React元素为用户自定义组件时,它会将JSX所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为“props”。
      每个组件对象都会有props(properties的简写)属性;组件标签的所有属性都保存在props中;
      作用:- 通过标签属性从组件外向组件内传递变化的数据;
      - 注意:组件内部不能修改props数据;
      在这里插入图片描述
      批量传递props(批量传递标签属性):
      在这里插入图片描述
      对props进行限制:(对标签属性进行类型的限制、必要性的限制、指定默认值)
      在这里插入图片描述
      注意:props是只读的
      在这里插入图片描述
      props的简写方式:(直接放在类的里面)注意:static的使用
      在这里插入图片描述
      类式组件中的构造器与props: 类中的构造器开发基本都是省略不写
      在这里插入图片描述
      函数式组件使用props:
      在这里插入图片描述
      脚手架中对 props 进行类型和必要性的限制:
      在这里插入图片描述
      - ref 组件内的标签可以定义ref属性来标识自己
      (1)、字符串形式的ref — 效率问题,官方不推荐使用,后期可能会被废弃
      在这里插入图片描述
      (2)、回调形式的ref
      在这里插入图片描述
      回调ref中回调次数的问题: 如果 ref 回调函数是以【内联函数】的方式定义的,在【更新】过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定(类绑定)函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
      在这里插入图片描述
      (3)、createRef
      在这里插入图片描述

React当中的事件处理:

  1. 通过onXxx属性指定事件处理函数(注意大小写)
    1)、 React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 — 为了更好的兼容性
    2)、 React中的事件是通过事件委托 方式处理的(委托给组件最外层的元素) — 为了高效
    2.通过event.target得到发生事件的DOM元素对象 — 不要过度使用ref,有的时候ref是可以避免的;
    发生事件的元素正好是要操作的元素,就可以不写ref,而是通过 event.target
    在这里插入图片描述
    非受控组件: 页面内所有输入类DOM的值,如input框、checkbox、radio等,是【现用现取】,那么就是非受控组件;
    在这里插入图片描述
    受控组件: 页面内所有输入类DOM,随着用户的输入将输入的值维护到状态里面去,用的时候直接从状态里面取出来;(类似于Vue里的双向数据绑定)— 受控组件能够省略ref,推荐使用;
    在这里插入图片描述
    高阶函数 — 函数的柯里化:
    高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
    (1)、若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数;
    (2)、若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数;
    常见的高阶函数:Promise( new Promise(()=>{}) );setTimeout(()=>{});arr.map()等等;
    函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式; 在这里插入图片描述
    上面的案例不用函数的柯里化实现: 在这里插入图片描述
    演示函数柯里化:
    在这里插入图片描述
    补充:对象相关的知识: 在这里插入图片描述
    组件的生命周期:
    引出生命周期钩子案例
复制代码
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>引出生命周期</title>
    </head>
    
    <body>
    <!-- 准备好一个容器 -->
    <div id='test'></div>
    
    <!-- 引入react核心库 -->
    <script src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js -->
    <script src="../js/babel.min.js"></script>
    
    <script type="text/babel">
        // 创建组件
        class Life extends React.Component {
            // 初始化状态
            state = { opacity: 1 }
    
            // 卸载组件的方法
            death = () => {
                // 卸载组件
                ReactDOM.unmountComponentAtNode(document.getElementById('test'))
            }
    
            // componentDidMount的调用时机:组件挂载页面之后调用
            componentDidMount() {
                console.log('@'); //只打印一次
                // 将定时器挂在组件实例对象自身上
                this.timer = setInterval(() => {
                    // 获取原状态
                    let { opacity } = this.state
                    // 减小0.1
                    opacity -= 0.1
                    if (opacity <= 0.1) { opacity = 1 }
                    // 设置新的透明度
                    this.setState({ opacity })
                }, 200)
            }
            // 组件将要卸载
            componentWillUnmount() {
                // 清除定时器
                clearInterval(this.timer)
            }
    
            // render的调用时机:初始化渲染、状态更新之后
            render() {
                console.log('render');
                return (
                    <div>
                        <h1 style={{ opacity: this.state.opacity }}>React学不会了怎么办?</h1>
                        <button onClick={this.death}>不活了</button>
                    </div>
                )
            }
        }
        // 渲染组件
        ReactDOM.render(<Life />, document.getElementById('test'))
    </script>
    </body>
    
    </html>

在这里插入图片描述
生命周期的三个阶段(旧):
1. 初始化阶段: 由ReactDOM.render()触发—初次渲染
1. constructor()
2. componentWillMount()
3. render()
4. componentDidMount() ===》常用,一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息等;
2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
1. shouldComponentUpdate() 控制组件更新的阀门
2. componentWillUpdate()
3. render()
4. componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1. componentWillUnmount() ----》常用,一般在这个钩子中,做一些收尾的事,例如:关闭定时器、取消消息订阅等;
在这里插入图片描述
forceUpdate() 不想让状态改变的情况下强制更新

复制代码
     <button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
      // 强制更新按钮的回调
            force = () => {
                this.forceUpdate()
            }

在这里插入图片描述
在新版本里,componentWillMount、componentWillReceiveProps、componentWillUpdate三个钩子前面需要加上 UNSAFE_
旧的生命周期废弃了:componentWillMount、componentWillReceiveProps、componentWillUpdate;新的生命周期增加了:getDerivedStateFromProps(得到一个派生的状态)、getSnapshotBeforeUpdate(获取更新前的快照)
在这里插入图片描述
componentDidUpdate函数可以接收三个参数:
在这里插入图片描述
getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()。
在这里插入图片描述

生命周期的三个阶段(新):
1. 初始化阶段: 由ReactDOM.render()触发—初次渲染

  • constructor()
  • getDerivedStateFromProps
  • render()
  • componentDidMount()
  1. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
  • getDerivedStateFromProps
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate
  • componentDidUpdate()
  1. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
  • componentWillUnmount()
    重要的钩子:

  • render:初始化渲染或更新渲染调用;

  • componentDidMount:开启监听, 发送ajax请求;

  • componentWillUnmount:做一些收尾工作, 如: 清理定时器;
    即将废弃的勾子: 现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。
    1. componentWillMount
    2. componentWillReceiveProps
    3. componentWillUpdate
    getSnapshotBeforeUpdate的使用场景:— 动态新闻列表展示,鼠标滚动到相关位置让它停止滚动

复制代码
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>getSnapshotBeforeUpdate的使用场景</title>
    <style>
        .list {
            width: 200px;
            height: 150px;
            background-color: skyblue;
            overflow: auto;
        }
    
        .news {
            height: 30px;
        }
    </style>
    </head>
    
    <body>
    <!-- 准备好一个容器 -->
    <div id='test'></div>
    
    <!-- 引入react核心库 -->
    <script src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js -->
    <script src="../js/babel.min.js"></script>
    
    <script type="text/babel">
        class NewsList extends React.Component {
            state = { newsArr: [] }
    
            componentDidMount() {
                setInterval(() => {
                    // 获取原状态
                    const { newsArr } = this.state
                    // 模拟一条新闻
                    const news = '新闻' + (newsArr.length + 1)
                    // 更新状态
                    this.setState({ newsArr: [news, ...newsArr] })
                }, 1000)
            }
    
            getSnapshotBeforeUpdate() {
                // 在新的新闻来到之前,获取到当前内容区的高度
                return this.refs.list.scrollHeight
            }
            componentDidUpdate(preProps, preState, height) {
                // 新的新闻出现之后的高度-之前的高度
                this.refs.list.scrollTop += this.refs.list.scrollHeight - height
            }
    
            render() {
                return (
                    <div className="list" ref="list">
                        {/*map返回的是数组,map里的return后面跟的是数组中每一项的值*/}
                        {this.state.newsArr.map((n, index) => {
                            return <div key={index} className="news">{n}</div>
                        })}
                    </div>
                )
            }
        }
        ReactDOM.render(<NewsList />, document.getElementById('test'))
    </script>
    </body>
    
    </html>

React脚手架:

  1. xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目;
  • 包含了所有需要的配置(语法检查、jsx编译、devServer…);
  • 下载好了所有相关的依赖;
  • 可以直接运行一个简单效果;
  1. react提供了一个用于创建react项目的脚手架库: create-react-app;
  2. 项目的整体技术架构为: react + webpack + es6 + eslint;
  3. 使用脚手架开发的项目的特点: 模块化, 组件化, 工程化;
    创建项目并启动: 第一步,全局安装:npm i -g create-react-app
    第二步,切换到想创项目的目录,使用命令:create-react-app hello-react
    第三步,进入项目文件夹:cd hello-react
    第四步,启动项目:npm start
    react脚手架项目结构:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    样式的模块化 ---- 防止类名相同产生冲突:
    在这里插入图片描述
    功能界面的组件化编码流程(通用):
    1. 拆分组件: 拆分界面,抽取组件;
    2. 实现静态组件: 使用组件实现静态页面效果;
    3. 实现动态组件:
    3.1 动态显示初始化数据
    3.1.1 数据类型
    3.1.2 数据名称
    3.1.2 保存在哪个组件?
    3.2 交互(从绑定事件监听开始)
    五、todoList案例相关知识点 * 1.拆分组件、实现静态组件,注意:className style 的写法;
    • 2.动态初始化列表,如何确定将数据放在哪个组件的state中?
  • 某个组件使用:放在其自身的state中;

  • 某些组件使用:放在它们共同的父组件state中,(官方称此操作为:状态提升

    • 3.关于父子之间通信:
  • 【父组件】给【子组件】传递数据:通过 props 传递;
    在这里插入图片描述

  • 【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数;
    在这里插入图片描述
    在这里插入图片描述

    • 4.注意 defaultChecked(只有在第一次指定时起作用) 和 checked 的区别,类似的还有:defaultValue 和 value;
      checked 必须配合 onChange 来使用,要不然就写死了;
      在这里插入图片描述

    • 5.状态在哪里,操作状态的方法就在哪里;

    • 6.鼠标移入移出效果
      在这里插入图片描述
      常用的 ajax 请求库: - jQuery 比较重,如要需要另外引入不建议使用;
      - axios 轻量级,建议使用;

  • 封装 XmlHttpRequest 对象的 ajax;

  • promise 风格;

  • 可以用在浏览器端和node服务器端;
    脚手架配置代理:
    在这里插入图片描述
    方法一:在 package.json里配置 — 只能配置一个
    在这里插入图片描述
    方法二:配多个 ---- src目录下新建 setupProxy.js文件(该文件只要改了就需要重启脚手架),react会将 setupProxy.js 文件加到 webpack的配置里面,webpack是基于node环境的,用的是commonjs;
    六、经典面试题:
    (1)react/vue 中的 key 有什么作用?(key 的内部原理是什么?)
    (2)为什么遍历列表时,key 最好不要用 index?
    1.虚拟 DOM 中 key 的作用:
    (1)简单的说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用。
    (2)详细的说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,
    随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
    a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
    若虚拟DOM中内容没变,直接使用之前的真实DOM;
    若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM;
    b. 旧虚拟DOM中未找到与新虚拟DOM相同的key:
    根据数据创建新的真实DOM,随后渲染到页面;
    2.用index作为key可能会引发的问题: (建议使用唯一标识id作为key)
    a.若对数据进行:逆序添加逆序删除 等破坏顺序的操作:
    会产生没有必要的真实DOM更新 》 页面效果没问题,但效率低。
    b.如果结构中还包含输入类的DOM:
    会产生错误的DOM更细》界面有问题
    c.注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,
    仅用于渲染列表用于展示,使用index作为key是没有问题的。
    在这里插入图片描述

github搜索案例

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
七、消息订阅与发布

  1. 工具库: PubSubJS

  2. 下载: npm install pubsub-js --save

  3. 使用:
    1. import PubSub from ‘pubsub-js’ //引入
    2. PubSub.subscribe(‘delete’, function(data){ }); //订阅
    3. PubSub.publish(‘delete’, data) //发布消息
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    fatch发送请求: (不用借助 xhr 发送ajax请求 —用 fatch 发送请求,jQuery 和 axios 都是对 xhr 的封装);
    fatch 不是第三方库,而是 windows 内置的,可以不用下载直接使用;也是 promise 风格的;
    文档: 1. https://github.github.io/fetch/
    2. https://segmentfault.com/a/1190000003810652
    特点:1. fetch: 原生函数,不再使用XmlHttpRequest对象提交ajax请求;
    2. 老版本浏览器可能不支持;
    相关API:

  4. GET请求

复制代码
    fetch(url).then(function(response) {
    return response.json()
      }).then(function(data) {
    console.log(data)
      }).catch(function(e) {
    console.log(e)
      });
复制代码
    2)	POST请求
复制代码
     fetch(url, {
    method: "POST",
    body: JSON.stringify(data),
      }).then(function(data) {
    console.log(data)
      }).catch(function(e) {
    console.log(e)
      })
复制代码
    import React, { Component } from 'react'
    // import axios from 'axios'
    import PubSub from 'pubsub-js'
    
    export default class Search extends Component {
    search = async () => {
        // 获取用户的输入(连续解构赋值+重命名---将 value 重命名为 keyWord )
        const { keyWordElement: { value: keyWord } } = this
        console.log(keyWord);
    
        // 发送请求前通知List更新状态
        PubSub.publish('atguigu', { isFirst: false, isLoading: true })
    
        // 发送网络请求 --- 使用axios发送
        // axios.get(`http://localhost:3000/api1/search/users?q=${keyWord}`).then(
        //     response => {
        //         console.log('成功了', response.data);
        //         // 请求成功后通知List更新状态
        //         PubSub.publish('atguigu', { isLoading: false, users: response.data.items })
        //     },
        //     error => {
        //         console.log('失败了', error);
        //         // 请求失败后通知List更新状态
        //         PubSub.publish('atguigu', { isLoading: false, err: error.message })
        //     }
        // )
    
        // 发送网络请求 --- 使用fetch发送(【关注分离】的思想)--- 未优化版本
        //fetch(`api1/search/users?q=${keyWord}`).then(
        //     // 先与服务器建立联系
        //     response => {
        //         console.log('联系服务器成功了');
        //         return response.json()
        //     },
        //     error => {
        //         console.log('联系服务器失败了', error);
        //     }
        // ).then(
        //     response => {
        //         console.log('获取数据成功了', response);
        //     },
        //     error => {
        //         console.log('获取数据失败了', error);
        //     }
        //)
    
        // 发送网络请求 --- 使用fetch发送(【关注分离】的思想)--- 优化版本
        try {
            const response = await fetch(`api1/search/users?q=${keyWord}`)
            const data = await response.json()
            console.log(data);
            PubSub.publish('atguigu', { isLoading: false, users: data.items })
        } catch (error) {
            console.log('请求出错', error);
            PubSub.publish('atguigu', { isLoading: false, err: error.message })
        }
    }
    render() {
        return (
            <section className="jumbotron">
                <h3 className="jumbotron-heading">Search Github Users</h3>
                <div>
                    <input
                        ref={c => { this.keyWordElement = c }}
                        type="text"
                        placeholder="enter the name you search" />&nbsp;
                    <button onClick={this.search}>Search</button>
                </div>
            </section>
        )
    }
    }

八、github搜索案例相关知识点
在这里插入图片描述

对SPA应用的理解

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

路由的理解

1. 什么是路由?

  1. 一个路由就是一个映射关系(key:value)

  2. key为路径, value可能是 function 或 component
    2. 路由分类

  3. 后端路由:

  4. 理解: value是function, 用来处理客户端提交的请求。

  5. 注册路由: router.get(path, function(req, res))

  6. 工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据

  7. 前端路由:BOM — history

  8. 浏览器端路由,value是component,用于展示页面内容。

  9. 注册路由:

  10. 工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件

前端路由的基石

复制代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>前端路由的基石_history</title>
    </head>
    <body>
    	<a href="http://www.atguigu.com" onclick="return push('/test1') ">push test1</a><br><br>
    	<button onClick="push('/test2')">push test2</button><br><br>
    	<button onClick="replace('/test3')">replace test3</button><br><br>
    	<button onClick="back()"><= 回退</button>
    	<button onClick="forword()">前进 =></button>
    
    	<script type="text/javascript" src="https://cdn.bootcss.com/history/4.7.2/history.js"></script>
    	<script type="text/javascript">
    		// let history = History.createBrowserHistory() //方法一,直接使用H5推出的history身上的API
    		let history = History.createHashHistory() //方法二,hash值(锚点)
    
    		function push (path) {
    			history.push(path)
    			return false
    		}
    
    		function replace (path) {
    			history.replace(path)
    		}
    
    		function back() {
    			history.goBack()
    		}
    
    		function forword() {
    			history.goForward()
    		}
    
    		history.listen((location) => {
    			console.log('请求路由路径变化了', location)
    		})
    	</script>
    </body>
    </html>

react-router-dom的理解

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

react-router-dom相关API

内置组件:
1.<BrowserRouter>
2. <HashRouter>
3. <Route>
4. <Redirect>
5. <Link>
6. <NavLink>
7. <Switch>

九、路由的基本使用
- npm i react-router-dom
- import { Link, BrowserRouter } from ‘react-router-dom’
- 明确好界面中的导航区、展示区;
- 导航区的a标签改为Link标签;
<Link to="/xxxx">Demo</Link>
- 展示区写Route标签进行路径的匹配;

- 的最外侧包裹了一个或
在这里插入图片描述
十、路由组件与一般组件
- 1.写法不同:

  • 一般组件:
  • 路由组件:
    - 2.存放位置不同:
  • 一般组件:components
  • 路由组件:pages
    - 3.接收到的 props 不同:
  • 一般组件:写组件标签时传递了什么,就能收到什么;
  • 路由组件:接收到如下三个固定的属性(V6版本没有这些固定属性,和一般组件一样是一个空对象)在这里插入图片描述
    十一、NavLink 与封装 NavLink
    - 1.NavLink 可以实现路由链接的高亮(是Bootstrap自带的效果),另外可以通过 activeClassName 指定样式名;(NavLink 实现点谁就给谁追加一个 active 的类名) -
    在这里插入图片描述
    - 2.标签体内容是一个特殊的标签属性;
    - 3.通过 this.props.children可以获取组件标签体内容;
    - 4.NavLink 的封装:
    在这里插入图片描述
    十二、Switch 的使用(V6版本使用routes即可)
    - 1.通常情况下,path 和 component 是一一对应关系;
    - 2.Switch 可以提高路由匹配效率(单一匹配);
    十三、解决样式丢失问题的3中办法 — 路由路径是多级解结构时,刷新页面,样式容易丢失。
    public 文件夹中的 index.html 文件
    在这里插入图片描述
    十四、路由的严格匹配与模糊匹配
    - 1.默认使用的是模糊匹配,(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致);
    - 2.开启严格匹配: ;
    - 3.严格匹配不要随便开启,需要再开,有些时候开启会导致无法匹配二级路由;
    十五、 Redirect 的使用(V6版本移除了 Redirect ,引入了 Navigate)
    - 1.一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到 Redirect 指定的路由;
    - 2.具体编码:
    在这里插入图片描述
    在这里插入图片描述
    十六、嵌套路由
    - 1.注册子路由时要写上父路由的path值;
    - 2.路由的匹配是按照注册路由的顺序进行的;
    在这里插入图片描述
    在这里插入图片描述

十七、向路由组件传递参数
ajax参数 : 1.query
2.params
3.body: urlencoded 如 key=value&key=value 这种多组key、value用 & 连接的形式;
json
- 1.params 参数

  • 路由链接(携带参数):<Link to={/demo/test/tom/18}>{详情}</Link>
  • 注册路由(声明接收):
  • 接收参数: const {name,age } = this.props.match.params
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    - 2.search参数
  • 路由链接(携带参数):<Link to={/demo/test?name=tom&age=18}>详情</Link>
  • 注册路由(无需声明,正常注册即可): <Route path="/demo/test" component={Test} />
  • 接收参数:this.props.location.search
  • 备注:获取到的 search 是 urlencoded 编码字符串,需要借助 querystring 库进行解析
    *react有一个 qs库,里面的 stringify、parse方法,可以对字符串和对象进行相关处理;(V5版本无需下载,可直接引用;如遇qs已弃用情况,npm i querystring-es3 import qs from 'querystring-es3 ’ 或 import {qs} from ‘url-parse’ )
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述 在这里插入图片描述 在这里插入图片描述
    - 3.state 参数
  • 路由链接(携带参数):<Link to={{ pathname: ‘/demo/test’, state: { name: ‘Tom’, title: age:19} }}>详情
  • 注册路由(无需声明,正常注册即可): <Route path="/demo/test" component={Test} />
  • 接收参数:this.props.location.state
  • 备注:刷新也可以保留住参数
    * 在这里插入图片描述

注意:页面刷新,通过路由传递的参数不会丢 — 原因:

BrowserRouter 操作浏览器里的 history (浏览器的历史记录叫 history ,history . ),location 是 history 里的一个属性,

push 与 replace模式:默认情况下用的是 push(压栈的操作),开启 replace — replace={true}

编程式导航:通过 js 跳转,如:location、href、this.$router.push(“xxx”)

十八、编程式路由导航
- 借助 this.props.history 对象上的 API 操作路由 跳转、前进、后退

  • this.props.history.push()
  • this.props.history.replace()
  • this.props.history.goBack()
  • this.props.history.goForward()
  • this.props.history.go()
    十九、withRouter 的使用 (withRouter 是一个函数,不是一个组件,所以import时首字母要小写)
    在这里插入图片描述
    二十、BrowserRouter 和 HashRouter 的区别:
    - 1.底层原理不一样:
  • BrowserRouter 使用的是 H5 的 history API ,不兼容 IE9及以下版本;
  • HashRouter 使用的是 URL 的哈希值;
    - 2.path 表现形式不一样:
  • BrowserRouter 的路径中没有 # ,例如:localhost:3000/demo/test
  • HashRouter 的路径中包含 # ,例如:localhost:3000/#/demo/test
    - 3.刷新后对路由 state 参数的影响:
  • BrowserRouter 没有任何影响,因为 state 保存在 history 对象中;
  • HashRouter 刷新后会导致路由 state 参数的丢失;
    - 4.备注:HashRouter 可以用于解决一些路径错误相关的问题;
    二十一、antd 的按需引入 + 自定主题 — 具体参考官网介绍 (antd 特别适用于成形的后台管理系统)
    - 1.安装依赖:yarn add react-app-rewired customize-cra babel-plugin-import less less-loader
    - 2.修改 package.json
    -
    - 3.根目录下创建 config-overrides.js
    // 配置具体的修改规则
    const { override, fixBabelImports, addLessLoader } = require(‘customize-cra’);
    module.exports = override(
    fixBabelImports(‘import’, {
    libraryName: ‘antd’,
    libraryDirectory: ‘es’,
    style: true,
    }),
    addLessLoader({
    lessOption: {
    javascriptEnabled: true,
    modifyVars: { ‘@primary-color’: ‘orange’ },
    }
    }),
    );
    在这里插入图片描述
    - 4.备注:不用在组件里亲自引入样式了,即:import ‘antd/dist/antd.css’ 应该删掉

二十二、redux

1、 redux是什么?

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

2、什么情况下需要使用redux?

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

3、学习文档:

  1. 英文文档: https://redux.js.org/
  2. 中文文档: http://www.redux.org.cn/
  3. Github: https://github.com/reactjs/redux

4、redux工作流程:

在这里插入图片描述

redux的三个核心概念

1、action(动作对象):

  1. 动作的对象;

  2. 包含2个属性:
     type:标识属性, 值为字符串, 唯一, 必要属性
     data:数据属性, 值类型任意, 可选属性

  3. 例子:{ type: ‘ADD_STUDENT’,data:{name: ‘tom’,age:18} }

2、reducer:

复制代码
    /*
    count_reducer.js
    1、该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数;
    2、reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
    */
    
    // 初始化状态方法3、const initSate = 0(比较清晰)
    const initSate = 0
    export default function count_reducer(preState = initSate, action) {
    console.log(preState, action); //0 {type: '@@redux/INITp.w.c.r.o.e'}
    // 初始化状态方法2、用if判断
    //if (preState === undefined) preState = 0
    // 从action对象中获取:type, data 
    const { type, data } = action
    // 根据type决定如何加工数据
    switch (type) {
        case "increment": //如果是“加”
            return preState + data
        case "decrement": //如果是“减”
            return preState - data
        //break;  //已经return了,就不需要break了
        default:
            // 初始化状态方法1、直接return 0(可读性不强)
            return preState
    }
    }
  1. 用于初始化状态(没有 previousState(之前的值) 时是 undefined)、加工状态。
  2. 加工时,根据旧的state和action, 产生新的state的纯函数。

3、store:

复制代码
    /*
    该文件专门用于暴露一个store对象,整个应用只有一个store对象
    */
    
    // 引入createStore,专门用于创建redux中最为核心的store对象
    import { createStore } from 'redux'
    // 引入为count组件服务的reducer
    import countReducer from './count_reducer'
    
    // 暴露store
    export default createStore(countReducer)
  1. 将state、action、reducer联系在一起的对象;

  2. 如何得到此对象?
    1. import {createStore} from ‘redux’
    2. import reducer from ‘./reducers’
    3. const store = createStore(reducer)

  3. 此对象的功能?
    1. getState(): 得到state

复制代码
      <h1>当前求和为:{store.getState()}</h1>
  1. dispatch(action): 分发action, 触发reducer调用, 产生新的state
复制代码
     // 加法
    increment = () => {
        const { value } = this.selectNumber
        store.dispatch({ type: 'increment', data: value * 1 })
    }
  1. subscribe(listener): 注册监听, 当产生了新的state时, 自动调用;
    注意 :redux里状态的更改是不会引起页面的更新的;
复制代码
      componentDidMount() {//生命周期钩子里的this都是组件的实例对象
        // 监测redux中状态的变化,只要变化,就调用render
        store.subscribe(() => {
            this.setState({}) //只要改变状态,react就会调render;这种写法可以解决问题,但不是完美的解决办法
        })
    }
复制代码
    //推荐写法:在入口文件 index.js 中
    import React from "react";
    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'))
    })

注意:如果用了react-redux就不用自己监测,容器组件自己具有监测的能力;

复制代码
    import React from "react";
    import ReactDOM from "react-dom";
    import App from "./App";
    //如果用了react-redux就不用自己监测,容器组件自己具有监测的能力;
    ReactDOM.render(<App />, document.getElementById('root'))

redux的核心API:

7.3.1. createstore()
作用:创建包含指定reducer的store对象
7.3.2. store对象
1. 作用: redux库最核心的管理对象
2. 它内部维护着:

  1. state
  2. reducer
    3. 核心方法:
  3. getState()
  4. dispatch(action)
  5. subscribe(listener)
    4. 具体编码:
  6. store.getState()
  7. store.dispatch({type:‘INCREMENT’, number})
  8. store.subscribe(render)
    7.3.3. applyMiddleware()
    作用:应用上基于redux的中间件(插件库)
    7.3.4. combineReducers()
    作用:合并多个reducer函数

求和案例精简版笔记:

在这里插入图片描述

求和案例_redux完整版笔记

在这里插入图片描述

求和案例_异步 action 版:

action
同步 action ,就是指action的值为Object类型的一般对象;
1、Object ---- 同步(是一个一般对象(plain Object))
异步 action ,就是指action的值为函数;异步action中一般都会调用同步action;异步 action不是必须要用的;
2、function ---- 异步(是一个函数) 在这里插入图片描述

7.5. redux异步编程:

7.5.1理解:
1. redux默认是不能进行异步处理的,
2. 某些时候应用中需要在redux中执行异步任务(ajax, 定时器)
7.5.2. 使用异步中间件
npm install --save redux-thunk

复制代码
    /*
    该文件专门用于暴露一个store对象,整个应用只有一个store对象
     npm install --save redux-thunk
    */
    
    // 引入createStore,专门用于创建redux中最为核心的store对象
    import { createStore, applyMiddleware } from 'redux'
    // 引入为count组件服务的reducer
    import countReducer from './count_reducer'
    // 引入 redux-thunk,用于支持异步action
    import thunk from 'redux-thunk'
    
    // 暴露store
    export default createStore(countReducer, applyMiddleware(thunk))

7.6. react-redux(faceBook出品,区别于Redux):

  1. 一个react插件库
  2. 专门用来简化react应用中使用redux

7.6.2. react-Redux将所有组件分成两大类

在这里插入图片描述
  1. UI组件
    1. 只负责 UI 的呈现,不带有任何业务逻辑
    2. 通过props接收数据(一般数据和函数)
    3. 不使用任何 Redux 的 API
    4. 一般保存在components文件夹下

  2. 容器组件
    1. 负责管理数据和业务逻辑,不负责UI的呈现
    2. 使用 Redux 的 API
    3. 一般保存在containers文件夹下

7.6.3. 相关API

  1. Provider:让所有组件都可以得到state数据,不用自己在容器组件中一个一个的传递store了;
复制代码
    import React from "react";
    import ReactDOM from "react-dom";
    import App from "./App";
    import store from './redux/store'
    import { Provider } from 'react-redux'
    
    ReactDOM.render(
    //Provider:让所有组件都可以得到state数据,不用自己在容器组件中一个一个的传递store了;
     //  此处需要用Provider包裹App,目的是让App所有的后代容器组件都能接收到store
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('root')
    )
  1. connect:用于包装 UI 组件生成容器组件
复制代码
    import { connect } from 'react-redux'
      connect(
    mapStateToprops,
    mapDispatchToProps
      )(Counter)
  1. mapStateToprops:将外部的数据(即state对象)转换为UI组件的标签属性
复制代码
    const mapStateToprops = function (state) {
      return {
    value: state
      }
    }
  1. mapDispatchToProps:将分发action的函数转换为UI组件的标签属性
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

mapDispatchToProps可以写成两种形式:函数、简写成一个对象

复制代码
    export default connect(
    state => ({ count: state }),
    
    // mapDispatchToProps的一般写法(写成一个函数)
    // dispatch => ({ //返回一个对象,对象里面是一组key value
    //     jia: data => {
    //         // 通知redux执行加法
    //         dispatch(createIncrementAction(data))
    //     },
    //     jian: (data) => {
    //         dispatch(createDecrementAction(data))
    //     },
    //     jiaAsync: (data, time) => {
    //         dispatch(createIncrementAyncAction(data, time))
    //     }
    // })
    // mapDispatchToProps的一般简写(可以简写成一个对象)
    {
        jia: createIncrementAction,
        jian: createDecrementAction,
        jiaAsync: createIncrementAyncAction,
    }
    )(CountUI)
在这里插入图片描述
复制代码
    //containers文件
    // 引入Count的UI组件
    import CountUI from "../../components/Count";
    // 引入connect用于连接UI组件与redux
    import { connect } from 'react-redux'
    // 引入action
    import { createIncrementAction, createDecrementAction, createIncrementAyncAction } from '../../redux/count_action'
    
    // mapStateToProps函数的返回值作为状态传递给了UI组件
    // mapStateToProps函数返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value;(把状态带过去)
    /*
    1、mapStateToProps函数返回一个对象;
    2、返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value;
    3、mapStateToProps用于传递状态;
    */
    function mapStateToProps(state) { //state为redux中保存的状态(由redux调用,不用自己引入)
    return { count: state }
    }
    
    // mapDispatchToProps函数返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value;(把操作状态的方法带过去)
    /*
    1、mapDispatchToProps函数返回一个对象;
    2、返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value;
    3、mapDispatchToProps用于传递操作状态的方法;
    */
    function mapDispatchToProps(dispatch) {
    return { //返回一个对象,对象里面是一组key value
        jia: data => {
            // 通知redux执行加法
            dispatch(createIncrementAction(data))
        },
        jian: (data) => {
            dispatch(createDecrementAction(data))
        },
        jiaAsync: (data, time) => {
            dispatch(createIncrementAyncAction(data, time))
        }
    }
    }
    
    // connect是一个函数,connect函数调用的返回值依然是一个函数
    //使用 connect()() 创建并暴露一个Count的容器组件
    export default connect(mapStateToProps, mapDispatchToProps)(CountUI)

数据共享版

在这里插入图片描述

combineReducers进行合并多个reducer:

复制代码
    /*
    该文件专门用于暴露一个store对象,整个应用只有一个store对象
    */
    
    // 引入createStore,专门用于创建redux中最为核心的store对象
    import { createStore, applyMiddleware, combineReducers } from 'redux'
    // 引入为count组件服务的reducer
    import countReducer from './reducers/count'
    // 
    import personReducer from './reducers/person'
    // 引入 redux-thunk,用于支持异步action
    import thunk from 'redux-thunk'
    
    // 汇总所有的 reducer 变为一个总的 reducer;
    const allReducer = combineReducers({
    he: countReducer,
    rens: personReducer,
    })
    
    // 暴露store
    export default createStore(allReducer, applyMiddleware(thunk))

7.7. 使用上redux调试工具

7.7.1. 安装chrome浏览器插件
在这里插入图片描述
7.7.2. 下载工具依赖包:
npm install --save-dev redux-devtools-extension
在这里插入图片描述
7.7.3. 在 store 文件中进行相关配置,该工具才能生效:

复制代码
    /*
    该文件专门用于暴露一个store对象,整个应用只有一个store对象
    */
    
    // 引入createStore,专门用于创建redux中最为核心的store对象
    import { createStore, applyMiddleware, combineReducers } from 'redux'
    // 引入为count组件服务的reducer
    import countReducer from './reducers/count'
    // 
    import personReducer from './reducers/person'
    // 引入 redux-thunk,用于支持异步action
    import thunk from 'redux-thunk'
    
    // 引入redux-devtools-extension
    import { composeWithDevTools } from 'redux-devtools-extension'
    
    // 汇总所有的 reducer 变为一个总的 reducer;
    const allReducer = combineReducers({
    he: countReducer,
    rens: personReducer,
    })
    
    // 暴露store 
    //将composeWithDevTools 作为第二个参数传进去,将原来的第二个参数包裹;
    export default createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))
在这里插入图片描述
复制代码
    /*
    该文件用于汇总所有的reducer为一个总的reducer
    */
    
    // 引入combineReducers,用于汇总多个reducer
    import { combineReducers } from 'redux'
    // 引入为Count组件服务的reducer
    import count from './count'
    // 引入为Person组件服务的reducer
    import person from './person'
    
    // 汇总所有的 reducer 变为一个总的 reducer;
    export default combineReducers({
    count: count,
    person: person,
    })
复制代码
    /*
    该文件专门用于暴露一个store对象,整个应用只有一个store对象
    */
    
    // 引入createStore,专门用于创建redux中最为核心的store对象
    import { createStore, applyMiddleware } from 'redux'
    // 引入 redux-thunk,用于支持异步action
    import thunk from 'redux-thunk'
    // 引入redux-devtools-extension
    import { composeWithDevTools } from 'redux-devtools-extension'
    // 引入汇总之后的reducer
    import reducer from './reducers'
    
    // 暴露store
    export default createStore(reducer, composeWithDevTools(applyMiddleware(thunk)))

项目打包部署到服务器:

npm run build
npm i serve -g 全局安装 serve ;直接用 serve build 命令快速开启一台服务器;

react扩展知识

1. setState: setState更新状态的2种写法

复制代码
    (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 }, () => {
            console.log('9行的输出', this.state.count); //1
        })
        // react状态的更新是异步的
        console.log('12行的输出', this.state.count); //慢setState一步  0
    }
    
    render() {
        return (
            <div>
                <h1>当前求和为:{this.state.count}</h1>
                <button onClick={this.add}>点我+1</button>
            </div>
        )
    }
    }
复制代码
    (2). setState(updater, [callback])------函数式的setState
        1.updater为返回stateChange对象的函数。
        2.updater可以接收到state和props。
        4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
复制代码
    import React, { Component } from 'react'
    
    export default class Demo extends Component {
    state = { count: 0 }
    
    add = () => {
        // 函数式的setState
         this.setState((state, props) => { //setState写成一个函数可以拿到 state 和 props;
             console.log(state, props); // {count: 0}  {x: 101}
             return { count: state.count + 1 }
         })
    
        // 省略 props后精简写法
       // this.setState(state => ({ count: state.count + 1 }))
    }
    render() {
        return (
            <div>
                <h1>当前求和为:{this.state.count}</h1>
                <button onClick={this.add}>点我+1</button>
            </div>
        )
    }
    }

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

2. lazyLoad

路由懒加载

( 浏览器设置 —》清除浏览数据 – 》清除缓存 )

复制代码
    	//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
    	const Login = lazy(()=>import('@/pages/Login'))
    	
    	//2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
    	<Suspense fallback={<h1>loading.....</h1>}>
        <Switch>
            <Route path="/xxx" component={Xxxx}/>
            <Redirect to="/login"/>
        </Switch>
    </Suspense>
复制代码
    import React, { Component, lazy, Suspense } from 'react' // 引入 lazy 函数和 Suspense 
    
    const About = lazy(() => { return import('./About') })
    const Home = lazy(() => { return import('./Home') })
复制代码
    <div className="panel-body">
    {/* 用 Suspense 包裹注册路由,fallback 是路由组件没有加载出来时展示的内容 */}
    <Suspense fallback={<h1>Loading...加载中</h1>}>
        {/* 注册路由 */}
     	<Route path="/about" component={About} />
    	<Route path="/home" component={Home} />
    </Suspense>
    /div>
在这里插入图片描述

3. Hooks

1. React Hook/Hooks是什么?

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

2. 三个常用的Hook:

(1). State Hook: React.useState()
(2). Effect Hook: React.useEffect()
(3). Ref Hook: React.useRef()

3. State Hook

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

复制代码
    import React from 'react'
    
    // 函数式组件
    function Demo() {
    console.log('Demo');
    
    // a是一个数组,里面包含了【状态(count)】和【操作状态的方法(setCount)】
    // const a = React.useState()
    const [count, setCount] = React.useState(0) //数组的解构赋值
    console.log(count, setCount);
    const [name, setName] = React.useState('Tom')
    
    //加的回调
    function add() {
        console.log('@@@@');
        // setCount(count + 1) //第一种写法
        setCount((count) => { return count + 1 }) //第二种写法
    }
    function changeName() {
        setName('小红')
    }
    
    return (
        <div>
            <h1>当前求和为:{count}</h1>
            <h1>当前名字为:{name}</h1>
            <button onClick={add}>点我+1</button>
            <button onClick={changeName}>点我改名</button>
        </div>
    )
    }
    
    export default Demo

4. Effect Hook

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

(4). 可以把 useEffect Hook 看做如下三个函数的组合
componentDidMount()
componentDidUpdate()
componentWillUnmount()

5. Ref Hook

(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
(2). 语法: const refContainer = useRef()
(3). 作用:保存标签对象,功能与React.createRef()一样

复制代码
    import React from 'react'
    import ReactDOM from 'react-dom'
    
    // 类式组件
    // class Demo extends React.Component {
    //     state = { count: 0 }
    
    //     myRef = React.createRef()
    
    //     add = () => {
    //         // 对象式的setState
    //         // const { count } = this.state
    //         // this.setState({ count: count + 1 }, () => {
    //         //     console.log('9行的输出', this.state.count);
    //         // })
    //         // // react状态的更新是异步的
    //         // console.log('12行的输出', this.state.count); //慢setState一步
    
    //         // 省略 props后精简写法
    //         this.setState(state => ({ count: state.count + 1 }))
    //     }
    //     unmount = () => {
    //         ReactDOM.unmountComponentAtNode(document.getElementById('root'))
    //     }
    //     show = () => {
    //         alert(this.myRef.current.value)
    //     }
    
    //     componentDidMount() {
    //         // 此处的 timer 挂在实例自身
    //         this.timer = setInterval(() => {
    //             this.setState(state => ({ count: state.count + 1 }))
    //         }, 1000)
    //     }
    //     componentWillUnmount() {
    //         clearTimeout(this.timer)
    //     }
    
    //     render() {
    //         return (
    //             <div>
    //                 <input type="text" ref={this.myRef} />
    //                 <h1>当前求和为:{this.state.count}</h1>
    //                 <button onClick={this.add}>点我+1</button>
    //                 <button onClick={this.unmount}>卸载组件</button>
    //                 <button onClick={this.show}>点击提示数据</button>
    //             </div>
    //         )
    //     }
    // }
    
    // 函数式组件
    function Demo() {
    console.log('Demo');
    
    // a是一个数组,里面包含了【状态(count)】和【操作状态的方法(setCount)】
    // const a = React.useState()
    const [count, setCount] = React.useState(0) //数组的解构赋值
    console.log(count, setCount);
    const [name, setName] = React.useState('Tom')
    
    // React.useEffect(() => {
    //     console.log('@');
    // }, [count]) //空数组里面写谁就监测谁,只写空数组就谁都不监测
    
    /*
    React.useEffect里面的函数相当于 componentDidMount 和 componentDidUpdate,主要看其第二个参数[]怎么配置;
    React.useEffect里面的函数返回的函数相当于 componentWillUnmount;
    */
    React.useEffect(() => {
        // 定义一个变量 timer
        let timer = setInterval(() => {
            setCount(count => count + 1)
        }, 1000)
        return () => {
            // 作用域查找找到 timer
            clearInterval(timer)
        }
    }, [])
    const myRef = React.useRef()
    
    //加的回调
    function add() {
        console.log('加');
        // setCount(count + 1) //第一种写法
        setCount((count) => { return count + 1 }) //第二种写法
    }
    // 改名的回调
    function changeName() {
        setName('小红')
    }
    // 卸载组件的回调
    function unmount() {
        ReactDOM.unmountComponentAtNode(document.getElementById('root'))
    }
    // 提示输入的回调
    function show() {
        alert(myRef.current.value)
    }
    
    return (
        <div>
            <input type="text" ref={myRef} />
            <h1>当前求和为:{count}</h1>
            <h1>当前名字为:{name}</h1>
            <button onClick={add}>点我+1</button>
            <button onClick={changeName}>点我改名</button>
            <button onClick={unmount}>卸载组件</button>
            <button onClick={show}>点击提示数据</button>
        </div>
    )
    }
    
    export default Demo

4. Fragment

使用:
复制代码
    //<Fragment>可以写 key属性;<></>不能
    <Fragment><Fragment>
    <></>
作用:可以不用必须有一个真实的DOM根标签了;

5. Context:

理解:一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信;
使用:
  1. 创建Context容器对象:
复制代码
    const XxxContext = React.createContext()
  1. 渲染子组件时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
复制代码
    	<xxxContext.Provider value={数据}>
    		子组件
    </xxxContext.Provider>
  1. 后代组件读取数据:
复制代码
    	//第一种方式:仅适用于类组件 
    	  static contextType = xxxContext  // 声明接收context
    	  this.context // 读取context中的value数据
    	  
    	//第二种方式: 函数组件与类组件都可以
    	  <xxxContext.Consumer>
    	    {
    	      value => ( // value就是context中的value数据
    	        要显示的内容
    	      )
    	    }
    	  </xxxContext.Consumer>
注意:在应用开发中一般不用context, 一般都用它的封装react插件。
复制代码
    import React, { Component } from 'react'
    import './index.css'
    
    // 创建Context对象
    const MyContext = React.createContext()
    //解构赋值,从 MyContext 身上取出 Provider;或者直接在下面写 <MyContext.Provider>
    // 函数式组件时引入 Consumer
    const { Provider, Consumer } = MyContext
    
    export default class A extends Component {
    state = { username: 'tom', age: 18 }
    
    render() {
        const { username, age } = this.state
        return (
            <div className='parent'>
                <h1>我是A组件</h1>
                <h4>我的用户名是:{username}</h4>
                <Provider value={{ username: username, age: age }}>
                    <B />
                </Provider>
            </div>
        )
    }
    }
    
    class B extends Component {
    render() {
        return (
            <div className='child'>
                <h1>我是B组件</h1>
                <h4>我从A组件接到的用户名是:{this.props.username}</h4>
                <C username={this.props.username} />
            </div>
        )
    }
    }
    
    // 第一种方式:仅适用于类式组件
    class C extends Component {
    // 注意:哪个组件要用 Context,就必须要声明接收 Context。
    static contextType = MyContext
    
    render() {
        console.log(this);
        console.log(this.context);
        return (
            <div className='grand'>
                <h1>我是C组件</h1>
                <h4>我从B组件接到的用户名是:{this.context.username},年龄是:{this.context.age}</h4>
            </div>
        )
    }
    }
    
    // 第二种方式:函数式组件和类式组件都可以
    // 如果 C 组件是函数式组件
    function C() {
    return (
        <div className='grand'>
            <h1>我是C组件</h1>
            <h4>我从B组件接到的用户名是:
                <Consumer>
                    {
                        // value => {
                        //     console.log(value);
                        //     return `${value.username},年龄是:${value.age}`
                        // }
    
                        // 箭头函数并且函数体只有一条语句,简写
                        value => `${value.username},年龄是:${value.age}`
                    }
                </Consumer>
            </h4>
        </div>
    )
    }

6. 组件优化:

Component的2个问题 :

  1. 只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低
  2. 只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低
效率高的做法:只有当组件的state或props数据发生改变时才重新render()
原因:Component中的shouldComponentUpdate()总是返回true
解决:

办法1: 重写shouldComponentUpdate()方法 比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false;
办法2: 使用PureComponent PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true;
注意: 只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false 不要直接修改state数据, 而是要产生新数据;项目中一般使用PureComponent来优化

复制代码
    //实际开发中不用shouldComponentUpdate(阀门里的逻辑需要自己去判断),而是用PureComponent(PureComponent帮忙判断)
    import React, { Component, PureComponent } from 'react'
    import './index.css'
    
    // export default class Parent extends Component {
    export default class Parent extends PureComponent {
    state = { carName: '奔驰C63' }
    changeCar = () => {
        this.setState({ carName: '迈巴赫' })
    }
    
    shouldComponentUpdate(nextProps, nextState) {
        console.log(this.props, this.state);//目前的 props 和 state
        console.log(nextProps, nextState); //接下来变化的目标  props 和目标 state   
        // if (this.state.carName === nextState.carName) return false
        // else return true
    
        // 简写
        return !this.state.carName === nextState.carName
    }
    
    render() {
        console.log('Parent---render');
        return (
            <div className='parent'>
                <h3>我是Parent组件</h3>
                <span>我的车名字是:{this.state.carName}</span><br />
                <button onClick={this.changeCar}>点我换车</button>
                <Child carName={this.state.carName} />
            </div>
        )
    }
    }
    
    // class Child extends Component {
    class Child extends PureComponent {
    shouldComponentUpdate(nextProps, nextState) {
        // if (this.props.carName === nextProps.carName) return false
        // else return true
    
        //简写
        return !this.props.carName === nextProps.carName
    }
    
    render() {
        console.log('Child---render');
        return (
            <div className='child'>
                <h3>我是Child组件</h3>
                <span>我接到的车是:{this.props.carName}</span>
            </div>
        )
    }
    }
PureComponent总结:
  • PureComponent的目的是为了优化性能,那么如何体现呢?通过不受父组件state发生变化的子组件就不渲染了;
  • 另外一个概念就是无状态组件,即自身没有state,渲染的数据全部来自于props获得的,都可以改写成PureComponent的方式;

7. render props

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

Vue中: 使用slot技术, 也就是通过组件标签体传入结构 ******
React中:
使用children props: 通过组件标签体传入结构
使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性**


**

children props:
复制代码
     <A><B>xxxx</B></A>
     {this.props.children}
    问题: 如果B组件需要A组件内的数据, ==> 做不到
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'
    import C from '../1_setState'
    import './index.css'
    
    export default class Parent extends Component {
    render() {
        return (
            <div className='parent'>
                <h3>我是Parent组件</h3>
                {/* 组件标签的标签体内容是一个特殊的标签属性,属性名叫 children ;通过 this.props.children 拿到*/}
    
                {/* Hello! */} {/* 相当于children props */}
                {/* 将 B 组件作为 A 组件的标签体内容,让它两形成父子关系 */}
                {/* <A>
                    <B />
                </A> */}
    
                <A render={(name) => <B name={name} />} />
    
                {/* 可以放入任意组件,类似于Vue里的插槽技术 */}
                <A render={(name) => <C name={name} />} />
            </div>
        )
    }
    }
    
    class A extends Component {
    state = { name: 'Tom' }
    render() {
        console.log(this.props); // {children: 'Hello!'}
        const { name } = this.state
        return (
            <div className='a'>
                <h3>我是A组件</h3>
                {/* {this.props.children} */}
    
                {/* 预留位置接收组件,类似于Vue里的插槽技术 */}
                {this.props.render(name)}
    
                {/* <B /> */}
            </div>
        )
    }
    }
    class B extends Component {
    render() {
        return (
            <div className='b'>
                <h3>我是B组件</h3>
            </div>
        )
    }
    }

8. 错误边界(ErrorBoundary)

理解:错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面(注意:只在生产环境有效);
特点:只能捕获【后代组件生命周期产生】(render也是生命周期)的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误;
使用方式:getDerivedStateFromError 配合 componentDidCatch
复制代码
    // 生命周期函数,一旦后台组件报错,就会触发
    static getDerivedStateFromError(error) {
    console.log(error);
    // 在render之前触发
    // 返回新的state
    return {
        hasError: true,
    };
    }
    
    componentDidCatch(error, info) {
    // 统计页面的错误。发送请求发送到后台去
    console.log(error, info);
    }
补充【快速搭建服务器】:

(1)、express框架 — node里面快速搭建服务器;
(2)、借助第三方库;(例如 serve)
serve 使用:
(1)、npm i serve -g 全局安装 serve;
(2)、serve 作为根路径的文件夹名(如:serve build);

复制代码
    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
    componentDidCatch() {
        console.log('此处统计错误,反馈给服务器,用于通知编码人员进行bug的解决');
    }
    
    render() {
        return (
            <div>
                <h2>我是Parent组件</h2>
                {this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child />}
            </div>
        )
    }
    }

9. 组件通信方式总结:

组件间的关系:
  • 父子组件
  • 兄弟组件(非嵌套组件)
  • 祖孙组件(跨级组件)
几种通信方式:

1.props:
(1).children props
(2).render props
2.消息订阅-发布:
pubs-sub、event等等
3.集中式管理:
redux、dva等等
4.conText:
生产者-消费者模式

比较好的搭配方式:

父子组件:props
兄弟组件:消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)

7.8. 纯函数和高阶函数

7.8.1. 纯函数

  1. 一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)

  2. 必须遵守以下一些约束 :
    (1) 不得改写参数数据;
    (2) 不会产生任何副作用,例如网络请求,输入和输出设备;
    (3) 不能调用Date.now()或者Math.random()等不纯的方法 ;

  3. redux的reducer函数必须是一个纯函数

7.8.2. 高阶函数

  1. 理解: 一类特别的函数
    (1) 情况1: 参数是函数
    (2) 情况2: 返回是函数

  2. 常见的高阶函数:
    (1) 定时器设置函数;
    (2) 数组的forEach()/map()/filter()/reduce()/find()/bind();
    (3) promise;
    (4) react-redux中的connect函数;
    3.作用:能实现更加动态,更加可扩展的功能。

ReactRouter6教程:

1、概述

  • React Router 以三个不同的包发布到npm上,它们分别为:
    (1)、react-router :路由的核心库,提供了很多的 组件、钩子;
    (2)、react-router-dom:包含 react-router 所有内容,并添加一些专门用于 DOM 的组件,例如:<BrowserRouter>等;
    (3)、react-router-native:包括 react-router 所有内容,并添加一些专门用于 ReactNative 的 API ,例如:<NativeRouter>等;

  • 与 React Router 5.x 版本相比,改变了什么?
    (1)、内置组件的变化:移除 <Switch> ,新增:<Routes>等;
    (2)、语法的变化:<component={About}>变为<element={About}>等;
    (3)、新增多个 hook :useParams useNavigate useMatch等;
    (4)、官方明确推荐函数式组件了!

2、Component

<BrowserRouter>
  • 用于包裹整个应用。
<HashRouter>
  • 作用和<BrowserRouter>一样,但<HashRouter>修改的是地址栏的hash值;
  • 6.x版本中<HashRouter><BrowserRouter>的用法与5.x相同;
<Routes><Route>
  • V6版本中移除了先前的<Switch>,引入了新的替代者:<Routes>;
  • <Routes><Route>要配合使用,且必须要用<Routes>包裹<Route>
  • <Route>相当于一个if语句,如果其路径与当前URL匹配,则呈现其对应的组件;
  • <Route caseSensitive>属性用于指定:匹配时是否区分大小写(默认 false);
  • 当URL发生变化时,<Routes>都会查看其所有子<Route>元素以找到最佳匹配并呈现组件;
  • <Route>也可以嵌套使用,且可配合useRoutes()配置“路由表”,但需要通过<Outlet>组件来渲染其子路由;
<Navigate>
  • 作用:只要<Navigate>组件被渲染,就会修改路径,切换视图;
  • replace 属性用于控制跳转模式(push 或replace,默认是push);
复制代码
    import { NavLink, Routes, Route, Navigate } from 'react-router-dom'
    
    <div className="panel-body">
              {/* 注册路由 */}
              <Routes>
                <Route path='/about' element={<About />} />
                <Route path='/home' element={<Home />} />
     {/* Navigate 可以用来处理重定向相当于5版本里的 Redirect,Navigate 只要渲染就会引起视图的切换 */}
                <Route path='/' element={<Navigate to='/about' replace={true} />} />
              </Routes>
            </div>
<Outlet>
  • <Route>产生嵌套时,渲染其对应的后续子路由;
useInRouterContext()
  • 作用:如果组件在<Router>的上下文中呈现,则useInRouterContext()钩子返回true,否则返回false;
复制代码
    import { useInRouterContext } from 'react-router-dom'
    
    // useInRouterContext() 返回值是一个布尔值
    // true,表示处于路由的上下文环境中,即目前所处的组件被<BrowserRouter>包裹;
    // 只要是App的子组件,都处于路由的上下文环境中;
    console.log(useInRouterContext());
useNavigationType()
  • 作用:返回当前的导航类型(用户是如何来到当前页面的);
  • 返回值:POP PUSH REPLACE
  • 备注:POP是指在浏览器中直接打开了这个路由组件(刷新页面);
useOutlet()
  • 作用:用来呈现当前组件中要渲染的嵌套路由;
  • 示例代码:
复制代码
    const result=useOutlet()
    console.log(result)
    //如果嵌套路由没有挂载,则result为null
    //如果嵌套路由已经挂载,则展示嵌套的路由对象
useResolvedPath()
  • 作用:给定一个URL值,解析其中的:path、search、hash值;
复制代码
    //atguigu 类名加在public目录下的 index.html 文件中;
     <style>
    .atguigu {
      background-color: orange !important;
      color: white !important;
    }
      </style>
复制代码
    import React from 'react'
    import { NavLink, Routes, Route, Navigate } from 'react-router-dom'
    import About from './pages/About'
    import Home from './pages/Home'
    
    export default function App() {
    //将以下高亮效果的类名抽成一个方法,便于复用;
      function computedClassName({ isActive }) {
    return isActive ? 'list-group-item atguigu' : 'list-group-item'
      }
    
      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>
      <div className="row">
        <div className="col-xs-2 col-xs-offset-2">
          <div className="list-group">
            {/* 路由链接 */}
            {/* V5版本加 activeClassName="atguigu" ,实现选中高亮  */}
            <NavLink
              // activeClassName="atguigu"
              //className={(a) => { console.log('666', a); }} //666 {isActive: true}
              // { isActive }是将 isActive 解构出来,
              //className={({ isActive }) => { return isActive ? 'list-group-item atguigu' : 'list-group-item' }}
              className={({ isActive }) => isActive ? 'list-group-item atguigu' : 'list-group-item'} //箭头函数简写
              to="/about"
            >
              About
            </NavLink>
            {/* 可以将以上样式写成一个函数,然后在需要的地方直接复用即可 */}
            <NavLink className={computedClassName} to="/home">Home</NavLink>
          </div>
        </div>
        <div className="col-xs-6">
          <div className="panel">
            <div className="panel-body">
              {/* 注册路由 */}
              <Routes>
                <Route path='/about' element={<About />} />
                <Route path='/home' element={<Home />} />
                {/* Navigate 相当于5版本里的 Redirect,Navigate 只要渲染就会引起视图的切换 */}
                <Route path='/' element={<Navigate to='/about' replace={true} />} />
              </Routes>
            </div>
          </div>
        </div>
      </div>
    </div>
      )
    }

useRouters路由表

在这里插入图片描述
复制代码
    import { NavLink, useRoutes } from 'react-router-dom'
    import routes from './routes'
    
    // 根据路由表生成对应的路由规则
    const element = useRoutes(routes)
    
    <div className="row">
        <div className="col-xs-2 col-xs-offset-2">
          <div className="list-group">
            <NavLink className='list-group-item' to="/about">About</NavLink>
            <NavLink className='list-group-item' to="/home">Home</NavLink>
          </div>
        </div>
        <div className="col-xs-6">
          <div className="panel">
            <div className="panel-body">
              {/* 注册路由 */}
              {element}
            </div>
          </div>
        </div>
     </div>

嵌套路由

复制代码
    import { Navigate } from "react-router-dom"
    import About from "../pages/About"
    import Home from "../pages/Home"
    import Message from "../pages/Message"
    import News from "../pages/News"
    
    export default [
    {
        path: '/about',
        element: <About />,
    },
    {
        path: '/home',
        element: <Home />,
        children: [
            { path: 'news', element: <News /> },
            { path: 'message', element: <Message /> },
        ]
    },
    {
        path: '/',
        element: <Navigate to='/about' />
    },
    ]
复制代码
    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>
                        {/* V6版本前面不用带着父级路径,注意:不用带 / */}
                        <NavLink className="list-group-item" to='news'>News</NavLink>
                    </li>
                    <li>
                        {/* 在父级路由链接里加上 end 属性,选中该子级路由时,其父级不会有高亮效果 */}
                        <NavLink className="list-group-item" to='message'>Message</NavLink>
                    </li>
                </ul>
                {/* 指定路由组件呈现的位置 */}
                <Outlet />
            </div>
        </div>
    )
    }

路由传参

在这里插入图片描述
复制代码
    import { Navigate } from "react-router-dom"
    import About from "../pages/About"
    import Detail from "../pages/Detail"
    import Home from "../pages/Home"
    import Message from "../pages/Message"
    import News from "../pages/News"
    
    export default [
    {
        path: '/about',
        element: <About />,
    },
    {
        path: '/home',
        element: <Home />,
        children: [
            { path: 'news', element: <News /> },
            {
                path: 'message', element: <Message />,
                // 路由传递 params 参数
                // children: [
                //     { path: 'detail/:id/:title/:content', element: <Detail /> },
                // ]
    
                // 路由传递 Search参数 和 state参数 都不需要占位(Search参数、state参数 不需要声明接收)
                children: [
                    { path: 'detail', element: <Detail /> },
                ]
            },
        ]
    },
    {
        path: '/',
        element: <Navigate to='/about' />
    },
    ]
复制代码
    import React, { useState } from 'react'
    import { Link, Outlet } from 'react-router-dom'
    
    export default function Message() {
    const [messages] = useState([
        { id: '001', title: '消息1', content: '1111' },
        { id: '002', title: '消息2', content: '2222' },
        { id: '003', title: '消息3', content: '3333' },
        { id: '004', title: '消息4', content: '4444' },
    ])
    
    return (
        <div>
            <ul>
                {
                    messages.map((m) => {
                        return (
                            // 路由链接
    
                            // <li key={m.id}>
                            //     {/* 路由传递 params 参数 */}
                            //     <Link to={`detail/${m.id}/${m.title}/${m.content}`} >{m.title}</Link>
                            // </li>
    
                            // <li key={m.id}>
                            //     {/* 路由传递 search 参数 */}
                            //     <Link to={`detail?id=${m.id}&title=${m.title}&content=${m.content}`} >{m.title}</Link>
                            // </li>
    
                            <li key={m.id}>
                                {/* 路由传递 state 参数 */}
                                <Link
                                    to='detail'
                                    state={{
                                        id: m.id,
                                        title: m.title,
                                        content: m.content
                                    }}
                                >
                                    {m.title}
                                </Link>
                            </li>
                        )
                    })
                }
            </ul>
            <hr />
            {/* 指定路由组件的展示位置 */}
            <Outlet />
        </div>
    )
    }
复制代码
    import React from 'react'
    import { useParams, useMatch, useSearchParams, useLocation } from 'react-router-dom'
    
    export default function Detail() {
    // 方法一:利用 useParams
    // const a = useParams()
    // console.log(a); //{id: '001', title: '消息1', content: '1111'}
    // const { id, title, content } = useParams() //解构赋值,拿到 id, title, content
    
    // 方法二:利用 useMatch(需传入完整 path)(用的不多)
    //const x = useMatch('/home/message/detail/:id/:title/:content')
    //console.log(x); //{params: {…}, pathname: '/home/message/detail/001/%E6%B6%88%E6%81%AF1/1111', pathnameBase: '/home/message/detail/001/%E6%B6%88%E6%81%AF1/1111', pattern: {…}}
    
    // 接收路由的 Search 参数
    // const [search, setSearch] = useSearchParams()
    // console.log(search.get('id'));
    // console.log(search.get('title'));
    // console.log(search.get('content'));
    // const id = search.get('id')
    // const title = search.get('title')
    // const content = search.get('content')
    
    // 接收路由的 state 参数
    const x = useLocation()
    console.log(x);
    const { state: { id, title, content } } = useLocation() //连续解构赋值
    
    return (
        <ul>
            {/*setSearch 用的不多,了解即可  */}
            {/* <li>
                <button onClick={() => setSearch('id=008&title=哈哈&content=嘻嘻')}>点我更新一下收到的search参数</button>
            </li> */}
    
            {/* 展示 useParams、Search、state 参数 */}
            <li>消息的编号:{id}</li>
            <li>消息的标题:{title}</li>
            <li>消息的内容:{content}</li>
        </ul>
    )
    }

编程式路由导航

复制代码
    import React, { useState } from 'react'
    import { Link, Outlet, useNavigate } from 'react-router-dom'
    
    export default function Message() {
    const navigate = useNavigate()
    
    const [messages] = useState([
        { id: '001', title: '消息1', content: '1111' },
        { id: '002', title: '消息2', content: '2222' },
        { id: '003', title: '消息3', content: '3333' },
        { id: '004', title: '消息4', content: '4444' },
    ])
    function showDetail(m) {
        // 子路由前面不需要带 /
        navigate('detail', {
            replace: true,
            state: {
                id: m.id,
                title: m.title,
                content: m.content
            }
        })
    }
    
    return (
        <div>
            <ul>
                {
                    messages.map((m) => {
                        return (
                            // 路由链接
    
                            // <li key={m.id}>
                            //     {/* 路由传递 params 参数 */}
                            //     <Link to={`detail/${m.id}/${m.title}/${m.content}`} >{m.title}</Link>
                            // </li>
    
                            // <li key={m.id}>
                            //     {/* 路由传递 search 参数 */}
                            //     <Link to={`detail?id=${m.id}&title=${m.title}&content=${m.content}`} >{m.title}</Link>
                            // </li>
    
                            <li key={m.id}>
                                {/* 路由传递 state 参数 */}
                                <Link
                                    to='detail'
                                    state={{
                                        id: m.id,
                                        title: m.title,
                                        content: m.content
                                    }}
                                >
                                    {m.title}
                                </Link>
                                <button onClick={() => showDetail(m)}>查看详情</button>
                            </li>
                        )
                    })
                }
            </ul>
            <hr />
            {/* 指定路由组件的展示位置 */}
            <Outlet />
        </div>
    )
    }
前进后退案例
复制代码
    import React from 'react'
    import { useNavigate } from 'react-router-dom'
    
    export default function Header() {
    const navigate = useNavigate()
    
    function back() {
        navigate(-1)
    }
    function forward() {
        navigate(1)
    }
    
    return (
        <div className="col-xs-offset-2 col-xs-8">
            <div className="page-header"><h2>React Router Demo</h2></div>
            <button onClick={back}>🔙后退</button>
            <button onClick={forward}>前进→</button>
        </div>
    )
    }

**

全部评论 (0)

还没有任何评论哟~