# 张天禹React基础学习记录 **Repository Path**: mr-nianj/react-learning-record ## Basic Information - **Project Name**: 张天禹React基础学习记录 - **Description**: React学习笔记备份 视频链接:https://www.bilibili.com/video/BV1wy4y1D7JT - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 4 - **Forks**: 7 - **Created**: 2022-05-12 - **Last Updated**: 2025-08-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # React笔记 ## 相关资源 [FrontEndNotes笔记](https://brucecai55520.gitee.io/my-notes) [原课程链接](https://www.bilibili.com/video/BV1wy4y1D7JT) [大佬仓库](https://gitee.com/hongjilin/hongs-study-notes) [React 中文官网](https://react.docschina.org/) ## 前言 | Reat简介 React:用于构建用户界面的 JavaScript 库。由 Facebook 开发且开源。是一个将视图渲染为html视图的开源库 ### 为何学习 React 原生 JavaScript 的痛点: - 操作 DOM 繁琐、效率低 - 使用 JavaScript 直接操作 DOM,浏览器进行大量重绘重排 - 原生 JavaScript 没有组件化编码方案,代码复用率低 ### React 的特点: - 采用组件化模式、声明式编码,提高开发效率和组件复用率 - 在 React Native 中可用 React 语法进行移动端开发 - 使用虚拟 DOM 和 Diffing 算法,减少与真实 DOM 的交互(数据-虚拟DOM-真实DOM) ### 需要掌握的js基础 判断this指向 class类 ES6语法规范 npm包管理 原型以及原型链 数组常用方法 模块化 ## 第一章:React入门 ### Hello React 相关js库 - react.development.js :React 核心库 - react-dom.development.js :提供 DOM 操作的 React 扩展库 - babel.min.js :解析 JSX 语法(js语法糖),转换为 JS 代码 ```html
``` ### 创建VDOM的两种方式 第一种 jsx方式(推荐) ```html
``` 第二种方式 ```html
``` ### VDOM | DOM 关于虚拟 DOM: - 本质是 Object 类型的对象(一般对象) - 虚拟 DOM 比较“轻”,真实 DOM 比较“重”,因为虚拟 DOM 是 React 内部在用,无需真实 DOM 上那么多的属性。 - 虚拟 DOM 最终会被 React 转化为真实 DOM,呈现在页面上。 ```html ``` ### React JSX 全称:JavaScript XML React 定义的类似于 XML 的 JS 扩展语法;本质是 React.createElement() 方法的语法糖 作用:简化创建虚拟 DOM 补充:js中,JSON的序列化和反序列化使用parse()/stringify() JSX 语法规则 - 定义虚拟 DOM 时,不要写引号 - 标签中混入 JS 表达式需要使用 {} - 指定类名不用 class,使用 className - 内联样式,使用 style={{ key: value }} 的形式 - 只能有一个根标签 - 标签必须闭合,单标签结尾必须添加 /:`` - 标签首字母小写,则把标签转换为 HTML 对应的标签,若没有,则报错 - 标签首字母大写,则渲染对应组件,若没有定义组件,则报错 ```html jsx语法规则
... ``` 补充:表达式与语句(代码)的区别 - 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方(如数值处理等) ``` a a+b demo(1) arr.map() function test () {} ``` - 语句(代码),下面这些都是语句(如逻辑判断语句) ``` if(){} for(){} switch(){case:xxxx} ``` ## 第二章:React面向组件 ### 创建组件的两种方式 #### 函数式组件 ```html
``` 要点: - 组件名称首字母必须大写,否则会解析成普通标签导致报错(JSX 语法规则) - 函数需返回一个虚拟 DOM - 渲染组件时需要使用标签形式,同时标签必须闭合 渲染组件的过程: - React 解析标签,寻找对应组件 - 发现组件是函数式组件,则调用函数,将返回的虚拟 DOM 转换为真实 DOM ,并渲染到页面中 #### 类组件 类的基本知识 ```html ``` 组件渲染过程: 1. React 解析组件标签,寻找组件 2. 发现是类式组件,则 new 该类的实例对象,通过实例调用原型上的 render 方法 3. 将 render 返回的虚拟 DOM 转为真实 DOM ,渲染到页面上 ### 组件实例的核心属性 #### 核心属性1:state | 状态 state 是组件实例对象最重要的属性,值为对象。又称为状态机,通过更新组件的 state 来更新对应的页面显示。 要点: - 初始化 state - React 中事件绑定 - this 指向问题 - setState 修改 state 状态 - constructor 、render 、自定义方法的调用次数 1. 标准写法: ```html ``` 2. 简写: ```html ``` #### 核心属性2:props | 标签属性 每个组件对象都有 props 属性,组件标签的属性都保存在 props 中。(注意:props 是只读的,不能修改。) 要点: - 展开运算符解构赋值 - props数据采用标签属性的方式传值 - 批量传递标签属性 1. 标准写法: ```html ``` 2. 限制标签属性 在 React 15.5 以前,React 身上有一个 PropTypes 属性可直接使用,即 name: React.PropTypes.string.isRequired ,没有把 PropTypes 单独封装为一个模块。 从 React 15.5 开始,把 PropTypes 单独封装为一个模块,需要额外导入使用。 ```html
``` 3. 函数式组件使用props: ```html ``` 4. 简写形式: ```html ``` #### 核心属性3:refs | 标识符 通过定义 ref 属性可以给标签添加标识。 1. 字符串形式的ref(这种形式已过时,效率不高,官方不建议使用。) ```html ``` 2. 回调函数形式的ref(便捷 使用最多) 要点: - currentNode => this.input1 = currentNode 就是给组件实例添加 input1 属性,值为节点 - 由于是箭头函数,因此 this 是 render 函数里的 this ,即组件实例 - 回调ref中调用次数问题:原文(如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。见[官方文档](https://react.docschina.org/docs/refs-and-the-dom.html)) ```html ``` 3. createRef API(官方推荐使用) 该方式通过调用 React.createRef 返回一个容器(对象)用于存储节点,且一个容器只能存储一个节点。 ```html ``` #### 事件处理 - React 使用自定义事件,而非原生 DOM 事件,即 onClick、onBlur :为了更好的兼容性 - React 的事件通过事件委托方式进行处理:为了高效 - 通过 event.target 可获取触发事件的 DOM 元素:勿过度使用 ref 当触发事件的元素和需要操作的元素为同一个时,可以不使用 ref : ```html class Demo extends React.Component { showData2 = (event) => { alert(event.target.value) } render() { return (
 
) } } ``` #### 受控组件&非受控组件 非受控组件:现用现取。即需要使用时,再获取节点得到数据 受控组件:类似于 Vue 双向绑定的从视图层绑定到数据层(推荐使用,因为非受控组件需要使用大量的 ref 。) 1. 受控组件 ```html ``` 2. 非受控组件 ```html ``` #### 补充:高阶函数&函数柯里化 高阶函数:参数为函数或者返回一个函数的函数,常见的如 Promise、setTimeout、Array.map()等 函数柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式 使用高阶函数简化受控组件: ```html ``` ### 组件的生命周期 理解 - 组件从创建到死亡它会经历一些特定的阶段。 - React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。 - 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。 #### 生命周期(旧) ![旧版生命周期](https://img-blog.csdnimg.cn/20210608114316605.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDk3MjAwOA==,size_16,color_FFFFFF,t_70#pic_center) 1.3.1 初始化阶段 - 由ReactDOM.render()触发—初次渲染 - constructor() —— 类组件中的构造函数 - componentWillMount() —— 组件将要挂载 【即将废弃】 - render() —— 挂载组件 - componentDidMount() —— 组件挂载完成 比较常用 一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息 1.3.2 更新阶段 【第一种情况】父组件重新render触发 - componentWillReceiveProps() —— 接收属性参数(非首次)【即将废弃】 - ... 【第二种情况】由组件内部this.setSate() - shouldComponentUpdate() —— 组件是否应该被更新(默认返回true) - ... 【第三种情况】强制更新 forceUpdate() - componentWillUpdate() ——组件将要更新 【即将废弃】 - render() —— 组件更新 - componentDidUpdate() —— 组件完成更新 1.3.3 卸载组件 - 由ReactDOM.unmountComponentAtNode()触发 - componentWillUnmount() —— 组件即将卸载 #### 生命周期(新) ![生命周期 16+](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a7d8676f379d4d96bbf0ebd9a8528594~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp) 1. 初始化阶段 - 由ReactDOM.render()触发 —— 初次渲染 - constructor() —— 类组件中的构造函数 - static getDerivedStateFromProps(props, state) 从props得到一个派生的状态【新增】 - render() —— 挂载组件 - componentDidMount() —— 组件挂载完成 比较常用 2. 更新阶段 - 由组件内部this.setSate()或父组件重新render触发或强制更新forceUpdate() - getDerivedStateFromProps() —— 从props得到一个派生的状态 【新增】 - shouldComponentUpdate() —— 组件是否应该被更新(默认返回true) - render() —— 挂载组件 - getSnapshotBeforeUpdate() —— 在更新之前获取快照【新增】 - componentDidUpdate(prevProps, prevState, snapshotValue) —— 组件完成更新 3. 卸载组件 - 由ReactDOM.unmountComponentAtNode()触发 - componentWillUnmount() —— 组件即将卸载 ```html ``` #### diffing算法与key 1. 虚拟DOM中key的作用 - 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。 - 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下: ``` a. 旧虚拟DOM中找到了与新虚拟DOM相同的key: (1).若虚拟DOM中内容没变, 直接使用之前的真实DOM (2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM b. 旧虚拟DOM中未找到与新虚拟DOM相同的key 根据数据创建新的真实DOM,随后渲染到到页面 ``` !{reffing算法基本原理图}(https://img-blog.csdnimg.cn/2021060821262976.png) 2. 用index作为key可能会引发的问题 - 若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低 - 如果结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的 ## 第三章 React脚手架 ### 脚手架安装与使用 全局安装配置 :`npm i -g create-react-app` 创建项目:`create-react-app 项目名` ``` C:\Users\Mrnianj>npm i -g create-react-app ... C:\Users\Mrnianj>create-reacrt-app 项目名 ... C:\Users\Mrnianj>cd 项目(文件夹)名 ... C:\Users\Mrnianj>npm start ``` 脚手架目录: ``` public ---- 静态资源文件夹 favicon.icon ------ 网站页签图标 index.html -------- 主页面 logo192.png ------- logo图 logo512.png ------- logo图 manifest.json ----- 应用加壳的配置文件 robots.txt -------- 爬虫协议文件 src ---- 源码文件夹 App.css -------- App组件的样式 App.js --------- App组件 App.test.js ---- 用于给App做测试 index.css ------ 样式 index.js ------- 入口文件 logo.svg ------- logo图 reportWebVitals.js --- 页面性能分析文件(需要web-vitals库的支持) setupTests.js ---- 组件单元测试的文件(需要jest-dom库的支持) ``` ### 最简单的项目实例 public/index.html ```html
``` src/index.js ```js import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; ReactDOM.render( , document.getElementById('root') ); ``` src/app.js ```jsx // 创建外壳组件 import React,{Component} from "react"; import Hello from "./components/Hello/Hello" import Welcome from "./components/Welcome" // 暴露并创建组件 export default class App extends Component { render() { return (
) } } ``` src/components/Hello ```jsx import React ,{ Component } from "react"; export default class Hello extends Component{ render() { return (

Hello React!!!

) } } ``` src/components/Welcome ```jsx import React,{Component} from "react" export default class Welcome extends Component { render() { return

Welcome

} } ``` TodoList案例 - 拆分组件、实现静态组件,注意:className 、style 的写法 - 动态初始化列表,如何确定将数据放在哪个组件的 state 中? 某个组件使用:放在其自身的 state 中 某些组件使用:放在他们共同的父组件 state 中,即状态提升 - 关于父子之间通信: 父传子:直接通过 props 传递 子传父:父组件通过 props 给子组件传递一个函数,子组件调用该函数 ```jsx // 父组件 class Father extends Component { state: { todos: [{ id: '001', name: '吃饭', done: true }], flag: true, } addTodo = (todo) => { const { todos } = this.state const newTodos = [todo, ...todos] this.setState({ todos: newTodos }) } render() { return } } // 子组件 class Son extends Component { // 由于 addTodo 是箭头函数,this 指向父组件实例对象,因此子组件调用它相当于父组件实例在调用 handleClick = () => { this.props.addTodo({ id: '002', name: '敲代码', done: false }) } render() { return } } ``` - 注意(标签属性) defaultChecked 和 checked 的区别,类似的还有:defaultValue 和 value - 状态在哪里,操作状态的方法就在哪里 ## 第四章 React网络请求(ajax) ### axios | 网络请求 安装 axios `npm i axios` ```jsx export default class App extends Component { getStuData = ()=> { axios.get('http://localhost:3000/api1/students').then( response => {console.log(response.data);}, err => {console.log(err);} ) } getCarData = ()=> { axios.get('http://localhost:3000/api2/cars').then( response => {console.log(response.data);}, err => {console.log(err);} ) } render() { return (
) } } ``` ### React 脚手架配置代理(跨域问题) [官方文档](https://www.html.cn/create-react-app/docs/proxying-api-requests-in-development/) 方法1: 优点:配置简单,前端请求资源可不加前缀 缺点:不能配置多个代理 工作方式:当请求了 3000 端口号(本机)不存在的资源时,就会把请求转发给 5000 端口号服务器 在 package.json 文件中进行配置:`"proxy": "http://localhost:5000"` 方法2: 在 src 目录下创建代理配置文件 setupProxy.js ,进行配置: ```cjs // const proxy = require('http-proxy-middleware') const { createProxyMiddleware } = require('http-proxy-middleware') module.exports = function (app) { app.use( //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000) createProxyMiddleware('/api1', { //配置转发目标地址(能返回数据的服务器地址) target: 'http://localhost:5000', //控制服务器接收到的请求头中host字段的值 /* changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000 changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000 changeOrigin默认值为false,但一般将changeOrigin改为true */ changeOrigin: true, //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置) pathRewrite: { '^/api1': '' }, }), createProxyMiddleware('/api2', { target: 'http://localhost:5001', changeOrigin: true, pathRewrite: { '^/api2': '' }, }) ) } ``` ### 消息订阅与发布机制 React 中兄弟组件或任意组件之间的通信方式。 [使用的工具库:pubsub-js](https://www.npmjs.com/package/pubsub-js) 1. 安装 `npm install pubsub-js --save` 2. 基础用法示例 ```jsx // A组件 import PubSub from 'pubsub-js' // 组件被挂载后订阅消息 componentDidMount() { this.token = PubSub.subscribe('topic', (msg,data)=>{//msg 订阅消息名,data 传递的数据 console.log('List组件收到订阅消息',data); this.setState(data) }) } // 组件将被卸载时取消订阅 componentWillUnmount() { PubSub.unsubscribe(this.token) } // B组件 // 发布消息 import PubSub from 'pubsub-js' export default class Search extends Component { saveUsers = ()=>{ // 发布消息 // 发送网络请求后 通知A组件更新状态 PubSub.publish('topic', {isLoading:true,isFirst:false}) axios.get(`https://api.github.com/search/users?q=username`).then( (res)=>{ PubSub.publish('topic', {isLoading:false,users:res.data.items}) }, (err)=>{ PubSub.publish('topic', {isLoading:false,err}) } ) } } ``` #### 扩展 fetch | 发送ajax请求(了解即可) [阮一峰 Fetch API 教程](https://www.ruanyifeng.com/blog/2020/12/fetch-tutorial.html) 常见可以发送ajax请求的方式 - xhr (常见的有:jQuery\axios) - fetch (原生,但是不常用) 下面是fetch使用 ```js // 使用fetch发送请求 fetch(url) .then(response => response.json()) .then(data => console.log(data)) .catch(e => console.log("获取数据失败", e)) // 使用 await 语法优化 async function getJSON() { let url = 'https://api.github.com/users/ruanyf'; try { let response = await fetch(url); return await response.json(); } catch (error) { console.log('请求出错', error); } } ``` #### Github 搜索框案例知识点总结 1. 设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办。 2. ES6 知识点:解构赋值 + 重命名 ```js let obj = { a: { b: 1 } } //传统解构赋值 const { a } = obj //连续解构赋值 const { a: { b }, } = obj //连续解构赋值 + 重命名 const { a: { b: value }, } = obj ``` 3. 消息订阅与发布机制 - 先订阅,再发布(隔空对话) - 适用于任意组件间通信 - 要在 componentWillUnmount 钩子中取消订阅 4. fetch 发送请求(*关注分离*的设计思想) ```js try { // 先看服务器是否联系得上 const response = await fetch(`/api1/search/users2?q=${keyWord}`) // 再获取数据 const data = await response.json() console.log(data) } catch (error) { console.log('请求出错', error) } ``` ## 第五章 React路由 ### SPA页面&路由理解 1. SPA页面的特点 - 单页web应用 - 整个应用只有一个完整页面 - 点击页面链接不会刷新页面,只会做页面的局部刷新 - 数据都需要经过ajax请求获取,并在前端一部展现 2. 何为路由? - 一个路由是一个映射关系 - key 为路径地址,value 可能是 function 或 component 3. 路由分类 - 后端路由: - value 是 function ,用于处理客户端的请求 - 注册路由:`router.get(path, function(req, res))` - 工作过程:Node 接收到请求,根据路径匹配路由,调用对应函数处理请求,返回响应数据 - 前端路由: - value 是组件 - 注册路由:`` - 工作过程:浏览器路径变为 /test ,展示 Test 组件 ### 补充:路由的基本原理 `window.history` BOM 浏览器对象中包含 history 对象用于管理浏览器历史记录,History 对象是 window 对象的一部分,可通过 window.history 属性对其进行访问。 ``` 方法 说明 back() 加载 history 列表中的前一个 URL forward() 加载 history 列表中的下一个 URL go() 加载 history 列表中的某个具体页面 ``` ### react-router-dom | 路由插件 react的一个插件库,用来实现一个SPA应用。 [react-router web应用官方文档](https://react-router.docschina.org/web/guides/philosophy) #### 路由的基本使用 1. 安装 react-router-dom : ``` // 安装 5.X 版本路由 npm install react-router-dom@5.2.0 -S 是 // 最新已经 6.X 版本,用法和 5.X 有所不同 npm install react-router-dom -S ``` [6.x 版本的基本使用参考](https://zhuanlan.zhihu.com/p/191419879) 2. 编写基本路由 index.js ```jsx import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import { BrowserRouter} from "react-router-dom"; ReactDOM.render( , document.getElementById('root') ); ``` App.jsx ```jsx import React, { Component } from 'react' import About from './components/About'; import Home from './components/Home'; import { Link,Route} from "react-router-dom"; export default class App extends Component { render() { return (
{/* 标题 */}

