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.1opacity -= 0.1if (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:

  1. 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 } } = thisconsole.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><inputref={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. 后端路由:
  1. 理解: value是function, 用来处理客户端提交的请求。
  2. 注册路由: router.get(path, function(req, res))
  3. 工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
  1. 前端路由:BOM — history
  1. 浏览器端路由,value是component,用于展示页面内容。
  2. 注册路由:
  3. 工作过程:当浏览器的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()">&lt;= 回退</button><button onClick="forword()">前进 =&gt;</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身上的APIlet 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.
2.
3.
4.
5.
6.
7.

九、路由的基本使用
- npm i react-router-dom
- import { Link, BrowserRouter } from ‘react-router-dom’
- 明确好界面中的导航区、展示区;
- 导航区的a标签改为Link标签;
Demo
- 展示区写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 参数
* 路由链接(携带参数):/demo/test/tom/18}>{详情}
* 注册路由(声明接收):
* 接收参数: const {name,age } = this.props.match.params
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
- 2.search参数
* 路由链接(携带参数):/demo/test?name=tom&age=18}>详情
* 注册路由(无需声明,正常注册即可):
* 接收参数: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 参数
* 路由链接(携带参数):{ pathname: ‘/demo/test’, state: { name: ‘Tom’, title: age:19} }}>详情
* 注册路由(无需声明,正常注册即可):
* 接收参数:this.props.location.state
* 备注:刷新也可以保留住参数
* 在这里插入图片描述

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

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

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

声明式导航:通过点击链接进行页面跳转(a 标签和 router-link )

编程式导航:通过 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.js1、该文件是用于创建一个为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 + datacase "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.selectNumberstore.dispatch({ type: 'increment', data: value * 1 })}
  1. subscribe(listener): 注册监听, 当产生了新的state时, 自动调用;
    注意:redux里状态的更改是不会引起页面的更新的;
  componentDidMount() {//生命周期钩子里的this都是组件的实例对象// 监测redux中状态的变化,只要变化,就调用renderstore.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. 核心方法:
1) getState()
2) dispatch(action)
3) subscribe(listener)
4. 具体编码:
1) store.getState()
2) store.dispatch({type:‘INCREMENT’, number})
3) 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 valuejia: 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])------对象式的setState1.stateChange为状态改变对象(该对象可以体现出状态的更改)2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
import React, { Component } from 'react'export default class Demo extends Component {state = { count: 0 }add = () => {const { count } = this.statethis.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])------函数式的setState1.updater为返回stateChange对象的函数。2.updater可以接收到state和props。4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
import React, { Component } from 'react'export default class Demo extends Component {state = { count: 0 }add = () => {// 函数式的setStatethis.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.通过指定在加载得到路由打包文件前显示一个自定义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 (
//             
//                 
//                 

当前求和为:{this.state.count}

// // // // // ) // } // }// 函数式组件 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(() => {// 定义一个变量 timerlet timer = setInterval(() => {setCount(count => count + 1)}, 1000)return () => {// 作用域查找找到 timerclearInterval(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

使用:
//可以写 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  // 声明接收contextthis.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;或者直接在下面写 
// 函数式组件时引入 Consumer
const { Provider, Consumer } = MyContextexport default class A extends Component {state = { username: 'tom', age: 18 }render() {const { username, age } = this.statereturn (<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 = MyContextrender() {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 和 stateconsole.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 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.statereturn (<div className='a'><h3>我是A组件</h3>{/* {this.props.children} */}{/* 预留位置接收组件,类似于Vue里的插槽技术 */}{this.props.render(name)}{/*  */}</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之前触发// 返回新的statereturn {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 }}// 如果组件在渲染过程中由于子组件出现报错,调用componentDidCatchcomponentDidCatch() {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 的组件,例如:等;
    (3)、react-router-native:包括 react-router 所有内容,并添加一些专门用于 ReactNative 的 API ,例如:等;
  • 与 React Router 5.x 版本相比,改变了什么?
    (1)、内置组件的变化:移除 ,新增:等;
    (2)、语法的变化:变为等;
    (3)、新增多个 hook :useParams useNavigate useMatch等;
    (4)、官方明确推荐函数式组件了!

2、Component

  • 用于包裹整个应用。
  • 作用和一样,但修改的是地址栏的hash值;
  • 6.x版本中的用法与5.x相同;
  • V6版本中移除了先前的,引入了新的替代者:;
  • 要配合使用,且必须要用包裹
  • 相当于一个if语句,如果其路径与当前URL匹配,则呈现其对应的组件;
  • 属性用于指定:匹配时是否区分大小写(默认 false);
  • 当URL发生变化时,都会查看其所有子元素以找到最佳匹配并呈现组件;
  • 也可以嵌套使用,且可配合useRoutes()配置“路由表”,但需要通过组件来渲染其子路由;
  • 作用:只要组件被渲染,就会修改路径,切换视图;
  • 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>
  • 产生嵌套时,渲染其对应的后续子路由;
useInRouterContext()
  • 作用:如果组件在的上下文中呈现,则useInRouterContext()钩子返回true,否则返回false;
import { useInRouterContext } from 'react-router-dom'// useInRouterContext() 返回值是一个布尔值
// true,表示处于路由的上下文环境中,即目前所处的组件被包裹;
// 只要是App的子组件,都处于路由的上下文环境中;
console.log(useInRouterContext()); 
useNavigationType()
  • 作用:返回当前的导航类型(用户是如何来到当前页面的);
  • 返回值:POP PUSH REPLACE
  • 备注:POP是指在浏览器中直接打开了这个路由组件(刷新页面);
useOutlet()
  • 作用:用来呈现当前组件中要渲染的嵌套路由;
  • 示例代码:
const result=useOutlet()
console.log(result)
//如果嵌套路由没有挂载,则result为null
//如果嵌套路由已经挂载,则展示嵌套的路由对象
useResolvedPath()
  • 作用:给定一个URL值,解析其中的:path、search、hash值;

NavLink高亮案例:

//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:  },// ]// 路由传递 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 (// 路由链接// 
  • // {/* 路由传递 params 参数 */}// {m.title}//
  • //
  • // {/* 路由传递 search 参数 */}// {m.title}//
  • <li key={m.id}>{/* 路由传递 state 参数 */}<Linkto='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 用的不多,了解即可  */}{/* 
  • */
    }{/* 展示 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 (// 路由链接// 
  • // {/* 路由传递 params 参数 */}// {m.title}//
  • //
  • // {/* 路由传递 search 参数 */}// {m.title}//
  • <li key={m.id}>{/* 路由传递 state 参数 */}<Linkto='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>)
    }


    本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

    相关文章