一、简介
1、React是什么?
一个将数据渲染为HTML视图的开源JavaScript库,操作DOM呈现页面。
2、特点
- 采用组件化模式,声明式编码,提高开发效率及组件复用率
- 在react native中可使用react语法进行移动端开发
- 使用虚拟DOM+优秀的diffing算法,减少与真实DOM的交互
二、案例体验
旧版本:16.x,新版本:18.x
依赖包认识:
- babel.min.js:将ES6转成ES5,将JSX转成JS
- react.development.js:核心库
- react-dom.development.js:扩展库
- prop-type.js
使用本地开发环境创建一个项目:
npx create-react-app my-app
删除掉新项目中 src/
文件夹下的所有文件。删除掉新项目中 src/
文件夹下的所有文件。
引入3个依赖包,引入顺序:react.development.js、react-dom.development.js、babel.min.js(没有老师的这个文件,可以直接引入官方网站)
1、创建虚拟DOM
2、渲染虚拟DOM到页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<!-- 引入development.js -->
<script
crossorigin
src="https://unpkg.com/react@18/umd/react.development.js"
></script>
<!-- 引入development-dom.js -->
<script
crossorigin
src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
></script>
<!-- 引入babel,将jsx转为js -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<!-- 节点 -->
<div id="root"></div>
<script type="text/babel">
// 1、创建虚拟节点
const VDOM = <h1>hello</h1>; // 不要写引号
// 2、渲染虚拟节点到页面
ReactDOM.render(VDOM, document.getElementById("root")); //ReactDOM.render(虚拟DOM,容器)
// react没有提供选择器,所以不能用id选择器获取容器
</script>
</body>
</html>
效果:
下载开发者工具:
三、虚拟DOM
注意:
1、用jsx创建虚拟DOM
<script type="text/babel">
// 1、创建虚拟节点
const VDOM = <h1 id="title">hello</h1>; // 不要写引号
// 2、渲染虚拟节点到页面
ReactDOM.render(VDOM, document.getElementById("root")); //ReactDOM.render(虚拟DOM,容器)
// react没有提供选择器,所以不能用id选择器获取容器
</script>
2、用js创建虚拟DOM
<script type="text/javascript">
// 1、创建虚拟节点
// React.createElement(标签名,标签属性,标签内容)
// react的createElement创建虚拟节点,js的createElement创建真实节点
const VDOM = React.createElement("h1", { id: "title" }, "hello"); // 不要写引号
// 2、渲染虚拟节点到页面
ReactDOM.render(VDOM, document.getElementById("root")); //ReactDOM.render(虚拟DOM,容器)
// react没有提供选择器,所以不能用id选择器获取容器
</script>
创建多个虚拟DOM,jsx更方便
3、虚拟DOM就是一个Object类型的对象,即一般对象
4、与真实DOM的区别:consolog输出的真实DOM就是标签及内容,用断点查看会看到真实DOM身上的属性很多,而虚拟DOM身上的属性只有几个 =》原因:虚拟DOM是react内部在用,无需真实DOM
5、虚拟DOM最终会被react转化为真实DOM显示在页面上
四、jsx
jsx = js +XML,是react创造的
语法规则:
1、定义虚拟DOM时,不需要写引号,因为不是字符串
2、标签中混入js表达式(≠ js代码=js代码)时用{ }包裹,且变量内容大小写都有时要转换成小写
3、样式的类名不要写class,要写classname
4、内联样式,用{{ }}包裹,因为样式style里是key-value组成的形式,但是jsx里不能这么写,需要写成对象形式。多个样式时,用逗号隔开,且样式属性名只能用驼峰写法
5、虚拟DOM只能有1个根标签
6、标签要闭合
7、标签首字母为小写,标签转为html同名标签,无同名标签就报错。标签首字母为大写,标签渲染同名组件,找不到该组件就报错
<body>
<!-- 节点 -->
<div id="root"></div>
<script type="text/babel"> // 不用babel报错Unexpected token '<
const myId = "aTgUiGu";
const myData = "Hello, rEact";
// 1、js表达式用{}包裹
const VDOM = (
// 3、类名要写className, 多个样式时,用逗号隔开,且样式属性名只能用驼峰写法
<h2 className="title" id={myId.toLowerCase()}>
<span style={{ color: "white", fontSize: "40px" }}>
{" "}
{myData.toLowerCase()}
</span>
</h2>
);
ReactDOM.render(VDOM, document.getElementById("root"));
</script>
</body>
案例:
关于遍历数据,只能遍历数组,不能遍历对象。
纯数组数据jsx无法渲染出来,但是数据如果是jsx形式就可以被直接渲染到页面,但很显然后端是不会提供这种形式的数据的
遍历时不能少了key,会报错
<body>
<!-- 节点 -->
<div id="root"></div>
<script type="text/babel">
// 不用babel报错Unexpected token '<
const data = ["angular", "react", "vue"];
const VDOM = (
<div>
<h1>框架</h1>
<ul>
{data.map((item, index) => {
return <li key={index}>{item}</li>;
})}
</ul>
</div>
);
ReactDOM.render(VDOM, document.getElementById("root"));
</script>
</body>
五、模块化与组件化
六、脚手架
安装开发工具:
七、组件
1、函数式组件
<body>
<!-- 节点 -->
<div id="root"></div>
<script type="text/babel">
// 1、创建函数式组件,!首字母必须大写!
function Demo(){
console.log(this); // undefine,因为babel转换之后ES5开启严格模式,不允许自定义函数指向window
return <h1>函数式组件</h1>
}
// 2、渲染到页面 !用标签,标签要闭合!
ReactDOM.render(<Demo/>, document.getElementById('root'));
</script>
</body>
执行render后发生了什么?
(1)react解析组件标签,找到组件
(2)发现组件是函数定义的,调用该函数,返回的虚拟DOM转换为真实DOM,渲染到页面
2、类组件
<body>
<!-- 节点 -->
<div id="root"></div>
<script type="text/babel">
// 1、创建类式组件,必须继承React.Component,必须render
// MyComponent使我们创建的,React.Component是react自己的
class MyComponent extends React.Component {
// super(); // 不初始化就不用写
// render放在MyComponent类的原型上,供实例使用
// 对于类,需要new才能有实例,但是这里没有new,render也能提供给实例使用,这是因为当我们渲染组件时写的<MyComponent/>标签,react就自动帮我们new了实例
render() {
return <h1>类式组件</h1>
}
console.log(this); // MyComponent的实例对象
}
// 2、渲染到页面 !用标签,标签要闭合!
ReactDOM.render(<MyComponent/>, document.getElementById('root'));
</script>
</body>
执行render后发生了什么?
(1)react解析组件标签,找到组件
(2)发现组件是类定义的,new该类的实例,并通过实例调用原型的render方法
(3)将render返回的虚拟DOM转换为真实DOM,渲染到页面
八、组件实例的三大属性——state 状态
1、初始化state + 事件绑定 + 修改方法的this指向:
<body>
<!-- 节点 -->
<div id="root"></div>
<script type="text/babel">
// 1、创建类式组件,必须继承React.Component,必须render
// MyComponent使我们创建的,React.Component是react自己的
class Weather extends React.Component {
// 修改数据状态需要构造器
constructor(props) {
super(props); // 如果A类继承B类,且A类写了构造器,A类需要super
// 初始化状态
this.state = { isHot: true }; // react要求使用对象
this.way = this.changeWeather.bind(this); //更改方法的this指向,指向Weather实例
// this.changeWeather指向的就是方法changeWeather,因为其在原型链上所以可以被找到,然后修改他的this(undefined)指向指向Weather实例
}
render() {
const { isHot } = this.state.isHot; // 解构赋值,可以写的更简便
// 绑定事件
return (
<h1 onClick={this.way}>今天天气很{isHot ? "炎热" : "凉爽"}</h1>
);
}
changeWeather() {
// changeWeather放在Weather的原型对象上,供实例使用
// changeWeather作为onClick的回调,不是通过实例调用的而是直接调用
// 类中的方法默认开启了局部的严格模式,所以changeWeather的this是undefined
console.log(this);
}
}
// 2、渲染到页面 !用标签,标签要闭合!
ReactDOM.render(<Weather />, document.getElementById("root"));
</script>
</body>
注意两个this后指的是什么:
2、setState:修改数据
changeWeather() {
// changeWeather放在Weather的原型对象上,供实例使用
// changeWeather作为onClick的回调,不是通过实例调用的而是直接调用
// 类中的方法默认开启了局部的严格模式,所以changeWeather的this是undefined
// console.log(this);
// this.state.isHot = !this.state.isHot; // 数据是不能直接改的,直接改没法渲染
const isHot = this.state.isHot;
// setState修改数据,更新是新旧数据的合并,只修改改变的,不变的数据没有丢失依旧不变
this.setState({ isHot: !this.state.isHot });
}
组件的构造器调用1次,render就调用n+1次,n次是状态更新的次数,方法用几次就调用几次
3、简写
(1)state可以不写在构造器里,单独写出来,与render同级
(2)自定义方法写成赋值语句+箭头函数,满足修改this指向
changeWeather = () => {
// changeWeather放在Weather的原型对象上,供实例使用
// changeWeather作为onClick的回调,不是通过实例调用的而是直接调用
// 类中的方法默认开启了局部的严格模式,所以changeWeather的this是undefined
// console.log(this);
// this.state.isHot = !this.state.isHot; // 数据是不能直接改的,直接改没法渲染
const isHot = this.state.isHot;
// setState修改数据,更新是新旧数据的合并,只修改改变的,不变的数据没有丢失依旧不变
this.setState({ isHot: !this.state.isHot });
}
九、组件实例的三大属性——props
1、基本实现
在组件标签上以key-value形式写的变量,实例里用this.props.变量名呈现
<body>
<!-- 节点 -->
<div id="root"></div>
<script type="text/babel">
class Person extends React.Component {
render() {
console.log(this); // Person实例
return (
<ul>
<li>name: {this.props.name}</li>
<li>sex: {this.props.sex}</li>
<li>age: {this.props.age}</li>
</ul>
);
}
}
// 2、渲染到页面 !用标签,标签要闭合!
ReactDOM.render(
<Person name="miikka" age="24" sex="women" />,
document.getElementById("root")
);
</script>
</body>
解构简写:
<body>
<!-- 节点 -->
<div id="root"></div>
<script type="text/babel">
class Person extends React.Component {
render() {
console.log(this); // Person实例
const {name, age, sex} = this.props
return (
<ul>
<li>name: {name}</li>
<li>sex: {sex}</li>
<li>age: {age}</li>
</ul>
);
}
}
// 2、渲染到页面 !用标签,标签要闭合!
ReactDOM.render(
<Person name="miikka" age="24" sex="women" />,
document.getElementById("root")
);
</script>
</body>
2、批量处理
用...展开运算符,这里是浅拷贝的意思,不是将对象展开,...是无法展开对象的
<body>
<!-- 节点 -->
<div id="root"></div>
<script type="text/babel">
class Person extends React.Component {
render() {
console.log(this); // Person实例
const {name, age, sex} = this.props
return (
<ul>
<li>name: {name}</li>
<li>sex: {sex}</li>
<li>age: {age}</li>
</ul>
);
}
}
const p = {name:"miikka", age:"18", sex:"girl"}
// 2、渲染到页面 !用标签,标签要闭合!
ReactDOM.render(
<Person {...p} />,
document.getElementById("root")
); // ...p 字面量复制一个对象,浅拷贝
</script>
</body>
3、限制props的数据形式
15版本之前,react有一个属性propTypes可以限制,之后版本就用到prop-types包
<!-- 引入prop-types,对输入数据进行限制 -->
<script src="https://unpkg.com/prop-types@15.6/prop-types.js"></script>
限制函数写成=》PropTypes.func
<body>
<!-- 节点 -->
<div id="root"></div>
<script type="text/babel">
class Person extends React.Component {
render() {
console.log(this); // Person实例
const { name, age, sex } = this.props;
return (
<ul>
<li>name: {name}</li>
<li>sex: {sex}</li>
<li>age: {age}</li>
<li>speak: {speak}</li>
</ul>
);
}
}
// 限制数据类型
Person.propTypes = {
name: PropTypes.string.isRequired, // 限制name为字符串,且必须有
};
// 设置默认值
Person.defaultProps = {
age: 18,
};
const p = { name: "miikka", age: "18", sex: "girl" };
// 2、渲染到页面 !用标签,标签要闭合!
ReactDOM.render(<Person {...p} />, document.getElementById("root"));
</script>
</body>
4、props只读不改
5、简写
将propTypes、defaultProps写成静态方法,放在类里
class Person extends React.Component {
// 限制标签属性值类型
static propTypes = {
name: PropTypes.string.isRequired, // 限制name为字符串,且必须有
};
// 给标签属性值设置默认值
static defaultProps = {
age: 18,
};
render() {
console.log(this); // Person实例
const { name, age, sex } = this.props;
return (
<ul>
<li>name: {name}</li>
<li>sex: {sex}</li>
<li>age: {age}</li>
<li>speak: {speak}</li>
</ul>
);
}
}
6、构造器与props
在构造器里可以不写props,但是用this无法找到当前类的props
constructor(props) {
super(props);
console.log(this.props);
}
constructor() {
super();
console.log(this.props); // undefined
}
7、函数式组件的props
<body>
<!-- 节点 -->
<div id="root"></div>
<script type="text/babel">
function Person(props) {
const { name, age, sex } = props;
return (
<ul>
<li>name: {name}</li>
<li>sex: {sex}</li>
<li>age: {age}</li>
</ul>
);
}
// 限制变量类型
Person.propTypes = {
name: PropTypes.string.isRequired, // 限制name为字符串,且必须有
};
// 给变量设置默认值
Person.defaultProps = {
age: 18,
};
// 2、渲染到页面 !用标签,标签要闭合!
ReactDOM.render(
<Person name="miikka" />,
document.getElementById("root")
);
</script>
</body>
8、总结
十、组件实例的三大属性——ref
1、字符串形式(已过时)
2、回调形式
3、回调执行次数
4、createRef
调用后可以返回一个容器,该容器可以存储被ref标识的节点,该容器专节点专用(也就是会被新的覆盖)
5、何时使用&不要过度使用
十一、事件处理
发生事件的元素和操作事件的元素是同一个,可以省略ref
十二、非受控组件与受控组件
1、非受控组件
元素现用现取,用ref,如form里的input
2、受控组件
元素不是现用现取,不用ref
十三、函数柯里化
十四、生命周期(生命周期钩子函数,生命周期回调函数)
注意!生命周期钩子函数的书写顺序与react调用顺序无关,react会在该调用的时候调用
卸载组件ReactDOM.unmountComponentAtNode (记忆:卸载组件在节点)
componentDidMount:组件挂载完毕
render:初始化渲染、状态更新之后
1、旧生命周期(16.x版本)
(1)组件挂载过程
(2)组件更新过程
第一条路:正常更新
shouldComponentUpdate:控制组件是否更新,默认值true,如果程序员不亲自写则保持默认值,如果亲自写成false则永远无法更新状态
第二条路:强制更新
应用场景:不想对状态中的数据更新,但想更新页面
第三条路: 父子组件的生命周期顺序
构建父子组件:
(3)总结
2、新生命周期(17.x版本)
如果新版本用了旧版本的钩子函数,在控制台3个带will的钩子函数,会被警告,且带上UNSAFE_钩子函数名称 :
18.x已废弃
(1)getDerivedStateFromProps
- a. 静态方法,使用时写static
- b. 要return 返回值,返回一个状态对象 or null
如果返回值是一个状态对象,返回状态对象就会影响状态更新componentWillUpdate,返回的状态对象含有初始状态的数据,页面渲染就按返回的状态对象来。强制更新、卸载是不受影响的。
如果向组件传入数据,组件用props接收,getDerivedStateFromProps(props),return props的效果与上面所述一致。
应用场景:state的值取决于props
(2)getSnapshotBeforeUpdate
- 要return 返回值,返回任意数据or null。return的值给下面的生命周期钩子函数render
- 在组件更新完毕之前进行一些需要的操作
(3)总结
十五、Diff算法
比较的最小粒度是节点
十六、初始化脚手架
脚手架基于webpack
1、安装与创建
安装脚手架:
npm i create-react-app -g
cmd打开并输入以下命令:
create-react-app 项目名称
2、文件介绍
3、模板
App.js
import './App.css';
// 创建组件App
import React from 'react';
class App extends React.Component {
render(){
return (
<div className="App">
hello
</div>
)
}
}
export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
// 检查App代码不合理的地方
<React.StrictMode>
<App />
</React.StrictMode>
);
reportWebVitals();
十七、样式的模块化
十八、组件化编码流程
十九、todolist案例
1、拆分组件
2、实现静态组件
3、动态展示数据
二十、React Ajax
1、配置代理:
package.json:
“proxy”:“http://localhost:5000” 5000是服务器的端口号
2、配置多个代理
二十一、GitHub搜索案例
二十二、兄弟组件/任意组件通信——消息发布与订阅
工具库:PubSubJS
安装:
npm i pubsub-js
使用:
在componentDidMount里写
二十三、fetch发送请求
浏览器window上内置,promise封装
二十四、SPA单页面应用
二十五、React路由 React-Router
1、简介
2、版本
有3个:web,native,any
3、版本5
BrowserRouter与HashRouter的区别
4、版本6
安装命令:
npm i react-router-dom
(1)简介 + 与版本5的区别
(2)组件使用
1)src / index.js引入路由:
import { BrowserRouter } from 'react-router-dom';
2)将a标签改为路由标签,将a里的herf属性改为to属性
Link:无高亮
NavLink:有高亮
在App.js引入:
import { NavLink } from "react-router-dom";
3)在呈现组件的位置注册路由
问题:为何废弃旧版本的switch,改为新版本的routes
书写要点:
①要用<Routes></Routes>包裹起来
②组件名称写在element属性
③组件写成标签形式
④一个路由匹配之后,就算path一样,也不会再匹配其他路由
⑤重定向:Navigator 只要渲染,就会引起视图的变化,默认路由跳转模式是push,replace需要打开replace={true}
⑥自定义类名:写成函数形式,可以通过类名改变导航高亮的样式
⑦useRoutes:将多个路由写成一个路由表
一般业务开发,路由配置单独写在routes/index.js:
在App.js使用:
⑧嵌套路由
⑨路由占位符
相当于Vue-router里的view-router
10)hooks
<1>useRoutes
<2>useNavigator
编程式路由导航,返回一个函数
<3>useParams
接收当前匹配路由的params参数,直接解构就可以直接使用
<4>useSearchParams
读取和修改当前位置的url中的查询字符串
返回一个数组:[当前的search参数,更新的search参数] 《==》 [search, setSearch]
search:search参数对象,获取某个参数 =》search.get(参数名)
setSearch:更新search参数的方法
<5>useLocation
获取当前location信息
<6>useMatch
返回当前匹配信息
<7>useInRouterContext
返回是布尔值,只要是App的子组件,不管是不是路由组件,该hook的输出都是true,说明处于上下文环境中
某个组件脱离路由器的管理,就会返回false
<8>useNavigationType
通过哪种路由方式来到当前组件
返回值:pop(通过刷新页面而来),push,replace
<9>useOutlet
呈现当前组件中渲染的嵌套路由组件
<10>useResolvedPath(URL)
给定URL,解析其中的path、search、hash
编程式路由导航
二十六、Ant Design组件库
1、安装
npm i antd --save
2、引入
// 按需引入组件
import {Button} from "antd";
// 引入antd的样式
import "antd/dist/reset.css";
引入ant.css出现错误可以尝试引入ant.min.css
3、按需引入
配置具体的修改规则,config-overrides.js
4、自定义主题
安装less:npm i less less-loader
二十七、redux
1、简介
2、何时使用
3、工作流程
react组件要进行行为时,创建一个动作对象action,action存储数据类型type和具体数据data,告诉action actors,action actors派发dispatch给store,store是一个调度者指挥者,并不对数据进行改动,store将当前数据状态和action对象发给reducers,reducers本身可以初始化状态state,此时previousState=understand,reducers把action数据对象进行加工更改后返回新的数据状态给store,store再把新的数据返回给react组件,组件通过getState获取
4、三个核心
5、API
(1)createStore():创建store 已弃用
新版本 =》import { legacy_createStore as createStore } from 'redux';
(2)store.getState():获取状态
(3)store.dispatch():派发action数据对象
(4)store.subscribe(()=>{}):检测redux数据变化,数据变化就调用render
6、action
(1)同步:object一般对象类型的action
(2)异步:函数类型的action,return也是函数
7、react-redux
react插件库
优化—
优化:
开发者工具
二十八、项目打包
二十九、扩展
1、setState
react状态更新是异步的
2、lazyload
lazy本身是一个函数,在内部还要调用一个函数
3、hook
(1)statehook ==》 React.useState()
返回值,数组,[状态,更新状态的方法]
(2)effecthook
(3)refhook
4、fragment
5、 context
6、PureComponent
7、render props
8、ErrorBoundary 错误边界
9、 组件通信方式