React 全家桶

article/2025/11/6 21:05:33

文章目录

  • 前言
  • 一、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 废弃)

旧生命周期总结:

  1. 初始化阶段(挂载时):由 ReactDOM.render() 触发初次渲染
    constructor()
    componentWillMount()
    render()
    componentDidMount()

  2. 更新阶段:由组件内部 this.setState() 或 父组件 或 render 触发
    shouldComponentUpdate()
    componentWillUpdate()
    render()
    componentDidUpdate()

  3. 卸载组件:由ReactDOM.unmountComponentAtNode() 触发
    componentWillUnmount()

新生命周期总结:

  1. 初始化阶段(挂载时):由 ReactDOM.render() 触发初次渲染
    constructor()
    getDerivedStateFromProps()
    render()
    componentDidMount()

  2. 更新阶段:由组件内部 this.setState() 或 父组件 或 render 触发
    getDerivedStateFromProps()
    shouldComponentUpdate()
    render()
    getSnapshotBeforeUpdate()
    componentDidUpdate()

  3. 卸载组件:由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、 使用 HashRouter
    

    4、路由( 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 特性

  1. 三个常用的 Hooks
    State Hook:React.useState()
    Effect Hook: React.useEffect()
    Ref Hook: React.useRef()

  2. 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
    
  3. 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
    
  4. 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(开发用的少,封装插件用的多)

http://chatgpt.dhexx.cn/article/ZDKmhJf0.shtml

相关文章

React全家桶

文章目录 第1章&#xff1a;React入门1.1. React简介1.1.1. 官网1.1.2. 介绍描述1.1.3. React的特点1.1.4. React高效的原因 1.2. React的基本使用1.2.1. 效果1.2.2. 相关js库1.2.3. 创建虚拟DOM的两种方式1.2.4. 虚拟DOM与真实DOM 1.3. React JSX1.3.1. 效果1.3.2. JSX1.3.3. …

react全家桶有哪些?(详细)

一 、 create-react-app脚手架 对于现在比较流行的三大框架都有属于自己的脚手架&#xff08;目前这些脚手架都是使用node编写的&#xff0c;并且都是基于webpack的&#xff09;&#xff1a; Vue的脚手架&#xff1a;vue-cliAngular的脚手架&#xff1a;angular-cliReact的脚手…

React全家桶详细讲解-图文并茂

文章目录 前言一、React-表单处理受控组件使用步骤多表单元素优化非受控组件 二、React-组件综合案例需求分析搭建任务的模板渲染列表添加任务删除任务 三、React-组件进阶组件通讯介绍组件的props 四、react-组件通讯的三种方式react-父组件传递数据给子组件react-子组件传递数…

【尚硅谷React】——React全家桶笔记

文章目录 第1章 React简介1.1 React的特点1.2 引入文件1.3 JSX1.3.1 为什么要用JSX1.3.2 JSX语法规则 1.4 虚拟DOM1.5 模块与组件1.5.1 模块1.5.2 组件 第2章 React面向组件编程2.1 创建组件2.1.1 函数式组件2.1.2 类式组件 2.2 组件实例的三大属性2.2.1 state属性2.2.2 props属…

React全家桶(收藏吃灰必备!)

文章目录 ECMAScript61、ES6简介1.1、什么是ES61.2、ECMAScript和JavaScript的关系1.3、为什么要学习ES6&#xff1f; 2、ES6环境搭建2.1、前期准备2.2、ES6环境搭建 3、let与const3.1、let命令3.2、const命令 4、ES6解构赋值4.1、解构赋值概述4.2、解构模型4.3、数组的解构赋值…

Neurosynth元分析——认知解码工具,软件包安装以及使用

Neurosynth元分析——认知解码工具,软件包安装以及使用 NeuroSynth 基本简介基本原理例子Neurosynth package安装及使用创建虚拟环境安装Dependencies:安装neurosynthNeurosynth使用加载必要的包下载neurosynth数据参考如上图所示。NeuroSynth 元分析感兴趣的区域沿功能连接梯…

编码和解码

概念 字符的三种形态 图片来自&#xff1a;https://zhuanlan.zhihu.com/p/25435644 编码&#xff1a;将字符转为字节序列&#xff08;abcdefg-------------> 0101010…10010&#xff09; 解码&#xff1a;将字节序列转为字符&#xff08;1001010…10010110------> abcd…

编码器和解码器

1.编码器—解码器&#xff08;seq2seq&#xff09; 编码器的作用是把一个不定长的输入序列变换成一个定长的背景变量c&#xff0c;并在该背景变量中编码输入序列信息。常用的编码器是循环神经网络。 编码器可以是一个单向的循环神经网络&#xff0c;每个时间步的隐藏状态只取决…

二维码解码工具

http://tool.chinaz.com/qrcode/?jdfwkeyweexu

编解码工具

支持各种文件摘要&#xff08;Hash&#xff09;、Base64编码、Hex编码、国密sm2加密、Rsa加密&#xff0c;以及各种对称加密算法的小工具 下载地址&#xff1a;https://pan.baidu.com/s/1AJwUaVizzk5HeLa_8Q5AqA 提取码&#xff1a;4567

编码与解码

什么是编码与解码 电脑是由电路板组成&#xff0c;电路板里面集成了无数的电阻和电容&#xff0c; 交流电经过电容的时候&#xff0c;电压比较低 记为低电平 &#xff0c; 用0表示&#xff0c;交流电流过电阻的时候&#xff0c;电压比较高&#xff0c;记为高电平&#xff0c;用…

Encoder编码器、Decoder解码器

知乎用户对编码器解码器的理解 Encoder&#xff1a; 本身其实就是一连串的卷积网络。该网络主要由卷积层&#xff0c;池化层和BatchNormalization层组成。卷积层负责获取图像局域特征&#xff0c;池化层对图像进行下采样并且将尺度不变特征传送到下一层&#xff0c;而BN主要对…

Protobuf在线解码工具推荐

P1: CyberChef 地址&#xff1a;CyberChef 非常灵活&#xff0c;支持各种导入格式可以直接转成json支持导入.proto文件 P2: protobuf-decoder 地址&#xff1a;protobuf-decoder 有对齐问题 P3: protogen 地址&#xff1a;protogen 有乱码问题 P4: PB-JCE-Decoder&…

在线JWT Token解析解码工具

1&#xff1a;JWT Token在线解析解码 - ToolTT在线工具箱 2&#xff1a; 3&#xff1a;

密码解码常用工具网站汇总

xssee:http://web2hack.org/xssee xssee:http://evilcos.me/lab/xssee 在线编码解码(多种并排):http://bianma.911cha.com 在线加密解密(多种):http://encode.chahuo.com Unicode转中文:http://www.bejson.com/convert/unicode_chinese 栅栏密码 && 凯撒密码 &&…

URL在线编码/解码工具

一刀工具箱提供在线URL编码解码工具:对网址Url进行UrlEncode编码转换,UrlEncode编码,UrlDecode解码。 代码片段 methods:{convertEncode(){this.item encodeURIComponent(this.value)},convertDecode(){this.item decodeURIComponent(this.value);}} URL在线编码/解码工具 …

URL 编码和解码工具

简介 本文简要介绍一款编解码工具&#xff0c;它支持URL编码和解码、Base64编码和解码。 官网地址&#xff1a;https://smart-tools.cn/dev/encoder/app 背景 URL编码 URL编码是浏览器发送数据给服务器时使用的编码。它是编码算法&#xff0c;而不是加密算法。其目的是把任…

HTK 3.5解码工具HVITE独立工程(Visual Studio Code实现)

HTK3.5支持DNN HTK3.5支持DNN了&#xff0c;本来想做一个HMMDNN的模型&#xff0c;作为HMMGMM的对比。但是HTK不支持实时的HMMDNN解码。原因有两个。 HTK不支持实时的计算MFCC_0_D_A_Z的特征&#xff0c;即无法实时计算出特征空间的均值。HTK不支持实时的DNN或者HMMDNN的解码…

全自动解密解码神器 — Ciphey

Ciphey 是一个使用自然语言处理和人工智能的全自动解密/解码/破解工具。 简单地来讲&#xff0c;你只需要输入加密文本&#xff0c;它就能给你返回解密文本。就是这么牛逼。 有了Ciphey&#xff0c;你根本不需要知道你的密文是哪种类型的加密&#xff0c;你只知道它是加密的&…

Linux搭建NTP时间服务器

1. NTP简介 NTP&#xff08;Network Time Protocol 网络时间协议&#xff09;是一个用于同步计算机时钟的网络协议。它可以使计算机与其他服务器或时钟源进行时间同步&#xff0c;进行高精度的时间校正。 简而言之&#xff0c;NTP就是使一台或多台服务器&#xff08;客户端&a…