文章目录
- 前言
- 一、React是什么?
- 二、基础内容
- 1. React 相关 js 库
- 2. React 开发者调试工具
- 3. JSX语法规则
- 4. 模块与组件、模块化与组件化
- 5. 类的基本知识
- 三、React 面向组件编程
- 1. 函数式组件
- 2. 类式组件
- 3. 组件实例的三个核心属性: state、refs、props
- - state
- - props
- - refs (注意 ref 与 refs)
- 4. React 中的事件处理
- 5. 受控组件 与 非受控组件
- 6. 高阶函数 - 函数柯理化
- 7. 组件生命周期
- 8. 虚拟DOM 与 DOM Diffing 算法
- 四、React 脚手架
- 1. 创建 react 应用
- 2. SPA的理解
- 3. react 脚手架项目结构
- 4. 简单案例 hello,react!
- 5. 样式模块化
- 6. 配置代理
- 7. 消息订阅 与 发布机制 PubSub
- 8. React-router 5
- - 路由的理解
- - React-router 相关api
- 8. React-router 6
- 10. ant-design 的基本使用
- 11. redux
- 12. React-redux
- 13. react 项目打包
- 扩展
- 1. setState
- 2. lazyLoad
- 3. Hooks
- 4. Fragment
- 5. Context
- 6. 组件优化
- 7. render props(相当于插槽)
- 8. 错误边界 ErrorBoundary
- 总结
- 1. 组件间的通信
前言
React 全家桶:React-Router 路由库、PubSub 消息管理库、Redux 集中状态管理库、Ant-Design UI
一、React是什么?
React 是 Facebook 开发的,用于构建用户界面的开源 JavaScript 库(是一个将数据渲染为HTML视图的开源 JavaScript 库)
页面渲染步骤:
- 1、发送请求获取数据
- 2、处理数据(过滤、整理格式等)
- 3、操作 DOM 呈现页面 (React 的工作)
原生 JavaScript 与 React 比较:
- 原生JavaScript
操作 DOM 繁琐、效率低
直接操作 DOM 、进行大量的重绘重排
没有组件化编码、复用率低
命令式编码 - React
组件化模式、声明式编码、提高开发效率及组件复用率
React Native 中可以使用 React 语法进行移动端开发
高效:虚拟 DOM(不总直接操作DOM) + 优秀 Diffing 算法(最小化页面重绘)尽量减少与真实 DOM 的交互
二、基础内容
1. React 相关 js 库
- react.js:React 核心库
- react-dom.js: 提供操作 DOM 的 React 扩展库
- babel.min.js:解析 JSX 语法代码转化为 JS 代码的库(es6 ⇒ es5、import 模块化、JSX ⇒ JS)
2. React 开发者调试工具
React Developer Tools
3. JSX语法规则
- 1、定义虚拟DOM时,不要写引号
- 2、标签中混入 JS 表达式时要用 {}
一定要注意区分:【JS表达式】 与 【JS语句(代码)】
JS 表达式: 一个表达式会产生一个值,可以放在任何一个需要值的地方:a、a+b、demo(1)、arr.map、function demo(){}
JS 代码: 控制代码走向,没有返回值:if(){}、 for(){}、switch(){case:xx}
- 3、样式的类名指定不要用 class,要用 className
- 4、内联样式,要用 style ={{key: value}} 的形式去写
- 5、虚拟 DOM 只能有一个根标签
- 6、标签必须闭合
- 7、标签首字母
若小写字母开头,则将该标签转为 html 中同名元素,若html中无该标签对应的同名元素,则报错
若大写字母开头,react 渲染对应的组件,若组件没有定义,则报错
<div id="root"></div><script type="text/babel">const catName = '花姐'// 用 JSX 创建虚拟 DOMconst VDOM = (<div><h2 className="name"><span style={{color: "orange", fontSize: "20px"}}>{catName}</span></h2></div>)ReactDOM.render(VDOM, document.getElementById('root'))</script>
4. 模块与组件、模块化与组件化
-
模块(JS模块)
理解:向外提供特定功能的 js 程序,一般就是一个 js 文件
作用:复用 js ,简化 js 的编写,提高 js 的运行效率 -
组件
理解:用来实现局部功能效果的代码和资源的集合(html、css、js、image等)
作用:复用编码、简化项目编码、提高运行效率 -
模块化:当应用的 js 都以模块来编写的,这个应用就是一个 模块化 的应用
-
组件化:当应用都是以组件的方式实现,这个应用就是一个 组件化 的应用
5. 类的基本知识
// 创建一个 cat 类class Cat{// 构造器方法constructor(name){// 构造器中的 this 指向类的实例对象: 谁调用了new, this 指向谁this.name = name}// 一般方法:放在了类的原型对象(_proto_)上, 供实例使用// 通过 Cat 实例调用一般方法时,this 指向 Cat 实例sayName(){console.log(`我叫${this.name}`)}}// 创建一个 cat 的实例对象const HJ = new Cat('花姐')// 创建一个 Minicat 类,继承于 Cat 类class Minicat extends Cat {constructor(name, age){// super 作用:调用父类的构造器函数;super 必须写在其他参数之前super(name);this.age = age}}// 创建一个 Minicat 的实例对象const mini = new Minicat('橘宝', 2)/*总结:1、类中的构造器不是必须写的,要对实例进行一些初始化操作的时候,如添加属性才写;2、如果 A 类继承了 B 类,且 A 类写了构造器,那 A 类中构造器的 super 是必须调用的;3、类中的方法,都是放在类的原型对象上(_proto_),供实例使用*/
三、React 面向组件编程
1. 函数式组件
适用于 简单组件(无state) 的定义
// 创建函数式组件function Demo(){// babel 编译后开启了严格模式,严格模式禁止自定义的函数 this 指向 widow console.log(this); // undefinedreturn <h2>我是函数式组件</h2>}// 渲染组件到页面ReactDOM.render(<Demo/>, document.getElementById('root'));/*执行了 ReactDOM.render((<Demo />.... 之后:1、React 解析组件标签,找到了 Demo 组件;2、发现是函数式组件的,随后调用该函数,将返回的虚拟 DOM 转为真实 DOM ,随后呈现在页面中*/
2. 类式组件
适用于 复杂组件(有state) 的定义
/*创建类式组件1、React.Component:React 内置类2、内部 render(){} 必须写,放在 Demo 原型对象上,供实例使用*/class Demo extends React.Component {render(){// render 中的 this 指向 Demo 组件实例对象console.log(this);return <h2>我是类式组件</h2>}}// 渲染组件到页面ReactDOM.render(<Demo />, document.getElementById('root'))/*执行了 ReactDOM.render((<Demo />.... 之后:1、React 解析组件标签,找到 Demo 组件;2、发现组件是使用类定义的,随后 new 出来该类的实例,并通过该实例调用到原型上的 render 方法3、将 render 返回的虚拟 DOM 转化为真实的 DOM, 随后呈现在页面中*/
3. 组件实例的三个核心属性: state、refs、props
- state
// state 复杂写法class Weather extends React.Component {constructor(props){super(props);this.state = { isHot: false };// .bind(this)用来解决 this 指向问题this.changeWeather = this.changeWeather.bind(this); }/*changeWeather 放在 Weather 原型对象上,供实例使用由于 changeWeather 是作为 onClick 回调,而不是通过实例调用的,是直接调用类中的方法默认开启了举报的严格模式,所以方法中的 this 为 undefined*/changeWeather(){ this.setState({isHot: !this.state.isHot}) }render(){const { isHot } = this.state;return <p onClick={this.changeWeather} >今天天气{isHot}</p>}}
// state 简写方式: 省略类构造器,函数修改 this 指向class Weather extends React.Component {// 类中可以直接写赋值语句,给类添加属性(不能写var\let\const)state = { isHot: fasle };/*赋值语句的形式 + 箭头函数,修改函数 this 指向箭头函数没有自己的this, 声明时会向外寻找 this,始终指向函数声明时所在的作用域下的 this 的值*/changeWeather = () => this.setState({isHot: !this.state.isHot})render(){const { isHot } = this.state;return <p onClick={this.changeWeather} >今天天气{isHot}</p>}}/*总结:1、组件中 render 方法中的 this 为组件实例对象2、组件自定义的方法中 this 为undefined, 解决方法:通过函数对象的 bind(), 强制绑定this箭头函数 + 赋值语句,声明方法3、状态数据,通过 setState 修改this.state.isHot = true 修改 isHot 的值会成功,但是不会触发渲染需要通过 setState 修改才会触发重新渲染4、标签绑定事件 onClick 要大写*/
- props
理解:每个组件都有一个 props(properties) 属性,组件标签的所有属性都保存在 props 中
作用:通过标签属性从组件外部向组件内部传递变化的数据
注意:组件内部不要修改 props 数据,会报错
let obj ={name: '花姐'}// 1、函数组件使用 props,参数形式function Demofn(props){const {name} = propsreturn(<p>{name}</p>)}// 对标签属性进行类型、必要性限制Demofn.propTypes = {name: PropTypes.string.isRequired, // string 表示为字符串,isRequired 表示必传}// 指定默认标签属性值Demofn.defaultProps = {name: '喵'}ReactDOM.render(<Demofn {...obj}/>, document.getElementById('root'))// 2、类式组件中的构造器 与 propsclass Democlass extends React.Component {// 构造器是都接收 props,是否传递给 super 取决于是否希望在构造器中通过 this 访问 props// 开发过程中一般不使用构造器constructor(props){super(props);console.log(this.props); // 需要调用super(props),才能在构造器中使用 this.props,否则为 undefined}// 对标签属性进行类型、必要性限制static propTypes = {name: PropTypes.string.isRequired, // string 表示为字符串,isRequired 表示必传}// 指定默认标签属性值static defaultProps = {name: '喵'}render(){let {name} = this.propsreturn(<p>{name}</p>)}}// {...obj} 扩展运算符批量传递ReactDOM.render(<Democlass {...obj}/>, document.getElementById('root2'))
- refs (注意 ref 与 refs)
- 字符串形式 (不推荐使用,存在效率问题)
class Demo extends React.Component {getRef = () => {// this.refs.btn 是真实 DOM const { btn } = this.refs}render(){return(<button ref="btn" onClick={this.getRef}>字符串形式 ref (不推荐使用)</button>)}}ReactDOM.render(<Demo/>, document.getElementById('root'))
- 回调函数形式(内联写法)
class Demo extends React.Component { getRef = () => {const { btn } = this}render(){return(<button ref={c=> this.btn = c} onClick={this.getRef}>回调形式 ref, 内联写法</button>)}}ReactDOM.render(<Demo/>, document.getElementById('root'))
回调 ref 在页面更新过程中调用次数的问题(可以忽略,影响不大):
第一次传入 null,第二次传入 DOM,每次更新渲染时会创建一个新的函数实例,React 会清空就的 ref 并设置新的 ref
解决:通过 ref 回调函数定义成 class 的绑定函数可以解决
class Demo extends React.Component {saveBtn = (c) => { this.btn = c }getRef = () => { const { btn } = this }render(){return(<button ref={this.saveBtn} onClick={this.getRef}>回调形式 ref, 内联写法</button>)}}ReactDOM.render(<Demo/>, document.getElementById('root'))
- createRef 形式
React.createRef()调用后可以返回一个容器,该容器可以存储被 ref 所标识的节点
该容器专人专用,需要多个 ref 需要重复 React.createRef()
class Demo extends React.Component {myRefs = React.createRef()getRef = () => {console.log(this.myRefs.current)}render(){return(<button ref={this.myRefs} onClick={this.getRef}>createRef 形式</button>)}}ReactDOM.render(<Demo/>, document.getElementById('root'))
4. React 中的事件处理
- 1、通过onXxx 属性指定事件处理函数(注意大小写)
React 使用的是自定义(合成)事件,而不是使用的原生 DOM 事件,为了更好的兼容性
React 中的事件是通过事件委托方式处理的(委托给组件最外层的元素)
事件的执行顺序为原生事件先执行,合成事件后执行
合成事件会冒泡绑定到 document 上,所以尽量避免原生事件与合成事件混用
如果原生事件阻止冒泡,可能会导致合成事件不执行,因为需要冒泡到 document 上合成事件才会执行
react 事件不能采用 return false 的方式来阻止浏览器的默认行为,而必须要地明确地调用 event.preventDefault() 来阻止默认行
- 2、通过 event.target 得到发生事件的 DOM 元素对象 – 不要过度使用 ref
发生事件的元素跟获取数据的元素是同个时,就省略 ref ,使用event.target
5. 受控组件 与 非受控组件
受控组件:页面所有输入都由 state 控制
非受控组件: 现取现用 ref
6. 高阶函数 - 函数柯理化
-
高阶函数
如果一个函数符合下面2个规范中的任何一个,那么函数就是高阶函数
1、若A函数,接收的参数是一个函数,A就可以称为高阶函数
2、若A函数,函数调用的返回值依然是一个函数,A就可以称为高阶函数
例:promise(() => {})、setTimeout(() => {})、arr.map(() => {}) (数据常见的方法)等 -
函数柯里化
通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
function sum(a){return b => {return c =>{return a + b + c}}}sum(1)(2)(3)
react 表单柯里化案例:
class Login extends React.Component {state = {username: '',password: ''}handleSubmit = () => {event.preventDefault();const {username, password} = this.state}// 柯里化实现saveFormData = (type) => {return (event) => {this.setState({[type]: event.target.value})}}// 不使用柯里化实现saveTypeData = (type, event) => {this.setState({[type]: event.target.value})}render(){return(<form onSubmit={this.handleSubmit}>{/* 柯里化实现 */}用户名:<input type="text" name="username" onChange={this.saveFormData('username')} />{/* 不使用柯里化实现 */}密码:<input type="password" name="password" onChange={event => this.saveTypeData('password', event)} /><button>登录</button></form>)}}
7. 组件生命周期

- constructor:构造函数
- render:渲染
- getDerivedStateFromProps:从props得到一个派生的state(17 新增),返回 null 或 状态对象
- componentDidMount:组件挂载完毕
- shouldComponentUpdate:控制组件是否更新 返回值 true 更新, false 不更新 (this.setState() 触发)
- getSnapshotBeforeUpdate:组件在更新之前获取快照(17 新增)
- componentDidUpdate:组件更新完毕
- React.unmountComponentAtNode(document.getElementById(‘root’)): 卸载组件
- componentWillUnmount:组件将要被卸载
componentWillMount :组件将要挂载(17 废弃)
componentWillUpdate :组件将要更新(17 废弃)(this.forceUpdate() 触发)
componentWillReceiveProp s:子组件将要接收新的 props,首次不调用(17 废弃)
旧生命周期总结:
-
初始化阶段(挂载时):由 ReactDOM.render() 触发初次渲染
constructor()
componentWillMount()
render()
componentDidMount() -
更新阶段:由组件内部 this.setState() 或 父组件 或 render 触发
shouldComponentUpdate()
componentWillUpdate()
render()
componentDidUpdate() -
卸载组件:由ReactDOM.unmountComponentAtNode() 触发
componentWillUnmount()
新生命周期总结:
-
初始化阶段(挂载时):由 ReactDOM.render() 触发初次渲染
constructor()
getDerivedStateFromProps()
render()
componentDidMount() -
更新阶段:由组件内部 this.setState() 或 父组件 或 render 触发
getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate() -
卸载组件:由ReactDOM.unmountComponentAtNode() 触发
componentWillUnmount()
常用:
componentDidMount() 在此做初始化:开启定时器,发送网络请求,订阅信息
componentWillUnmount() 在此做收尾工作:关闭定时器,取消订阅信息
getSnapshotBeforeUpdate 使用场景
// 数据不停新增,固定滚动条位置class NewList extends React.Component {state = { newArr: [] } componentDidMount() {setInterval(() => {const { newArr } = this.stateconst news = '新闻' + (newArr.length + 1)this.setState({newArr: [...news, newArr]})}, 1000)}// getSnapshotBeforeUpdate 在最近渲染输出(提交到DOM节点)之前调用,能在组件发生更改之前从 DOM 中捕获一些信息// 返回值将作为参数传递给 componentDidUpdategetSnapshotBeforeUpdate(){return this.refs.list.scrollHeight}componentDidUpdate(preProps, preState, scrollHeight) {// preProps: 先前props, preState: 先前状态this.refs.list.scrollTop += this.refs.list.scrollHeight - scrollHeight}render(){return (<div>{this.state.newArr.map((n, index) => {return <div key={index}> {n} </div>})}</div>)}}
8. 虚拟DOM 与 DOM Diffing 算法
-
虚拟 DOM 中 key 的作用
key 是虚拟 DOM 对象的表示
当状态中的数据发生变化后,react 会根据【新数据】 生成 【新的虚拟DOM】,随后React进行【新虚拟 DOM】与【旧虚拟DOM】的 diff 比较,规则:
1)、旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key
- 若虚拟 DOM 中的内容没变,直接使用之前的真实 DOM
- 若虚拟 DOM 中的内容变了,则生成新的真实 DOM ,随后替换掉页面之前的真实 DOM
2)、旧虚拟 DOM 中未找到新虚拟 DOM 相同的 key,根据数据创建新的 DOM , 随后渲染到页面 -
用index 作为 key 可能会引发的问题
1)、若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没必要的DOM更新,界面没问题,效率低
2)、如果结构中包含输入类的 DOM(input 等):会产生错误的DOM更新,界面有问题(输入类的 DOM 内容会保留)
3)、如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表用于展示,使用index 作为 key 是没有问题的 -
开发中如何选择key
1)、使用每条数据的唯一标识作为 key,例如id、手机号、学号等
2)、如果确定只是简单的展示数据,则用 index 也是没关系的
四、React 脚手架
1. 创建 react 应用
- 全局安装:npm install -g create-react-app
- 切换到想要创建项目的目录,使用命令: create-react-app 项目名
- 进入项目文件夹:cd 项目名
- 启动项目:npm start
vacode 的 react 扩展插件:ES7+ React/Redux/React-Native snippets
2. SPA的理解
- 单页面 Web 应用 (sing page application, SPA),单页面多组件
- 整个应用只有一个完整的页面,页面js、css、等资源只加载一次
- 点击页面中的链接不会刷新页面,只会做页面的局部刷新
- 数据都需要通过 ajax 请求获取,在前端一步展现
3. react 脚手架项目结构
public ----- 静态文件index.html ----- 应用主页面manifest.json ----- 应用加壳时的相关配置文件robots.txt ----- 爬虫规则文件src ----- 静态文件App.js ----- App组件App.test.js ----- 用于给 App 做测试index.js ----- 入口文件reportWebVitals.js ----- 页面性能分析文件(需要 web-vitals 库的支持)setupTests.js ----- 用于组件测试
4. 简单案例 hello,react!
创建App组件:两种引入 React.Component 的写法
- 第一种:import React from ‘react’
import React from 'react' // 创建并暴露app组件export default class App extends React.Component{render() {return(<div>hello,react!</div>)}}
- 第二种:import React, {Component} from ‘react’
// 能直接拿到 Component 不是因为解构赋值, 而是因为虽然 Component 在 react 的原型上,// react 默认暴露了React,并且分别暴露(export)了 Component 方法import React, {Component} from 'react' // 创建并暴露app组件export default class App extends Component{render() {return(<div>hello,react!</div>)}}
创建入口文件 index.js
import React from 'react'// 18版本的写法 react-dom/clientimport ReactDOM from 'react-dom/client'import App from './App' // 18版本的写法 ReactDOM.createRootconst root = ReactDOM.createRoot(document.getElementById('root'));root.render(<React.StrictMode><App /></React.StrictMode>);
5. 样式模块化
React 的样式是全局化,需做处理避免样式冲突,解决方法:
- 使用 css 预编译语言,Less,Sass,Stylus 等
- 样式模块化
1、样式文件后缀 .css 改成 module.css;例:index.module.css
2、使用 jsx 引入 module.css;例:import cssName from './index.module.css '; 使用 className = {cssName.title}
6. 配置代理
假设项目端口是:http://localhost:3000
-
方式一:在 package.json 中配置 proxy
优点:配置简单,前端请求资源时可以不加任何前缀
缺点:只能配置一个代理,不能配置多个代理
工作方式:上述方式配置代理,当请求了 3000 不存在的资源时,那么请求会转发给 5000 (优先匹配前端资源){"proxy": "http://localhost:5000",} -
方式二:在项目根目录处创建 setupProxy.js (名字不可改)
优点:可以配置多个代理,可以灵活的控制请求是否走代理
缺点:配置繁琐,前端请求资源时必须加前缀// 注意区分 http-proxy-middleware 版本不同写法const proxy = require('http-proxy-middleware') module.exports = function(app) {app.use(// 1、http-proxy-middleware低版本(2以下) proxyproxy('/api', { // 遇见/api前缀请求,就会触发该代理配置target: 'http://localhost:5000', // 请求转发给谁changeOrigin: true, // changeOrigin 控制服务器收到的请求头中 HOST 字段 pathRewrite: {'^/pai': ''} // 重写请求路径(必须),去除请求前缀,保证交给服务器的是正常请求地址}),// 2、http-proxy-middleware高版本(2以上) proxy.createProxyMiddlewareproxy.createProxyMiddleware('/api2',{target:'http://localhost:5001',changeOrigin:true,pathRewrite:{'^/api2':''}}),)}/*** changeOrigin 设置为 true 时,服务器收到的请求头 host 为:localhost:5000* changeOrigin 设置为 false 时,服务器收到的请求头 host 为:localhost:3000* changeOrigin 默认为 false, 一般会设置为 true*/方法一 或 方法二 添加后,需重启项目才生效
请求接口:http://localhost:5000/api/index/index
假设项目端口是:http://localhost:3000
实际请求写法可以是:// 此时 url 两种值均可 "http://localhost:3000/api/index/index" 或 "/api/index/index"let url = "/api/index/index"axios.get(url).then(response => {console.log('成功了',response.data);},error => {console.log('失败了',error);})
7. 消息订阅 与 发布机制 PubSub
-
1、先订阅,再发布;在需要数据的组件订阅,在发送数据的组件发布
订阅: let 订阅名 = PubSub.subscribe(消息名, function(消息名, 数据){})
发布: PubSub.publish(消息名,数据) -
2、适用于任意组件间的通讯
-
3、要在组件的 componentWillUnmount 中取消订阅
取消订阅:PubSub.unsubscribe(订阅名) -
4、react 使用 PubSub 实现搜索例子(先订阅,再发布)
List.jsx
import React, { Component } from 'react'import PubSub from 'pubsub-js'export default class List extends Component {state = { users:[],isFirst: true, // 记录是否是第一次请求isLoading: false, // 标识是否处于加载中err: "", // 储存请求错误信息}componentDidMount() {// 订阅消息this.searchPubSub = PubSub.subscribe('getsearchData', (msgName, data) => {console.log(msgName); // getsearchDatathis.setState(data)})}componentWillUnmount() {// 页面销毁,取消消息订阅PubSub.unsubscribe(this.searchPubSub)}render() {const {users, isFirst, isLoading, err} = this.statereturn (<div className='flex-wrap'>{isFirst? <h2>欢迎使用!</h2> :isLoading ? <h2>loading....</h2> :err ? <h2>{err}</h2> : users.map((userObj) => {return (<div className='box' key={userObj.id}><img src={ userObj.avatar_url } alt="head_portrait"></img> <span>{ userObj.login }</span></div>)})}</div>)}}Search.jsx
// Search.jsx 设置发布import React, { Component } from 'react'import PubSub from 'pubsub-js'import axios from 'axios' export default class Search extends Component { search = () => {const { keyWordElement: {value: keyword} } = this// 发送请求前通知 list 更新状态PubSub.publish('getsearchData', {isFirst: false, isLoading: true})axios.get(`https:api.github.com/search/users?q=${keyword}`).then(response => {// 请求成功通知 list 更新状态PubSub.publish('getsearchData', {isLoading: false, users: response.data.items, err: ""})},err => {// 请求失败通知 list 更新状态PubSub.publish('getsearchData', {isLoading: false, err: err.message})})}render() {return (<div><section><h3> Search 用户</h3><input ref={c => this.keyWordElement = c} type="text"></input><button onClick={this.search}>搜索</button></section></div>)}}App.jsx
import React, { Component } from 'react'import Search from './components/Search'import List from './components/List'export default class App extends Component {render() {return (<div><Search /><List /></div>)}}
8. React-router 5
react-router 的理解:react 专门用来实现一个 SPA 应用的一个插件库: 分web(用于网页:react-router-dom) 、native(原生应用开发用)、any(任何平台都可用)
- 路由的理解
一个路由就是一个映射关系(key对应value),key 为路径,value 可能是function 或 component。
路由分类(后端路由 与 前端路由):
1)、 后端路由:value 是 function 用来处理客户端提交的请求(后端路由即接口) 注册路由:router.get(path, function()=>{}) 工作过程:node 接收到一个提交,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据2)、前端路由: 浏览器端路由 value 是component,用于展示页面内容注册路由:<Router path='/test' component={Test} /> 工作过程:当浏览器的 path 变为 /test 时,当前路由组件就会变为Test 组件路由原理:基石是浏览器 BOM 身上的 history(window.history), 浏览器的 history 数据结构是栈:先进后出; 用于管理浏览器历史记录等; 可用 history.listen 监听路由变化history 分两种:history: H5 推出的 History Interface,利于 pushState() 和 replaceState()方法 - 兼容性差hash: 利用的是 hash 值,类似锚点,链接带#,兼容性强hash 模式和 history 模式都属于浏览器自身的特性vue 的 history 模式需要后端配合,如果后端不配合,刷新页面会出现404hash不需要url 中的 # 后的参数,不会作为资源发送给服务器
- React-router 相关api
路由安装:
5版: npm i react-router-dem@56版: npm i react-router-dem
-
BrowserRouter、hashRouter、Route、Link
路由器 Router 管理着路由Route, Route 要包在路由器 Router(BrowserRouter or HashRouter) 之中;整个应用只能用一个路由器管理(注意区分 路由器Router 与 路由 Route)。
1、BrowserRouter、hashRouter 区别:
1、底层原理不一样BrowserRouter 使用的是 H5 的 history API,不兼容 IE9 及以下版本hashRouter 使用的是 URL 的哈希值2、 path 表现形式不一样BrowserRouter 的路径中没有#,例如:http://localhost:3000/homeHashRouter 的路径有#,例如:http://localhost:3000/#/home3、刷新后对路由 state 参数的影响BrowserRouter 没有任何影响,因为 state 保存在 history 对象中hashRouter 刷新后会导致路由 state 参数丢失4、hasRouter 可以用于解决一些路径错误相关的问题2、路由的基本使用:
导航区用 Link 或 NavLink,展示区用 Route, 最外侧要包裹一个 BrowserRouter 或 HashRouter
index.js 文件 设置路由器(BrowserRouter or HashRouter):import {BrowserRouter} from 'react-router-dom'ReactDOM.render(<BrowserRouter><App/></BrowserRouter>,document.getElementById('root'))App.js 编写路由链接、注册路由:
import React, { Component } from 'react'import {Link, Route} from 'react-router-dom'import Home from './components/Home'export default class App extends Component {render() {return (<div className='main'><div className='left'>{/* 编写路由连接 - react 中靠路由连接实现切换组件*/}<Link to="/home">home</Link></div><div className='right'>{/* 注册路由 - 组件展示区 */}<Route path="/home" component={Home}/></div></div>)}} -
Route
1、Route 模糊匹配 和 严格匹配(exact)路由匹配遵循 - 最左侧匹配原则模糊匹配:输入的路径 必须要包含 匹配的路径,且顺序要一致,默认使用例子: <Link to="/home/a/b">home</Link> 可以匹配到<Route path="/home" component={Home}/> 严格匹配:输入的路径与匹配的路径必须一致 exact={true} 开启严格匹配:<Route exact={true} path='/demo' component={Demo}/>严格匹配不要随便开启,有时候开启会导致无法继续匹配多级路由2、Route 嵌套路由(多级路由)
1、注册子路由要写上父级路由的 path 值2、路由的匹配时按照注册路由的顺序进行的多级路由匹配的步骤:首先路由匹配第一批注册的路由,例如 /home/tab 当匹配到 /home 时,就会先显示 home 组件随后在home 组件中继续匹配路由/home/tab, 匹配到则展示,匹配不到则跳转到 Redirect 指定的路由3、解决多级路径刷新页面样式丢失的问题
public 中的 index.html 引入样式时:1、 './' 改为 '%PUBLIC_URL%/' (常用, PUBLIC_URL react 特有)2、 路径 './' 改为 '/'3、 使用 HashRouter4、路由( Link、NavLink )的两种跳转方式 push、replace
1、默认是 push 模式,会留下痕迹2、replace 不留下痕迹开启 replace: <Link replace to='/demo'/>5、编程式路由导航
不写 Link 和 NavLink,借助this.props.history对象身上的 API 对路由进行跳转、前进、后退利用: go、goBack、goForward、push、replace 实现路由跳转this.props.history.go(n)this.props.history.goBack()this.props.history.goForward()replace:this.props.history.replace(`/home/tabTow/detail/${id}`)this.props.history.replace(`/home/tabTow/detail?id=${item.id}`)this.props.history.replace(`/home/tabTow/detail`,{id})push: this.props.history.push(`/home/tabTow/detail/${id}`)this.props.history.push(`/home/tabTow/detail?id=${item.id}`)this.props.history.push(`/home/tabTow/detail`,{id})6、向路由组件传参
1、params 参数路由连接(携带参数):<Link to={`/home/tabTow/detail/${id}/${title}`}>{item.title }</Link> 注册路由(声明接收): <Route path="/home/tabTow/detail/:id/:title" component={Details} />组件接收参数:this.props.match.params2、search 参数路由连接(携带参数):<Link to={`/home/tabTow/detail?id=${item.id}&title=${item.title}`}>{item.title}</Link>注册路由(无需声明,正常注册即可):<Route path="/home/tabTow/detail" component={Details} />组件接收参数:this.props.location备注:获取到的 search 是 urlencode 编码字符串,需要借助 querystring 解析import qs from 'qs'const {id, title} = qs.parse(search.slice(1))3、state 参数路由连接(携带参数):<Link to={{pathname:'/home/tabTow/detail', state:{id:item.id,title:item.title}}}>{item.title}</Link>注册路由(无需声明,正常注册即可):<Route path="/home/tabTow/detail" component={Details} />组件接收参数:this.props.location.state || {}备注:此处的 state 是路由身上的 state,不是组件的 state, 刷新也可以保留住参数7、路由组件 与 一般组件
1、写法不同:路由组件: <Route path='/demo' component={Demo} />一般组件: <Demo /> 2、存放位置不同:路由组件:pages一般路由:components 3、接受到的 props 不同:一般组件: 组件标签传递了什么,子组件 props 就收到什么路由组件:接受到三个属性histor:{ go, goBack, goForward, push, replace }location: { pathname, search, state }match: { isExact, params, path, url } isExact: 模糊匹配 or 精准匹配 -
Link 和 NavLink
Link 没有高亮效果
NavLink 标签可以通过 activeClassName 设置高亮类名,默认值是 active// NavLink 封装import React, { Component } from 'react'import {NavLink} from 'react-router-dom'export default class MyNavLink extends Component {render() {return (<NavLink activeClassName='linkactive' {...this.props} />)}}// 封装使用<MyNavLink to="/home" >home</MyNavLink>// 标签体内容是一个特殊的标签属性 children// children 可以通过 this.props.children 获取标签体内容,再利用{...this.props}绑定到组件上 -
Switch
通常情况下,path 和 component 是一一对应关系,不使用 Switch, 路由匹配后还会继续往下匹配
Siwtch 作用:实现单一匹配 - 路由匹配到后,终止往下匹配,提高路由匹配效率// 语法<Switch><Route path='/demo' component={Demo} /></Siwtch> -
Redirect 重定向
一般写在所有路由注册的最下方,当所有路由都匹配不上的时候,跳转到 Redirect 指定的路由// 语法<Switch> <Route path='/demo' component={Demo} /><Redirect to="/demo" /></Switch> -
withRouter
只有路由组件才有 history, 一般组件没有 history,无法使用 this.props.history 的AP方法
withRouter 可以加工一般组件,让一般组件具备路由组件所特有的 API
withRouter 返回值是一个新组件
语法: export default withRouter(一般组件)import React, { Component } from 'react'import { withRouter } from 'react-router-dom'class Header extends Component {back = () => {this.props.history.back()}render() {return (<div><button onClick={this.back}>回退</button></div>)}}// 暴露 withRouter 加工过的一般组件export default withRouter(Header)// 使用组件<Header />
8. React-router 6
与 react-router 5 版本小相比,改变了什么?1、内置组件的变化:移除<Switch/>,新增了<Routes/>等2、语法的变化:component = {Home} 变为 element = {<Home/>}等3、新增多个hook:useParams、useNavigate、useMatch 等4、官方明确推荐函数式组件
-
Route
语法变化 component 变为 element,属性值为标签形式
caseSensitive属性用于指定,匹配时是否区分大小写(默认false)
Route 可以嵌套使用,且可配合 useRoutes() 配置“路由表”,当需要通过<Outlet> 组件来渲染其子路由// React-router 5<Route path="/home" component={Home} />// React-router 6<Routes>// path属性用于定义路径,element 属性用于定义当前路径所对应的组件<Route path="/home" element={<Home />} />// 定义嵌套路由,home是以及路由,对应的路径 /home*/<Route path="home" element={<Home />} ><Route path="news" element={<news />} ></Route><Route path="news2" element={<news2/>} ></Route></Route>// Router 也可以不写 element 属性,这是就是用于展示嵌套的路由,对应的路径是 /home/xxx<Route path="home"><Route path="xxx" element={<xxx/>} /></Route></Routes> -
Routes 代替 Switch
React-router 6 移除了 Switch,新增了Routes, Routes 跟 Switch的规则一样,匹配到一个,就不会继续向下匹配,
Routes 要和 Route 配合使用,且必须用 Routes 包裹 Route:React-router 5 不写 Switch 不会报错,React-router 6 Route 不用 Routes 包裹会报错// React-router 5<Switch><Route path="/home" component={Home} /></Switch>// React-router 6<Routes><Route path="/home" element={<Home />} /></Routes> -
Navigate 代替 Redirect 重定向
Navigate 只要渲染就会引起试图的切换,to 的属性必须写,replace 属性可选
// React-router 5<Switch><Route path="/home" component={Home} /><Redirect to="/about" /></Switch>// React-router 6<Routes><Route path="/home" element={<Home />} /><Route path="/" element={<Navigate to="/about" />} /></Routes>
页面跳转例子:
import React,{useState} from 'react'import {Navigate} from 'react-router-dom'export default function Demo(){ const [sum, setSum] = useState(1) return ( <div>{sum === 2? <Navigate to="/home" replace={true}/> : <h3>当前的sum的值是:{sum}</h3> }<button onClick={()=>setSum(2)}>点击变2</button></div>)}
-
NavLink
React-router 6指定高亮的类名语法修改了// React-router 5<NavLink activeClassName='linkactive' {...this.props} />// React-router 6<NavLink className={isActive => isActive ? 'linkactive' : 'link'} {...this.props} />// 优化写法function computedClassName(isActive){return isActive ? 'linkactive' : 'link'}<NavLink className={computedClassName} {...this.props} /> -
useRoutes路由表 与 Outlet嵌套路由
定义路由表:创建routes文件夹,生成inde.jsx文件import Home from './pages/Home' import About from './pages/About' import TabOne from './pages/Home/TabOne' import TabTow from './pages/Home/TabTow' import {Navigator} from 'react-router-dom' export default[{path: '/home',element: <Home />,// 定义子路由children:[{path: 'tabOne',element: <TabOne />},{path: 'tabTow',element: <TabTow />}]},{path: '/about',element: <About />},{path: '/',element: <Navigator to="/home" />} ]使用路由表 uesRoutes
import React from 'react' import {NavLink, uesRoutes} from 'react-router-dom'import routes from './routes' export default function App() {// 根据路由表生成对应的规则const element = uesRoutes(routes)render() {return (<div className='containt'>{/* 路由连接 */}<NavLink to="/home">home</NavLink><NavLink to="/about">about</NavLink>{/* 注册路由 */}{element}</div>)} }Outlet嵌套路由:Home 组件展示子路由,因为App已经引入了routes.js,所以 Home作为App组件不需要引入
import React from 'react' import { NavLink, Outlet } from 'react-router-dom'export default function Home() {return (<div className='containt'>{/* 路由连接-注意子路由不写/ */}<NavLink to="tabOne">home</NavLink><NavLink to="tabTow">about</NavLink>{/* 指定路由组件呈现的位置 */}{Outlet}</div>) } -
路由传参
1、传递 Prarams 参数,使用useParams 或 useMatch 获取// 路由表 import Home from './pages/Home' import {Navigator} from 'react-router-dom' export default[{// Prarams 参数需要占位path: '/home/:id/:title',element: <Home />,}{path: '/',element: <Navigator to="/home" />}]// 使用路由表uesRoutesimport React from 'react' import {NavLink, uesRoutes} from 'react-router-dom'import routes from './routes' export default function App() {// 根据路由表生成对应的规则const element = uesRoutes(routes)render() {return (<div className='containt'>{/* 路由连接- 定义参数 */}<NavLink to={`/home/${id}/${title}`}>home</NavLink>{/* 注册路由 */}{element}</div>)} }Home 组件 接收参数
import React from 'react' import { useParams, useMatch } from 'react-router-dom'export default function Home() {// useParams获取 路由参数const {id, title} = useParams()// useMatch 获取 路由参数const x = useMatch('/home/:id/:title')return (<div className='containt'> </div>) }2、传递 search参数,使用useSearchParams 或 useLocation获取
import Home from './pages/Home'import {Navigator} from 'react-router-dom'export default[{path: '/home',element: <Home />,}{path: '/',element: <Navigator to="/home" />}]// 使用路由表uesRoutesimport React from 'react' import {NavLink, uesRoutes} from 'react-router-dom'import routes from './routes' export default function App() {// 根据路由表生成对应的规则const element = uesRoutes(routes)render() {return (<div className='containt'>{/* 路由连接- 定义参数 */}<NavLink to={`/home?id=${id}&title=${title}`}>home</NavLink>{/* 注册路由 */}{element}</div>)} }Home 组件 接收参数
import React from 'react' import { useSearchParams, useLocation } from 'react-router-dom'export default function Home() {// useSearchParams获取路由参数,search.get()获取参数值const [search, setSearch] = useSearchParams()console.log(search.get('id')) // useLocation 获取参数 - 获取的是对象const x = useLocation()return (<div className='containt'>// setSearch 用于更新参数值 <button onClick={()=>setSearch('id=1&title=花姐')}>更新链接参数</button></div>) }3、传递 state 参数,使用useLocation获取
import Home from './pages/Home'import {Navigator} from 'react-router-dom'export default[{path: '/home',element: <Home />,}{path: '/',element: <Navigator to="/home" />}]// 使用路由表uesRoutesimport React from 'react' import {NavLink, uesRoutes} from 'react-router-dom'import routes from './routes' export default function App() {// 根据路由表生成对应的规则const element = uesRoutes(routes)render() {return (<div className='containt'>{/* 路由连接- 定义参数 */}<NavLink to="/home" state={{ id, title }}>home</NavLink>{/* 注册路由 */}{element}</div>)} }Home 组件 接收参数
import React from 'react' import { useLocation } from 'react-router-dom'export default function Home() {// useLocation 获取参数 - 获取的是对象const state = useLocation()return (<div className='containt'></div>) } -
编程式路由导航 useNavigate
import React from 'react' import {NavLink, uesRoutes, useNavigate} from 'react-router-dom'import routes from './routes' export default function App() {const navigate = userNavigate()// 根据路由表生成对应的规则const element = uesRoutes(routes)function goToDetail(id){// 子路由不能写 /navigate('tabOne', {repalce: false, // 不写默认false// 如果传递params参数与search参数,则拼接在链接上,不写statestate:{id}})// navigate(-1), navigate(1) 实现前进后退}render() {return (<div className='containt'>{/* 路由连接- 定义参数 */}<NavLink to="/home" state={{ id, title }}>home</NavLink><button onClick="goToDetail">点击跳转</button>{/* 注册路由 */}{element}</div>)} } -
useInRouterContext()、useNavigation()返回当前的导航类型、userOutlet()呈现当前组件中渲染的嵌套路由、useResolvePath()用于解析path,search,hash值
10. ant-design 的基本使用
官网: https://ant.design/index-cn
安装: npm i antd
antd 提倡按需加载模块,使用相关 api 时,需要注意引入的文件名
antd 适用于搭建后台管理系统
按需引入样式:官方配置文档-高级配置
11. redux
redux 是一个专门用于做状态管理的JS库,可以用在 react、angular、 vue等项目中,但是基本与 react 配合使用
官网: https://www.redux.org.cn/
安装:npm i redux
redux开发者工具:谷歌安装扩展 redux-devtools,还要在项目安装 redux-devtools-extension 库,并在 store.js 引用:
import {composeWithDevTools} from 'redux-dextools-extension'export default createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))
-
作用: 集中式管理 react 应用中多个组件共享的状态(独立于所有组件之外)
-
使用场景:
某个组件的状态,需要让其他组件可以随时拿到(共享)
一个组件需要改变另外一个组件的状态(通信)
总体原则:能不用就不用,如果不用比较吃力才考虑使用 -
redux 工作流程:

React Components: 触发动作 -
三个核心概念 action、reducer、Store
action(动作对象):1、同步 action当 action 的值为一般对象是为同步 action,包含两个属性:type:标识属性,值为字符串,唯一,必要属性data:数据属性,值任意属性,可选属性2、异步 action当 action 的值为函数时,为异步 action,异步 action 中一般都会调用同步 action reducer:reducer 的本质是一个函数,接收 preState, action, 返回加工后的新 state reducer 有两个作用:初始化状态,加工状态reducer 被第一次调用时,是 store 自动触发的,传递的 preState 是 undefinedStore:将state、action、reducer 联系在一起的对象(不做数据处理)如何得到此对象?import { createStore } from 'redux'import reducer from './reducers'const store = createStore (reducer )此对象的功能:getState():得到 state dispatch(action):分发 action,触发 reducer 调用。产生新的 statesubscribe(listener):注册监听,当产生了新的 state 时,自动调用在组件的 componentDidMount 中或在 index.js 中通过 store.subscribe() 检测 store 中状态的改变一旦改变重新调用 this.setState({}) 或 重新渲染 <App/> -
合并Reducers
每个reducer只负责管理全局state中它负责的一部分。每个reducer的state参数都不同,分别对应它管理的那部分state数据
Redux 提供了combineReducers()来合并所有的reducer/*** 该文件用于汇总所有的reducer为一个总的reducer* combineReducers 用于汇总多个reducer*/ import { combineReducers} from 'redux' // 引入为 count 组件服务的 reducerimport count from './count'import personObj from './person' // 汇总所有的reducer 变为一个总的reducerexport default combineReducers({count,personObj})
redux 实践:实现运算:
-
store.js
引入 redux 中的 createStore 函数,创建一个 store, createStore 调用时要传入一个为其服务的 reducer
记得暴露 store 对象/*** 该文件专门用于暴露一个 store 对象,整个应用只有一个 store 对象*/ // 引入 createStore, 专门用于创建 redux 最核心的 store 对象, applyMiddleware 用于执行中间件import {createStore, applyMiddleware} from 'redux'// 引入为 count 组件服务的 reducerimport countReducer from './count_reducer'// 引入 redux-thunk 用于支持异步 action import thunk from 'redux-thunk' export default createStore(countReducer, applyMiddleware(thunk)) -
count_reducer.js
reducer 的本质是一个函数,接收 preState, action,返回加工后的状态
reducer 有两个作用:初始化状态,加工状态
reducer 被第一次调用时,是 store 自动触发的,传递的 preState 是 undefined/*** 该文件是用于创建一个 Count 组件服务的 reducer, reducer 的本质就是一个函数* reducer 是一个纯函数,相同输入得到相同结果* reducer 函数会接到两个参数,分别为:之前的状态(preState)、动作对象(action)*/import {INCREMENT} from './constant'export default function countReducer(preState = 0, action) {const {type, data} = action// 根据 type 决定如何加工数据switch (type) {case INCREMENT:return preState + datadefault:return preState}} -
count_action.js
专门用于创建 action 对象/*** 该文件专门为 Count 组件生成 action 对象* 当 action 的值为对象时,为同步 action, action({type,data})* 当 action 的值为函数时,为异步 action, action(function()) 异步 action 中一般都会调用同步 action,异步 action 不是必要的*/import {INCREMENT} from './constant'// 同步 actionexport const createIncrementAction = data => ({type: INCREMENT, data})// 异步 action : 注意异步action 里面调用 dispatch 的写法export const createIncrementAsyncAction = (data, time) => {return (dispatch)=>{setTimeout(()=>{dispatch(createIncrementAction(data))}, time)}} -
constant.js
放置容易写错 action 中的 type 值/*** 该模块用于定义action 对象中 type 类型的常量值*/ export const INCREMENT = 'increment' -
CountRedux.jsx
使用 action 的组件,注意 store.subscribe 的写法
在组件的 componentDidMount 中或在 index.js 中通过 store.subscribe() 检测 store 中状态的改变
一旦改变重新调用 this.setState({}) 或 重新渲染 <App />import React, { Component } from 'react'// 引入 store 用于获取 redux 中保存的状态import store from '../redux/store'// 引入 actionCreator, 用于创建 action 对象import {createIncrementAction, createIncrementAsyncAction} from '../redux/count_action'export default class CountRedux extends Component {componentDidMount(){// 检测 redux 中状态的变化,只要变化,就调用 setState 触发 render 刷新store.subscribe(()=> {this.setState({})}) /**store.subscribe 也可以写在入口文件 index.js - 一劳永逸store.subscribe(()=> {root.render( <React.StrictMode> <App /> </React.StrictMode> );})*/}increment = ()=> {let {value} = this.selectNumber// 通知 redux 操作 同步store.dispatch(createIncrementAction(value*1))} incrementAsync = ()=> {let {value} = this.selectNumber// setTimeout(()=>{// store.dispatch(createIncrementAction(value*1))// }, 1000)// 异步 action,与上面的 setTimeout 效果一致store.dispatch(createIncrementAsyncAction(value*1, 1000)) }render() {return (<div><div>当前求和为: { store.getState() }</div><select ref={c => this.selectNumber = c}><option value="1">1</option><option value="2">2</option></select><button onClick={this.increment}>+</button><button onClick={this.incrementAsync }>异步+</button></div>)}}
12. React-redux
react-redux 是由 react 官方提供,redux不是 react 官方提供
react-redux 实际也是通过 redux 库创建 store ,再由 react-redux 的 connect 函数创建容器组件,并由组件容器与store交互,而不是ui组件自身去触发
注意: React-redux 的store.js、action.js、reducer.js、constant.js 创建,以及合并多个redux都与 redux 一样
1、所有的 UI 组件都应该包裹一个容器组件,他们是父子关系2、容器组件是真正和 redux 打交道的,里面可以随意的使用 redux 的 api3、UI 组件不能使用任何 redux 的 api4、容器组件会传给 UI 组件:1)、redux 中所保存的状态,2)、用于操作状态的方法5、UI 组件放在 component,容器组件放在 containers6、备注:容易给 UI 组件传递:状态、操作状态的方法,均通过 props 传递
基本使用:
1、容器组件和ui组件整合一个文件UI 组件:不能使用任何 redux 的 api,只负责页面的呈现、交互等,在 UI 组件中通过 this.props.key 读取和操作状态容器组件: 由 react-redux 的 connect 函数创建,负责和 redux 通信,将结果与操作状态的方法传递给 UI 组件创建一个容器组件:connect(mapStateToProps,mapDispatchToProps)(UI 组件)mapStateToProps:映射状态,返回值是一个对象mapDispatchToProps: 映射操作状态的方法,返回值是一个对象, 可以简写成一个对象简写语法:connect(state => {key: value}, // 映射状态{key: xxxAction} // 映射操作状态的方法)(UI组件)
// 整合容器组件和ui组件示例代码import React, { Component } from 'react' // 引入 connect 用于连接 ui 组件 与 reduximport {connect} from 'react-redux'// 引入 action 方法 import {increment, incrementAsync} from '../../redux/action/count'// 此处的 ui 组件不暴露class Count extends Component { increment = ()=> {let {value} = this.selectNumberthis.props.increment(value*1)} incrementAsync = ()=> {let {value} = this.selectNumber this.props.incrementAsync(value*1, 2000)} render() {return (<div><h2>我是Count组件</h2><div>总人数为: {this.props.presonCount},当前求和为: { this.props.count }</div><select ref={c => this.selectNumber = c}><option value="1">1</option><option value="2">2</option></select><button onClick={this.increment}>+</button><button onClick={this.incrementAsync}>异步加</button></div>)}}/** 引入 connect 生成一个 Count 容器组件,并暴露 */export default connect(// mapStateToProps 的简写state => ({ count: state.count, presonCount: state.personObj.length }), // mapDispatchToProps 一般写法// dispatch => ({// increment: number => dispatch(increment(number)),// incrementAsync: (number, time) => dispatch(incrementAsync(number, time))// })// mapDispatchToProps 的简写{increment,incrementAsync})(Count)
2、无需自己给容器组件传递 store, 给 <App/> 包裹一个 <Provider store={store}> 即可使用react-redux 后也不用再自己检测redux 中状态的改变,容器组件可以自动完成这个工作
import React from 'react'import ReactDOM from 'react-dom/client'import App from './App'// Provider 用于批量给组件传递 storeimport store from './redux/store';import {Provider} from 'react-redux'const root = ReactDOM.createRoot(document.getElementById('root'));root.render(<React.StrictMode>{/* 此处需要用 Provider 包裹 App, 目的是让 App 的所有后代容器组件都能接收到 store */}<Provider store={store}><App /></Provider></React.StrictMode>);
13. react 项目打包
打包后可以借助serve库运行打包好的文件
安装: npm i serve -g
到打包好的index目录运行:serve
扩展
1. setState
setState 更新状态的2种写法:1、setState(stateChange, [callback]) 对象式的 setState1)、stateChange 为状态改变对象(该对象可以体现出状态的更改)2)、callback 是可选的回调函数,它在状态更新完毕,界面也更新后(render调用后)才被调用2、setState(updater, [callback]) 函数式的 setState1)、updater 为返回 stateChange 对象的函数2)、updater 可以接收到 state 和 props 3)、callback 是可选的回调函数,它在状态更新、界面也更新后(render调用后)才被调用总结:对象式的 setSate 是函数式的 setState 的简写方式(语法糖)使用原则:如果新状态不依赖原状态,使用对象方式如果新状态依赖原状态,使用函数方式如果需要在 setState() 执行后获取最新的状态数据,要在第二个 callback 函数中读取
2. lazyLoad
路由组件的 lazyLoad
1、通过react的lazy函数配合import()函数动态加载路由组件 ===》 路由组件代码会被分开打包
const Login = Lazy(()=>import('@/pages/Login'))
2、通过指定在加载得到路由打包文件前显示一个自定义 loading 界面
<Suspense fallback={<h1>loading...</h1>}><Switch><Route path='/xxx' componet={Xxx}><Redirect to="login"></Switch></Suspense>
3、 标签的 fallback 可以传入一个组件
// 注意写在 Suspense fallback 中的 Loading 组件不能使用懒加载import Loading from '../Loading'<Suspense fallback={<Loading/>}><Switch><Route path='/xxx' componet={Xxx}><Redirect to="login"></Switch></Suspense>
3. Hooks
Hook是React 16.8.0版本增加的新特性/新语法,可以在函数组件中使用 state 以及其他的 React 特性
-
三个常用的 Hooks
State Hook:React.useState()
Effect Hook: React.useEffect()
Ref Hook: React.useRef() -
State Hook
State Hook 能让函数组件也可以有 state 状态,并进行状态数据的读写操作 语法:const [xxx, setXxxx] = React.useState(initValue) useState()说明:参数:第一次初始化指定的值在内部作缓存返回值:包含2个元素的数组,第1个为内部当前状态值,第2个为更新状态值的函数 setXxx 2种写法:setXxx(newValue):参数为非函数值,直接指定新的状态值,内部用其覆盖原来的状态值setXxx(value => newValue): 参数为函数,接收原本的状态值,返回新的状态值,内部用其覆盖原来的状态值import React from 'react' function Demo(){const [count, setCount] = React.useState(0)const [name, setName] = React.useState('橘宝') function add() {// 第一种写法// setCount(count + 1)// 第二种写法setCount(count => count+1)} function changeName() {setName('花姐')} return ( <div><h2>当前数字为{count}, 名字:{name}</h2><button onClick={add}>加1</button><button onClick={changeName}>改名</button></div>)} export default Demo -
Effect Hook
Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子) React 中的副作用操作:发送ajax请求、设置订阅/启动定时器、手动更改真实DOM 语法和说明:React.useEffect(()=>{// 在此处可以执行任何带副作用操作, 相当于 componentDidMount 与 componentDidUpdate比如页面初始化,加载数据等return()=>{// 在组件卸载前执行,相当于 componentWillUnmount,可以做一些收尾工作,比如清除定时器,取消订阅等}}, [stateVlaue]) // 如果指定的是[],回调函数只会在第一次render() 后执行// 如果不指定,则会监听所有的 state 更新,并每次更新都执行,如果传对应的state,则监听对应的state 可以把 useEffect Hook 看做是三个函数的结合:componentDidMount()、componentDidUpdate()、componentWillUnmount()import React from 'react'import root from '../../index' function Demo(){ const [count, setCount] = React.useState(0) React.useEffect(()=> {// 此处返回的函数体相当于 componentDidMount 与 componentDidUpdatelet timer = setInterval(()=>{// 注意此处的setCount需要写函数形式,否则拿不到countsetCount(count => count+1)}, 1000)// 此处返回的函数相当于 componentWillUnmountreturn () => {clearInterval(timer)}}, [])function unmount() {// 注意 18 版本的卸载,与之前的区别,需要在入口文件中,将 root 暴露root.unmount()} return ( <div><h2>当前数字为{count}</h2><button onClick={unmount}>卸载组件</button></div>)} export default Demo -
Ref Hook
Ref Hook 可以在含糊组件中储存/查找组件内的标签或任意其他数据 语法:const refContainer = useRef() 作用:保存标签对象,功能与 React.createRef() 一样import React from 'react' function Demo(){ const myRef = React.useRef() function show() {console.log(myRef.current.value)} return ( <div><input type="text" ref={myRef}/><button onClick={show}>打印数据</button></div>)}export default Demo
4. Fragment
import React, { Component, Fragment } from 'react' export default class Demo extends Component {render() {return (<Fragment key={1}><div>Fragment 标签可以代表外层根节点,并且会被解析,页面渲染时不显示</div><div>Fragment 可以设置key值</div></Fragment>)}}
5. Context
用于组件间通信方式,常用于【祖组件】与【后代组件】间通信
注意:在应用开发中,一般不用context,一般用来封装 react 插件
1)、创建 Context 容器对象const XxxContext = React.createContext()2)、渲染子组件时,外面包裹 XXXContext.Provider,通过 Value 属性给后代组件传递数据:<XxxContext.Provider value={数据}> 子组件 </XxxContext.Provider>3)、后代组件读取数据: // 第一种方式:适用于类组件static contextType = XxxContext // 声明接收 contextthis.context // 读取context的value数据// 第二种方式:函数组件与类组件都可以<XxxContext.Consumer>{value => { 要显示的内容 } // value 就是context中的value数据}</XxxContext.Consumer>
import React, { Component } from 'react'const MyContext = React.createContext()const {Provider, Consumer} = MyContextexport default class A extends Component {state = {username:'花姐'}render() {let {username} = this.statereturn (<div><p>我是A组件,我的用户名是: {username}</p><Provider value={{username, age: 5}}><B/></Provider></div>)}}class B extends Component {render() {return (<div><p>我是B组件</p><C/><D/></div>)}}// 类式接收contextclass C extends Component {// 声明接收contextstatic contextType = MyContextrender() {const {username, age} = this.contextreturn (<div><p>我是类式 C 组件,A组件的用户名是:{username}, {age}岁</p></div>)}}// 函数接收contextfunction D() {return (<div><p>我是函数式 D 组件,A组件的用户名是:<Consumer>{value => `${value.username}, ${value.age}`}</Consumer>岁</p></div>)}
6. 组件优化
Component 的2个问题1)、只要执行了setState,即使不改变状态数据,组件也会重新render()2)、即使子组件没有用到父组件的任何数据,只要父组件重新render(),就会自动重新render子组件 ==> 效率低原因:Component中的 shouldComponentUpdate()总是返回 true效率高的做法:只有当组件的 state 或 props 数据发生改变时才重新render()
解决:
方法1:重写 shouldComponentUpdate() 方法,比较新旧 state 或 props 数据,如果有变化才返回 true,如果没有返回false
import React, { Component } from 'react'export default class Parent extends Component {state = {catName: '花姐'}changeCat = ()=> {this.setState({catName: '橘宝'})}shouldComponentUpdate(nextProps, nextState){if(this.state.catName === nextState.catName) return falsereturn true}render() {console.log('Parent---Chils')return (<div><h3>Parent,{this.state.catName}</h3><button onClick={this.changeCat}>改名</button><Chils catName="橘宝"/></div>)}}class Chils extends Component {shouldComponentUpdate(nextProps, nextState){if(this.props.catName === nextProps.catName) return falsereturn true}render() {console.log('render---Chils')return (<div><h3>Chils</h3></div>)}}
方法2:,使用PureComponent,PureComponent 重写了 shouldComponentUpdate(),只有sate或props数据有变化才返回true
注意:只是进行 state 与 props 数据的浅比较,如果只是数据对象内部变了,返回false不要直接修改 state 数据,而是要产生新数据
项目中一般用 PureComponent 来优化
import React, { PureComponent } from 'react'export default class Parent extends PureComponent {state = {catName: '花姐'}changeCat = ()=> {this.setState({catName: '橘宝'})}render() {console.log('Parent---Chils')return (<div><h3>Parent,{this.state.catName}</h3><button onClick={this.changeCat}>改名</button><Chils catName="橘宝"/></div>)}}class Chils extends PureComponent {render() {console.log('render---Chils')return (<div><h3>Chils</h3></div>)}}
7. render props(相当于插槽)
向组件内部动态传入带内容的结构
vue 中:使用slot 技术,通过组件标签体传入结构 <A><B/></A>
React中:使用 children props:通过组件标签体传入结构使用 render props:通过组件标签属性传入结构,一般用 render 函数属性
children Props:语法:<A><B/></A>A组件渲染:{this.props.children }问题:如果B组件需要 A组件内的数据,无法传递
render Props:语法:<A rende={data => <B data={data}></B>}</A>A组件:{this.props.render(内部state数据)}B组件:读取A组件传入的数据显示 {this.props.data}
import React, { Component } from 'react'export default class Parent extends Component {render() {return (<div><p>我是Parent组件</p>// 组件的标签属性 render 返回一个组件<ChilsA render={name => <ChilsB name={name} />} /></div>)}} class ChilsA extends Component {state = {name: 'ChilsA'}render() {return (<div><p>我是ChilsA组件</p>// 使用props.render 渲染传入的组件{this.props.render(this.state.name)}</div>)}} class ChilsB extends Component {render() {return (<div><p>我是ChilsB组件, 是{this.props.name}的子组件</p></div>)}}
8. 错误边界 ErrorBoundary
错误边界 Error boundary :用来捕获后代组件错误,渲染出备用页面
特点:只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
使用方式:getDerivedStateFromError 配合 componentDidCatch// 当Parent的子组件出现报错稍后,会触发 getDerivedStateFromError 调用,并携带错误信息static getDerivedStateFromError(error){return {hasError: error}}// 子组件渲染错误会调用-给后台做出反馈componentDidCatch(){console.log('统计错误此处,反馈给服务器,用于通知编码人员进行bug的解决')}
import React, { Component } from 'react'export default class Parent extends Component {state = {hasError:'', // 用于标识子组件是否产生错误}// 当Parent的子组件出现报错稍后,会触发 getDerivedStateFromError 调用,并携带错误信息static getDerivedStateFromError(error){return {hasError: error}}// 子组件渲染错误会调用-给后台做出反馈componentDidCatch(){console.log('统计错误此处,反馈给服务器,用于通知编码人员进行bug的解决')}render() {return (<div><p>我是Parent组件</p>{this.state.hasError? <h2>当前网络不稳定,请稍后再试</h2>: <ChilsA />}</div>)}}class ChilsA extends Component {state = {list:'123'}render() {return (<div><p>我是ChilsA组件</p>{this.state.list.map((item,index) => {return <div key={index}>{item.name}</div>})}</div>)}}
总结
1. 组件间的通信
组件间的关系: 父子组件、兄弟组件(非嵌套组件)、祖孙组件(跨级组件)
几种通信方式:
1、props:1)、children props父传子: 通过标签属性传递,子组件通过 props 获取子传父:props+回调的⽅式,父组件通过 props 传递给子组件一个函数,子组件调用这个函数2)、render props父传子: 通过组件标签属性 render 传入结构, 父组件通过 {this.props.render(内部state数据)} 渲染子组件: {this.props.data} 读取数据状态提升:某些组件的 state 放在它们共同的父组件 state 中
2、消息订阅 - 发布:发布者发布事件(PubSub.publish()),订阅者监听事件(PubSub.subscribe())并做出反应,我们可以通过引⼊event 模块进⾏通信 -- 适用于任意组件间的通讯
3、集中式管理:借助 Redux 或者 Mobx 等全局状态管理⼯具进⾏通信,这种⼯具会维护⼀个全局状态中⼼Store,并根据不同的事件产⽣新的状态。
4、conText :生产者-消费者模式Context 设计⽬的是为了共享那些对于⼀个组件树⽽⾔是“全局”的数据,例如当前认证的户、主题或⾸选语⾔,对于跨越多层的全局数据通过 Context 通信再适合不过
比较好的搭配方式:
父子组件:props
兄弟组件:消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅发布、集中式管理、conText(开发用的少,封装插件用的多)















