React

项目基础

  1. 创建项目

    • 使用官方脚手架

      通过npm create-react-app 能创建一个基本的react项目

      只做了react基本的搭建和构建,没有配上任何路由和状态管理。项目使用webpack构建

    • 使用一些市场上的集成脚手架

      官方脚手架提供的项目模板非常简单,因此也有很多集成的脚手架

      典型的比如umi。这一类脚手架创建出来的项目会集成好多功能,比如路由、mock

  2. 核心库

    • react react核心库,提供react的个个功能
    • React-dom 提供一些dom操作方法用于把react创建出来的react对象挂载到真正的htmlDom中,或者从htmlDom中卸载。核心作用类似于vue的mount

项目入口文件

  • index.js

    通过ReactDOM的createRoot方法创建ReactDOMRoot实例,通过render函数进行根组件渲染

  • React.StrictMode - 是React的严格模式

    可以进行组件的功能审查

image-20240706005130473

App根组件

​ React 组件就是一个函数形式,React函数组件是从React 16.8版本开始引入的

​ !!! jsx 只能返回一个根元素

image-20240706005929613

jsx 插值表达式

​ jsx是javascript跟html结合的语法

​ 使用 {}

  • 使用jsx语法渲染html属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function App(){
// 可以将img标签非必须得属性提取出来写
const imgData = {
className: 'small',
style: {
width: 200,
height: 200,
backgroundColor: 'red'
}
}

return (
<div>
<!-- 这里不是展开运算符, 是jsx的插值表达式 -->
<img src="./logo.svg" alt="" {...imageData}></img>
</div>
)
/*
展开运算符是需要一个对象自变量进行接受的,直接{...imageData}在js语法中是会报错
*/
}

Fragment

​ 当渲染的标签需要在标签上添加属性便不能在使用幽灵标签 - 而使用Fragment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Fragment } from "react"

function App(){
const list = {
{id:1, name: '小张'},
{id:2, name: '小光'}
}

const listContent = list.map(item =>{
<Fragment key={item.id}>
<li>{item.name}</li>
<span>yyds</span>
</Fragment>
})

return (
<ul>{listContent}</ul>
)
}

export default App;

React组件和jsx

  1. React 组件分类

    • 函数组件( 首字母大写) ( 新版本)
    1
    2
    3
    function Hello() {
    return <div>hello</div>
    }
    • class 类组件( 首字母大写)
    1
    2
    3
    4
    5
    class Hello extends React.Component {
    render() {
    return <div>hello</div>
    }
    }
    • 通过React.createElement() 创建组件,与vue类似( 首字母大写)
    1
    2
    3
    function Hello(){
    return React.createElement('div',[],"hello,word")
    }
    • todo 组件 (容器组件)
    1
       
  2. jsx 特点

    • 直接在js中混用

      React项目利用babel做了对js的编译,所以我们是可以直接在js里面写jsx的

    • 写法接近js

      jsx几乎和js一样,不同点在于,可以更方便的写html在js里,写在js里面的html最终会被编译成一个js对象,我们也可以用react自带的createElement创建这个对象

image-20240614133209493

  • jsx里面渲染不同内容的区别
    • 字符串;数组: 直接渲染
    • 方法:无法渲染
    • 对象:只能渲染element
    • 布尔值:不渲染任何内容
    • 数组:把数组里面的每一项单独渲染
    • undefine、null:不渲染任何内容
    • 表达式:运行表达式

react事件绑定

  1. 规则模式

    • 类似于原生,on+方法名( 首字母大写)
    • 一定要赋值给事件一个方法

    注意:

    • 不做处理的情况下,this会指向undefind
    • 给到事件绑定的一定得是一个方法,不要直接调用方法,调用方法只会在页面初次与渲染指向方法( 与vue不同)
  2. 事件绑定的其他操作

    • 传递参数
    1
    <div onClick={ this.f1.bind(this, 1, 2)}></div>
    • 获取事件对象

    当没有传递任何参数时,事件对象就是第一个参数;传递参数时,事件对象就是最后一个参数

    该事件对象是合成的事件对象,并非原生事件对象,原生事件对象在e.nativeEvent

    • 阻止默认行为,冒泡等
    1
    2
    e.stopPropagation()
    e.preventDefault()

​ 3.使用了类组件可以在类初始化函数内进行 this.f1.bind() 的操作,这样就可以直接使用 this.f1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// ./Event.tsx
import { Button, Space} from 'antd' // 使用antd UI组件库
import React, {Component} from 'react'

export default class Event extends Component {
// 类初始化 - constructor
constructor(porps:any){
super(porps)
// 此处先修改了this指向,后续onClick内直接调用函数即可
this.click1 = this.click1.bind(this)
}

click1() {
console.log('click1')
}
click2 = ()=>{
console.log('click2')
}
click3(str:string) {
console.log(str)
}
click4 = (str:string)=>{
console.log(str)
}

render(){
return (
<div>
<Space>
<Button type='primary' onClick={this.click1}>click1</Button>
<Button type='primary' onClick={this.click2}>click2</Button>
<Button type='primary' onClick={this.click3.bind(this, 'click3')}>click2</Button>
<Button type='primary' onClick={this.click4.bind(this, 'click4')}>click2</Button>
<Button type='primary' onClick={()=>{this.click4('click4')}}>click2</Button>
</Space>
</div>
)
}
}

