# react-book
**Repository Path**: react-stack/react-book
## Basic Information
- **Project Name**: react-book
- **Description**: react基本语法及进阶
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2023-10-23
- **Last Updated**: 2023-11-20
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README

## React基础
### 初识react:
#### 01 react特点:
react框架具有以下特点:
- 声明式编码;
- 组件化编码;
- 使用visual DOM,不总是直接操作页面上的真实DOM;
- DOM diffing算法,最小化页面重绘;
#### 02 虚拟DOM:
虚拟DOM本质是一个object对象。虚拟DOM最终会被React转换成真实DOM,展示在页面上。
```html
```
来一段hello world的代码
```react
const Title =
这里是是一段虚拟DOM
;
ReactDOM.render(Title, document.getElementById('app'));
```
虚拟DOM的创建可以直接创建,也可以使用`React.createElement`进行创建。
```react
const App=React.createElement('h1',{className:'text-danger'},'APPLICATION');
ReactDOM.render(App,document.getElementById('app'))
```
### JSX的使用:
#### 01 jsx的规则:
**JSX的规则:**
- 定义虚拟DOM时,不要写引号;
- 样式类名使用className代替class;
- JSX中混入的js语句使用`{}`包裹;
- 内联样式要写成对象键值对的形式;
- 虚拟DOM只能有一个根标签;
- 虚拟DOM中的标签必须闭合;
- 只能写js表达式,不能写js语句**【if判断、for循环之类】**;
- JSX中的列表渲染,每一项必须有唯一的Key值。
#### 02 样式定义:
样式可以使用类名和内联样式定义,类名可以定义成变量。
```react
const container = 'container';
class App extends React.Component {
render() {
return (
这里是标题
)
}
}
```
样式也可以使用style内联样式定义:
```react
class App extends React.Component {
render() {
return (
这里是标题
)
}
}
```
#### 03 数据填充:
在jsx中,数据填充使用**`{}`**包裹。
```react
const btnTitle = "增加";
class App extends React.Component {
render() {
return (
)
}
}
```
#### 04 列表渲染:
列表渲染一般使用map遍历,每一个循环体中要使用`key`进行标记。
```react
const list = [
{
id: 1,
name: 'react'
},
{
id: 2,
name: 'vue'
},
{
id: 3,
name: 'angular'
}
];
class App extends React.Component {
render() {
return (
列表渲染
{
list.map(item => (- {item.name}
))
}
)
}
}
```
### 三大核心之state:
#### 01 state是什么:
state是组件中控制状态的参数,其实质是一个对象,初始化时使用直接赋值,但是更新状态时,需要调用内置方法`setState`方法,并且更新状态是合并操作而不是替换操作。
state一般是定义在组件类中的构造函数中,当然也可以直接定义在构造器之外。
```react
// 定义在构造函数中
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
counts: 0
}
}
render() {
return (
)
}
}
```
state值也可以定义在构造函数外部。
```react
class App extends React.Component {
state = { counts: 0 }
render() {
return (
)
}
}
```
#### 02 state的更改:
state的更改使用**`setState`**方法,setState可以直接更改对象值,也可以使用回调函数进行状态的更改。
- **setState( {...}, callback ):**
这种方式需要提前获取当前state的值,然后再修改更新最新的值,回调函数是组件render之后调用,在其中可以获取到更新之后的state值。
```react
this.setState({
counts: this.state.counts + 1
})
```
- **setState( fn ):**
setState也可以使用函数式更改state的值,回调函数中可以获取到当前state值和props值。
```react
this.setState((state, props) => {
return ({
counts: state.counts + 1
})
})
// 简写为
this.setState(state => ({ counts: state.counts + 1 }))
```
#### 03 setState的异步性:
setState函数式异步的,与setState同步的代码获取的state值是没有更新后的值,只有在setState回调函数中才会获得最新的state的值。
```react
increment = () => {
this.setState({
counts: this.state.counts + 1
}, () => {
// 获取的是更新后的state的值
console.log("回调函数内:", this.state.counts)
})
// 获取的是未更新的state值
console.log("事件触发后:", this.state.counts)
}
```
### 三大核心之props:
#### 01 props的传值:
props是react中父子组件相互传递值的媒介,默认传递的值为字符串。
```react
class Alert extends React.Component {
render() {
return (
{this.props.title}
)
}
}
class App extends React.Component {
render() {
return (
)
}
}
```
#### 02 props约束传值:
如果需要规定prop的类型和默认值,需要借助于`prop-types`来定义。
```react
class Alert extends React.Component {
render() {
return (
{this.props.title}
)
}
}
// 约束Alert组件的props类型
Alert.propTypes = {
type: PropTypes.string.isRequired,
title: PropTypes.string.isRequired
}
// 定义Alert组件的props默认值
Alert.defaultProps = {
type: 'danger',
title: '默认是一段警示文本'
}
```
借助于类的static关键字,我们可以将props约束定义成静态属性。
```react
class Alert extends React.Component {
static propTypes = {
type: PropTypes.string.isRequired,
title: PropTypes.string.isRequired
}
static defaultProps = {
type: 'danger',
title: '默认是一段警示文本'
}
render() {
return (
{this.props.title}
)
}
}
```
#### 03 构造器中的props:
对于类式组件,如果类中定义了构造函数,我们要将props通过super关键词进行传递,这样才能确保在构造器函数中正确的获得到props的值。
```react
class Alert extends React.Component {
constructor(props) {
super(props);
console.log(this.props)
}
render() {
return (
{this.props.title}
)
}
}
```
#### 04 函数式组件中的props:
函数式组件中只能通过传值的形式来获取props值。
```react
function Alert(props) {
return (
{props.title}
)
}
```
### 三大核心之ref:
react中如果要定位到某个DOM元素,可以使用ref进行寻找定位,ref的使用方式分为以下三种:
- 直接定义字符串ref:【即将放弃的一种】
- 使用回调的方式获取ref:
- createRef函数获取ref:【当前最推荐的一种】
#### 01 字符串定义ref:
字符串定义的ref都会挂载在组件实例的`refs`属性中,访问的时候通过`this.refs.xxx`即可得到。
```react
// 定义
// 获取
this.refs.refInput
```
#### 02 回调函数定义ref:
字符串定义ref会带来无法预知的性能问题,因此会被逐渐废弃掉,可以使用回调函数的形式定义ref。
```react
// 定义
this.refInput = r} className="form-control" type="text" />
// 获取
this.refInput
```
#### 03 createRef函数获取ref:
`createRef()`函数可以定义独一无二的ref对象。
```react
// 定义
refInput = React.createRef()
// 获取
this.refInput.current
```
### 事件响应:
react中使用类似于`onClick`的方式来定义事件,在事件调取时要注意this的指向问题。
#### 01 this指向问题:
react事件定义时经常会出现this指向不正确的问题。
```react
// this指向问题
class App extends React.Component {
incrementEvent() {
// this指向为undefined
console.log(this);
}
render() {
return (
当前数值 {this.state.num}
点击加1
)
}
}
```
#### 02 bind纠正this指向:
在构造函数中,借助bind能更改this指向的特点,将事件更改为新的函数。
```react
constructor(props) {
super(props)
this.state = { num: 0 }
this.incrementEvent = this.incrementEvent.bind(this);
}
```
#### 03 箭头函数解决this指向:
由于箭头函数本身没有this,它依赖于其作用域外层的this,因此可以将事件定义成一个箭头函数。
```react
incrementEvent = () => {
this.setState({
num: this.state.num + 1
})
}
```
#### 04 事件对象的获取:
react事件中,事件对象直接依赖于原生js事件,在原生js事件中可以获取到事件对象。
```react
changeEvent = (e) => {
const { value } = e.currentTarget;
this.setState({
content: value
})
}
```
### 生命周期:
组件从创建到死亡要经历一些特定的阶段,在不同的阶段会触发想对应的钩子函数,这些构造函数组成了react组件完整的生命周期。
#### 01 初始阶段:
组件在初始阶段会调用构造器函数和render函数第一次的调用以及组件的相关生命周期函数
- **`constructor`:**
构造器函数,用来初始化组件的状态
- **`componentWillMount`:**
组件即将加载时调用
- **`render`:**
组件渲染虚拟DOM元素
- **`componentDidMount`:**
虚拟DOM元素渲染在页面上,组件加载完毕
#### 02 更新阶段:
组件在更新时,会依次经过更新阀门检测,虚拟DOM生成等过程:
- **`render`:**
组件初次加载时也需要render初次渲染。
- **`shouldComponentUpdate(props,curState)`:**
可以看做组件更新的阀门,默认返回值为true,即允许更新,当定义为false时组件不会再更新。
- **`componentWillUpdate(props,curState)`:**
组件在即将更新时调用。
- **`render`:**
组件更新时生成新的虚拟DOM元素,并渲染在页面中。
- **`componentDidUpdate`:**
组件更新完成。
> **`forceUpdate()`**函数会使得更新阀门`shouldComponentUpdate`失效。
#### 03 子组件接收props阶段:
父组件在给组件传递props值会触发**`componentWillReceiveProps`**
- **`componentWillReceiveProps(curProps)`:**
组件接受的props值在发生变化时触发该函数。
#### 04 卸载阶段:
组件卸载使用**`ReactDOM.unmountComponentAtNode(rootNode)`**函数,在即将卸载时会触发**`componentWillUnmount`**函数。
- **`componentWillUnmount`:**
组件在即将卸载时触发该函数。
#### 05 常用的生命周期函数:
react众多的生命周期函数中,常见的生命周期函数其实只有三个:
- **`componentDidMount()`:**
在该生命周期函数中,主要完成数据请求、定时器开启等动作。
- **`componentWillUnmount()`:**
在组件即将卸载时,需要关闭定时器等收尾工作。
#### 06 新旧生命周期函数的异同:
在React16之后,react考虑将一些不常用的生命周期函数逐渐废弃,并新增了两个新的生命周期函数(不常用)。
**废弃:**
- **`UNSAFE_componentWillMount`**
- **`UNSAFE_componentWillUpdate`**
- **`UNSAFE_componentWillReceiveProps`**
**新增:**
- **`getDerivedStateFromProps`(了解)**
- **`getSnapshotBeforeUpdate`(了解)**
### 开发环境搭建:
#### 01 webpack版本:
构建webpack5版本的react开发环境。
#### 02 Vite版本:
构建vite4版本的react开发环境。
### 样式及动画:
#### 01 样式绑定:
样式的改变只能通过state值的更改来改变不同的className的值,不同className对应不同的样式。
常用的将类名存放在数组中,操作数组中的数据,进而改变不同的样式。
```react
class App extends Component {
state = {
classnames: []
}
changeShape = (type) => {
let list = [...this.state.classnames];
if (list.includes(type)) {
list = list.filter(item => item !== type);
} else {
list.push(type)
}
this.setState({ classnames: list })
}
render() {
return (
这里是一段文本
)
}
}
```
#### 02 动画方案:
#### 03 样式冲突:
### Hooks函数:
hooks是react 16.8之后加入的新特性和新语法,可以让我们在函数组件中使用state或者其他的react特性,旨在用函数组件替换类组件。
#### 01 useState Hook:
React中state相关的hooks函数为**`useState(initValue)`**,返回值则是包含两个元素的数组:**`[xxx,setXxxx]`**
- 返回值xxx为为第一次初始化指定的值内部缓存用
- setXxx用作更新状态值xxx的函数。
```react
function Alert() {
let [num, setNum] = useState(0);
const increment = () => {
setNum(num => num += 1)
}
const decrement = () => {
setNum(num => num -= 1)
}
return (
当前数值为:{num}
)
}
```
#### 02 useEffect Hook:
useEffect Hook可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)。
可以把useEffect Hook看做三个生命周期函数的组合:
```react
// 第二个参数为空 -> 组件render
useEffect(() => {
console.log("====render====")
})
// 第二个参数为空数组 -> 组件componentDidMount
useEffect(() => {
console.log("====componentDidMount====")
timer = setInterval(() => setNum(num => num += 1), 1000);
}, [])
// 第二个参数为非空数组,监测数组中值的变化,类似于vue中的watch -> componentDidUpdate
useEffect(() => {
console.log("====time change====")
}, [time])
// 第二个参数返回的函数可以认为组件销毁 -> componentWillUnmount
useEffect(() => {
console.log("====componentDidMount====")
timer = setInterval(() => setNum(num => num += 1), 1000);
return () => {
clearInterval(timer)
}
}, [])
```
> - 不要在useEffect中改变依赖项的值,这样会造成死循环。
> - 多个不同功能的副作用尽量分开声明,不要写在一个useState中。
#### 03 useRef Hook:
Ref Hook可以在函数式组件中存储 / 查找组件内的标签或任意其他数据。
```react
function Box() {
let inputRef = useRef();
function getvalue() {
console.log(inputRef.current.value)
}
return (
)
}
```
#### 04 forwardRef:
在父组件中不能使用ref给函数式子组件赋值,必须借助于**`forwardRef(props,ref)`**包裹子组件,并结合**`useImperativeHandle(ref,fn,watch)`**将子组件中的状态值和私有方法暴露。
```react
// 子组件定义
import React, { Fragment, useState, forwardRef, useImperativeHandle } from "react";
const Child = forwardRef((_, ref) => {
const [num, setNum] = useState(0);
const increment = () => {
setNum(num => num += 1)
}
/**
* 将函数式组件中的值和改变值的方法以及私有方法都暴露给父组件
*/
useImperativeHandle(ref, () => ({ num, setNum, increment }))
return (
当前值为:{num}
)
})
export default Child
// 父组件调用
const childRef = useRef();
const { increment } = childRef.current;
const { setNum } = childRef.current;
```
> **`useImperativeHandle`:** 函数接受三个参数,第一个参数为**`forwardRef`**传递过来的**`ref`**值,第二个参数为一个暴露函数(将需要暴露的数据以对象的形式暴露),第三个参数则是需要监测的参数列表。
>
> - 监测参数列表不定义时:暴露函数每次都会重新执行,函数式组件每次更新都会返回新的状态值;
> - 监测参数列表为空数组时:暴露函数只会执行一次;
> - 监测参数列表不为空时:只要需要监测的状态值更新时,暴露函数就会执行一次。
#### 05 自定义Hook:
对于一些复杂、重复性的状态改变函数,我们可以将其抽离出来组成自定义Hook,在自定义Hook函数中导出组件所需要的值。
```react
import { useState, useEffect } from 'react'
const useTime = (time) => {
const [count, setCount] = useState(time);
const [status, setStatus] = useState(true);
useEffect(() => {
let timer = null;
timer = setTimeout(() => {
if (count > 1) {
setCount(count => count -= 1);
} else {
clearTimeout(timer);
setStatus(status => status = false);
}
}, 1000)
return () => clearTimeout(timer)
}, [count])
return [count, status]
}
export default useTime
```
#### 06 useLayoutEffect:
**`useLayoutEffect`**和**`useEffect`**的使用方式相似,都需要接收一个函数和一个依赖项数组,并且都可以返回一个清理函数,不同之处在于:
- **`useEffect`** 是在浏览器重新绘制屏幕之后触发,异步执行,不阻塞浏览器绘制;
- **`useLayoutEffect`** 是在浏览器重新绘制屏幕之前触发,同步执行,阻塞浏览器绘制;
#### 07 useReducer:
当状态更新逻辑复杂时可以使用**`useReducer`**,`useReducer`可以同时更新多个状态,而且能把对状态的修改从组件中独立出来。
reducer是一个函数,类似于`(prevState, action) => newState`,形参`prevState`表示旧状态,形参`action`表示本次的行为,`newState`表示处理完毕后的新状态。
```react
import { useReducer } from 'react'
// 初始state
const defaultState = { num: 0 }
// state改变函数reducer
const reducer = (state, action) => {
const newState = { ...state };
const { type, payload } = action;
switch (type) {
case "DECREMENT":
newState.num > 0 ? newState.num -= payload : 0;
break;
case "INCREMENT":
newState.num += payload;
break;
default:
break;
}
return newState;
}
// 自定义hook函数返回state值和dispatch函数
const useNumReducer = () => {
const [state, dispatch] = useReducer(reducer, defaultState);
return [state, dispatch];
}
export default useNumReducer
```
#### 08 createContext和useContext:
函数式组件中,如果组件嵌套层级很深,当顶层组件向底层组件传递数据时,传统的方式是通过props一层一层的把数据向下传递。
**`useContext`**结合**`createContext()`**可以轻松实现多层组件间的数据传递。
```react
import React from 'react'
```
#### 09 memo和useMemo:
当父组件被重新渲染的时候,会触发子组件的重新渲染,对于一些只通过props传值的组件,这其实是多出了无意义的性能开销。
借助`React.memo(fn)`将函数组件进行包裹,返回一个新的组件,该组件只会在父组件传递的props值发生变化时才会重新渲染。
```react
import React, { memo } from "react";
const Item1 = memo(props => {
console.log("====Item1 Render====")
return (
当前值为:{props.num}
)
})
export default Item1
```
**`useMemo()`**函数会返回一个新的数据,该数据只会在所依赖的值发生变化时重新执行,节约开销。
```react
import React, { useState, useMemo } from "react";
const [num, setNum] = useState(0);
const [time, setTime] = useState(new Date().getTime());
// 类似于vue中的计算属性
const isodd = useMemo(() => {
console.log("依赖于num")
return num % 2 == 0;
}, [num])
```