Welcome to React
一.create-react-app
全局安装create-react-app
$npm install -g create-react-app
检查版本
create-react-app -V
创建项目
//npm
npm init react-app my-app
//yarn
create-react-app my-app(项目名要小写)
运行项目
npm start
生成的目录结构如下
扩展
- 安装nrm
npm的镜像源管理工具,有时候国外资源太慢,使用这个就可以快速地在 npm 源间切换
安装: npm i -g nrm
查看: nrm ls
切换: nrm use 镜像名
- 删除src
二. 入门
// 从 react 的包当中引入了 React。只要你要写 React.js 组件就必须引入React, 因为react里有
一种语法叫JSX,稍后会讲到JSX,要写JSX,就必须引入React
import React from 'react'
// ReactDOM 可以帮助我们把 React 组件渲染到页面上去,没有其它的作用了。它是从 react-dom 中
引入的,而不是从 react 引入。
import ReactDOM from 'react-dom'
2.1 . 虚拟DOM的两种创建方式
- 原生的JS
把组件渲染并且构造 DOM 树,然后插入到页面上某个特定的 元素上
==React.createElement(标签,标签属性,标签内容)==
- JSX语法
全称JavaScript XML,作用:用来简化创建虚拟DOM
2.2真实DOM和虚拟DOM
- 虚拟DOM,本质是Object一般对象
- 虚拟DOM更轻
- 虚拟DOM最终会被React转换成真实DOM
2.3 JSX
-
全称: JavaScript XML
-
react定义的一种类似于XML的JS扩展语法: JS + XML本质是React.createElement(component, props, …children) 方法的语法糖
-
作用: 用来简化创建虚拟DOM
- 写法:var ele =
* Hello JSX!*
- 注意1:它不是字符串, 也不是HTML/XML标签
- 注意2:它最终产生的就是一个JS对象
- 写法:var ele =
-
标签名任意: HTML标签或其它标签
-
标签属性任意: HTML标签属性或其它
-
基本语法规则
XML, 早期用于存储和传输数据,后来使用JSON,
JSON的
-
parse: 解析JSON字符串,构造由字符串描述的JavaScript值或对象。
-
stringfy: 将JavaScript 对象或值转换为 JSON 字符串
-
语法规则
-
定义DOM时,不要写引号
-
标签中混入JS表达式时,需要{}
-
要是用className,而不是class
-
内联样式,要用 style = {{key:value}}的形式
-
只能有一个根标签,可以用<>幽灵节点代替
-
标签必须闭合(标签只有一个的也要闭合)
-
标签:
- 小写字母开头,将转成HTML中的元素,若HTML中无该标签对应的同名元素,则报错
- 大写字母开头,react则渲染对应的组件
Style有两对花括号的原因:
①外层花括号:因为React使用的是JSX语法,JSX语法中嵌入任何js变量、表达式、对象都要用花括号{}扩起来,
②内层花括号:JSX如果用到行内CSS style样式时,这个行内样式必须是一个js对象,即{width:’233px’, marginRight:’10px’}是一个对象所以用花括号扩起来。
{}内只能写表达式,不能写语句
JS的表达式,语句
表达式: 会产生值的语句
表达式:
-
a a+b demo(1) //函数调用 arr.map() //函数方法 function test(){} //定义函数
语句:
if(){}
for(){}
switch(){case}
2.4模块,组件,模块化,组件化
模块:
- 理解:向外提供特定功能的js程序, 一般就是一个js文件
- 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。
- 作用:复用js, 简化js的编写, 提高js运行效率
组件:
- 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)
- 为什么要用组件: 一个界面的功能更复杂
- 作用:复用编码, 简化项目编码, 提高运行效率
三. React面向组件编程
-
组件中,render方法中的this为组件的实例化对象
-
组件自定义方法中的this为undefined,如何解决:
a.强制绑定this,通过函数对象的bind()
b.箭头函数
-
状态数据,不能直接修改和更新
2.1 基本使用
函数式组件
- 函数名首字母需要大写
- 需要写成标签形式
Demo中的this为undefined,因为babel编译后开启了严格者模式
-
类中的构造器不是必须写的,要对实例进行一系列初始化的时候才写
-
如果A继承了,且存在构造器,则super必须使用
-
类中定义的方法,放在了原型对象上
-
render发生了什么:
- React解析组件标签,找到了MyDome组件
- 发现组件使用函数定义随后调用函数
类式组件
-
首字母仍需要大写
-
render发生了什么:
- React解析组件标签,找到了MyDome组件
- 发现组件使用类定义,随后New出来该类的实例,并通过该实例调用原型上的render方法
- 将render返回的虚拟DOM转为真实的DOM,随后渲染界面
render放在MyDome的原型对象上,供实例使用
this指向MyDome的实例化对象
简单组件
没有状态的组件
复杂组件
有状态的组件
组件核心的三大核心
前提:使用类组件的定义
状态: state
React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。
React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。
以下实例创建一个名称扩展为 React.Component 的 ES6 类,在 render() 方法中使用 this.state 来修改当前的时间。
添加一个类构造函数来初始化状态 this.state,类组件应始终使用 props 调用基础构造函数。
state
初始化状态和使用
import React from "react"
import {createRoot} from "react-dom/client"
class Weather extends React.Component{
//构造器
constructor(props){
super(props);
this.state = {
isHot:false,
}
console.log(this);
}
render(){
return <h1>天气{this.state.isHot===true?"炎热":"凉爽"}</h1>
}
}
//渲染
createRoot(document.getElementById("root")).render(<Weather/>);
事件绑定
babel: 禁止自定义的函数指向window
类中所有已经定义的方法,在局部都开启了严格者模式
bind:.bind()方法返回的是一个新函数
class Weather extends React.Component{
//构造器
constructor(props){
super(props);
this.state = {
isHot:false,
}
//强制绑定this解决undefined
this.test = this.change.bind(this);
console.log(this);
}
//注意
change(){
//1.change放在了Weather的原型对象上,供实例使用
//2.由于change是作为onClick的回调,所以不是通过实例调用的,是直接调用
//3. 类中的方法默认在局部开启了严格者模式,所以change中的this为undefined
console.log(this);
}
render(){
return <h1 onClick={this.test}>天气{this.state.isHot===true?"炎热":"凉爽"}</h1>
}
}
state的数据不能直接修改
直接更改:(错误)
this.state.isHot = !this.state.isHot;
- 需要借助API(需要借助setState,且更新是一种合并操作)
- 构造器执行1次
- render调用 1+n次
- 点击事件调用 n次
简写
class Weather extends React.Component {
state = {
isHot: false,
width:"1000px",
}
change=()=>{
let {isHot} = this.state;
this.setState({
isHot:!isHot,
});
}
//组件渲染核心
render(){
const {isHot,width} = this.state;
return <h1 onClick={this.change}>你好好好{isHot===true?"热":"冷"} {width}</h1>
}
}
使用赋值语句+箭头函数使用自定义方法
组件被称为状态机
props
- babel+react等,可以实现 用展开运算符去展开对象(原生JS不允许) , 且只能在标签属性的传递时候使用
使用 PropTypes 进行类型检查
安装和引入
npm install prop-types
import PropTypes from 'prop-types'
文档: zh-hans.reactjs.org/docs/typech…
使用
- 限制为函数为PropTypes.func
- props只允许读
简写
简写
- 使用defaultProps
- 函数参数默认值(React推荐)
构造器
- 类中的构造器,是否接收props,是否传递props,取决于是否在构造器中使用this访问props
- 能省就省,开发中用不到
函数式组件可以使用props,借助hooks,也可以使用state
refs
ref:打标识,组件内的标签可以通过ref属性来标识自己
createRef()
React.createRef()调用后返回一个容器,该容器存储被ref标识,专人专用
字符串形式
不被推荐使用
创建
使用
回调形式
关于ref回调函数的执行次数
内联函数: 内联函数是指在 React 进行 “rendering” 时定义的函数。
如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
状态驱动页面显示,state改变,render重新执行
JSX注释
{/* 注释 */}
事件处理
show = (e) => {
console.log(e.target.value)
//alert(data);
}
<input onBlur={this.show} placeholder="失去焦点"/>
四.收集表单数据
受控组件和非受控组件
- 受控组件: 在React中,可变状态通常保存在组件的状态属性中,并且只能使用 setState() 进行更新,而呈现表单的React组件也控制着在后续用户输入时该表单中发生的情况,以这种由React控制的输入表单元素而改变其值的方式,称为受控组件。
- 非受控组件:,表单数据由DOM本身处理。即不受setState()的控制,与传统的HTML表单输入相似,input输入值即显示最新值。
高阶函数
满足以下两条之一就是高阶函数,例如promise,setTimeout,Map
- 接收的参数为函数
- 返回值依然是函数
函数柯里化:通过函数调佣继续返回函数的方式,实现多次接收参数最后统一处理的函数编程风格
React生命周期
旧版:
- componentDidMount:组件挂载完毕
- componentWillUnmount:组件将要卸载
- componentWillMount:组件将要挂载
- shouldComponentUpdate: 阀门,true,更新,返回false,则不能更新(不写默认返回true),写了的话必须加返回值,否则会报错
- componentWillReceiveProps:第一次接收不算,每次接收新的才算
- componentDidMount(常用):发送请求,开启定时器,订阅消息
- componentWillUnmount(常用):关闭定时器,取消订阅消息,
新版:
- 所有带will的生命钩子,都需要在前面加上UNSAFE_,除componentWillUnmount
- 即将废弃componentWillMount,componentWillUpdate,componentWillReceiveProps
getDerivedStateFromProps:从props中获取state,可以说,这个生命周期的功能实际上就是将传入的props映射到state上面。state的值在任何时候都取决于props,试用前需要加static
getSnapshotBeforeUpdate : getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()。
diffing算法
react/vue中的key作用
为什么遍历列表的时候,key最好不要是用index
虚拟DOM中key的作用:
-
key使虚拟DOM对象的标示,在更新显示key其极其重要的作用
-
详细的说:当状态中数据发生变化时,react会根据新数据生成新的虚拟DOM,随后,react进行新的虚拟DOM和旧虚拟DOM的diff比较,规则如下:
-
就虚拟DOM中找到与新虚拟DOOM相同的key:
- 若虚拟DOM中内容无变,直接使用之前的真实DOM
- 否则,生成新的真实DOM,替换
-
未找到相同的key:根据数据创建新的真实DOM,随后渲染页面
-
用index做key可能引发的错误
- 对数据进行:逆序添加,逆序删除等破坏顺序的操作:会产生没有必要的真实DOM更新,效率低
- 结构中包含输入类的DOM: 产生错误DOM更新
三 脚手架
HTML界面
关于模块化
import React,{Component} from 'react'
//之所以能使用{Component},是因为使用了分别暴露,不是解构赋值
export default class App extends Component {
}
为了区别入口文件和组件,我们给组件后缀改为*.jsx*
样式模块化
讲解 www.bilibili.com/video/BV1wy…
应用
rcc快速生成代码片段
功能界面的组件化编码
-
拆分组件
-
实现静态组件:使用组件实现静态的页面效果
-
实现动态组件
-
页面初始化数据
- 数据类型
- 数据名称
- 保存在哪个组件
-
交互(从绑定事件监听开始)
-
Element UI
Element-React (elemefe.github.io)
安装和使用
不推荐
Ant Design of React - Ant Design
组件通信
父传子
父亲使用:
子组件
子传父
父组件
子组件
消息订阅与发布
工具库: PubSubJS
安装
npm i pubsub-js
todoList
使用
四 react Ajax
1. 配置
2. 常用ajax请求库
-
jQuery
-
axios
- promise风格
3. 跨域
跨域的本质,就是同源策略的限制
浏览器的同源策略
同源: 协议,域名,端口三个都相同就是同源
上图中
React;http://localhost:3000
Spring Boot: http://localhost:5000/students
其中协议(http),域名(localhost)都一样,但是端口号不一样(3000和5000)
于是不同源,导致跨域
解决
使用CORS: 跨源资源共享(CORS)
跨源资源共享 (CORS)(或通俗地译为跨域资源共享)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其它 origin(域,协议和端口),使得浏览器允许这些 origin 访问加载自己的资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求, 该机制通过浏览器发起一个到服务器托管的跨源资源的“预检”请求。在预检中,浏览器发送的头中标示有HTTP方法和真实请求中会用到的头。
使用代理
方法1
方法2: 手动配置解决跨域
创建src/setupProxy.js
const {createProxyMiddleware} = require('http-proxy-middleware')
module.exports = function (app) {
app.use(
//遇见前缀为api1的,触发此配置
createProxyMiddleware('/api1', {
target: 'http://localhost:5000',//请求转发目标
changeOrigin: true,//控制服务器收到的请求头中的Host值
pathRewrite: {//重写请求路径
'^/api1': ''
}
}),
createProxyMiddleware('/api2',{
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: {
'^/api2': ''
}
})
)
}
4.xhr和fetch
window内置的fetch也可以发送,也是promise风格
5.promise(ES6新规范)
6. fetch
使用fetch和axios发送Ajax请求
axios({
methods:"get",
url:"https://api.apiopen.top/api/getHaoKanVideo?page=0&size=2"
}).then((res)=>{
this.setState({
res:res.data.message
})
})
//使用fetch
fetch({
method:"get",
url:"https://api.apiopen.top/api/getHaoKanVideo?page=0&size=2"
}).then(res=>{
console.log(res)
console.log("使用fetch发送的为"+ res);
})
fetch发送请求返回的是是否与
使用await等优化
Rcc与rsf快速搭建
五 React Router 6
5. 1理解
什么是路由
- 一个路由就是一个映射关系
- key为路径,value可能使function或component
路由分类
- 后端路由
- 前端路由
react-router理解
- react的一个插件库
- 专门实现SPA应用
- 基于React项目基本都会用到
router也分为BrowserRouter和HashRouter
底层原理不一样:
BrowserRouter调用的是H5 history API,低版本兼容性问题。
HashRouter 使用的是URL哈希值
地址栏表现形式不一样:
BrowserRouter的路径:localhost:3000/demo/a
HashRouter的路径:localhost:3000/#/demo/a
刷新后对路由state参数的影响
BrowserRouter没有任何影响,因为state保存在history对象中。
HashRouter刷新后会导致路由state参数的丢失!!!
官方会更推荐使用browserRouter,貌似是因为其构建于H5的History API,比起hashRouter,它多出了更多的方法操控url。
本人建议:前端路由使用HashRouter ,服务路由使用BrowerRouter
一般组件和路由组件
区别:
- 写法不同
-
存放位置不同,一般的放在components,路由的放在pages
-
接收到的props不同,
- 一般组建,传递什么接收什么
- 路由组件,接收到三个固定属性,
5.2 react-router-dom相关的API
中文文档:react-router.docschina.org/web/guides/…
安装
yarn add react-router-dom@版本号
5.3 使用
1. 基本
2. 重定向Navigate
作用 : 只要Navigate组件被渲染.就回渲染路径,切换视图
replace : 默认为false , 即为 push模式, 为 true , 为 replace 模式
3. Route 和 Routes
Routes 包裹 Route
Route相当于 if 语句,匹配到 URL , 则渲染对应的组件
<Route caseSensitive/>属性用于指定: 匹配是否区分大小写
Route 可以嵌套使用,且可以使用 useRoutes配置路由表,但必须通过组件渲染其子路由
4. NavLink 的 className
5. 路由表的使用
注意,多级路由使用Outlet渲染
6 .路由传参/子路由
- params,需要占位
- search
- location.state
路由表配置占位
传参
接收
search 参数
传参
接收
state参数
传参
接收
7 . 编程式路由
六 redux
6.1 理解
中文文档 cn.redux.js.org/
redux是什么
- redux是作用于状态管理的JS库
- 可以用在react,angular,vue等项目,但基本与react配合
- 集中式管理react应用中多个组件的共享状态
引用场景
- 某个组件需要其他组件可以随时拿到状态
- 一个组件需要改变另一个组件状态(通信)
- 原则: 能不用就不用
工作流程
6.2 核心概念
action
-
动作对象
-
两个属性
- type:
- data
action的值是一个一般对象,为同步action,是函数,为异步action
注意,使用异步action,请确保安装了
yarn add redux-thunk
reducer
- 用于初始化状态,加工状态
- 加工时,依照久的state和action,产生新的纯函数
store
- 将state,action,reducer联系在一起
6.3 redux核心API
- 创基store
- 创建reducer
- 组件中引用
import store from '../../redux/stroe'
- action生成
6.4 编写应用
6.5 react-redux
原理图
-
安装 react-redux
-
建立containers文件夹,建立为某个组件服务的文件夹,建立index.jsx
纯函数
-
同样的输入,同样的输出
-
遵循以下约束
- 不能改写参数数据
- 不会产生副作用,列如网络请求,输入和输出设备
- 不能调用Date.now()或者Math.randow()等不纯的方法
-
redux的reducer函数必须是一个纯函数
使用redux开发者工具
安装
yarn add redux-devtools-extension
//store中引入
import {composeWithDevTools} from 'redux-devtools-extension'//
同步和异步action
- 函数类型action:异步action
- 一般对象的action:异步action
异步action:
- 引入 npm i redux-thunk,处理异步 action 中间件
- 在 store 中配置
七打包
八 拓展
1. context
import React, {Component} from 'react';
//1.创建context
const MyContext = React.createContext();
export default class A extends Component {
state = {
name: "谭凯中",
age:121,
sex:'meal'
}
render() {
const {name,age,sex} = this.state;
return (
<div>
组件A,名字:{name}
{/*2. 使用Provider郭,传递的值写入value*/}
<MyContext.Provider value={{name,age,sex}}>
<B/>
</MyContext.Provider>
</div>
);
}
}
class B extends Component {
render() {
return (
<div>
组件B
<C/>
</div>
);
}
}
class C extends Component {
//3.声明接收context
static contextType = MyContext;
render() {
const {name,age,sex} = this.context;
return (
<div>
组件C,拿到了: {name+"---"+age+"---"+sex}
</div>
);
}
}
组件通信方式总结
组件间的关系:
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
几种通信方式:
1.props:
(1).children props
(2).render props
2.消息订阅-发布:
pubs-sub、event等等
3.集中式管理:
redux、dva等等
4.conText:
生产者-消费者模式
比较好的搭配方式:
父子组件:props
兄弟组件:消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
九 Hooks
不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层以及任何 return 之前调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确。(如果你对此感到好奇,我们在下面会有更深入的解释。)
9.1 useState状态改变
useState()是改变状态的开关,将状态添加到函数组件需要4个步骤:启用状态、初始化、读取和更新。
不能出现在条件判断中
用值更新状态
用回调更新状态
回调函数作为参数
场景: 初始化的参数要通过一定的计算获得的时候
9.2 useEffect替换生命周期
实现ComponentWillUnmount
return 即可,
use
9.3 useContext共享状态
接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。
配合createContext
9.4 useRef
在函数组件中获得真是DOM 或组件对象
使用
- 导入 useRef
- 执行 ,传入null,返回值为一个对象,内部有一个current属性存放拿到DOM 的对象 或者 组件实例(类组件)
- 通过 ref 绑定要获得的元素或者组件
9.5 useMemo
useEffect 和 useMemo 区别
- useEffect是在
DOM改变之后触发,useMemo在DOM渲染之前就触发 - useMemo是在
DOM更新前触发的,就像官方所说的,类比生命周期就是[shouldComponentUpdate] - useEffect可以帮助我们在
DOM更新完成后执行某些副作用操作,如数据获取,设置订阅以及手动更改 React 组件中的 DOM 等 - 不要在这个useMemo函数内部
执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo - 在useMemo中使用
setState你会发现会产生死循环,并且会有警告,因为useMemo是在渲染中进行的,你在其中操作DOM后,又会导致触发memo
9.6 useCallback
\