react 组件中的响应式数据

​ 在 React 中,如果不定义响应式数据,视图就不会自动更新。React 是通过比较虚拟 DOM 树的差异来决定何时重新渲染组件的。而为了触发视图更新,你需要显式地调用 setState 方法来更改组件的状态。

 1. react 响应式原理
  * react不能像vue一样直接修改触发更新
  * react修改能改的值,但无法触发更新,因为react没有像vue一样监听get和set,而是在调用setState的时候调用react的更新操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import './App.css'
import React from 'react'

class App extends React.Component {
// 响应式数据
state = {
a:0
}

addA = ()=>{
// 调用setState 执行页面更新操作
this.setState({
a: ++this.state.a
})
// 或者在外头操作,然后只是单纯的调用setState方法,触发更新
this.state.a += 1;
this.setState({})
}

render(){
return <div className='App'>
{this.state.a}
<button onClick={this.addA}> +1 </button>
</div>
}
}
  1. setState工作流程

image-20240614171135240

关键点:

  • 通过浅合并来修改数据
  • 调用setState方法会触发更新,修改state并不会触发更新
  • 在修改对象属性的时候需要将数据写全,不然会覆盖;可以先将原对象展开,在赋值
  • setState修改变量操作是异步的,需要获取到修改后的数据需要在第二个参数下获取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import './App.css'
import React from 'react'

class App extends React.Component {
// 响应式数据
state = {
a:0
}

addA = ()=>{
// 调用setState 执行页面更新操作
this.setState({
a: ++this.state.a
},()=>{
// 在这里获取最新数据
console.log(this.state.a)
})
// 或者在外头操作,然后只是单纯的调用setState方法,触发更新
this.state.a += 1;
this.setState({})
}

render(){
return <div className='App'>
{this.state.a}
<button onClick={this.addA}> +1 </button>
</div>
}
}
  • setState方法多次修改,会合并为一次,统一更新
  • setState返回触发更新,不管你是否修改,这就造成了一个新问题,重复修改相同的值也会让组件进行更新
1
2
3
4
5
6
7
8
9
10
11
12
// 解决方法
可以使用 React.PureComponent

// 缺点
当使用React.PureComponent的时候
如果修改的是数组和对象,就对导致页面没有更新
因为数组和对象的判断是否改变是判断内存地址是否改变
// 解决方法 解除引用,重新赋值新数组或对象
this.setState({
arr:[ ...this.state.arr],
object: { ...this.state.object}
})
  • 一定不要在render里直接使用setState ( 在React.Component 下会触发死循环)
  1. 如果不使用响应式数据(例如使用 useState [setState的hock] 或 useReducer),但是需要手动触发更新视图,可以使用 forceUpdate 方法

    forceUpdate 是 React 组件的内置方法。它用于强制组件重新渲染并更新视图。然而,官方文档建议尽量避免使用 forceUpdate,因为它会跳过 React 的优化流程,并可能导致性能问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    /* 函数式组件 */
    import React, { useRef } from 'react';

    function MyComponent() {
    //使用了 useRef 来创建一个非响应式的引用变量 countRef
    const countRef = useRef(0);

    function handleClick() {
    countRef.current += 1; // 更新计数值
    component.forceUpdate(); // 强制组件重新渲染
    //在实际开发中,除非特殊情况下需要手动操作更新视图,一般不建议频繁地使用 forceUpdate 方法。
    }

    return (
    <div>
    <p>Count: {countRef.current}</p>
    <button onClick={handleClick}>Increment</button>
    </div>
    );
    }

    /* 类组件 */
    class MyComponent extends React.Component {
    handleClick() {
    // 在事件处理程序中调用 forceUpdate()
    this.forceUpdate();
    }

    componentDidMount() {
    // 在生命周期方法中调用 forceUpdate()
    this.forceUpdate();
    }

    render() {
    // 组件的渲染逻辑
    return (
    <div>
    <button onClick={this.handleClick.bind(this)}>强制更新</button>
    {/* ... */}
    </div>
    );
    }
    }

React 条件渲染和列表循环

条件循环
  1. react 没有像vue一样的指令,一切操作本质上都是我们通过运算生成不同的内容,拿去渲染,得到不同页面

  2. 条件渲染本质

    原则:

    • react 渲染undefined,null,空字符串,false不会渲染成任何内容
    • 如果渲染一个jsx编写的html元素,就会渲染成页面上的内容

    image-20240614184524074

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import './App.css'
import React from 'react'