React Router Demo

{/* 导航栏 */}
{/*编写路由链接*/} About Home
{/* 内容区 */}
{/*注册路由*/}
) } } ``` 3. 总结 - 导航区正宗的跳转链接改为Link标签 - 展示区写的Route标签进行路由匹配 - index.js入口文件中的``外侧需要包裹一个``或者``标签 #### 路由组件和一般组件 1. 存放位置不同: 一般组件:components 路由组件:pages 2. 写法不同: 一般组件: 路由组件: 3. 接收到的 props 不同: 一般组件:标签属性传递 路由组件:会接收到三个固定的属性(*history,location=history.location | 语法糖,match*) ``` history: go: ƒ go(n) goBack: ƒ goBack() goForward: ƒ goForward() push: ƒ push(path, state) replace: ƒ replace(path, state) location: pathname: "/home/message/detail/2/hello" search: "" state: undefined match: params: {} path: "/home/message/detail/:id/:title" url: "/home/message/detail/2/hello" ``` #### NavLink的使用 NavLink是Link的迭代,NavLink 可以通过 activeClassName 属性指定点击之后追加的样式名,默认追加类名 active , ``` About Home ``` **封装NavLink:** MyNavLink.jsx ```jsx /* 针对 NavLink 的二次封装 */ import React, { Component } from 'react' import { NavLink } from "react-router-dom"; export default class MyNavLink extends Component { render() { let {} = this.props return ( // {this.props.children} ) } } ``` 调用方式 `MyNavLink` #### Switch 单一路由的匹配 1. 一般情况下,一个路由对应一个组件(需要借助Switch),注意:在v6.xx版本中,Switch 已经被 Routes 标签代替! 2. Switch 可以提高路由匹配效率,如果匹配成功,则不再继续匹配后面的路由,即单一匹配。 ```jsx import { Route,NavLink,Switch } from "react-router-dom"; {/* ... */} {/* 编写路由链接 */} About Home {/* 注册路由 包裹 Switch 标签 */} ``` #### 解决多级路由样式丢失 当路由路径中出现多级路由时,如``时,页面加载后可能会出现样式文件丢失 三种解决方案: 1. public/index.html 中 引入样式时不写 ./ 写 / (常用),如:`` 2. public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用),如:`` 3. 使用 HashRouter(哈希路由/锚点路由),如: ```jsx import { HashRouter } from "react-router-dom"; ReactDOM.render( , document.getElementById('root') ); ``` #### 路由的严格匹配与模糊匹配 模糊匹配(左侧匹配)示例 ```jsx Home {/* 可以匹配 */} ``` 开启严格匹配示例 ```jsx Home {/* 可以匹配 */} {/* 不可以匹配 */} ``` - react-router-dom 默认使用模糊匹配(输入的路径必须包含要匹配的路径,且是顺序匹配) - 使用`exact={true}`(语法糖:可以省略属性值) 开启严格匹配:`` - 严格匹配需要再开,开启可能会导致无法继续匹配二级路由 #### Redirect | 重定向、重定向路由 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到 Redirect 指定的路由 ```jsx import { Route,Switch,Redirect } from "react-router-dom"; ``` #### 多级路由 注册子路由需写上父路由的 path 路由的匹配是按照注册路由的顺序进行的 home.jsx | 不要开启严格匹配 ```jsx About Home ``` /pages/home/news.jsx & ../message.jsx ```jsx
  • News
  • Message
``` #### 路由传参 三种方式:params, search, state 参数 state 方式当前页面刷新可保留参数,但在新页面打开不能保留。 params 和 search 参数都会变成字符串,两种方式由于参数保存在 URL 地址上,因此都能保留参数。 1. 传递 params 参数方式 ```jsx {/* 向路由组件传递 params 参数 */} {msgObj.title} {/* 声明接收 params 参数 */} // 接收 params 参数 const {id,title} = this.props.match.params ``` 2. 传递 search 参数方式(需要自己处理传递后的参数,较麻烦) ```jsx {/* 向路由组件传递 search 参数 */} {msgObj.title} {/* search 参数无需声明接收 */} // 接收 params 参数(需要自己处理 字符串截取转对象) const {search } = this.props.location //search:"?id=01&title=消息1" // 处理 search 参数 let searchObj = {} var splitArr = search.slice(1,search.length).split("&") //['id=03','title=消息3'] splitArr.map((item) => { let Objkey,Objvalue = '' let Obj = {} item.split("=").map((value,index) => { index==0 ? Objkey = value : Objvalue = value }) Obj[Objkey] = Objvalue searchObj = {...searchObj,...Obj} }) ``` 3. 传递 state 参数方式 ```jsx {/* 向路由组件传递 state 参数 */} {msgObj.title} {/* state 参数也无需声明接收 */} // 接收 state 参数 const { id,title } = this.props.location.state || {} ``` 补充:开启replace模式 ,rreact-router-dom默认启用push模式 push与replace的区别 push 压栈操作,会产生历史记录 replace 替换操作,不会产生历史记录,无法返回 ```jsx {msgObj.title} // 简写( == 严格匹配模式) {msgObj.title} ``` #### 编程式路由 通过 history 对象提供的api实现路由跳转 history: props.history.go: ƒ go(n) 参数传递整数(正数前进 负数后退) props.history.goBack: ƒ goBack() 前进 props.history.goForward: ƒ goForward() 后退 props.history.push: ƒ push(path, state) 添加历史记录 props.history.replace: ƒ replace(path, state) 替换历史记录 ```jsx // 三种编程式导航传参的方式 this.props.history.push(`/home/message/detail/${id}/${title}`) this.props.history.push(`/home/message/detail?id=${id}&title=${title}`) this.props.history.push(`/home/message/detail`, { id: id, title: title }) ``` #### withRouter 的使用 withRouter 的作用:加工一般组件,让其拥有路由组件的 API ,如 this.props.history.push 等。 ```jsx import React, { Component } from 'react' import { withRouter } from 'react-router-dom' class Header extends Component { render() { {/*...*/} } } export default withRouter(Header) ``` #### BrowserRouter 和 HashRouter 1. 底层原理不一样: BrowserRouter 使用的是 H5 的 history API,不兼容 IE9 及以下版本。 HashRouter 使用的是 URL 的哈希值。 2. 路径表现形式不一样 BrowserRouter 的路径中没有 # ,如:localhost:3000/demo/test HashRouter 的路径包含#,如:localhost:3000/#/demo/test 3. 刷新后对路由 state 参数的影响 BrowserRouter 没有影响,因为 state 保存在 history 对象中。 HashRouter 刷新后会导致路由 state 参数的丢失! 4. 备注:HashRouter 可以用于解决一些路径错误相关的问题(多级路由刷新样式丢失问题)。 ## 第六章 UI组件库 antd-UI [Ant Design官方网站](https://ant.design/index-cn) 1. 安装 ` npm install antd --save` 2. 基本使用 ```jsx import React, { Component } from 'react' // 引入antd import { Button } from 'antd'; import { PoweroffOutlined }from '@ant-design/icons'; import "antd/dist/antd.css"; export default class App extends Component { render() { return (
) } } ``` 3. 3.x按需引入 Antd,默认情况下:组件的js是支持按需引入,但css并不支持按需引入,即默认情况下Antd的CSS样式被整体引入, - 安装依赖 `npm install react-app-rewired customize-cra babel-plugin-import` - 修改 package.json ```json /* package.json */ "scripts": { - "start": "react-scripts start", + "start": "react-app-rewired start", - "build": "react-scripts build", + "build": "react-app-rewired build", - "test": "react-scripts test", + "test": "react-app-rewired test", } ``` - 项目根目录下创建 config-overrides.js ```jsx //配置具体的修改规则 const { override, fixBabelImports, addLessLoader } = require('customize-cra') module.exports = override( fixBabelImports('import', { libraryName: 'antd', libraryDirectory: 'es', style: 'css', }) ) ``` 4. 补充 :3.x版本配置主题已不适用与antd4.x版本,配置按需引入&定制主题参考: [官网文档](https://ant.design/docs/react/use-with-create-react-app-cn) [Antd4.x 按需引用&自定义主题-简书](https://juejin.cn/post/6995446514881396773) ## 第七章 Redux [阮一峰 Redux入门](https://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html) [Redux 中文文档](https://www.redux.org.cn/) ### Redux 概述 - Redux 是用于做 状态管理 的 JS 库,除此之外还有[DvaJS](https://dvajs.com/)(推荐)等 - 可用于 React、Angular、Vue 等项目中,常用于 React - 集中式管理 React 应用多个组件共享的状态 何时用 Redux ? - 某个组件的状态,需要让其他组件拿到(状态共享) - 一个组件需要改变另一个组件的状态(通信) - 使用原则:不到万不得已不要轻易动用 Redux 工作流程 ![Redux工作流程](https://brucecai55520.gitee.io/my-notes/React/images/redux.png) 组件想操作 Redux 中的状态:需要把动作类型和数据(对象形式)告诉 Action Creators - Action Creators 创建 action :同步 action 是一个普通对象,异步 action 是一个函数 - Store 调用 dispatch() 分发 action 给 Reducers 执行 - Reducers 接收 previousState 、action 两个参数,对状态进行加工后返回新状态 - Store 调用 getState() 把状态传给组件 #### redux基本使用: store.js ```jsx // 引入 createStore 创建 store 对象 import { createStore } from "redux"; // 引入为 Count 服务的 countReducer import countReducer from "./reduce"; // 暴露 store export default createStore(countReducer) ``` reduce.js ```jsx /* 为Count组件创建的服务reducer 作用: 初始化store 接收两个参数:之前的状态(preState),动作对象(action) */ const initState = 0 export default function countReducer(preState = initState,action) {//默认参数 const { type,data } = action switch (type) { case 'increment': return data + preState default: return preState//无参数,返回默认值 0 } } ``` index.jsx ```jsx import React, { Component } from 'react' import store from "../../redux/store"; export default class Count extends Component { componentDidMount(){ // 组件挂载完毕,监测状态更新,重新渲染 store.subscribe(()=>{ this.setState({}) }) } // 加 increment = ()=>{ // 获取用户输入数据 const userInput = (this.selectNumber.value)*1 // 通知redux 更新状态 store.dispatch({type:'increment',data:userInput}) } render() { return (

