文章目录
- [1. React介绍](#1. React介绍)
-
-
- [1.1.1. React是什么?](#1.1.1. React是什么?)
- [1.1.2. 为什么学?](#1.1.2. 为什么学?)
- [1.1.3. React特点](#1.1.3. React特点)
-
- [2. 创建React元素--三个API](#2. 创建React元素--三个API)
-
- [2.1. React.createElement() :创建一个React元素](#2.1. React.createElement() :创建一个React元素)
- [2.2. ReactDOM.createRoot() :创建React的根容器,用来放置React元素](#2.2. ReactDOM.createRoot() :创建React的根容器,用来放置React元素)
- [2.3. root.render() :](#2.3. root.render() :)
- [3. JSX(JS扩展)](#3. JSX(JS扩展))
-
-
- [3.1.1. 基本使用](#3.1.1. 基本使用)
- [3.1.2. 注意事项:](#3.1.2. 注意事项:)
- [3.1.3. 渲染列表](#3.1.3. 渲染列表)
-
- [4. 虚拟DOM](#4. 虚拟DOM)
- [5. 创建React项目](#5. 创建React项目)
-
- [5.1. 手动操作](#5.1. 手动操作)
- [5.2. 自动创建](#5.2. 自动创建)
- [6. 组件](#6. 组件)
-
- [6.1. 函数组件](#6.1. 函数组件)
- [6.2. 类组件](#6.2. 类组件)
- [6.3. 类组件的状态数据](#6.3. 类组件的状态数据)
- [6.4. 组件间通信:父传子--props](#6.4. 组件间通信:父传子--props)
- [7. 事件](#7. 事件)
- [8. State](#8. State)
-
- [8.1. useState( )](#8.1. useState( ))
- [8.2. 类组件中的State](#8.2. 类组件中的State)
- [9. Ref()](#9. Ref())
- [10. 表单](#10. 表单)
-
- [10.1. 获取表单数据](#10.1. 获取表单数据)
- [10.2. 表单的双向绑定(使用useState)](#10.2. 表单的双向绑定(使用useState))
- [11. 组件生命周期](#11. 组件生命周期)
-
- [11.1. 定义](#11.1. 定义)
- [11.2. 钩子函数](#11.2. 钩子函数)
- [12. 条件渲染](#12. 条件渲染)
-
- [12.1. if渲染](#12.1. if渲染)
- [12.2. 三目运算符](#12.2. 三目运算符)
- [12.3. 逻辑与运算符](#12.3. 逻辑与运算符)
- [13. 列表渲染](#13. 列表渲染)
- [14. portal](#14. portal)
- [15. React中的css写法](#15. React中的css写法)
-
- [15.1. 内联样式](#15.1. 内联样式)
- [15.2. 单独的css文件](#15.2. 单独的css文件)
- [15.3. css moudules](#15.3. css moudules)
- [15.4. css in js(styled-components)](#15.4. css in js(styled-components))
- [16. Fragement](#16. Fragement)
- [17. Context](#17. Context)
- [18. setState()的执行流程](#18. setState()的执行流程)
- [19. useEffect](#19. useEffect)
- [20. useReducer](#20. useReducer)
- [21. React.memo](#21. React.memo)
- [22. useCallback()](#22. useCallback())
1. React介绍
1.1.1. React是什么?
是一个将数据渲染为HTML视图的开源JavaScript库
1.1.2. 为什么学?
- 原生JS操作DOM繁琐,效率低(DOM-API操作UI)
- 使用JS直接操作DOM,浏览器会进行大量的重绘重排
- 原生JS没有组件化编码方案,代码复用率低
1.1.3. React特点
- 采用组件化模式 ,声明式编码,提高开发效率和组件复用率
- 在React Native中可以使用React语法进行移动端开发
- 使用虚拟DOM 和优秀的Diffing算法,尽量减少与真实DOM的交互
2. 创建React元素--三个API
2.1. React.createElement() :创建一个React元素
参数:
- 元素的名称,html标签必须是小写
- 标签中的属性
- class属性需要使用className来代替
- 在设置事件时,属性名需要修改为驼峰命名
- 元素的内容(子元素)
注意:
- React元素最终会通过虚拟DOM转换为真实DOM
- React元素一旦创建就不能修改,只能通过新创建元素来替换
2.2. ReactDOM.createRoot() :创建React的根容器,用来放置React元素
2.3. root.render() :
- 当首次调用时,容器节点里的所有DOM元素都会被替换,后续的调用则会使用
React的DOM差分算法(DOM diffing algorithm)进行高效的更新。
- 不会修改容器节点(只会修改容器的子节点)。可以在不覆盖现有子节点的情况下,
将组件插入已有的DOM节点中。
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<!-- React 核心库 -->
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<!-- React DOM 渲染库 -->
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
</head>
<body>
<div id="root"></div>
<script>
//创建一个React元素
const button = React.createElement(
"button",
{
id: "btn",
type: "button",
className: "hello",
onClick: () => {
alert("点击了");
},
},
"点击我",
);
//获取根元素
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(button);
</script>
</body>
</html>
3. JSX(JS扩展)
- 是JavaScript的语法扩展,JSX使得我们可以以类似于HTML的形式去使用JS。
- 是React中声明式编程 的体现方式。声明式编程,简单理解就是以结果为导向的编程。
- 使用JSX将我们所期望的网页结构编写出来,然后React再根据JSX自动生成JS代码。
- JSX 是
React.createElement()的语法糖,我们所编写的JSX代码,最终都会转换为以调用React.createElement()创建元素的代码。 - 在React中使用JSX,需要引入
babel来完成"翻译"工作
3.1.1. 基本使用
- 引入 babel ,因为浏览器本身不认识jsx
- script标签加上type="text/babel"
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<!-- React 核心库 -->
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<!-- React DOM 渲染库 -->
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<!-- Babel 用于浏览器端编译 JSX -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const div = (
<div>
<h1>Hello World</h1>
</div>
);
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(div);
</script>
</body>
</html>
3.1.2. 注意事项:
- JSX不是字符串,不要加引号
- JSX中的html标签应该小写,React组件应该大写开头
- JSX中有且只有一个跟标签
- JSX的标签必须正确结束(自结束标签必须写
/) - 可以使用
{}嵌入表达式(有值的语句就是表达式) - 如果表达式是空值,布尔值,
undefined,将不会显示
javascript
const myId='atfenghui'
const myData='hello,REACT'
// <!-- 1.创建虚拟DOM -->
const VDOM = (
<h2 id={myId.toUpperCase()}>
<span>{myData.toLowerCase()}</span>
</h2>
)
- 属性可以在标签中直接设置
- 样式的类名指定不要用class,要用
className - 内联样式,要用
style={``{key.value}}的形式,外面大括号表示里面是一个表达式,里面的大括号表示对象,里面的属性要使用驼峰命名
html
<div style={{color:"white",fontSize:"20px"}}>天气真好</div>
3.1.3. 渲染列表
javascript
const arr=['孙悟空','猪八戒','小牛']
const list=<ul>{arr.map((item,index)=><li key={index}>{item}</li>)}</ul>
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(list);
4. 虚拟DOM
在React我们操作的元素被称为React元素,并不是真正的原生DOM元素,
React通过虚拟DOM将React元素和原生DOM,进行映射,虽然操作的React元素,但是这些操作最终都会在真DOM中体现出来
虚拟DOM的好处:
- 降低API复杂度
- 解决兼容问题
- 提升性能(减少DOM的不必要操作)
每当我们调用root.render()时,页面就会发生重新渲染,React会通过diffing算法,将新的元素和旧的元素进行比较,通过比较找到发生变化的元素,并且**只对变化的元素进行修改,没有发生的变化不予处理,**在控制台中的体现:发生变化的元素会变成紫色
在比较时,先比较父元素,若父元素不一样,直接将该父元素及起所有子元素全部替换;父元素一致,则去逐个比较子元素,直到找到发生变化的元素。
当我们在JSX中显示数组 中,数组中每一个元素都需要设置一个唯一key ,否则控制台会显示红色警告。重新渲染页面时,React会按照顺序依次比较 对应的元素,当渲染一个列表时如果不指定key,同样也会按照顺序进行比较,如果列表的顺序永远不会发生变化,这么做当然没有问题,但是如果列表的顺序会发生变化,这可能会导致性能问题出现。
比如逆序添加时,由于新元素插入到了列表中的第一个位置,其余元素内容不变,只是顺序发生改变,而React默认根据位置比较元素,所以,此时所有元素都会被修改。
为了解决这个问题,React为列表设计了一个key属性,key在页面中无法查看,这样React就会比较key相同的元素,而不是按照顺序比。所设置的key在当前列表中唯一即可,一般使用ID。尽量不使用元素索引index,因为索引会跟着元素位置改变而改变,和没有设置一样。当元素顺序不会发生改变时,可以使用index
5. 创建React项目
5.1. 手动操作
- 目录:
latex
src
--index.js
public
--index.html
- 初始化和安装
bash
npm init -y
yarn add react react-dom react-scripts
- 打包命令,会生成build目录
bash
npx react-scripts build
- 运行,会实时更改
bash
npx react-scripts start
- 替换两种复杂的操作:在package.json中添加:
javascript
"scripts": {
"start": "react-script start",
"build": "react-script build"
},
运行:npm start
打包:npm run build
- 样式:在
src下建立index.css文件,和index.js同级,样式写在这里面
css
body{
background-color: #f5aaaa;
}
5.2. 自动创建
bash
npx create-react-app
6. 组件
6.1. 函数组件
一、函数组件的定义
- 函数组件就是一个函数
- 函数名就是组件名
- 函数名首字母必须大写【因为组件在调用的时候,是使用jsx语法调用的】
- 函数的返回值,就是组件渲染的结果
二、函数组件的调用
<组件名/>调用该组件,就是执行组件函数- 函数的返回值,就是渲染的结果,替换掉调用标签
javascript
const App = () => {
return (
<div>
<h1 className="heading">我是App组件</h1>
</div>
);
};
export default App;
//src/index.js :入口文件
import ReactDOM from "react-dom/client";
import './index.css'
import App from './App.js'
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App/>);

6.2. 类组件
一、类组件定义:
- 类组件使用class进行声明定义
- 类组件要继承
React.Component类 - 类组件的类名就是组件名
- 类组件类名首字母必须大写
- 类组件,必须有
render方法 - render方法执行后,必须要有返回值,返回值是什么,调用组件后渲染的内容就是什么。99.999% 返回值都是一个react元素
二、类组件的调用
- 组件使用jsx语法进行调用
单标签:<组件名 />
对标签:<组件名></组件名>
- 类组件被调用,是如何执行的?
- 当遇到类组件 时,首先会实例化 该组件
new App() - 使用实例化出来的对象,调用
render方法 - 将
render方法return的内容,替换掉 调用标签
javascript
import React from 'react'
class App extends React.Component{
render(){
return (
<div>
<h1 className="heading">我是App 类 组件</h1>
</div>
)
}
}
export default App;

6.3. 类组件的状态数据
- 创建状态数据
javascript
constructor(){
// 因为App继承了 Component,所以首先要调用父类的构造函数
super()
// 定义自身的属性
// state属性是特殊的,状态数据的属性,值一般是一个对象,对象中的属性就是状态数据
this.state = {
count:10,
msg:'atguigu'
}
// 构造函数,都是类的实例调用的,所以此处的this永远指向组件的实例对象
}
- 读取
- 直接读取
{this.state.count} - 解构之后读取
javascript
let {count,msg} = this.state
- 改变
通过setState函数
javascript
this.setState({
count:this.state.count + 1
})
6.4. 组件间通信:父传子--props
-
传递方式:调用子组件的标签,通过属性传递
-
- 逐个属性单独赋值传递
- 通过...扩展运算批量传递
-
Props是只读的,不能更改
-
类组件的接收方式: 子组件中通过
this.props.特殊属性接收
javascript
import Child from './components/Child'
export default class App extends Component {
render() {
return (
<div>
<h3>App组件</h3>
<hr />
{/*通过属性传递数据给子组件*/}
<Child username='atguigu' age={10}/>
</div>
)
}
}
import React, { Component } from 'react'
export default class Child extends Component {
render() {
console.log('child: ',this.props)
let {username, age} = this.props
return (
<div>
<h4>Child</h4>
<p>props username: {this.props.username} - {username}</p>
<p>props age: {this.props.age}- {age}</p>
</div>
)
}
}
- 函数组件接收方式:将props作为参数
javascript
import LogItems from "./LogItems";
const Logs = () => {
return (
<div className="Logs">
{/* {父组件向子组件传递数据:props} */}
<LogItems
date={new Date(2021, 7, 21, 19, 20)}
time="2021-09-09"
desc="学习react"
/>
</div>
);
};
export default Logs;
import MyDate from "./MyDate";
const LogItem=(props)=>{
// props.desc="嘻嘻"//props属性是只读的,不能改
return(
<div className="item">
<MyDate date={props.date}/>
<div className="log-item-title">{props.desc}</div>
<div className="log-item-content">{props.time}</div>
</div>
)
}
export default LogItem;
import React from "react";
const MyDate = (props) => {
const month = props.date.toLocaleString("zh-CN", { month: "long" });
const date = props.date.getDate();
return (
<div className="date">
<div className="month">{month}</div>
<div className="day">{date}</div>
</div>
);
};
export default MyDate;
7. 事件
事件对象
React事件中同样会传递事件对象,可以在响应函数中定义参数来接收事件对象
React中的事件对象同样不是原生的事件对象,是经过React包装后的事件对象
由于对象进行过包装,所以使用过程中我们无需再去考虑兼容性问题
javascript
//1. 函数组件
const App = () => {
const clickHandler = (event) => {
event.preventDefault(); //React中取消默认行为(a标签)
event.stopPropagation();//取消事件冒泡
alert("点击了按钮");
//return false;React中无法通过这种方式来取消默认行为
};
return (
<div>
<h1 className="heading">我是App组件</h1>
<button onClick={clickHandler}>我是按钮</button>
<a href="https://www.baidu.com" onClick={clickHandler}>
我是超链接
</a>
</div>
);
};
export default App;
8. State
props中的所有属性都是不可变的,这使得React组件不能随着props的改变而改变。但在实际
的开发中,我们更希望的是数据发生变化时,页面也会随着数据一起变化。React为我们提供了
state用来解决这个问题。
state只属于自身组件,其他组件无法访问,是可变的
state相当于一个变量,只是这个变量在React中进行了注册,React会监控这个变量的变化,当state发生变化时,会自动触发组件的重新渲染,在函数组件中,我们需要使用钩子函数,获取state
8.1. useState( )
- 使用钩子函数
useState()来创建state - 需要一个值作为参数,这个值是
state的初始值 - 该函数会返回一个数组
- 数组的第一个元素,是初始值,初始值只是用来显示数据,直接修改不会触发组件的重新渲染
- 数组的第二个元素,是一个函数,通常会命名为
setXXX,用来修改state,调用这个函数来修改state,只有stat发生变化时才会触发组件的重新渲染,并不是说只要调用了这个函数就会重新渲染。并且使用函数中的值作为新值。 - 当state的值是一个对象时,新对象会全部替换旧对象
- 当通过setState去修改一个state时,并不表示修改当前的state,他修改的是下一次渲染时的state值
setState()会触发组件的重新渲染,是异步的,所以当调用setState()需要用旧state的值时,一定要注意有可能出现计算错误的情况(连续修改,但是第一次还没执行就已经执行第二次了,则第二次函数执行时拿到的旧值不是上一次执行后的值),为了避免这种情况,可以通过为setState()传递回调函数的形式来修改state
javascript
import React, { useState } from "react";
//1. 函数组件
const App = () => {
const result = useState(1);
// let counter=result[0];//1
const [counter, setCounter] = result;
const addHandler = () => {
// setCounter(counter + 1);
//使用回调函数,React会确保函数的返回值一直是最新的
// setCounter((prevCounter) => {
// return prevCounter + 1;
// });
//简写
setCounter((prevCounter) => prevCounter + 1);
};
const lessHandler = () => {
setCounter(counter - 1);
};
const [user, setUser] = useState({ name: "张三", age: 18 });
const updateHandler = () => {
console.log("重新渲染");
//方式一
// setUser({ name: "张三", age: 19 });//点击修改按钮时,页面会重新渲染,将旧对象替换
//方式二
// user.name="lisi"
// setUser(user);//这是相当于直接修改了就得state对象,由于对象还是那个对象,引用没变,所以不会重新渲染,点击修改按钮时,实际数据已经变了,但是页面不会改变,也没有发生重新渲染。因为只改变了属性,而这个对象的地址没有发生改变
//方式三:浅复制
const newUser = Object.assign({}, user);//把user中的值全都复制到新创建的这个空对象里面
console.log(newUser===user);//false
newUser.name = "lisi";
setUser(newUser);
//方式四(三的简写,其实也是浅复制)
setUser({ ...user, name: "lisi" });
};
return (
<div>
<h1 className="heading">{counter}</h1>
<h1 className="heading">
{user.name}--{user.age}
</h1>
<button onClick={addHandler}>点击+1</button>
<button onClick={lessHandler}>点击-1</button>
<button onClick={updateHandler}>点击修改</button>
<p>{counter}</p>
</div>
);
export default App;
8.2. 类组件中的State
- 类组件中state统一存储到了实例对象的state属性中,可以通过
this.state获取state数据,通过this.setState()来修改,这时React只会修改设置了的属性,不会影响其他的属性,但是这仅限于直接存储于state中的属性,比如代码中obj中的name和age,如果只修改name,则age会丢失 - 获取DOM对象
- 创建一个属性,用来存储DOM对象
javascript
divRef = React.createRef();
- 在render方法中,将ref属性绑定给DOM元素
javascript
<div ref={this.divRef}></div>
- 函数组件中,响应函数直接以函数的形式定义在组件中的;在类组件中,响应函数是以类的方法来定义的
javascript
import React, { Component } from "react";
//类组件的props
class User extends Component {
state = {
count: 0,
test: "test",
obj: { name: "张三", age: 18 },
};
//获取DOM对象
//1. 创建一个属性,用来存储DOM对象
divRef = React.createRef();
//2. 在render方法中,将ref属性绑定给DOM元素
//类组件中,用箭头函数,避免this问题
clickHandler = () => {
// this.setState({count:this.state.count+1})
// this.setState({count:10})这里只对count进行了修改,但是原来的数据test属性不会丢失
// this.setState((prevState)=>{
// return {count:prevState.count+1}
// })
// this.setState({obj:{name:'沙和尚'}})//这种情况只改变name,而且age会丢失
this.setState({ obj: { ...this.state.obj, name: "沙和尚" } }); //这样改会改掉name并且会保留age
};
render() {
return (
<div ref={this.divRef}>
<h1>{this.state.count}</h1>
<button onClick={this.clickHandler}>点击</button>
<ul>
<li>姓名:{this.props.name}</li>
<li>年龄:{this.props.age}</li>
<li>性别:{this.props.gender}</li>
</ul>
</div>
);
}
}
export default User;
9. Ref()
直接从React处获取DOM对象步骤:
- 创建一个存储DOM对象的容器:使用
useRef()钩子函数 - 将容器设置为想要获取DOM对象的ref属性,React会自动将当前元素的DOM对象,设置为容器current属性
钩子函数的注意事项:
- React中的钩子函数只能用于函数组件或自定义钩子
- 钩子函数只能直接在函数组件中调用,不能在某个内部函数中调用
javascript
import React, {useRef } from "react";
const App = () => {
const h1Ref = useRef(); //h1Ref相当于一个容器;这种方式创建对象,可以确保每次渲染,
//所获取到的对象都是同一个对象
//const h1Ref={current:null},这种方式创建的对象,组件每次重新渲染都会创建一个新对象
const clickHandler = () => {
const header = document.getElementById("header");
h1Ref.current.innerHTML = "嘻嘻";
console.log(header === h1Ref.current); //true
};
return (
<div>
//将ref写在h1里面,表示要获取这个容器
<h1 id="header" ref={h1Ref}>
我是Ref
</h1>
<button onClick={clickHandler}>点击修改header内容</button>
</div>
);
export default App;
10. 表单
10.1. 获取表单数据
javascript
import React, { useRef } from "react";
import Card from "../UI/Card";
const LogsForm = () => {
let inputDate = "";
let inputDesc = "";
let inputTime = "";
//监听日期变化
const dateChangeHandler = (e) => {
//获取到当前触发事件的对象,时间对象中保存了当前时间触发时的所有信息
//e.target是出发事件的对象
inputDate = e.target.value;
};
//监听内容变化
const descChangeHandler = (e) => {
inputDesc = e.target.value;
};
const timeChangeHandler = (e) => {
inputTime = e.target.value;
};
//当表单提交时,要汇总数据
//在React中表单不需要自行提交,而是要通过React提交
const submitHandler = (e) => {
e.preventDefault(); //取消默认提交行为
//获取数据
const newLog = {
data: new Date(inputDate),
desc: inputDesc,
time: +inputTime, //+表示将字符串转换为数字
};
console.log(newLog);
};
return (
<Card className="logs-form">
<form onSubmit={submitHandler}>
<div className="form-item">
<label htmlFor="date">日期</label>
<input type="date" id="date" onChange={dateChangeHandler}></input>
</div>
<div className="form-item">
<label htmlFor="desc">内容</label>
<input
type="text"
id="desc"
onChange={descChangeHandler}
></input>
</div>
<div className="form-item">
<label htmlFor="time">时长</label>
<input type="number" id="time" onChange={timeChangeHandler}></input>
</div>
<button>添加</button>
</form>
</Card>
);
};
export default LogsForm;
10.2. 表单的双向绑定(使用useState)
上面的表单属于非受控组件
实现双向绑定的表单是受控组件,如下
将表单中的数据存储到state中
将state设置为表单项的value值,这样当表单项发生改变时,state也会随之变化,反之,state发生变化时,表单项也会跟着改变,这种操作步骤称之为双向绑定,这样一来,表单成为了一个受控组件
javascript
import React, { useState } from "react";
import Card from "../UI/Card";
const LogsForm = () => {
// const descRef = useRef();
// let inputDate = "";
// let inputDesc = "";
// let inputTime = "";
const [inputDate, setInputDate] = useState("");
//监听日期变化
const dateChangeHandler = (e) => {
// inputDate = e.target.value;
setInputDate(e.target.value);
};
const submitHandler = (e) => {
e.preventDefault(); //取消默认提交行为
//获取数据
const newLog = {
date: new Date(inputDate),
desc: inputDesc,
time: +inputTime, //+表示将字符串转换为数字
};
//清空表单
setInputDate("");
setInputDesc("");
setInputTime("");
console.log(newLog);
};
return (
<Card className="logs-form">
<form onSubmit={submitHandler}>
<div className="form-item">
<label htmlFor="date">日期</label>
<input
type="date"
id="date"
onChange={dateChangeHandler}
value={inputDate}
></input>
</div>
<button>添加</button>
</form>
</Card>
);
};
export default LogsForm;
11. 组件生命周期
11.1. 定义
- 组件的生命周期:组件从被创建到挂载到页面中运行,在到组件不用时卸载的过程
- 钩子函数:生命周期的每个阶段总是伴随者一些方法的调用,为开发人员在不同阶段操作组件提供了时机
- 只有类组件才有生命周期。
11.2. 钩子函数
- 创建时:construct->render->React更新DOM和refs->componentDidMount
javascript
// Parent.js
import React from 'react';
class Parent extends React.Component{
constructor(props){
super(props)
this.state = {
data:0
}
console.log('生命周期钩子函数: constructor')
}
componentDidMount(){
console.log('生命周期钩子函数 componentDidMount')
}
render(){
console.log('生命周期钩子函数: render')
return (
<div>
<h1>{this.state.data}</h1>
</div>
)
}
}
export default Parent
- 更新时:render(new Props,setState(),forceUpdate())->React更新DOM和refs -> componentDidUpdate
javascript
// Parent.js
import React from 'react';
class Parent extends React.Component{
constructor(props){
super(props)
this.state = {
count:0
}
console.log("=============创建时开始===============")
console.log('生命周期钩子函数 constructor')
}
handleClick = ()=>{
this.setState({
count:this.state.count + 1
})
}
refreshClick = ()=>{
this.forceUpdate()
}
componentDidMount(){
console.log('生命周期钩子函数 componentDidMount')
console.log('=============创建时结束=================')
}
componentDidUpdate(){
console.log("生命周期钩子函数 componentDidUpdate")
if(this.state.count > 10){
console.log("已达到最大数目,重置")
this.setState({
count:0
})
}
console.log(document.getElementById('title').innerHTML)
console.log("===============更新完成===================")
}
render(){
console.log("===============渲染开始===================")
console.log('生命周期钩子函数 render')
return (
<div>
<Counter count={this.state.count}/>
<button onClick={this.handleClick}>add+1</button>
<button onClick={this.refreshClick}>刷新页面</button>
</div>
)
}
}
class Counter extends React.Component{
componentDidUpdate(prevProps){
console.log("子组件-生命周期钩子函数 componentDidUpdate")
console.log("上一次的props:",prevProps,",当前的props:",this.props)
if(prevProps.count !== this.props.count && this.props.count > 10){
console.log("已达到最大数目,重置")
this.setState({
count:0
})
}
console.log("===============子组件-更新完成===================")
}
render(){
console.log("===============子组件-渲染开始===================")
console.log("子组件-生命周期钩子函数 render")
return (
<h1 id='title'>统计的数目:{this.props.count}</h1>
)
}
}
export default Parent
- 卸载时:componentWillUnmount
javascript
// Parent.js
import React from 'react';
class Parent extends React.Component{
constructor(props){
super(props)
this.state = {
count:0
}
}
handleClick = ()=>{
this.setState({
count:this.state.count + 1
})
}
componentDidUpdate(){
console.log("生命周期钩子函数 componentDidUpdate")
console.log("===============更新完成===================")
}
render(){
console.log("===============渲染开始===================")
console.log('生命周期钩子函数 render')
return (
<div>
{
this.state.count <= 10?
(
<div>
<Counter count={this.state.count}/>
<button onClick={this.handleClick}>add+1</button>
</div>
)
:
(
<div>已达到10</div>
)
}
</div>
)
}
}
class Counter extends React.Component{
componentWillUnmount(){
console.log("==============子组件-卸载开始=============")
console.log("当前数目:",this.props.count)
console.log("==============子组件-卸载完成=============")
}
componentDidUpdate(){
console.log("子组件-生命周期钩子函数 componentDidUpdate")
console.log("===============子组件-更新完成===================")
}
render(){
console.log("===============子组建渲染开始==============")
return (
<h1 id='title'>统计的数目:{this.props.count}</h1>
)
}
}
export default Parent
12. 条件渲染
12.1. if渲染
javascript
const {isShow} = this.state;
let element;
if(isShow) {
element = <div>显示内容</div>
}
return (
<div>
{element}
</div>
)
12.2. 三目运算符
javascript
const {isShow} = this.state;
return (
<div>
{isShow ? <div>内容1</div> : <div>内容2</div>}
</div>
)
12.3. 逻辑与运算符
javascript
const {isShow} = this.state;
return (
<div>
{isShow && <div>loading...</div>}
</div>
)
13. 列表渲染
在 React 中通常使用 filter() 筛选需要渲染的组件和使用 map() 把数组转换成组件数组。
通常要在循环中加入key属性,以进行性能优化。
javascript
const {list} = this.state;
return (
<div>
<ul>
{list.filter(item => item.age > 18).map(item => <li key={item.id}>{item.name}</li>)}
</ul>
</div>
)
14. portal
组件默认会作为父组件的后代渲染到页面中,但是有些情况下,这种方式会带来一些问题,通过portal可以将组件渲染到页面中的指定位置
使用方法:
-
在index.html中添加一个新元素
-
修改组建的渲染方式:
-
- 通过
ReactDOM.creatPortal()作为返回值创建元素 - 参数:jsx(修改前return的代码);目标位置
- 通过
javascript
import React from 'react';
import './BackDrop.css';
import ReactDOM from 'react-dom';
//获取backdrop的根元素
const backdropRoot=document.getElementById("backdrop-root");
const BackDrop=(props)=>{
//将这一部分代码传到backdropRoot根元素中
return ReactDOM.createPortal(
<div className="backdrop">
{props.children}
</div>,backdropRoot
)
}
export default BackDrop;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<!-- 这个容器专门用来放遮罩层 -->
<div id="backdrop-root"></div>
</body>
</html>
15. React中的css写法
15.1. 内联样式
- 优点
- 不会产生样式冲突
- 动态设置
- 缺点
- 驼峰写法
- 有些样式没提示
- 大量样式代码臃肿混乱
- 无法编写如伪类、伪元素的样式等
javascript
class App extends PureComponent {
constructor(props){
super(props)
this.state = {
color: 'red'
}
}
render(){
const { color } = this.state
return (
<div>
<div style={{fontSize: '18px', color: color}}>App Component</div>
</div>
)
}
}
15.2. 单独的css文件
缺点:都属于全局css,容易造成样式冲突
css
/* App.css */
.title {
font-size: 18px;
color: red;
}
/* App.jsx */
import './App.css'
class App extends PureComponent {
render(){
return (
<div>
<div className="title">App Component</div>
</div>
)
}
}
15.3. css moudules
css modules并不是react特有的,所有类似于webpack配置环境下都可以使用,react脚手架已经内置了css modules的配置。
注意:.css、.less、.scss后缀的样式文件都需要改成.module.css、.module.less、.module.scss
优点:解决了局部作用域问题
缺点:
- 类名不能使用连接符"-",如:.home-title
- 所有className都必须要用
{ style.className }的形式来编写
css
.title {
font-size: 18px;
color: red;
}
.footer {
text-align: center;
}
/* App.jsx */
import appStyle from './App.module.css'
class App extends PureComponent {
render(){
return (
<div>
<div className={appStyle.title}>App Component</div>
<div className={appStyle.footer}>Footer Component</div>
</div>
)
}
}
15.4. css in js(styled-components)
styled-components使用标签模板语法
javascript
let a = 5;
let b = 10;
tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);
16. Fragement
React.Fragement:是一个专门用来做父容器的组件,他只会将它里面的子元素直接返回,不会创建任何的多余元素
javascript
import React, { useState } from "react";
const App = () => {
// 原先写法,return中必须有一个根元素,否则报错,但是这样页面中会出现一个不需要的div
return (
<div>
<LogsForm onSaveLog={saveLogHandler} />
<Logs logsData={logsData} onDeleteLog={deleteLogHandler} />
</div>
);
// 使用React.Fragement
return (
<React.Fragement>
<LogsForm onSaveLog={saveLogHandler} />
<Logs logsData={logsData} onDeleteLog={deleteLogHandler} />
</React.Fragement>
);
//语法糖:使用空标签
return (
<>
<LogsForm onSaveLog={saveLogHandler} />
<Logs logsData={logsData} onDeleteLog={deleteLogHandler} />
</>
);
};
export default App;
17. Context
相当于一个公共的存储空间,我们可以将多个组件都需要访问的数据统一存储到一个Context中,这样就无需通过props逐层传递,即可使组件访问到这些数据
创建:
javascript
import React from"react";
const TextContext=React.createContext({
name:'fff',
age:18
})
export default TextContext;
使用方式一:
- 引入
Context - 使用Xxx.Consumer组件来创建元素,Consumer的表歉意需要一个回调函数,他会讲context设置为回调函数的参数,通过参数可以访问到context中存储的数据
javascript
import React from "react";
import TextContext from "../store/textContext";
const A=()=>{
return(
<TextContext.Consumer>
{
(ctx)=>{
return <div>{ctx.name},{ctx.age}</div>
}
}
</TextContext.Consumer>
)
}
使用方式二:使用钩子函数
- 引入
Context - 使用钩子函数
useContext()获取到context,useCOntext()需要一个Context作为参数,他会讲Context中的数据获取并作为返回值返回
javascript
import React, { useContext } from "react";
import TextContext from "../store/textContext";
const B=()=>{
const ctx=useContext(TextContext)
return(
<div>{ctx.name},{ctx.age}</div>
)
}
数据应该在App.js中写
Xxx.Provider表示数据的生产者,可以使用它来指定Context中的数据
通过value来指定Context中存储的数据,这样在该组件的所有子组件中都可以通过Context来访问数据。
会读取理他最近的Provider中的数据,如果没有,就读取Context中的默认数据
javascript
import TestContext from "./store/testContext";
import A from "./components/A";
import B from "./components/B";
const App=()=>{
return (
<div>
<TestContext.Provider value={{ name: "Alice", age: 25 }}>
<A />
<TestContext.Provider value={{ name: "Alice", age: 25 }}>
<B />
<Meals mealsData={mealsData} />
</TestContext.Provider>
</TestContext.Provider>
</div>
);
}
18. setState()的执行流程
写项目时,如果要使用setState()来修改某个变量,不可以直接在函数体中书写setCount(1);
javascript
import React from "react";
// 1. 函数组件
const App = () => {
const [count, setCount] = React.useState(0);
console.log("App组件重新渲染了");
// setCount(1);
//会报错 Too many re-renders
//正确写法
const clickHandler=()=>{
setCount(1)
}
return (
<div>
<h1 className="heading" onClick={clickHandler}>{count}</h1>
</div>
);
};
export default App;
通过前面的学习,我们知道,当新的state值和旧值相同时,他是不会触发组件的重新渲染的
但是 setState()的执行流程(函数组件):
底层:dispatchSetDate(),会先判断组件当前处于什么阶段,
如果是渲染阶段,不会检查state值是否相同;
如果是非渲染阶段,会检查state的值是否相同,如果值不同,会对组件进行重新渲染

如果值相同,React在一些情况下会继续执行当前组件的渲染,但是这个渲染不会触发其子组件的渲染,这次渲染不会产生实际效果,这种情况通常发生在值第一次相同时
19. useEffect
useEffect()是一个钩子函数,需要一个函数作为参数,这个作为参数的函数,将会在组件渲染完毕后执行;第二个参数是一个数组,在数组中可以指定Effect的依赖项,只有当依赖项发生变化时,Effect才会触发。通常会将Effect中使用的所有变量都设置为依赖项
像`useState()`,是由钩子函数`useState()`生成的,`useState()`会确保组件的每次渲染都会获取到相同的`setState()`对象
如果依赖项设置了一个空数组,那么Effect只会在组件初次渲染时执行一次
在开发中,可以将那些会产生副作用的代码编写到useEffect的回调函数中,可以避免这些代码影响到组件的渲染
javascript
// 1. 函数组件
const App = () => {
const [count, setCount] = React.useState(0);
console.log("App组件重新渲染了");
// setCount(1);
// Too many re-renders
useEffect(() => {
setCount(1);
},count);
return (
<div>
<h1 className="heading" onClick={clickHandler}>
{count}
</h1>
</div>
);
};
export default App;
useEffect(()=>{
if(ctx.totalNum === 0){
setShowDetails(false);
setShowCheckout(false);
}
},[ctx,setShowDetails,setShowCheckout])
20. useReducer
useReducer可以向组件中添加一个reducer,用法和redux很像,但却不能用于共享数据,它可以在一些场景下替代useState。
useReducer有三个参数:
reducer整合函数:
-
对于我们当前state的所有操作都应该在该函数中定义,该函数的返回值,会成为state的新值,如果没有返回值,则默认返回undefined,他在页面中不显示。
-
reducer在执行时,会收到两个参数
-
state:当前最新的stateaction:action需要一个对象,在对象中会存储dispatch所发送的命令。
initialArg:state的初始值,- init函数:用于计算初始值的函数,可以不传,使用init(initialArg)的执行结果作为初始值,不传时直接使用initialArg
返回值是一个数组:
- 第一个参数:
state,用来获取state的值 - 第二个参数,
state修改的派发器,通过派发器可以发送操作state的命令,具体的修改行为将会由另外一个函数(reducer)执行
使用方法:
javascript
import React, { useReducer } from "react";
//为了避免reducer会重复创建,会把他定义在组件外面
const countReducer=(state, action) => {
//可以根据action中的不同type来执行不同的操作
// if (action.type === "ADD") {
// return state + 1;
// } else if (action.type === "SUB") {
// return state - 1;
// }
switch (action.type) {
case "ADD":
return state + 1;
case "SUB":
return state - 1;
default:
return state;
}
//这一步是防止传错而返回undefined,如果不符合上面两个if则还返回原来的数据
return state;
}
const App = () => {
const [count, countDispatch] = useReducer(countReducer, 1);
const addHandler = () => {
countDispatch({ type: "ADD" });
};
const subHandler = () => {
countDispatch({ type: "SUB" });
};
return (
<div>
<button className="heading" onClick={addHandler}>
增加
</button>
{count}
<button className="heading" onClick={subHandler}>
减少
</button>
</div>
);
};
export default App;
21. React.memo
App组件的子组件是A组件,A组件的子组件是B组件,当App组件中有setState语句,并且会触发App组件的重新渲染时,A和B组件家也会跟着重新渲染,但是这两个组件并没有发生任何变化,也不会产生虚拟dom,重新渲染的话很没必要,所以可以使用React.memo()来避免组件的重新渲染
React.memo()是一个高阶组件,会接受另一个组件作为参数,并且会返回一个包装过的新组建。包装过的新组建就会具有缓存功能,包装过后,只有组件的props发生变化才会触发组件的重新渲染,否则总是返回缓存中的结果。
javascript
const A=()=>{
return (
<div>我是A组件<div/>
)
}
export default React.memo(A)
22. useCallback()
useCallback()是一个钩子函数,用来创建React中的回调函数,他创建的回调函数不会总在组件重新显然是重新创建。
第一个参数是一个函数,第二个参数是依赖项,只有依赖项发生变化时,回调函数才会重新创建,如果不指定依赖数组,那么回调函数每次都会重新创建。如果是空的依赖数组,那么组件只会在初始化时创建。一定要讲函数当中用到的所有变量添加到依赖项,除了setState,和useEffect很像
const clickHandler=useCallback(()=>{ setCount(prevState=>prevState+num); setNum(prevState=>num+1) },[num])