class App extends React.PureComponect {
state = {
show: true
}

function Hello() {
return <div>hello</div>
}

f1(){
if(this.state.show) {
return <div>hello</div>
} else {
return ""
}
}


render(){
return <div className="App">
<div>条件渲染</div>
// 三元运算符
<div>{this.state.show ? <Hello/> : ""}</div>
// 与运算符 当this.state.show 为false时,不会向后执行
<div>{this.state.show && <Hello/>}</div>
// 使用函数判断
<div>{this.f1()}</div>
<button onClink={()=>{this.setState({show: !this.state.show})})}>{this.state.show ? "隐藏" : "显示"}</button>
</div>
}
}
列表循环

​ 原则:

  • 渲染一个数组会把数组里的灭意向单独取出渲染
  • 那么我们编写一个里面存放的都是html结构的数组,就会渲染成列表

image-20240614191840245

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import './App.css'
import React from 'react'

class App extends React.PureComponect {
state = {
array_1: [1,2,3] // [1,2,3] => [<div>1</div>, <div>2</div>, <div>3</div>]
}

getArr(){
return this.state.array_1.map((item) =>{
<div key={item}>{item}</div>
})
}

render(){

{this.getArr()}
}
}
react的思想
  1. 更少的封装,更高的自由度,能够让使用者更加高度的去自定义实现,不想vue一样必须遵守许多规则
  2. 纯粹的js,没有什么特殊的指令

表单绑定

  1. 基本思路
    • react中很多思路都是按照原生的操作去做的,表单绑定也是如此
    • 原生表单获取表单输入值,我们可以通过监听input,change等事件,然后获取e.target.value
    • 如果要设置表单的值,通常设置value属性,如果选择框是checked属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// input 双向数据绑定
import './App.css'
import React from 'react'

class App extends React.PureComponect {
state = {
inputValue : ""
}

changeInput(e){

}
render(){
return <div className="App">
inputValue: {this.state.inputValue}
<input type="text" value={this.state.inputValue} onInput={(e)={
this.setState({
inputValue: e.target.value
})
}} />
</div>
}
  1. 受控组件( 双向监听)

    表单值可以由使用者手动去设置,也就是进行了双向绑定。我们可以修改state 数据从而影响到表单

  2. 非受控组件( 单向监听)

    表单的值由自己维护。我们只能获取表单的值,不能直接修改state 数据来修改表单值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// checkbox 复选框 双向数据
import './App.css'
import React from 'react'

class App extends React.PureComponect {
state = {
checkboxArr : []
}

handleChecked = ()=>{
let arr = [...this.state.checkedArr]
if(e.target.checked){
arr.push(e.target.value)
}else{
arr.splice(arr.indexOf(e.target.value),1)
}
this.setState({
checkedArr: arr
})
}

render() {
return <div className="App">
{this.state.checkboxArr}
<input
onChange={this.handleChecked}
checked={this.state.checkedArr.indexOf("c1") !== -1}
value="c1"
type="checkbox"
name="choose"
/>选项一
<input
onChange={this.handleChecked}
checked={this.state.checkedArr.indexOf("c2") !== -1}
value="c21"
type="checkbox"
name="choose"
/>选项二
<input
onChange={this.handleChecked}
checked={this.state.checkedArr.indexOf("c3") !== -1}
value="c3"
type="checkbox"
name="choose"
/>选项三
</div>
}
}

Prop和组件通信、插槽

  1. props是react的核心

    在react中,一切写在组件上的属性和子节点都被规划为了props。所以props是react很多功能的根本。父子传值,插槽全是基于props,不像vue又事件监听,emit,专门的插槽这一类东西

1
2
3
4
5
6
7
8
9
10
11
12
// Son.js
import React from "react"

class Son extends React.PureComponent {
render() {
// 通过this.porps 获取父组件传给子组件的值
return <div>
{this.porps.message}
</div>
}
}
export default Son;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// App.js
import './App.css'
import React from 'react'
import Son from './Son.js'

class App extends React.PureComponect {
state = {
msg: "父组件给子组件传值"
}

render() {
return <div className="App">
<Son message={this.state.msg}></Son>
</div>
}
  • 函数式写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 子组件可以接受到props, 或者{title, content} 解构
function Artitle(props){
return (
<div>
<h2>{porps.title}</h2>
<p>{props.content}</p>
</div>
)
}

export default function App(){
return (
<>
<Artitle title="标题" content="内容"/>
</>
)
}
  1. props 类型验证和默认值 - defineProps

image-20240615145443001

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Son.js
import React from "react"

class Son extends React.PureComponent {
render() {
// 通过this.porps 获取父组件传给子组件的值
return <div>
{this.porps.message}
</div>
}
}
// 传值类型判断 第三库proptypes
Son.propTypes = {
// 必须自定义函数判断类型,不能像vue一样写类型
message: funciton(props){
if (typeof props.message !== "string"){
throw new Error("message must be a string")
}
},
}
// 默认值设置
Son.defaultProps = {
massage: "message is undefind",
}
export default Son;
  1. 模拟Vue中的插槽