当前求和结果为:Redux:{store.getState()}


) } } ``` 补充:监测redux中数据改变重新绘排,可以在index.js入口文件中监测整个App ```jsx // ... store.subscribe(()=>{ ReactDom.render(,document.querySelector('#root')) }) ``` #### redux完整流程 1. 文件结构 redux + constant.js 定义 action 中 type 的常量值,一次定义,多处引用 + count_action.js 创建 action 对象 + count_reducer.js 1.初始化store 2.接收两个参数:之前的状态(preState),动作对象(action) + store.js ```js // redux/count_action.js import { INCREMENT, DECREMENT } from './constant' export const createIncrementAction = (data) => ({ type: INCREMENT, data }) export const createDecrementAction = (data) => ({ type: DECREMENT, data }) ``` ```js // redux/constant.js // 保存常量值 export const INCREMENT = 'increment' export const DECREMENT = 'decrement' ``` ### Redux 异步Action 要点: - 延迟的动作不想交给组件,而是 action - 当操作状态所需数据要靠异步任务返回时,可用异步 action - 创建 action 的函数返回一个函数,该函数中写异步任务 - 异步任务完成后,分发一个同步 action 操作状态 - 异步 action 不是必要的,完全可以在组件中等待异步任务结果返回在分发同步 action 1. 安装 异步支持中间件 `npm install redux-thunk -S` 2. 定义异步 action ```js // count_action.js import { INCREMENT, DECREMENT } from './constant.js' // 同步 action 返回一个对象 export const createIncrementAction = (data) => ({ type: INCREMENT, data }) export const createDecrementAction = (data) => ({ type: DECREMENT, data }) // 异步 action 返回一个函数 export const createIncrementAsyncAction = (data, time) => { return (dispatch) => { setTimeout(() => { dispatch(createIncrementAction(data)) }, time) } } ``` 3. 配置 thunk 插件 ```js // store.js // 引入 applyMiddleware 异步支持函数 import { createStore, applyMiddleware } from 'redux' import countReducer from './count_reducer' import thunk from 'redux-thunk' export default createStore(countReducer, applyMiddleware(thunk)) ``` 4. 正常调用异步 action ```js // Count.jsx incrementAsync = () => { const { value } = this.selectNumber store.dispatch(createIncrementAsyncAction(value * 1)) } ``` 总结: store 在分发 action 时,发现返回一个函数(为异步 action)。因此 store 执行这个函数,同时给这个函数默认传递 dispatch 参数,等待异步任务完成取到数据后,直接调用 dispatch 方法分发同步 action 。 ### react-redux Redux 官方提供的 React 绑定库。 具有高效且灵活的特性。 [react-redux 中文文档](https://www.redux.org.cn/docs/react-redux/) ![react-redux模型图](https://brucecai55520.gitee.io/my-notes/React/images/react-redux.png) #### react-redux的基本使用 安装: `npm install react-redux` store.js ```js // 引入 createStore 创建 store 对象 import { createStore } from "redux"; // 引入为 Count 服务的 countReducer import countReducer from "./count_reduce"; // 暴露 store export default createStore(countReducer) ``` count_action.js ```js import { INCREMENT,DECREMENT } from "./constant"; export const createIncrementAction = data => ( {type:INCREMENT,data:data} ) export const createDecrementAction = data => ( {type:DECREMENT,data:data} ) ``` count_reducer.js ```js /* 为Count组件创建的服务reducer 作用: 初始化store 接收两个参数:之前的状态(preState),动作对象(action) */ import { INCREMENT,DECREMENT } from "./constant"; const initState = 0 //初始化默认参数 export default function countReducer(preState = initState,action) {//默认参数 const { type,data } = action switch (type) { case INCREMENT: return data + preState case DECREMENT: return preState - data default: return preState//无参数,返回默认值 0 } } ``` App.jsx ```js /* 为容器组件传入store 并渲染 */ import React, { Component } from 'react' import store from "./redux/store"; import Count from "./containers/Count"; export default class App extends Component { render() { return (

