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当中的事件处理:
- 通过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()
- 更新阶段: 由组件内部this.setSate()或父组件重新render触发
- getDerivedStateFromProps
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate
- componentDidUpdate()
- 卸载组件: 由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脚手架:
- xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目;
- 包含了所有需要的配置(语法检查、jsx编译、devServer…);
- 下载好了所有相关的依赖;
- 可以直接运行一个简单效果;
- react提供了一个用于创建react项目的脚手架库: create-react-app;
- 项目的整体技术架构为: react + webpack + es6 + eslint;
- 使用脚手架开发的项目的特点: 模块化, 组件化, 工程化;
创建项目并启动: 第一步,全局安装: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搜索案例




七、消息订阅与发布
-
工具库: PubSubJS
-
下载: npm install pubsub-js --save
-
使用:
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: -
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" />
<button onClick={this.search}>Search</button>
</div>
</section>
)
}
}
八、github搜索案例相关知识点

对SPA应用的理解
- 单页Web应用(single page web application,SPA)。
- 整个应用只有一个完整的页面。
- 点击页面中的链接【不会刷新页面】,只会做页面的【局部更新】。
- 数据都需要通过ajax请求获取, 并在前端异步展现。
路由的理解
1. 什么是路由?
-
一个路由就是一个映射关系(key:value)
-
key为路径, value可能是 function 或 component
2. 路由分类 -
后端路由:
-
理解: value是function, 用来处理客户端提交的请求。
-
注册路由: router.get(path, function(req, res))
-
工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
-
前端路由:BOM — history
-
浏览器端路由,value是component,用于展示页面内容。
-
注册路由:
-
工作过程:当浏览器的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的理解
- react的一个插件库。
- 专门用来实现一个SPA应用。
- 基于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}
声明式导航:通过点击链接进行页面跳转(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是什么?
- redux是一个专门用于做【状态管理】的JS库(不是react插件库)。
- 它可以用在react, angular, vue等项目中, 但基本与react配合使用。
- 作用: 集中式管理react应用中多个组件【共享】的状态。
2、什么情况下需要使用redux?
- 某个组件的状态,需要让其他组件可以随时拿到(共享)。
- 一个组件需要改变另一个组件的状态(通信)。
- 总体原则:能不用就不用, 如果不用比较吃力才考虑使用。
3、学习文档:
- 英文文档: https://redux.js.org/
- 中文文档: http://www.redux.org.cn/
- Github: https://github.com/reactjs/redux
4、redux工作流程:

redux的三个核心概念
1、action(动作对象):
-
动作的对象;
-
包含2个属性:
type:标识属性, 值为字符串, 唯一, 必要属性
data:数据属性, 值类型任意, 可选属性 -
例子:{ 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
}
}
- 用于初始化状态(没有 previousState(之前的值) 时是 undefined)、加工状态。
- 加工时,根据旧的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)
-
将state、action、reducer联系在一起的对象;
-
如何得到此对象?
1. import {createStore} from ‘redux’
2. import reducer from ‘./reducers’
3. const store = createStore(reducer) -
此对象的功能?
1. getState(): 得到state
<h1>当前求和为:{store.getState()}</h1>
- dispatch(action): 分发action, 触发reducer调用, 产生新的state
// 加法
increment = () => {
const { value } = this.selectNumber
store.dispatch({ type: 'increment', data: value * 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. 它内部维护着:
- state
- reducer
3. 核心方法: - getState()
- dispatch(action)
- subscribe(listener)
4. 具体编码: - store.getState()
- store.dispatch({type:‘INCREMENT’, number})
- 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):
- 一个react插件库
- 专门用来简化react应用中使用redux
7.6.2. react-Redux将所有组件分成两大类

-
UI组件
1. 只负责 UI 的呈现,不带有任何业务逻辑
2. 通过props接收数据(一般数据和函数)
3. 不使用任何 Redux 的 API
4. 一般保存在components文件夹下 -
容器组件
1. 负责管理数据和业务逻辑,不负责UI的呈现
2. 使用 Redux 的 API
3. 一般保存在containers文件夹下
7.6.3. 相关API
- 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')
)
- connect:用于包装 UI 组件生成容器组件
import { connect } from 'react-redux'
connect(
mapStateToprops,
mapDispatchToProps
)(Counter)
- mapStateToprops:将外部的数据(即state对象)转换为UI组件的标签属性
const mapStateToprops = function (state) {
return {
value: state
}
}
- 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:
理解:一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信;
使用:
- 创建Context容器对象:
const XxxContext = React.createContext()
- 渲染子组件时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
<xxxContext.Provider value={数据}>
子组件
</xxxContext.Provider>
- 后代组件读取数据:
//第一种方式:仅适用于类组件
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个问题 :
- 只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低
- 只当前组件重新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) 不会产生任何副作用,例如网络请求,输入和输出设备;
(3) 不能调用Date.now()或者Math.random()等不纯的方法 ; -
redux的reducer函数必须是一个纯函数
7.8.2. 高阶函数
-
理解: 一类特别的函数
(1) 情况1: 参数是函数
(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 :useParamsuseNavigateuseMatch等;
(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():
- 作用:返回当前的导航类型(用户是如何来到当前页面的);
- 返回值:
POPPUSHREPLACE; - 备注:
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: <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>
)
}
**