    插槽本质上就是给子组件的html内容由父组件传入,jsx的加持下,我们可以把html像普通的字符串,数组一样传递,所以插槽只需要直接作为props传入就可以,内容放在了children下( 函数式方法一样)

image-20240615150837146

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Son.js
import React from "react"

class Son extends React.PureComponent {
state ={
scoptValue : "SonValue"
}
render() {
// 父组件会将插槽内容通过porps的children 数组中存放,直接渲染即可
return <div>
<!-- 普通插槽 默认名字为children -->
{this.porps.children}
<!-- 具名插槽 -->
{this.props.SonName01}
<!-- 作用域插槽 利用父组件传的组件调用子组件的值 -->
{this.props.scopeSlot(this.state.scoptValue)}
</div>
}
}
export default Son;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// App.js
import './App.css'
import React from 'react'
import Son from './Son.js'

class App extends React.PureComponect {

render() {
return <div className="App">
<!-- 普通插槽 默认名字为children -->
<Son>
<div>给子组件的插槽内容</div>
</Son>
<!-- 具名插槽 -->
<Son SonName01={<div>给子组件的插槽内容</div>}></Son>
<!-- 作用域插槽 -->
<!-- 父组件传递的插槽只能使用父组件的数据,如果需要用到子组件内的数据就可以用作用域插槽 -->
<Son scopeSlot={(scope)=>{ return <div>{scope}</div>}}></Son>
</div>
}
  1. 父组件给子组件传值

​ props不能直接修改父组件传递过来的值( vue同意),我们需要通过子组件触发父组件事件,从而改变父组件的值,达到一个传递值的效果

![image-20240616152015968](./react/props传值 (2).png)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// App.js
import './App.css'
import React from 'react'
import Son from './Son.js'

class App extends React.PureComponect {
state {
messAge: "hello"
}

changeMessage(msg) => {
this.state.messAge = msg
}

render() {
return <div className="App">
<!-- 将方法通过porps传递给子组件 -->
<Son message={this.state.messAge} changeMes={this.changeMessage}></Son>
</div>
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Son.js
import React from "react"

class Son extends React.PureComponect {
render() {
return <div>
<!-- 点击时触发父组件方法,通过父组件的方法修改父组件的值 -->
{this.props.changeMes}
<button onClick={()=>{
this.props.changeMes("hello word")
}}> 修改父组件值 </button>
</div>
}
}
export default Son;
  1. 兄弟组件传值
    • 子组件1 -> 父组件 -> 子组件2
    • eventbus
    • 多级的,在函数式写法中有一个hocks - Context进行操作

React中的样式操作

  1. class 类名设置
    • 必须为className
    • 类名和样式写在css文件里
    • 必须接受一个字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
// App.js
import './App.css'
import React from 'react'

class App extends React.PureComponent {
render() {
return (
<div className="header"> header </div>
)
}
}

export default App;
1
2
3
4
5
// App.css

.header {
background-color: red;
}
  1. style 内联
  • 不能像原生一样写成字符串,必须写成对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// App.js
import './App.css'
import React from 'react'

class App extends React.PureComponent {
render() {
return (
<!-- 第一个{} 是模板表达式, 第二个{} 是对象 -->
<div style={
{
color: red,
fontSize: 12px.
}
}> header </div>
)
}
}

export default App;
  1. 如何解决组件专用css

    • 将导入的css文件使用.model.css 模块化方式导入,对象的方式使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // App.js
    import styleModel from './App.model.css'
    import React from 'react'

    class App extends React.PureComponent {
    render() {
    return (
    <!-- 使用 " " 的方法对css类名进行空格分开 -->
    <div className={ styleModel.header + " " + styleModel.appStyle}> header </div>
    )
    }
    }

    export default App;
    1
    2
    3
    4
    5
    6
    7
    8
    // App.model.css

    .header {
    background-color: red;
    }
    .appStyle {
    color: pink;
    }
  2. 当需要动态的删减类名,如何更舒服的控制类名的添加和减少

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import React from 'react'

class Son extends React.PureComponent {
state {
hasSonClass: true,
}

render() {
return (
<div className={"App " + (this.state.hasSonClass ? "Son" : "")}></div>
<button onClick(()={
this.state.hasClass = !this.state.hasClass
})>动态添加Son style class名</button>
)
}
}


// 第二种方法
// 使用第三方库 classnames
// 如果使用了模块化方法后 需要将classnames this指向为模块化,引入的classnames更改为classnames/bind
/**
import classnames from "classnames/bind"
import sonStyle from "./son.model.css"
let bindCN = classnames.bind(sonStyle)
*/
import classNames from "classnames"
class Son extends React.PureComponent {
state = {
hasSonClass: true,
}
render() {
return (
<div className={classNames({
App: true,
Son: this.state.hasClass
})}></div>
<button onClick(()={
this.state.hasClass = !this.state.hasClass
})>动态添加Son style class名</button>
)
}
}

React 生命周期

  1. react 生命周期分为三大阶段
    • 挂载 更新 卸载

image-20240616161309108

  • 挂载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import classNames from "classnames"
class App extends React.PureComponent {
// 首先执行 类似类初始化方法
constructor(props) {
super(props)
this.state = {

}
console.log("1")
}
// 第二执行 getDerivedStateFromProps 静态方法
static getDervedStateFromProps(props,state){
console.log("2")
return null
}

// 第三执行 render函数
render() {
console.log("3")
}

// 最后执行componentDidMount
componentDidMount(){
console.log("4")
}
}
  • 更新
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import classNames from "classnames"
class App extends React.Component {
state = {
msg: "hello"
}
// 首先执行 getDerivedStateFromProps 静态方法
static getDervedStateFromProps(props,state){
console.log("1")
return null
}

// 第二执行 shouldComponentUpedate PureComponent下不执行
shouldComponentUpedate(props, state) {
// 优化生命周期
console.log("2")
//如果return 为false 则以下不执行
// this.state 是修改前的数据, state是修改后的数据
let change = true
for (let item in this.state){
if(state[item] !== this.state[item]){
change = false
}
}
return change
}
// 第三执行 render函数
render() {
console.log("3")
return <div>
<div>{this.state.msg}</div>
<button onClick={()={
this.state.msg = "hello word"
}}>修改msg</button>
</div>
}
//第四执行 getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(){
console.log("4")
}

// 最后执行componentDidUpdate
componentDidUpdate(){
console.log("5")
}
}
  • 卸载 ( componentWillUnmount)
  1. 重点生命周期讲解