求和案例-react实现


) } } ``` Count.jsx ```js /* CountUI容器组件 引入connet 链接ui组件与redux 映射状态和状态方法 */ import CountUI from "../../components/Count"; import { connect } from "react-redux"; import { createIncrementAction,createDecrementAction,createIncrementActionAsync } from "../../redux/count_action"; // 映射状态 function mapStateToProps(state) { return {count:state} } // 映射操作状态的方法 function mapDispathToProps(dispath) { return { increment: inputNumber => {dispath(createIncrementAction(inputNumber))}, decrement: inputNumber => {dispath(createDecrementAction(inputNumber))}, incrementAsync: (inputNumber,time) => {dispath(createIncrementActionAsync(inputNumber,time))}, } } // 映射状态和方法 并暴露容器组件 export default connect( mapStateToProps, mapDispathToProps )(CountUI) ``` CountUI.jsx ```js import React, { Component } from 'react' import './Count.css' export default class Count extends Component { // 加 increment = ()=>{ const userInput = (this.selectNumber.value)*1 this.props.increment(userInput) } // 减 decrement = ()=>{ const userInput = (this.selectNumber.value)*1 this.props.decrement(userInput) } // 奇数加 incrementIfOdd = ()=>{ const userInput = (this.selectNumber.value)*1 if (this.props.count % 2 !== 0) { this.props.increment(userInput) } } // 异步加 incrementAsync = ()=>{ const userInput = (this.selectNumber.value)*1 this.props.incrementAsync(userInput,500) } render() { // const userInput = (this.selectNumber.value)*1 const {count} = this.props return (

当前求和结果为:{count}


) } } ``` #### react-redux的优化 1. mapDispatchToProps 可以写成对象形式,React-Redux 底层会帮助自动分发。 ```js ... export default connect( state => ({count:state}),// 映射状态 { increment:createIncrementAction, decrement:createDecrementAction, incrementAsync:createIncrementActionAsync } //映射状态的简写方式(对象 react-redux 会自动 分发dispath) )(Count) ``` 2. React-Redux 容器组件可以自动监测 Redux 状态变化,因此 index.js 不需要手动监听: 3. index.js入口文件中 Provider 组件可以让所有组件都能获得状态数据,不必一个一个传递 4. 整合容器组件和 UI 组件为一个文件: ```js import React, { Component } from 'react' import { createIncrementAction, createDecrementAction, } from '../../redux/count_action' import {connect} from 'react-redux' // 定义 UI 组件 class Count extends Component { ... } // 创建容器组件 export default connect( state => ({count: state}), { add: createIncrementAction, sub: createDecrementAction } )(Count) ``` #### 多组件共享状态 文件结构, 容器组件和 UI 组件合为一体后放在 containers 文件夹(存放react-redux管理的需要管理状态的组件)。 redux 文件夹新建 actions 和 reducers 文件夹分别用于存放每个容器组件需要的 action 和 reducer 。 重点:*在 store.js 中引入 combineReducers() 整合多个 reducer 来合并总状态对象,组件中通过对象的形式访问* redux/store.js | redux中管理状态的store模块 ```js // 引入 createStore 创建 store 对象 import { createStore,applyMiddleware,combineReducers } from "redux"; // 引入 redux-thunk , 用于支持异步action import thunk from 'redux-thunk' // 引入为 Count 服务的 countReducer import countReducer from "./reducers/count" // 引入为 Persion 服务的 countReducer import persionReducer from "./reducers/persion" // 汇总 reducers const allReducers = combineReducers({ sum: countReducer,//合并后的key 会作为总状态对象中的key,value为状态 persions: persionReducer }) // 暴露 store export default createStore(allReducers,applyMiddleware(thunk)) ``` redux/actions/persion.js | 创建action对象模块 ```js import { ADDPERSION } from "../constant"; export const createAddPersionAction = data => ({ type:ADDPERSION,data }) ``` redux/reducers/persion.js | 初始化状态和处理状态的reducer模块 ```js import { ADDPERSION } from "../constant"; const initState = [{id:136780,name:'root',age:'0'}] export default function persionReducer(preState=initState,action) { const { type,data } = action switch (type) { case ADDPERSION: return [data,...preState] default: return preState } } ``` redux/constant.js | redux中的常量模块 ```js export const INCREMENT = 'increment' export const DECREMENT = 'decrement' export const ADDPERSION = 'addPersion' ``` containers/Persion/index.jsx | UI组件和容器组件 ```js import React, { Component } from 'react' import { connect } from "react-redux"; import { nanoid } from "nanoid"; import { createAddPersionAction } from "../../redux/actions/persion"; import './Persion.css' class demoUI extends Component { addPersion = ()=>{ let name = this.nameNode.value let age = this.ageNode.value const persionObj = {id:nanoid(),name,age} this.props.addPersionObj(persionObj) } render() { return (

Perssion组件,上方组件总和为{this.props.sum}

this.nameNode = c} type="text" id="name" placeholder='请输入你的名字' /> this.ageNode = c} type="text" id="age" placeholder='请输入你的年龄'/>
    { this.props.persionObjArr.map((persionObj)=>{ return
  • 姓名:{persionObj.name} ,年龄:{persionObj.age}
  • }) }
) } } export default connect( state => ({ persionObjArr:state.persions , sum:state.sum , }), { addPersionObj: createAddPersionAction } )(demoUI) ``` #### 补充 数组与对象方法 ```js let arr = [1,2] // 给arr数组追加并return,下面两种方式的区别 return ['a','b',...arr] arr.unshift('a','b') return arr /* 返回的内存地址不同 */ ``` #### Redux 开发者工具 1. Chrome 安装 Redux DevTools 扩展工具 2. 项目下载依赖包 `npm i redux-devtools-extension --save-dev`, 3. 配置: ```js redux/store.js import { composeWithDevTools } from 'redux-devtools-extension' ... //需要异步中间件 export default createStore(Reducers, composeWithDevTools(applyMiddleware(thunk))) // 不需要异步中间件 export default createStore(Reducers, composeWithDevTools()) ``` ## foot