    • render 通过render函数来执行决定组件渲染什么内容,所以无论是更新还是初次挂载都必须执行render
    • compinentDidMount 组件挂载完成,一般用来做些 页面初始化操作,比如初始化请求,echart绘制等,也就是vue的mounted里能做的事
    • shouldComponentUpdate 更新阶段调用,如果return false则不会执行render函数继续更新,从而达到阻止更新的效果,一般用来做性能优化
    • componentDidUpdate 更新完成,等同于vue的update
    • componentWillUnmount 组件即将卸载,通常做些全局事件的监听的卸载;定时器,计时器的卸载等。来优化性能
  2. vue的更新机制与react的问题所在

    • vue

      vue是在get和set里触发更新的; vue在get部分有一个重要的操作——依赖收集; 这样我们在更改了数据后,只会更新用到了这个数据的地方。做到最小的更新范围

    • react

      react的更新是调用方法时触发的,并没有依赖收集的过程。所以她会更新整个组件数。也就是会把子组件一起更新。即使更新的数据和子组件没有任何关系

ref和context

  • ref

    和vue中的ref是一个道理,用于获取真是dom。

    注意事项:

    • ref 必须在挂载后才能获取,通常在componentDidMount
    • ref 获取组件,不能获取函数组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React from 'react'


class App extends React.PureComponent {

let div1 = React.createRef();

componentDidMount() {
//废弃
console.log(this.refs.dev1)

console.log(this.div1.current)
}

render() {
return (
<!-- 幽灵节点 -->
<>
<div className="App" ref={this.div1}>
div
</div>
</>
)
}
}
  • context

    类似于vue的provider 和 injected ,用于嵌套很深的爷孙组件之间的传值

    注意事项:

    ​ 子组件使用父组件创建的context对象,不能创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// App.js
import React from 'react'
import Son from './Son'

// Context 是一个组件,并暴露出去; 函数式中直接使用 createContext() hocks
export let Context = React.createContext()


class App extends React.PureComponent {
state = {
msg: "父组件消息,传给后代组件"
hello: "word"
}



render() {
return (
<>
<dev className="App">
App
</dev>
<Context.Provider value={ {msg:this.stage.msg, hello:this.state.hello} }>
<Son></Son>
</Context.Provider>
</>

)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Son.js
import React from 'react'
import GrandSon from './grandSon'

class Son extends React.PureComponent {


render() {
return (
<>
<div className="Son">
Son
</div>
<GrandSon></GrandSon>
</>
)
}
}
export default Son;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// grandSon.js
import React from 'react'
import {Context} from './App'
class GrandSon extends React.PureComponent {
render() {
return (
<>
<div className="GrandSon">GrandSon</div>
<!-- 这里渲染App的数据 -->
<Context.Consumer>
{
(value)=>{
return (
<>
<div>{value.msg}</div>
<div>{value.hello}</div>
</>
)
}
}
</Context.Consumer>
</>
)
}
}

export default GrandSon;

React 新版本函数组件跟hook

  1. 函数组件和类组件主要区别

    • 函数组件没有生命周期
    • 函数组件没有this
    • 函数组件通过hook来完成各种操作
    • 函数组件本身的函数体相当于render函数
    • props在函数的第一个参数接受
  2. 函数组件的使用

1
2
3
4
5
6
7
8
9
10
// App.js
function App(){
function Son(){
return <div>Son</div>
}
return <>
<div>App</div>
<Son></Son>
</>
}
  1. state的创建和更新- useState()

    image-20240617132509799

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { useState } from 'react'

function App (){

let [msg, setMsg] = useState("hello")

function changeMsg(e){
setMsg("hello "+e)
}

return <div>
{msg}
<button onClick={changeMsg.bind(this,"work")}>change</button>
</div>
}

export default App;
  1. useReducer

  2. useEffect

    定义副作用

    • 不传第二个参数 = componentDitMount 和 componentDidUpdate
    • 第二个参数传空数组 = componentDidMount
    • 第二个参数数组里放某个数据 = watch监听 || 更新
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function App(){
/**
当为监听时
vue-watch 默认是不会开始就执行的
useEffect 监听某个数据,开始就会执行一次 (didMount)
*/
useEffect(()={
//componentDitMount 和 componentDidUpdate
})

useEffect(()={
// componentDidMount
},[])

useEffect(()={
// componentDidUpdate
},[msg])

return <div></div>
}

/**
在子组件中使用,表示的是卸载
*/
useEffect(()={
// componentWillUnmount
},[])
useEffect(()={
// componentDitMount 和 componentDidUpdate
})
  1. useMemo

    让一段计算在开始运算一次,后续只有依赖的数据发生变化时才重新运算

    作用:

    1. 起类似于vue的一个计算属性的效果
    1. 缓存一个数据,让其不会重新创建
    
1
2
3
4
5
6
7
8
9
10
function App(){
/**

*/
let all = useMemo(()={
return 0
},[msg])

return <div></div>
}
  1. useCallback

    缓存一个方法,不会让方法每次都创建

  2. 其他hock

    • useRef——函数组件中使用ref 来获取DOM
    1
    let divRef = useRef()
    • useContext——更方便的解析context的provider的数据
    1
    2
    3
    4
    5
    6
    // 父组件 首先创建context对象
    export let context1 = React.createContext()


    // 子组件使用 useContext 获取
    let SonContext = useContext(context1)
    • hock 只能于函数组件

高阶组件

  • 逻辑复用

    如果使用ui 内容和操作的复用我们会使用组件,但是如果是单纯的逻辑复用,写为组件不免麻烦

    vue: Mixin 和自定义指令

    react: hoc高阶组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// TestHoc.js
import React from 'react'
export default function TestHoc (UseCom){
return class extends React.Component {
state = {
a: 123
}

render(){
return <>
<UseCom a={this.state.a} {...this.props}></UseCom>
</>
}
}
}
  • 使用例子
    • 提供复用的数据和方法,给到组件的props,你可以把很多页面都有的一些逻辑操作提取出来混入
    • 提供生命周期操作,比如写一个高阶组件形式的PureComponent

React - router

  1. 三个版本
    • React-router 服务端渲染使用
    • React-router-dom 浏览器端渲染使用
    • React-router-native React-native混合开发使用
  2. React-router 使用步骤
    • 通过BroserRouter或者HashRouter包裹要使用路由的根组件
    • 使用Routes组件,定义路由显示区域
    • 使用Router组件,定义具体路由规则
    • 使用NavLink或者Link组件,定义调整链接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import logo from './logo.svg'
import './App.css'
import { Routes, Route, NavLink, Link } from 'react-router-dom'
import Page1 from "./Page1"
import Page2 from "./Page2"

function APP(){
return (
<div className="App">
<div>菜单</div>
<!-- NavLink -->
<NavLink to="/page1">page1</NavLink>
<NavLink to="/page2">page2</NavLink>
<!-- Link -->
<Link to="/page1">page1</Link>
<Link to="/page2">page2</Link>
<Routes>
<Route path="/page1" element={<Page1></Page1>}></Route>
<!-- v5 -->
<Route path="/page1" Component={Page2}></Route>
</Routes>
</div>
)
}
  1. react-router 提供的一些其他组件

    • Navigate——路由重定向
    • Outlet——嵌套路由的子路由显示处

    React中没有vue那样的vue.use方法,react中使用一个插件,库,都是引入一个组件,然后把要使用该插件的部分包起来

  2. 动态路由和嵌套路由

    • 嵌套路由
    1
    2
    3
    4
    5
    6
    import { Routes, Route, NavLink, Link } from 'react-router-dom'

    <Route path="/page2" element={<Page2/>}>
    <Route path="son1" element={<Page2Son1/>}></Route> <!-- "/page2/son1" -->
    <Route path="son2" element={<Page2Son2/>}></Route> <!-- "/page2/son2" -->
    </Route>
    1
    2
    3
    4
    5
    6
    7
    // page2.js
    // 嵌套路由在子路由需要定义Outlet 类似vue的RouterView
    import { Outlet } from "react-router-dom"
    <div>
    page2
    <Outlet></Outlet>
    </div>
    • 动态路由

      1
      <Route path="/page/:id" Component={Page3} ></Route>
      • Params参数

        V6 —— useParams (hock)

        V5—— this.props.match.params (class)

      1
      2
      3
      4
      5
      6
      7
      import { useParams } from "react-route-dom"

      function Page3(){
      let params = useParams()
      return <>
      </>
      }
      • Query参数

        V6—— useSearchParams

        V5—— this.props.location.search

      1
      2
      3
      4
      5
      6
      7
      8
      import { useSearchParams } from "react-router-dom"

      function Page4(){
      let [searchParams, setSearchParams] = useSearchParams();
      console.log(searchParams.get("a"), setSearcjParams({a: 888}))
      return <>
      </>
      }
      • Location信息

        V6—— useLocation

        V5—— this.props.location.state

      1
      2
      3
      4
      5
      6
      7
      8
      import { useLocation } from "react-router-dom"

      function Page5(){
      let location = useLocation();
      console.log(location.search)
      return <>
      </>
      }
  3. js 控制跳转地址

    • v6——useNavigate 创建跳转方法,然后跳转
    • V5——this.props.history.push()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import { Outlet, useNavigate } from "react-route-dom"

    function Page6(){
    let nav = useNavigate()
    return <>
    <button onClick={()=>{
    nav("/page6?a=111",{ state:{aaa:"1111"} })
    }
    }>跳转</button>
    <Outlet></Outlet>
    </>
    }
  4. 控制权限

    • 通过 navigate跳转, 直接不生成
    1
    <Route path="/page1" element={_token ? <Page1></Page1> : <Navigate to="/page4"></Navigate>}
  5. 异步路由( 路由懒加载)

    React做异步路由,要配合到react本身的一个方法——lazy和一个组件suspense

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 懒加载
let LayPage4 = Lazy(() =>{ return import("./Page4")})
function App(){
<>
<Suspense fallback={<h3>加载中。。</h3>}>
<Routes>
<Route path="/page4" Component={LayPage4}><Route>
</Routes>

</Suspense>

</>
}

React 状态管理

  1. 状态管理
    • react,没有专门的状态管理库,都是通用的js状态管理库,所以我们首先创建一个全局的数据储存和管理工具
    • 通过其他工具,数据修改能触发react页面的更新

image-20240618164227474

  1. redux 创建仓库

    image-20240618174604887

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ./src/store/index.js
import {legacy_createStore as createStore } from "redux"

function reducer (state = {msg:"default"}, action){
// 具体修改数据的行为
switch (action.type){
case 'changeMes':
state.meg = action.paylod;
return { ...state }
case 'resetMes':
state.msg = "hello"
return { ...state }
default :
return state
}
}

let store = createStore(reducer)
export default store
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//App.js
import store from './src/store/index'

function App(props) {
let storeApp = store.getState()
console.log(storeApp)
return (
<div className="App">
{storeApp.msg}
<button onClick(()=>{
// 修改msg ;相当于上面的renducer的aciton参数
store.dispatch({
type: "changeMes"
paylod: "hello word"
})
console.log(storeApp)
}</button>)>修改store</button>
</div>
)
}
export default App;

触发修改并不能让页面数据响应式改变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// index.js
import store from './store'

//监听store修改,一旦修改重新渲染;但成本大,所以借助react-redux做关联
store.subscribe(()=>{
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
})

root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
  1. react-redux

image-20240618181758922

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// ./src/store/index.js
import {legacy_createStore as createStore, combineReducers} from "redux"

function reducer1 (state = {msg:"default"}, action){
// 具体修改数据的行为
switch (action.type){
case 'changeMes':
state.meg = action.paylod;
return { ...state }
case 'resetMes':
state.msg = "hello"
return { ...state }
default :
return state
}
}

function reducer2 (state = { num:0 }, action){
// 具体修改数据的行为
switch (action.type){
case 'changeMes':
state.num += 1
return { ...state }
case 'resetMes':
state.num = 10
return { ...state }
default :
return state
}
}

// 分模块
let reducer = combineReducers({
reducer1,
reducer2
})

let store = createStore(reducer)
export default store
1
2
3
4
5
6
7
8
9
10
// index.js
import { Provider } from 'react-redux'
import { store } from "redux"
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//App.js
import {connect} from 'react-redux'

function App(props) {
// props会多一个dispatch 和 msg
console.log(props)
return (
<div className="App">
{storeApp.msg}
<button onClick(()=>{
// 修改msg数据
props.dispatch({
type: "changeMes",
payload: "hello word"
})
})>修改store</button>
<button onClick(()=>{
// 修改msg数据
props.changeMes2()
})>修改store</button>
</div>
)
}
/**
connect:
第一个参数:state的映射,那些state映射到该组件props(当前组件为App)
第二个参数方法映射,你要给props里边加那些方法
*/
let ReduxApp = connect((state)=>{
// 将详细模块返回
return { state.reducer1.msg }
}, ()=>{
return {
changeMes2(){
dispatch({
type: "changeMes",
payload: "hello world2"
})
}
}
})(App)
export default ReduxApp;
  1. @reduxjs/tooikit (基于redux)

image-20240618191546117

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// ./src/store/index.js
import {createSlice, configurStore} from '@reduxjs/tooikit';

let mesSlice = createSlice({
name: "mesSlice",
initialState: {
mes: "hello"
},
reducers: {
changeMes(state,action) {
state.mes = action.payload
}
}
})

let mesSlice = createSlice({
name: "numSlice",
initialState: {
num: 0
},
reducers: {
addNum(state,action) {
state.num += 1
}
}
})

let store = configureStore({
reducer: {
mesReducer: mesSlice.reducer,
numReducer: mesSlice.reducer
}
})

export let { changeMes } = mesSlice.actions;
export let { addNum } = numSlice.actions;

export default store
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// app.js
import store from "./store/index"
import {connect} from 'react-redux'
import {changeMes} from './store/index'
function App(props) {
// props会多一个dispatch 和 msg
console.log(props)
return (
<div className="App">
{storeApp.msg}
<button onClick(()=>{
// 修改msg数据
props.dispatch({
type: "changeMes",
payload: "hello word"
})
})>修改store</button>
<button onClick(()=>{
// 修改msg数据
props.changeMes2()
})>修改store</button>
</div>
)
}
/**
connect:
第一个参数:state的映射,那些state映射到该组件props(当前组件为App)
第二个参数方法映射,你要给props里边加那些方法
*/
let ReduxApp = connect((state)=>{
// 将详细模块返回
return { state.reducer1.msg }
}, ()=>{
return {
changeMes2(){
dispatch(changeMes("hello word"))
}
}
})(App)
export default ReduxApp;

React中的路由控制

  1. 根据配置生成路由

image-20240619114144376

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ./src/route/route.js

import Page1 from "./page1"
import Page2 from "./page2"
import Page3 from "./page3"

// 定义路由规则
export default [
{
path: "/page1",
component: Page1
},
{
path: "/page2",
component: Page2
},
{
path: "/page3",
component: Page3
},
]
1
2
3
4
5
6
7
8
9
10
11
// App.js

import routersArr from "./src/route/route"
import createRoute from "./src/route/createRoute"
function App(){
return (
<Routes>
{createRoute()}
</Routes>
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ./src/route/createRoute.js
import {Route} from "react-route-dom"

export function createRoute(route){
return routesArr.map((item)=>{
// 递归实现子路由
if (item.children && item.children.length > 0){
return <Route path={item.path} key={item.path} Component={item.component}>
{createRoute(item.children)}
</Route>
}else{
return <Route key={item.path} path={item.path} Component={item.component}
}
})
}
  1. 根据权限控制生成路由 ( 补充!)

    image-20240619121028643

  2. 路由传参

结合实际项目 react坑

  1. 基础知识

    • react的更新问题,react更新会重新执行react函数组件方法本身,并且子组件也会一起更新

    这明显是不合理的,合理的更新机制是只有当这个组件自己的state或者props发生了改变,才进行更新,为了达到这个策略,vue和react分别做了两个解决方案

    ​ vue——源码里自动依赖收集

    ​ react——开发者自己去React.memo 包装组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 子组件Pager.js
    import React from "react"
    function Pager(){
    return (
    <div className="Pager">
    <div>1</div>
    <div>2</div>
    <div>3</div>
    <div>4</div>
    </div>
    )
    }
    // 使用React.memo() 只有当组件本身或props更新才会更新
    export default React.memo(Pager)
    • useCallback和useMemo的滥用

    react每次更新都会重新执行一次方法体。那么这个时候就会有两个问题。

    当有对象或方法传递给子组件时,更新相当于内存里新建了一个对象或方法 。就会导致子组件props也会更新。

    这个方法需要用useCallback()包裹;

    1
    2
    3
    let getClick = useCallback(()=>{

    },[])

    是对象并且和组件数据有关,可以用useMemo()包裹;如果死数据可以丢外层

    • react的state有个经典的闭包导致拿不到新数据的问题,常见于useEffect,useMemo,useCallback

    缓存更新问题; useCallback/ useEffect/ useMemo 里的方法如果依赖了某个state数据,那么这个数据必须写进依赖组,否则不会更新( 副作用方案)

    ​ ——不会更新

    1
    2
    3
    const getList = useCallback(()=>{
    console.log(counter)
    },[])

    ——会更新

    1
    2
    3
    const getList = useCallback(()=>{
    console.log(counter)
    },[counter])
    • 副作用方案( useEffect)

      image-20240619204641607

React + TS

组件传值 porps
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React, {Component} from 'react'
// 定义组件porps需要的值
interface IProps {
// 父组件必须要带的值
name: string
// 可选
address?: string
}

export default class Company extends Component<IPorps> {
render(){
return (
<div>
{this.props.name}
<hr/>
{this.props.address}
</div>
)
}
}
案例 TodoList