0%

image-20230407160308173

一、物理层

物理层所传数据单位为比特。要求发送法发送1/0时,接受方应当准确接受到1/0。因此物理层要考虑用多大的电压代表1或0,以及接受方如何识别出发送法发送的比特。

​ 物理层的作用正是要尽可能的屏蔽掉多种多样的传输媒体和通信手段的差异,使物理层上面的数据链路层感觉不到这些差异。

​ 信道:向某一个方向传送信息的媒体。单工通信、半双工通信、全双工通信

二、数据链路层

​ 两台主机之间的数据传输,总是在一段一段的链路上完成的,这就需要专门的链路层协议。在两个相邻节点之间传输数据时,数据链路层将网络层交下来的IP数据报组装成帧,两个相邻节点在链路中传送帧

​ 每一帧会包括必要的控制信息:同步信息、地址信息、差错控制。

React创建与配置

React创建项目

React脚手架搭建项目

1
create-react-app name

image-20230405102510876

删除默认生成的文件:

image-20230405102711804

项目目录结构配置

image-20230405103045842

项目基本配置

配置项目的icon

直接把需要使用的icon替换public下面的favicon.ico

配置项目的标题

设置html文件下的title属性

配置jsconfig.json文件

配置后编写项目的智能提示会好很多

image-20230405103730430

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*":[
"src/*"
]
},
"jsx":"preserve",
"lib": [
"ESNext",
"DOM",
"DOM.Iterable",
"ScriptHost"
]
}
}

项目的别名配置

image-20230405103925434

采用craco配置

webpack的配置:

  • craco会使项目的配置与webpack的配置融合
1
npm install @craco/craco@alpha -D
  • craco.config.js的配置

image-20230405104646852

1
2
3
4
5
6
7
8
9
10
11
const path = require('path')
const resolve = pathName=>path.resolve(__dirname, pathName)
module.exports = {
webpack:{
alias:{
"@":resolve("src"),
"components":resolve("src/components"),
"utils":resolve("src/utils")
}
}
}

要是craco生效,项目启动必须以craco启动,才能和webpack的配置融合——修改package.json中的scripts

1
2
3
4
5
6
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "react-scripts eject"
},

此时就可以用别名去写路径,避免长串的../../

image-20230405104959491

项目的less配置:

1
npm i craco-less@2.1.0-alpha.0 --save -D
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const path = require('path')
const CraLessPlugin = require('craco-less')
const resolve = pathName=>path.resolve(__dirname, pathName)
module.exports = {
//less
plugins:[
{
plugin:CraLessPlugin
}
],
// webpack
webpack:{
alias:{
"@":resolve("src"),
"components":resolve("src/components"),
"utils":resolve("src/utils")
}
}
}

CSS样式的重置

对默认样式进行重置:

  • normalize.css
  • reset.css

normalize.css:

1
npm install normalize.css --save

在index.js中导入

reset.css:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 把需要重置的元素名称放在这里 */
body,p,button,input,dd,dl,dt,form{
padding: 0;
margin: 0;
}
a{
color: #484848;
text-decoration: none;
}
img {
vertical-align: top;
}
ul, li {
list-style: none;
}

React全家桶配置

路由配置

1
npm install react-router-dom --save

index.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom/client';
import App from '@/App';



import "normalize.css"
import "@/assets/css/reset.css"
import { HashRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
{/* 懒加载是异步的,因此需要在保证异步未完成时提供一个可替换方案,可以是字符串也可以是组件 */}
<Suspense fallback="locding">
<HashRouter>
<App />
</HashRouter>
</Suspense>
</React.StrictMode>
);

router/index.js:

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

import React from "react"
import { Navigate } from "react-router-dom"
// 懒加载的实现
const Detail = React.lazy(()=>import("@/viewer/detail"))
const Entire = React.lazy(()=>import("@/viewer/entire"))
const Home = React.lazy(()=>import("@/viewer/home"))

const routes = [
{
path:"/",
element:<Navigate to="/home" />
},
{
path:"/home",
element:<Home/>
},
{
path:"/entire",
element:<Entire/>
},
{
path:"/detail",
element:<Detail/>
}
]
export default routes

react中的路由设置

Redux状态管理

  • 普通方式:目前使用效率依然非常高
  • @reduxjs/toolkit方式:推荐方式,未来的趋势
1
npm install @reduxjs/toolkit react-redux --save

普通方式:

image-20230405114121514

  • reducer.js代码:
1
2
3
4
5
6
7
8
9
10
const initialState = {

}
function reducer(state=initialState, action){
switch(action.type){
default:
return state
}
}
export default reducer
  • index.js代码:
1
2
3
import reducer from "./reducer";

export default reducer

@reduxjs/toolkit方式:

image-20230405114538084

  • home.js代码
1
2
3
4
5
6
7
8
9
10
11
12
import { createSlice } from "@reduxjs/toolkit";

const honeSlice = createSlice({
name:"home",
initialState:{

},
reducers:{

}
})
export default honeSlice.reducer
  • index.js代码
1
2
3
4
5
6
7
8
9
10
import { configureStore } from "@reduxjs/toolkit";
import homeReducer from "./modules/home"
import entireReducer from "./modules/entire"
const store = configureStore({
reducer:{
home:homeReducer,
entire:entireReducer
}
})
export default store

React与Redux

网络请求配置

发起网络请求库:

1
npm install axios --save

image-20230405120733930

index.js代码:

1
2
3
import owwRequest from "./request"

export default owwRequest

request/index.js代码:

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
import axios from "axios";
import {BASE_URL, TIMEOPUT} from "./config"
class OwwRequest {
constructor(baseURL, timeout){
// 获取axios实例
this.instance = axios.create({
baseURL,
timeout
})
// 发起网络请求后的拦截
this.instance.interceptors.response.use(res=>{
return res.data
},err=>{
return err
})
}
request(config){
return this.instance.request(config)
}
get(config){
return this.request({...config, method:"get"})
}
post(config){
return this.request({...config, method:"post"})
}
}

export default new OwwRequest(BASE_URL, TIMEOPUT)

request/config.js:

1
2
export const BASE_URL = ""
export const TIMEOPUT = 10000

内存管理

内存的管理都会有如下的生命周期:

  • 第一步:分配申请你需要的内存(申请)
  • 第二步:使用分配的内存(存放一些东西,比如对象等)
  • 第三步:不需要使用时,对其进行释放

不同的编程语言对于第一步和第三步会有不同的实现:

  • 手动管理内存:比如C、C++,包括早期的OC,都是需要手动来管理内存的申请和释放的(malloc和free函数)
  • 自动管理内存:比如Java、JavaScript、Python、Swift、Dart等,它们有自动帮助我们管理内存

对于开发者来说,JavaScript 的内存管理是自动的、无形的:

  • 我们创建的原始值、对象、函数……这一切都会占用内存
  • 但是我们并不需要手动来对它们进行管理,JavaScript引擎会帮助我们处理好它

JavaScript内存管理

  • JS对于原始数据类型内存的分配会在执行时, 直接在栈空间进行分配
  • JS对于复杂数据类型内存的分配会在堆内存中 开辟一块空间,并且将这块空间的指针返回值 变量引用

image-20230404202250630

JavaScript的垃圾回收

因为内存的大小是有限的,所以当内存不再需要的时候,我们需要对其进行释放,以便腾出更多的内存空间

在手动管理内存的语言中,我们需要通过一些方式自己来释放不再需要的内存,比如free函数:

  • 但是这种管理的方式其实非常的低效,影响我们编写逻辑的代码的效率
  • 并且这种方式对开发者的要求也很高,并且一不小心就会产生内存泄露

垃圾回收的英文是Garbage Collection,简称GC

常见GC算法——引用计数

引用计数:

  • 当一个对象有一个引用指向它时,那么这个对象的引用就+1

  • 当一个对象的引用为0时,这个对象就可以被销毁掉

  • 循环引用不会被销毁

    image-20230404202553182

常见GC算法——标记清除

标记清除:

  • 标记清除的核心思路是可达性(Reachability)
  • 这个算法是设置一个根对象(root object),垃圾回收器会定期从这个根开始,找所有从根开始有引用到的对象,对于哪些 没有引用到的对象,就认为是不可用的对象
  • 这个算法可以很好的解决循环引用的问题

常见GC算法——算法优化

标记整理法

  • 不同的是,回收期间同时会将保留的存储对象搬运汇集到连续的内存空间,从而整合空闲空间,避免内存碎片化

分代收集法

  • 对象被分成两组:“新的”和“旧的”
  • 许多对象出现,完成它们的工作并很快死去,它们可以很快被清理
  • 那些长期存活的对象会变得“老旧”,而且被检查的频次也会减少

增量收集

  • 如果有许多对象,并且我们试图一次遍历并标记整个对象集,则可能需要一些时间,并在执行过程中带来明显的延迟
  • 所以引擎试图将垃圾收集工作分成几部分来做,然后将这几部分会逐一进行处理,这样会有许多微小的延迟而不是一个大的 延迟

闲时收集

  • 垃圾收集器只会在 CPU 空闲时尝试运行,以减少可能对代码执行的影响

image-20230404203527050

JavaScript闭包

JavaScript函数式编程

  • 在JavaScript中,函数是非常重要的,并且是一等公民
    • 那么就意味着函数的使用是非常灵活的
    • 函数可以作为另外一个函数的参数,也可以作为另外一个函数的返回值来使用
  • avaScript存在很多的高阶函数
    • 自己编写高阶函数
    • 使用内置的高阶函数

闭包

  • 一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包
  • 闭包让你可以在一个内层函数中访问到其外层函数的作用域
  • 在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来
  • 个普通的函数function,如果它可以访问外层作用域的自由变量,那么这个函数和周围环境就是一个闭包
  • 从广义的角度来说:JavaScript中的函数都是闭包
  • 从狭义的角度来说:JavaScript中一个函数,如果访问了外层作用域的变量,那么它是一个闭包

闭包访问过程:

image-20230404204008476

image-20230404204014899

闭包执行过程:

  • makeAdder函数执行完毕,正常情况下我们的AO对象会被释放

  • 但是因为在0xb00的函数中有作用域引用指向了这个AO对象,所以它不会被释放掉

    image-20230404204409944

闭包的内存泄露问题:

  • 因为在全局作用域下add10变量对0xb00的函数对象有引用,而0xb00的作用域中AO(0x200)有引用,所以最终 会造成这些内存都是无法被释放的
  • 所以我们经常说的闭包会造成内存泄露,其实就是刚才的引用链中的所有对象都是无法释放的
  • 因为当将add10设置为null时,就不再对函数对象0xb00有引用,那么对应的AO对象0x200也就不可达了
  • 在GC的下一次检测中,它们就会被销毁掉

AO不使用的属性优化:

AO对象不会被销毁时,是否里面的所有属性都不会被释放?

  • 下面这段代码中name属于闭包的父作用域里面的变量

  • 我们知道形成闭包之后count一定不会被销毁掉,那么name是否会被销毁掉呢

    image-20230404204655316

    image-20230404204704016

为什么需要Hook

不编写class的情况下使用state以及其他的React特性

class组件

class组件的优势

  • class组件可以定义自己的state,用来保存组件自己内部的状态
    • 函数式组件不可以,因为函数每次调用都会产生新的临时变量
  • class组件有自己的生命周期,我们可以在对应的生命周期中完成自己的逻辑
    • 比如在componentDidMount中发送网络请求,并且该生命周期函数只会执行一次
    • 函数式组件在学习hooks之前,如果在函数中发送网络请求,意味着每次重新渲染都会重新发送一次网络请求
  • class组件可以在状态改变时只会重新执行render函数以及我们希望重新调用的生命周期函数componentDidUpdate
    • 函数式组件在重新渲染时,整个函数都会被执行,似乎没有什么地方可以只让它们调用一次

class组件存在的问题

  • 复杂组件变得难以理解

    • 我们在最初编写一个class组件时,往往逻辑比较简单,并不会非常复杂。但是随着业务的增多,我们的class组件会变得越来越复杂
    • 比如componentDidMount中,可能就会包含大量的逻辑代码:包括网络请求、一些事件的监听(还需要在
      componentWillUnmount中移除)
    • 而对于这样的class实际上非常难以拆分:因为它们的逻辑往往混在一起,强行拆分反而会造成过度设计,增加代码的复杂度
  • 难以理解的class

    • 在class中,我们必须搞清楚this的指向到底是谁,所以需要花很多的精力去学习this
  • 组件复用状态很难
    • 状态的复用我们需要通过高阶组件
    • 像我们之前学习的redux中connect或者react-router中的withRouter,这些高阶组件设计的目的就是为了状态的复用
    • 或者类似于Provider、Consumer来共享一些状态,但是多次使用Consumer时,我们的代码就会存在很多嵌套

Hook的作用

Hook可以解决上述class和函数组件存在的问题

它可以让我们在不编写class的情况下使用state以及其他的React特性

Hook的使用场景

  • Hook的出现基本可以代替我们之前所有使用class组件的地方
  • 但是如果是一个旧的项目,你并不需要直接将所有的代码重构为Hooks,因为它完全向下兼容,你可以渐进式的来使用它
  • Hook只能在函数组件中使用,不能在类组件,或者函数组件之外的地方使用

Hooks的使用

Hooks只能在函数组件中使用

为什么Hooks中的函数都叫use,不叫create呢?

  • Hooks的一大作用是保存组件的状态,在下一次渲染时,返回当前的state
  • 而create的意思是,每次渲染都重新创建,那么就与state只在组件初次渲染时被创建相反

useState的使用

useState主要在函数组件中定义state

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

function App() {
const [count, setCounter] = useState(0)
console.log("count", count)
console.log("setCounter", setCounter)
return (
<div>
<h2>count:{count}</h2>
{/**
* 注意:onClick传入的是一个函数,setCounter(count+1)是一个函数的调用,所以这里只能使用箭头函数,在函数调用里调用setCounter
*/}
{/* <button onClick={setCounter(count+1)}>+1</button> */}
<button onClick={()=>{setCounter(count+1)}}>+1</button>
</div>
)
}
export default memo(App)
  • useState传入的参数是state中变量的初始值,不传则为默认值undefined
  • useState返回的是一个数组,其中数组中的第一个值是state变量的值,第二个值是设置状态值的函数

  • 只能在最外层调用Hook,不能在循环、条件判断或者子函数中调用

  • 只能在React的函数组件中调用Hook,不能在其他JavaScript函数中调用

useState如何实现状态

useState原理

Effect Hook

  • Effect Hook可以在函数组件中实现类似于生命周期的功能
  • 网络请求、手动更新DOM、一些事件的监听,都是React更新DOM的一些副作用(不是页面渲染)
  • 对于完成这些功能的Hook被称之为 Effect Hook

Effect Hook的基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, { memo, useEffect, useState } from 'react'

function App() {
const [count, setCounter] = useState(0)
useEffect(()=>{
console.log("监听redux中的数据变换,监听eventBus中的事件")
})
return (
<div>
<h2>count:{count}</h2>
<button onClick={()=>{setCounter(count+1)}}>+1</button>
</div>
)
}
export default memo(App)
  • useEffect传入的参数为一个回调函数,这个回调函数在更新DOM操作之后,就会执行
  • 但是在默认情况下,无论第一次渲染,还是在上面更改count更新后的渲染,在渲染结束后都会执行这个回调函数

  • 由于每次渲染都会执行useEffect中的函数,那么每次都会添加监听,最后会添加若干监听,因此需要在函数组件卸载或渲染前卸载掉之前的监听

Effect Hook的清除

  • 在class组件中,某些副作用的代码,可以在componentWillUnmount中进行清除,比如在这个函数中清除Redux的订阅
  • useEffect通过传入的回调函数1的返回值函数2,在返回值函数2中做清除操作

    • 这样做可以将添加和移除订阅的逻辑放在一起
    • 同属于一个effect的一部分,更好管理
  • React何时清除useEffect

    • 在组件更新和卸载时执行清除操作(装载时不会清除)

多个Effect的使用

  • 使用Hook的其中一个目的就是解决class中生命周期经常将很多的逻辑放在一起的问题

    • 比如网络请求、事件监听、手动修改DOM,这些往往都会放在componentDidMount中
  • 使用Effect Hook,我们可以将它们分离到不同的useEffect中

    • Hook 允许我们按照代码的用途分离它们, 而不是像生命周期函数那样
    • React 将按照 effect 声明的顺序依次调用组件中的每一个 effect
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { memo, useEffect, useState } from 'react'

function App() {
const [count, setCounter] = useState(0)
useEffect(()=>{
console.log("监听redux中的数据变换,监听eventBus中的事件")
return ()=>{
console.log("清除监听redux中的数据变换,清除监听eventBus中的事件")
}
})
useEffect(()=>{
console.log("获取网络请求")
})
return (
<div>
<h2>count:{count}</h2>
<button onClick={()=>{setCounter(count+1)}}>+1</button>
</div>
)
}
export default memo(App)

Effect的性能优化

  • 默认情况下,useEffect的回调函数会在每次渲染时都重新执行,但是这会导致两个问题

    • 某些代码我们只是希望执行一次即可,类似于componentDidMount和componentWillUnmount中完成的事情;(比如网络请求、订阅和取消订阅)
    • 另外,多次执行也会导致一定的性能问题
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import React, { memo, useEffect, useState } from 'react'

    function App() {
    const [count, setCounter] = useState(0)
    const [message, setMessage] = useState("Hello!")
    useEffect(()=>{
    console.log("监听redux中的数据变换,监听eventBus中的事件")
    return ()=>{
    console.log("清除监听redux中的数据变换,清除监听eventBus中的事件")
    }
    })
    return (
    <div>
    <h2>count:{count}</h2>
    <h2>message:{message}</h2>
    <button onClick={()=>{console.log("加count");setCounter(count+1)}}>+1</button>
    <button onClick={()=>{console.log("修改message");setMessage("你哈个锤子")}}>修改message</button>
    </div>
    )
    }
    export default memo(App)

    image-20230403201741719

  • useEffect的实际上有两个参数

    • 参数一:执行的回调函数
    • 参数二:该useEffect在哪些state发生变化时,才重新执行;(受谁的影响)——如果不希望受到任何依赖的影响,则传入[],这时候仅会在创建和销毁时分别执行对应回调
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { memo, useEffect, useState } from 'react'

function App() {
const [count, setCounter] = useState(0)
const [message, setMessage] = useState("Hello!")
useEffect(()=>{
console.log("监听redux中的数据变换,监听eventBus中的事件")
return ()=>{
console.log("清除监听redux中的数据变换,清除监听eventBus中的事件")
}
},[count])
return (
<div>
<h2>count:{count}</h2>
<h2>message:{message}</h2>
<button onClick={()=>{console.log("加count");setCounter(count+1)}}>+1</button>
<button onClick={()=>{console.log("修改message");setMessage("你哈个锤子")}}>修改message</button>
</div>
)
}
export default memo(App)

image-20230403201907764

那么这里的两个回调函数分别对应的就是componentDidMount和componentWillUnmount生命周期函数了

useContext的使用

获取Context的方式:

  • 类组件可以通过 类名.contextType = MyContext方式,在类中获取context
  • 多个Context或者在函数式组件中通过 MyContext.Consumer 方式共享context

但是多个Context共享时会存在大量的嵌套——Context Hook允许我们通过Hook来直接获取某个Context的值

但是Provider依然会有嵌套

Provider代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { HashRouter } from 'react-router-dom';
import { ColorContext, BgrContext } from './Context';

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(

<ColorContext.Provider value={{color:"red"}}>
<BgrContext.Provider value={{fontSize:36}}>
<HashRouter>
<App/>
</HashRouter>
</BgrContext.Provider>
</ColorContext.Provider>
);

useContext代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React, { memo, useContext, useEffect, useState } from 'react'
import { ColorContext, BgrContext } from './Context'


function App() {
const colorContext = useContext(ColorContext)
const bgrContext = useContext(BgrContext)
return (
<div>
<h2 style={{color:colorContext.color,fontSize:bgrContext.fontSize}}>哈哈哈</h2>
</div>
)
}
export default memo(App)

image-20230403204016535

useReducer的使用

useReducer仅仅是useState的一种替代方案——与Redux关系不大

  • 如果state的处理逻辑比较复杂,我们可以通过useReducer来对其进行拆分
  • 或者这次修改的state需要依赖之前的state时,也可以使用
  • 数据是不会共享的,它们只是使用了相同的counterReducer的函数而已
  • useReducer只是useState的一种替代品,并不能替代Redux
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
import React, { memo, useReducer } from 'react'

const counterReducer = function(state, action){
switch(action.type){
case "increment":
return {...state, counter:state.counter+1}
case "decrement":
return {...state, counter:state.counter-1}
case "addNum":
return {...state, counter:state.counter+action.num}
case "decNum":
return {...state, counter:state.counter-action.num}
}
}
function App() {
const [state, dispatch] = useReducer(counterReducer,{message:"Hello!",counter:100})
return (
<div>
<h2>当前计数:{state.counter}</h2>
<button onClick={()=>{dispatch({type:"increment"})}}>+1</button>
<button onClick={()=>{dispatch({type:"decrement"})}}>-1</button>
<button onClick={()=>{dispatch({type:"addNum", num:5})}}>+5</button>
<button onClick={()=>{dispatch({type:"decNum", num:10})}}>-10</button>
</div>
)
}
export default memo(App)

useReducer的参数与返回值:

  • 第一个参数:reducer函数
    • reducer函数的参数一:上一次的state/useReducer的第二个参数
    • reducer函数的参数二:dispatch所传入的参数
    • reducer的返回值:新的状态(…state是为了保障除了这次改变的其余状态依然在新的状态中)
  • 第二个参数:state初始值
  • 第一个返回值:更新后的state值
  • 第二个返回值:用于发起改变state的函数

useCallback的使用

useCallback实际的目的是为了进行性能的优化

  • 函数式组件在每次刷新时都会重新执行函数,那么里面的函数就会重新定义,虽然刷新后,原来定义的函数会被回收,但每次都定义也会影响性能,同时会生成新的foo
  • useCallback就可以实现当满足某些条件时重新定义函数,但得到相同的foo,而如果有子组件依赖这个foo,相同的foo不会引起子组件的刷新

父组件代码:

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 React, { memo, useCallback, useState } from 'react'
import ChildComponent from './component/ChildComponent'

function App() {
const [count, setCount] = useState(100)
const [message, setMessage] = useState(22)
const changeCount = ()=>{
console.log("修改count")
setCount(count+1)
}
const changeMessage = ()=>{
console.log("修改message")
setMessage(Math.round())
}
return (
<div>
<h2>当前计数:{count}</h2>
<h2>Message:{message}</h2>
<ChildComponent changeMessage={changeMessage}/>
<button onClick={changeCount}>+1</button>
</div>
)
}
export default memo(App)

子组件代码:

1
2
3
4
5
6
7
8
9
10
11
12
import React, { memo } from 'react'

function ChildComponent(props) {
const {changeMessage} = props
console.log("子组件刷新")
return (
<div>
<button onClick={changeMessage}>子组件按钮修改Message</button>
</div>
)
}
export default memo(ChildComponent)

image-20230403211929250

可以看出每次修改count都会刷新不依赖count子组件,会造成性能浪费

useCallback的基本使用

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 React, { memo, useCallback, useState } from 'react'
import ChildComponent from './component/ChildComponent'

function App() {
const [count, setCount] = useState(100)
const [message, setMessage] = useState(22)
const changeCount = useCallback(()=>{
console.log("修改count")
setCount(count+1)
},[count])
const changeMessage = useCallback(()=>{
console.log("修改message")
setMessage(Math.round())
},[])
return (
<div>
<h2>当前计数:{count}</h2>
<h2>Message:{message}</h2>
<ChildComponent changeMessage={changeMessage}/>
<button onClick={changeCount}>+1</button>
</div>
)
}
export default memo(App)

image-20230403212738937

可以看出点击更改count后,子组件并未刷新

useCallback的参数与返回值:

  • 参数一:需要处理的函数
  • 参数二:该函数改变依赖的状态,如果该依赖的状态不变,就返回相同的函数,相同的函数不会引起依赖该函数子组件的刷新
  • 返回值:返回一个函数的 memoized(记忆的) 值

使用useCallback的目的是不希望子组件进行多次渲染,并不是为了函数进行缓存

  • 并不是不会新定义函数,新定义函数是一定的
  • 只针对子组件的渲染次数做优化,在本组件层面是没有优化的

useMemo的使用

useMemo也是为了进行性能的优化:

  • 同样是返回一个memoized(记忆的)值
  • 在依赖不变的情况下,多次定义的时候,返回的值是相同的
  • useCallback是在依赖不变的情况下,多次定义的时候,返回的函数是相同的
  • useMemo针对变量进行优化,useCallback针对函数进行优化

useMemo的使用案例:

  • 进行大量的计算操作,是否有必须要每次渲染时都重新计算
  • 对子组件传递相同内容的对象时,使用useMemo进行性能的优化

父组件代码:

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 React, { memo, useCallback, useState } from 'react'
import ChildComponent from './component/ChildComponent'

function App() {
const [count, setCount] = useState(100)
function changeCount(){
console.log("修改count")
setCount(count+1)
}
function foo(information){
console.log("生成information")
return {...information,message:"helloWorld"}
}
const information = foo({message:message})
return (
<div>
<h2>当前计数:{count}</h2>
<h2>Message:{information.message}</h2>
<ChildComponent information={information}/>
<button onClick={changeCount}>+1</button>
</div>
)
}
export default memo(App)

子组件代码:

1
2
3
4
5
6
7
8
9
10
11
12
import React, { memo } from 'react'

function ChildComponent(props) {
const {information} = props
console.log("子组件刷新")
return (
<div>
<h3>子组件:{information.message}</h3>
</div>
)
}
export default memo(ChildComponent)

image-20230404094727924

可以看出:

  • 每次更新count后,message是不变的,那么information,也就不必要每次都去运行foo函数,当foo函数逻辑赋值时会引起性能浪费
  • 子页面仅依赖于message,这里明显每次都是得到的information值是一样的,但由于是不同的对象,子组件每次也会刷新

useMemo的基本使用

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
import React, { memo, useMemo, useState } from 'react'
import ChildComponent from './component/ChildComponent'

function useFoo(message){
const res = useMemo((message)=>{
console.log("生成information")
return {message:message+"修改后"}
},[message])
return res
}
function App() {
const [count, setCount] = useState(100)
const [message, setMessage] = useState("helloWorld")
function changeCount(){
console.log("修改count")
setCount(count+1)
}
let information = useFoo(message)
return (
<div>
<h2>当前计数:{count}</h2>
<h2>Message:{message}</h2>
<ChildComponent information={information}/>
<button onClick={changeCount}>+1</button>
</div>
)
}
export default memo(App)

image-20230404101149326

可以看出:

修改count后,依赖于message的函数不再重新执行,子组件也不再刷新

注意:

  • useMemo这种Hook函数,只能在函数组件和hook函数中使用,自定义hook函数,以use开头,use后的第一个字母为大写
  • useMemo不会传递参数,因为useMemo是由React调用的,我们不知道其在上面时候调用,也就无法传参数进行
  • useMemo的参数与返回值
    • 参数一:useMemo判断是否执行的函数
    • 参数二:useMemo依赖的值,如果这个值没变,就不会执行传入的函数,直接返回上一次的计算值
    • 返回值:返回一个带记忆的值

useRef的使用

useRef返回一个ref对象,返回的ref对象再组件的整个生命周期保持不变

useRef基本使用

  • 用法一:引入DOM(或者组件,但是需要是class组件)元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, { memo, useRef} from 'react'
function App() {
const inputRef = useRef()
const buttonClick = ()=>{
console.log(inputRef.current)
inputRef.current.focus()
}
return (
<div>
<input type="text" ref={inputRef}/>
<button onClick={buttonClick}>修改</button>
</div>
)
}
export default memo(App)
  • 用法二:保存一个数据,这个对象在整个生命周期中可以保存不变(解决闭包陷阱)
    • 由于函数里的count是获取上层作用域里的,也就是闭包,那么无论页面刷新多少次,函数里的count都是函数定义是的count 0,因此无论多少次点击得到的结果都是1(当然这里可以设置依赖count——仅颜色useRef的作用,所以没有添加)
    • 而使用inputRef所得到的对象每次本身就是一样的,那么有没有闭包陷阱也就无所谓,因为我们本身就是要获取相同的值,而通过修改inptRef这个对象里面的值,从而获得相同的对象不同的值,解决闭包陷阱
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { memo, useCallback, useRef, useState} from 'react'
function App() {
const [count,setCount] = useState(0)
const buttonClick = useCallback(function(){
console.log("+1",count+1)
setCount(count+1)
},[])
const inputRef = useRef()
inputRef.count = count
const buttonClick1 = useCallback(function(){
console.log("-1",inputRef.count-1)
setCount(inputRef.count-1)
},[])
return (
<div>
<h2>count:{count}</h2>
<button onClick={buttonClick}>+1</button>
<button onClick={buttonClick1}>-1</button>
</div>
)
}
export default memo(App)

image-20230404105354101

父组件传ref给子组件

  • 方式一:作为参数放到props中传递过去——参数名不能使用ref
  • 方式二:利用forwardRef传递给子组件

父组件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React, { memo, useRef, useState} from 'react'
import ChildComponent from './component/ChildComponent'
function App() {
const inputRef = useRef()
const buttonClick = ()=>{
console.log(inputRef.current)
inputRef.current.focus()
inputRef.current.value = ""
}
return (
<div>
<ChildComponent ref = {inputRef}/>
<button onClick={buttonClick}>showref</button>
</div>
)
}
export default memo(App)

子组件代码:

1
2
3
4
5
6
7
8
9
10
import React, { forwardRef, memo } from 'react'

function ChildComponent(props, ref) {
return (
<div>
<input type="text" ref={ref}/>
</div>
)
}
export default memo(forwardRef(ChildComponent))

注:要使用ref参数,子组件需用forwardRef包裹,且要使用memo的话,memo在forwardRef外层

useImperativeHandle的使用

  • 上述通过父组件中的ref传递给子组件中的元素,可以在父组件中获得子组件元素,对子组件元素进行操作
  • 将整个元素给到父组件,就容易在父组件中过渡使用,从而在某种情况下产生某种bug,如上述代码,点击showref会在聚焦子组件input的同时,将其值修改为空
  • useImperativeHandle就是为了解决样的问题,不直接把子组件元素给到父组件,而是把子组件元素的某些操作给到父组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { forwardRef, memo, useImperativeHandle, useRef } from 'react'

function ChildComponent(props, ref) {
const inputRef = useRef()
useImperativeHandle(ref, ()=>{
return {
focus:()=>{
inputRef.current.focus()
}
}
})
return (
<div>
<input type="text" ref={inputRef}/>
</div>
)
}
export default memo(forwardRef(ChildComponent))

useImperativeHandle的基本使用

  • 参数一:父组件传给子组件的ref
  • 参数二:回调函数,函数返回结果是传递给父组件的属性值对象,如这里只传了focus函数,那么父组件中就只有focus属性生效,其余属性无效

useLayoutEffect使用

useLayoutEffect看起来和useEffect非常的相似,事实上他们也只有一点区别而已:

  • useEffect会在渲染的内容更新到DOM上后执行,不会阻塞DOM的更新
  • useLayoutEffect会在渲染的内容更新到DOM上之前执行,会阻塞DOM的更新

image-20230404143022932

useEffect与useLayoutEffect执行顺序演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React, { memo, useEffect, useLayoutEffect} from 'react'
function App() {
useEffect(()=>{
console.log("useEffect")
})
useLayoutEffect(()=>{
console.log("useLayoutEffect")
})
console.log("render app")
return (
<div>
<h2>哈哈哈</h2>
</div>
)
}
export default memo(App)

image-20230404150134192

注:先”render app”,等数据更新后,渲染到DOM前执行useLayoutEffect,渲染结束后执行useEffect

useLayoutEffect的作用

在页面即将刷新错误数据前及时拦截

使用useEffect代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { memo, useEffect, useState} from 'react'
function App() {
const [count, setCount] = useState(10)
useEffect(()=>{
if(count===0){
setCount(10)
}
console.log("useEffect",count)
}, [count])
console.log("render app")
function buttonClick(){
setCount(count-1)
}
return (
<div>
<h2>count:{count}</h2>
<button onClick={buttonClick}>-1</button>
</div>
)
}
export default memo(App)

动画

使用useLayoutEffect代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { memo, useLayoutEffect, useState} from 'react'
function App() {
const [count, setCount] = useState(10)
useLayoutEffect(()=>{
if(count===0){
setCount(10)
}
console.log("useEffect",count)
}, [count])
console.log("render app")
function buttonClick(){
setCount(count-1)
}
return (
<div>
<h2>count:{count}</h2>
<button onClick={buttonClick}>-1</button>
</div>
)
}
export default memo(App)

动画

自定义Hook

自定义Hook本质上只是一种函数代码逻辑的抽取,严格意义上来说,它本身并不算React的特性

注:

  • 自定义Hook以use开头,且use后的字母要大写

自定义Hook——所有的组件在创建和销毁时都进行打印

  • 组件被创建:打印 组件被创建了
  • 组件被销毁:打印 组件被销毁了

image-20230404161751779

image-20230404161758920

自定义Hook——Context的共享

image-20230404161934780

自定义Hook——获取滚动位置

image-20230404162001534

redux hooks的使用

  • 在之前的redux开发中,为了让组件和redux结合起来,我们使用了react-redux中的connect
    • 但是这种方式必须使用高阶函数结合返回的高阶组件
    • 且必须编写:mapStateToProps和 mapDispatchToProps映射的函数
  • useSelector的作用是将state映射到组件中
    • 参数一:将state映射到需要的数据中
    • 参数二:可以进行比较来决定是否组件重新渲染;(后续讲解)
  • useSelector默认会比较我们返回的两个对象是否相等
    • 也就是我们必须返回两个完全相等的对象才可以不引起重新渲染
  • useDispatch非常简单,就是直接获取dispatch函数,之后在组件中直接使用即可
  • 我们还可以通过useStore来获取当前的store对象

redux的常规使用——使用connect函数

整个项目index.js代码:

1
2
3
4
5
6
7
8
9
10
11
12
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux';
import store from './stero';
const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
<Provider store={store}>
<App/>
</Provider>
);

countSlice.js代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { createSlice } from "@reduxjs/toolkit";

const countSlice = createSlice({
name:"count",
initialState:{
count:5
},
reducers:{
addCountAction(state, action){
state.count += action.payload
}
}
})

export const {addCountAction} = countSlice.actions
const countReducer = countSlice.reducer
export default countReducer

导出的stero的index.js代码:

1
2
3
4
5
6
7
8
9
import { configureStore } from "@reduxjs/toolkit";
import countReducer from "./countSlice";

const store = configureStore({
reducer:{
counter:countReducer
}
})
export default store

stero使用的jsx代码:

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
import React, { memo, useLayoutEffect, useState} from 'react'
import { connect } from 'react-redux';
import { addCountAction } from './stero/countSlice';

function App(props) {
const {count, addCount} = props
function buttonCLick(){
addCount(3)
}
return (
<div>
<h2>count:{count}</h2>
<button onClick={buttonCLick}>+3</button>
</div>
)
}

function mapStateToProps(state){
return {
count:state.counter.count
}
}
function mapAction20Props(dispatch){
return {
addCount(count){
dispatch(addCountAction(count))

}
}
}
export default connect(mapStateToProps, mapAction20Props)(memo(App))

注:

  • 这种方式必须使用高阶函数结合返回的高阶组件
  • 并且必须编写:mapStateToProps和 mapDispatchToProps映射的函数

redux的Hook使用

useSelector与useDispatch的使用

App.jsx代码:

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 React, { memo} from 'react'
import { useDispatch, useSelector } from 'react-redux';
import ChildComponent from './component/ChildComponent';
import { addCountAction } from './stero/countSlice';

function App(props) {
console.log("App渲染")
/**
* useSelector的第一个参数为函数,其返回一个对象,会把这个返回对象作为useSelector的值
*/
const {count} = useSelector((state)=>({
count:state.counter.count
}))
/**
* dispatch通过useDispatch直接获取,不再需要像
*/
const dispatch = useDispatch()
function buttonCLick(){
dispatch(addCountAction(4))
}
return (
<div>
<h2>count:{count}</h2>
<ChildComponent/>
<button onClick={buttonCLick}>改变count</button>
</div>
)
}
export default memo(App)

注:

可以看到能够直接使用useSelector对stero中的数据进行映射,直接通过useDispatch获取dispatch

但是上述会出现一些问题:

  • useSelector是监听的是state,也就是state改变后,所有使用useSelector的组件都会刷新

ChildComponent代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, {  memo } from 'react'
import { useSelector } from 'react-redux'

function ChildComponent(props) {
console.log("子组件渲染")
const {message} = useSelector((state)=>({
message:state.counter.message
}))
return (
<div>
<h2>子组件:{message}</h2>
</div>
)
}
export default memo(ChildComponent)

子组件只与state中的message有关,但当count改变时子组件依然会刷新,会引起性能的降低

动画

给useSelector设置shallowEqual就可以避免上述问题,实际上是进行一个浅层比较,如果相同就不更新

1
2
3
4
5
6
7
const {message} = useSelector((state)=>({
message:state.counter.message
}),shallowEqual)

const {count} = useSelector((state)=>({
count:state.counter.count
}),shallowEqual)

动画

中间件的实现

React-Router路由

URL的hash:

  • URL的hash也就是锚点(#), 本质上是改变window.location的href属性
  • 我们可以通过直接赋值location.hash来改变href, 但是页面不发生刷新

React Router的安装

1
npm install react-router-dom

基本使用

BrowserRouter或HashRouter:

  • Router中包含了对路径改变的监听,并且会将相应的路径传递给子组件
  • BrowserRouter使用history模式
  • HashRouter使用hash模式

路由的配置与使用

要使用的组件前面添加路由设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { HashRouter } from 'react-router-dom';


const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
// 可以选择HashRouter/BrowserRouter
<HashRouter>
<App />
</HashRouter>
</React.StrictMode>
);

路由的映射

映射设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React, { PureComponent } from 'react'
import { Routes, Route } from 'react-router-dom'
import Home from './components/Home'
import MyMusic from './components/MyMusic'
export default class App extends PureComponent {
render() {
return (
<div>
<h2>Header</h2>
<div className="content">
{/* 这里会con上往下匹配,直至匹配到一个结果,后续就不匹配了*/}
<Routes>
<Route path="/" element={<Home/>}/>
<Route path="/my" element={<MyMusic/>}/>
</Routes>
</div>
</div>
)
}
}

注:

  • 如果使用HashRouter,浏览器的路径设置一定要加/#/,否则是失效的

image-20230402152504555

image-20230402152522490

路由的配置跳转

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
25
26
import React, { PureComponent } from 'react'
import { Routes, Route, Link } from 'react-router-dom'
import Home from './components/Home'
import MyMusic from './components/MyMusic'
export default class App extends PureComponent {
render() {
return (
<div>
{/* 主要是通过路由的配置来更改当前页面的location,从而使下面的Routes匹配结果改变 */}
<div className='header'>
<Link to="/">发现音乐</Link>
<br />
<Link to="/my">我的音乐</Link>
</div>
<div className="content">
{/* 这里会con上往下匹配,直至匹配到一个结果,后续就不匹配了*/}
<Routes>
<Route path="/" element={<Home/>}/>
<Route path="/my" element={<MyMusic/>}/>
</Routes>
</div>
</div>
)
}
}

image-20230402153039156

注:

  • Link元素会自动渲染为a元素
  • 如果要更改这个a元素的样式,需要使用NavLink组件
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 React, { PureComponent } from 'react'
import { Routes, Route, NavLink } from 'react-router-dom'
import Home from './components/Home'
import MyMusic from './components/MyMusic'
import "./index.css"
export default class App extends PureComponent {
render() {
return (
<div>
{/* 主要是通过路由的配置来更改当前页面的location,从而使下面的Routes匹配结果改变 */}
<div className='header'>
<NavLink to="/">发现音乐</NavLink>
<br />
<NavLink to="/my">我的音乐</NavLink>
</div>
<div className="content">
{/* 这里会con上往下匹配,直至匹配到一个结果,后续就不匹配了*/}
<Routes>
<Route path="/" element={<Home/>}/>
<Route path="/my" element={<MyMusic/>}/>
</Routes>
</div>
</div>
)
}
}

index.css代码:

1
2
3
.active{
color: red;
}

image-20230402153943491

注:

  • NavLink元素会自动给当前渲染的a元素添加active类,所以可以通过active去设置当前渲染的样式
  • 而如果active类在其他地方已经使用过了呢,使用active就容易引起冲突,react-router提供了className属性去自定义我们的类名称,属性应该传入函数
  • 同样react-router为我们提供了style属性去定义样式,属性应该传入函数

使用style属性:

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 React, { PureComponent } from 'react'
import { Routes, Route, NavLink } from 'react-router-dom'
import Home from './components/Home'
import MyMusic from './components/MyMusic'
export default class App extends PureComponent {
render() {
return (
<div>
{/* 主要是通过路由的配置来更改当前页面的location,从而使下面的Routes匹配结果改变 */}
<div className='header'>
{/* 这里给style传入的函数,返回的一个对象,而且会自动传入一个包含isActive的参数,可以通过解构出isActive用于逻辑判断 */}
<NavLink to="/" style={({isActive})=>{ return isActive?{color:"red",fontSize:24}:{}}}>发现音乐</NavLink>
<br />
<NavLink to="/my" style={({isActive})=>{ return isActive?{color:"red",fontSize:24}:{}}}>我的音乐</NavLink>
</div>
<div className="content">
{/* 这里会con上往下匹配,直至匹配到一个结果,后续就不匹配了*/}
<Routes>
<Route path="/" element={<Home/>}/>
<Route path="/my" element={<MyMusic/>}/>
</Routes>
</div>
</div>
)
}
}

image-20230402154824768

使用className属性:

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 React, { PureComponent } from 'react'
import { Routes, Route, NavLink } from 'react-router-dom'
import Home from './components/Home'
import MyMusic from './components/MyMusic'
import "./index.css"
export default class App extends PureComponent {
render() {
return (
<div>
{/* 主要是通过路由的配置来更改当前页面的location,从而使下面的Routes匹配结果改变 */}
<div className='header'>
<NavLink to="/" className={({isActive})=>{ return isActive?"nav-active one":"one"}}>发现音乐</NavLink>
<br />
<NavLink to="/my" className={({isActive})=>{ return isActive?"nav-active two":"two"}}>我的音乐</NavLink>
</div>
<div className="content">
{/* 这里会con上往下匹配,直至匹配到一个结果,后续就不匹配了*/}
<Routes>
<Route path="/" element={<Home/>}/>
<Route path="/my" element={<MyMusic/>}/>
</Routes>
</div>
</div>
)
}
}

image-20230402155112040

  • 如当页面location为/时,自动跳转到/home/页面
  • 这种情况与上面直接element的直接区别为:这种情况下不会出现”/“的情况,因为会直接跳转,而上面是替换匹配到的组件
  • 导航的所有东西都是放在#后面的,#前面在解析时会忽略

image-20230402160154144

image-20230402160916685

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
import React, { PureComponent } from 'react'
import { Routes, Route, NavLink, Navigate } from 'react-router-dom'
import Home from './components/Home'
import MyMusic from './components/MyMusic'
import "./index.css"
export default class App extends PureComponent {
render() {
return (
<div>
{/* 主要是通过路由的配置来更改当前页面的location,从而使下面的Routes匹配结果改变 */}
<div className='header'>
<NavLink to="/" className={({isActive})=>{ return isActive?"nav-active one":"one"}}>发现音乐</NavLink>
<br />
<NavLink to="/my" className={({isActive})=>{ return isActive?"nav-active two":"two"}}>我的音乐</NavLink>
</div>
<div className="content">
{/* 这里会con上往下匹配,直至匹配到一个结果,后续就不匹配了*/}
<Routes>
<Route path='/' element={<Navigate to="/home"/>}/>
<Route path="/home" element={<Home/>}/>
<Route path="/my" element={<MyMusic/>}/>
</Routes>
</div>
</div>
)
}
}
Not Found页面配置
  • 当所有的Route都未匹配成功时,当前组件内容为空,而有时想给用户提醒输入了错误的url,则需要一个可以匹配所有字符的默认Route

image-20230402160512717

  • react-route中的通配符为’*‘,因此将一个path=’*‘放在最后,那么如果前面的path都没匹配上,最后一个肯定可以匹配,并找到默认页面

    • 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 React, { PureComponent } from 'react'
      import { Routes, Route, NavLink, Navigate } from 'react-router-dom'
      import Home from './components/Home'
      import MyMusic from './components/MyMusic'
      import NotFound from './components/NotFound'
      import "./index.css"
      export default class App extends PureComponent {
      render() {
      return (
      <div>
      {/* 主要是通过路由的配置来更改当前页面的location,从而使下面的Routes匹配结果改变 */}
      <div className='header'>
      <NavLink to="/" className={({isActive})=>{ return isActive?"nav-active one":"one"}}>发现音乐</NavLink>
      <br />
      <NavLink to="/my" className={({isActive})=>{ return isActive?"nav-active two":"two"}}>我的音乐</NavLink>
      </div>
      <div className="content">
      {/* 这里会con上往下匹配,直至匹配到一个结果,后续就不匹配了*/}
      <Routes>
      <Route path='/' element={<Navigate to="/home"/>}/>
      <Route path="/home" element={<Home/>}/>
      <Route path="/my" element={<MyMusic/>}/>
      <Route path='*' element={<NotFound/>}/>
      </Routes>
      </div>
      </div>
      )
      }
      }

路由的嵌套

在实际开发中,经常出现路由之间存在嵌套关系——如网易云中的路由

image-20230402161440408

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 React, { PureComponent } from 'react'
import { Routes, Route, NavLink, Navigate } from 'react-router-dom'
import Home from './components/Home'
import MyMusic from './components/MyMusic'
import NotFound from './components/NotFound'
import Recommend from './components/Recommend'
import TopList from './components/TopList'
import "./index.css"
export default class App extends PureComponent {
render() {
return (
<div>
{/* 主要是通过路由的配置来更改当前页面的location,从而使下面的Routes匹配结果改变 */}
<div className='header'>
<NavLink to="/" className={({isActive})=>{ return isActive?"nav-active one":"one"}}>发现音乐</NavLink>
<NavLink to="/my" className={({isActive})=>{ return isActive?"nav-active two":"two"}}>我的音乐</NavLink>
</div>
<div className="content">
{/* 这里会con上往下匹配,直至匹配到一个结果,后续就不匹配了*/}
<Routes>
<Route path='/' element={<Navigate to="/home"/>}/>
<Route path="/home" element={<Home/>}>
<Route path='/home' element={<Navigate to="/home/discover"/>}/>
<Route path='/home/discover' element={<Recommend/>}/>
<Route path='/home/toplist' element={<TopList/>}/>
</Route>
<Route path="/my" element={<MyMusic/>}/>
<Route path='*' element={<NotFound/>}/>
</Routes>
</div>
</div>
)
}
}

注:

  • 通过上述写法可以实现嵌套,在Home组件中编写下一层的路由跳转逻辑

    • Home.jsx的代码——Outlet是NavLink元素得到组件的位置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import React, { PureComponent } from 'react'
    import { NavLink, Outlet } from 'react-router-dom'
    import "../index.css"
    export default class Home extends PureComponent {
    render() {
    return (
    <div className='home-container'>
    <div>home</div>
    <div className='home-content'>
    <NavLink to="/home/discover">推荐</NavLink>
    <NavLink to="/home/toplist">排行榜</NavLink>
    </div>
    {/* <Outlet/>是NavLink元素得到组件的位置 */}
    <Outlet/>
    </div>
    )
    }
    }

    image-20230402164901837

  • 第二层路由最终会变成第一层路由的子组件,而如果我们在两个’/‘路由的时候,想让他在第一层,而不出现在第二层呢——可以在第一层时,不写element

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <Routes>
    <Route path='/' element={<Navigate to="/home"/>}/>
    <Route path="/home">
    <Route path='/home' element={<Navigate to="/home/discover"/>}/>
    <Route path='/home/discover' element={<Recommend/>}/>
    <Route path='/home/toplist' element={<TopList/>}/>
    </Route>
    <Route path="/my" element={<MyMusic/>}/>
    <Route path='*' element={<NotFound/>}/>
    </Routes>

    image-20230402165329191

手动实现路由的跳转

  • 上面的跳转实现主要靠NavLink自动生成的a元素,但有时候我们想要使用其他组件跳转,或者在逻辑过程中跳转,这种方法就不太好实现
  • hooks提供了useNavigate函数,返回一个navigate对象,可以通过这个对象在代码中进行跳转
  • 但是hooks只能在函数组件中使用,类组件不能直接使用useNavigate
    • 对于对状态要求不高的类组件,可以更改为函数组件使用useNavigate
    • 对于有很多状态的类组件,可以使用高阶组件,在其外包裹一个函数组件,通过参数的形式,将navigate对象传递给类组件使用

高阶函数代码:

1
2
3
4
5
6
7
8
9
10
11
12
import { useNavigate } from "react-router-dom"

function hooksFunc(Wrapper){
return (props)=>{
const navigate = useNavigate()
const router = {
navigate
}
return <Wrapper {...props} router = {router}/>
}
}
export default hooksFunc

要被包裹的组件代码:

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 React, { PureComponent } from 'react'
import { Routes, Route, NavLink, Navigate } from 'react-router-dom'
import Home from './components/Home'
import hooksFunc from './components/HooksFunction'
import MyMusic from './components/MyMusic'
import NotFound from './components/NotFound'
import Recommend from './components/Recommend'
import TopList from './components/TopList'
import Friend from './components/Frined'
import "./index.css"
class App extends PureComponent {
navigateFriend(){
const {navigate} = this.props.router
navigate("/friend")
}
render() {
return (
<div>
{/* 主要是通过路由的配置来更改当前页面的location,从而使下面的Routes匹配结果改变 */}
<div className='header'>
<NavLink to="/" className={({isActive})=>{ return isActive?"nav-active one":"one"}}>发现音乐</NavLink>
<NavLink to="/my" className={({isActive})=>{ return isActive?"nav-active two":"two"}}>我的音乐</NavLink>
<button onClick={()=>{this.navigateFriend()}}>关注</button>
</div>
<div className="content">
{/* 这里会con上往下匹配,直至匹配到一个结果,后续就不匹配了*/}
<Routes>
<Route path='/' element={<Navigate to="/home"/>}/>
<Route path="/home" element={<Home/>}>
<Route path='/home' element={<Navigate to="/home/discover"/>}/>
<Route path='/home/discover' element={<Recommend/>}/>
<Route path='/home/toplist' element={<TopList/>}/>
</Route>
<Route path="/my" element={<MyMusic/>}/>
<Route path='/friend' element={<Friend/>}/>
<Route path='*' element={<NotFound/>}/>
</Routes>
</div>
</div>
)
}
}
// 这里可以选择导出时用高阶函数处理,也可以选择先导出,在使用前处理
export default hooksFunc(App)

路由参数的传递

  • 动态路由的方式
  • search传递参数

动态路由

  • 比如/detail的path对应一个组件Detail
  • 如果我们将path在Route匹配时写成/detail/:id,那么 /detail/abc、/detail/123都可以匹配到该Route,并且进行显示
  • 这个匹配规则,我们就称之为动态路由
  • 通常情况下,使用动态路由可以为路由传递参数

App.jsx代码——配置Route:

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 React, { PureComponent } from 'react'
import { Routes, Route, NavLink, Navigate } from 'react-router-dom'
import Home from './components/Home'
import hooksFunc from './components/HooksFunction'
import MyMusic from './components/MyMusic'
import NotFound from './components/NotFound'
import Recommend from './components/Recommend'
import TopList from './components/TopList'
import Friend from './components/Frined'
import "./index.css"
class App extends PureComponent {
navigateFriend(){
const {navigate} = this.props.router
navigate("/friend")
}
render() {
return (
<div>
{/* 主要是通过路由的配置来更改当前页面的location,从而使下面的Routes匹配结果改变 */}
<div className='header'>
<NavLink to="/" className={({isActive})=>{ return isActive?"nav-active one":"one"}}>发现音乐</NavLink>
<NavLink to="/my/123/oww" className={({isActive})=>{ return isActive?"nav-active two":"two"}}>我的音乐</NavLink>
<button onClick={()=>{this.navigateFriend()}}>关注</button>
</div>
<div className="content">
{/* 这里会con上往下匹配,直至匹配到一个结果,后续就不匹配了*/}
<Routes>
<Route path='/' element={<Navigate to="/home"/>}/>
<Route path="/home" element={<Home/>}>
<Route path='/home' element={<Navigate to="/home/discover"/>}/>
<Route path='/home/discover' element={<Recommend/>}/>
<Route path='/home/toplist' element={<TopList/>}/>
</Route>
{/* Route配置/:id/:name标志这里的id为参数 */}
<Route path="/my/:id/:name" element={<MyMusic/>}/>
<Route path='/friend' element={<Friend/>}/>
<Route path='*' element={<NotFound/>}/>
</Routes>
</div>
</div>
)
}
}
//
export default hooksFunc(App)

hooksFunction代码:——获取参数使用useParams的hook,依然不能直接在类组件中使用,因此与navigate一样,使用高阶组件包含

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { useNavigate, useParams } from "react-router-dom"

function hooksFunc(Wrapper){
return (props)=>{
const navigate = useNavigate()
const params = useParams()
const router = {
navigate,
params
}
return <Wrapper {...props} router = {router}/>
}
}
export default hooksFunc

MyMusic.jsx代码:——获取参数的组件

1
2
3
4
5
6
7
8
9
10
11
12
13
import React, { PureComponent } from 'react'
import hooksFunc from './HooksFunction'

class MyMusic extends PureComponent {
render() {
const {params} = this.props.router
console.log(params)
return (
<div>MyMusic-{params.id}-{params.name}</div>
)
}
}
export default hooksFunc(MyMusic)

image-20230403175400198

search传递参数

search传递参数直接在跳转路由时设置(Link/navigate)——如navigate(“/friend?name=oww&age=22”)

App.jsx代码:

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
import React, { PureComponent } from 'react'
import { Routes, Route, NavLink, Navigate } from 'react-router-dom'
import Home from './components/Home'
import hooksFunc from './components/HooksFunction'
import MyMusic from './components/MyMusic'
import NotFound from './components/NotFound'
import Recommend from './components/Recommend'
import TopList from './components/TopList'
import Friend from './components/Frined'
import "./index.css"
class App extends PureComponent {
navigateFriend(){
const {navigate} = this.props.router
// search参数传递不需要在Route中配置,而直接在Link或navigate中配置
navigate("/friend?name=oww&age=22")
}
render() {
return (
<div>
{/* 主要是通过路由的配置来更改当前页面的location,从而使下面的Routes匹配结果改变 */}
<div className='header'>
<NavLink to="/" className={({isActive})=>{ return isActive?"nav-active one":"one"}}>发现音乐</NavLink>
<NavLink to="/my/123/oww" className={({isActive})=>{ return isActive?"nav-active two":"two"}}>我的音乐</NavLink>
<button onClick={()=>{this.navigateFriend()}}>关注</button>
</div>
<div className="content">
{/* 这里会con上往下匹配,直至匹配到一个结果,后续就不匹配了*/}
<Routes>
<Route path='/' element={<Navigate to="/home"/>}/>
<Route path="/home" element={<Home/>}>
<Route path='/home' element={<Navigate to="/home/discover"/>}/>
<Route path='/home/discover' element={<Recommend/>}/>
<Route path='/home/toplist' element={<TopList/>}/>
</Route>
{/* Route配置/:id/:name标志这里的id为参数 */}
<Route path="/my/:id/:name" element={<MyMusic/>}/>
<Route path='/friend' element={<Friend/>}/>
<Route path='*' element={<NotFound/>}/>
</Routes>
</div>
</div>
)
}
}
//
export default hooksFunc(App)

hooksFunction代码:要获取传递的参数同样需要hooks,则同样需要用高阶函数包裹

  • useLocation:会获取到当前页面url的一些信息

  • useSearchParams:获取到当前页面的参数相关信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom"

function hooksFunc(Wrapper){
return (props)=>{
const navigate = useNavigate()
const params = useParams()
const location = useLocation()
const [searchParams] = useSearchParams()
const query = Object.fromEntries(searchParams)
console.log("location", location)
console.log("searchParams", query)
const router = {
navigate,
params,
query
}
return <Wrapper {...props} router = {router}/>
}
}
export default hooksFunc

image-20230403181604295

路由的配置文件

  • 之前直接用Route组件定义路由,如果项目中路由很多的情况下,页面会相当混乱
  • 可以通过useRoutes的hook配置到一个地方集中管理
    • 早期,Route没有相关的api,需要借助于react-router-config
    • Router6.x中,为我们提供了useRoutes API完成相关的配置

image-20230403184434151

Redux

为什么要使用Redux

  • JavaScript开发的应用程序,已经变得越来越复杂了
    • JavaScript需要管理的状态越来越多,越来越复杂
    • 这些状态包括服务器返回的数据、缓存数据、用户操作产生的数据等等,也包括一些UI的状态,比如某些元素是否被选中,是否显示 加载动效,当前分页
  • 管理不断变化的state是非常困难的
    • 状态之间相互会存在依赖,一个状态的变化会引起另一个状态的变化,View页面也有可能会引起状态的变化
    • 当应用程序复杂时,state在什么时候,因为什么原因而发生了变化,发生了怎么样的变化,会变得非常难以控制和追踪
  • React是在视图层帮助我们解决了DOM的渲染过程,但是State依然是留给我们自己来管理
    • 无论是组件定义自己的state,还是组件之间的通信通过props进行传递;也包括通过Context进行数据之间的共享
    • React主要负责帮助我们管理视图,state如何维护最终还是我们自己来决定
  • React主要负责帮助我们管理视图,state如何维护最终还是我们自己来决定
  • Redux除了和React一起使用之外,它也可以和其他界面库一起来使用(比如Vue),并且它非常小(包括依赖在内,只有2kb)

Redux三大原则

  • 单一数据源
    • 整个应用程序的state被存储在一颗object tree中,并且这个object tree只存储在一个 store 中
    • Redux并没有强制让我们不能创建多个Store,但是那样做并不利于数据的维护
    • 单一的数据源可以让整个应用程序的state变得方便维护、追踪、修改
  • State是只读的
    • 唯一修改State的方法一定是触发action,不要试图在其他地方通过任何的方式来修改State
    • 这样就确保了View或网络请求都不能直接修改state,它们只能通过action来描述自己想要如何修改state
    • 这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行,所以不需要担心race condition(竟态)的问题
  • 使用纯函数来执行修改、
    • 通过reducer将 旧state和 actions联系在一起,并且返回一个新的State
    • 随着应用程序的复杂度增加,我们可以将reducer拆分成多个小的reducers,分别操作不同state tree的一部分
    • 但是所有的reducer都应该是纯函数,不能产生任何的副作用

Redux项目搭建与使用

  • 安装redux

    1
    npm install redux --save
  • 创建一个对象,作为我们要保存的状态

  • 创建Store来存储这个state

    • 创建store时必须创建reducer

    • 我们可以通过 store.getState 来获取当前的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
    const { createStore } = require("redux")
    // 初始化数据——要保存的状态
    const initialState = {
    name:"ouwenwu",
    age:22
    }
    // 定义reducer函数:纯函数
    /**
    * 参数1:store中目前报错的state——在createStore时会调用reducer一次,此时参数state为undefined,因此需要为其定义一个初始值,即我们的初始化数据
    * 参数2:传入的action
    * 返回值:返回值会作为store之后存储的state
    */
    function reducer(state=initialState,action){
    console.log("reducer",state,action)
    // 这里使用switch更好
    switch(action.type){
    case "change_name":
    return {...state,name:action.name}
    case "add_age":
    return {...state,age:state.age+action.age}
    default:
    return state
    }
    }

    //创建store
    const store = createStore(reducer)
    module.exports = store
  • 通过action来修改state

    • 通过dispatch来派发action
    • 通常action中都会有type属性,也可以携带其他的数据
  • 修改reducer中的处理代码

    • 这里一定要记住,reducer是一个纯函数,不需要直接修改state
    • 后面我会讲到直接修改state带来的问题
  • 可以在派发action之前,监听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
    const store=require("./store");

    // 开启store订阅——这个订阅是在派发action之前订阅的,会在store所监听的state修改时调用
    const unsubscribe = store.subscribe(()=>{
    console.log("订阅数据发生变化",store.getState())
    })

    // actionFunction——将action抽取为函数,那么后面每次需要创建action不用手动编写,调用函数生成
    const changeNameAction = (name)=>({
    type:"change_name",
    name
    })
    const addAgeAction = (age)=>({
    type:"add_age",
    age
    })
    // 修改store中的数据:必须action——手动写action
    const nameAction ={type:"change_name",name:"kobe"}
    store.dispatch(nameAction)
    store.dispatch(changeNameAction("kebi1"))
    // 取消订阅
    unsubscribe()
    store.dispatch({type:"add_age",age:1})
    store.dispatch(addAgeAction(1))

    输出image-20230330092932419

Redux项目抽取

如果我们将所有的逻辑代码写到一起,那么当redux变得复杂时代码就难以维护

  • store/index.js文件——利用reducer创建store

    1
    2
    3
    4
    import {createStore} from "redux"
    import reducer from "./reducer"
    //创建store
    export const store = createStore(reducer)
  • store/reducer.js文件——reducer函数和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
    import * as actionType from "./constants.js"

    const initialState = {
    name:"ouwenwu",
    age:22
    }
    // 定义reducer函数:纯函数
    /**
    * 参数1:store中目前报错的state
    * 参数2:传入的action
    * 返回值:返回值会作为store之后存储的state
    */
    function reducer(state=initialState,action){
    console.log("reducer",state,action)
    // 这里使用switch更好
    switch(action.type){
    case actionType.CHANGE_NAME:
    return {...state,name:action.name}
    case actionType.ADD_AGE:
    return {...state,age:state.age+action.age}
    default:
    return state
    }
    }
    export default reducer
  • store/actionCreator.js文件——将store要派发的action,抽取成函数放在此处

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import * as actionType from "./constants.js"

    export const changeNameAction = (name)=>({
    type:actionType.CHANGE_NAME,
    name
    })
    export const addNameAge = (age)=>({
    type:actionType.ADD_AGE,
    age
    })
  • store/constants.js文件——常量文件,将action中的type抽取出来

    1
    2
    export const CHANGE_NAME = "change_name"
    export const ADD_AGE = "add_age"

Redux与React融合

image-20230330102727040

redux配置代码如上所述

jsx代码:

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
import React from "react";
import store from "./store/index.js"
import { changeNameAction, addAgeAction } from "./store/actionCreator";
//编写组件
class App extends React.PureComponent{
constructor(){
super()
// 初始化值使用store中的初始化变量
this.state = {
name:store.getState().name,
age:store.getState().age
}
}
// 加载页面前订阅store,使得其更改时调用对应的函数改变state引起页面的刷新
componentDidMount(){
this.unsubScribe = store.subscribe(()=>{
const state = store.getState()
this.setState({
name:state.name,
age:state.age
})
})
}
// 页面卸载前取消订阅
componentWillUnmount(){
this.unsubScribe()
}
changeName(){
store.dispatch(changeNameAction("www"))
}
addAge(){
store.dispatch(addAgeAction(1))
}
render(){
const {name,age} = this.state
return (
<div>
<button onClick={()=>{this.changeName()}}>改变名字</button>
<button onClick={()=>{this.addAge()}}>age+1</button>
<div>
<h2>{name}</h2>
<h3>{age}</h3>
</div>
</div>
)
}
}
export default App

react-redux的使用

  • index.js(整个react的入口):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import React from "react";
    import ReactDOM from "react-dom/client";
    import App from "./App"
    import { Provider } from "react-redux";
    import store from "./store";



    // 编写REACT代码,通过REACT渲染内容
    /**
    * 这里的Provider实际上是基于Context实现的,用它包裹App及其子组件可以使用传入的stero
    * stero在底层是通过value实现的
    */
    const root = ReactDOM.createRoot(document.querySelector('#root'))
    root.render(
    <Provider store={store}>
    <App/>
    </Provider>
    )
  • App.js代码(获取stero并使用代码):

    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
    import React from "react";
    import { connect } from "react-redux";
    import { addAgeAction } from "./store/actionCreator";
    //编写组件
    class App extends React.PureComponent{
    constructor(){
    super()
    }
    addAgeByButton(){
    // store.dispatch(addAgeAction(1))
    this.props.addAge(1)
    }
    render(){
    return (
    <div>
    <button onClick={()=>{this.addAgeByButton()}}>age+1</button>
    <div>
    <h3>{this.props.age}</h3>
    </div>
    </div>
    )
    }
    }
    // 相当于添加监听,会把stero中的state中的对应属性添加到props中
    function mapStateToProps(state){
    // 需要用哪些就使用哪些,会将这个返回对象和本来的props合并
    return{
    age:state.age
    }
    }
    // 设置dispatch,会把return中的函数放到this.pros中去,通过调用这个函数可以发送action给stero
    const mapDispatchToProps = (dispatch)=>{
    return{
    addAge(age){
    dispatch(addAgeAction(age))
    }
    }
    }
    // connect()返回值是一个高阶函数
    /**
    * connect()参数1:store中的哪些数据需要映射到这个组件的props——函数
    * connect()参数2:
    */
    export default connect(mapStateToProps,mapDispatchToProps)(App)
  • stero的代码,index.js/constants.js/actionCreate.js/reducer.js依然像上方写的一样

redux异步处理

react获取网络请求:

  • componentDidMount中发起网络请求,并在结束后赋值给state

    • 这种方式数据管理和react耦合性高,和redux的要求不符
  • 通过redux获取网络请求,由jsx组件中发起action,在action中获取数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /**
    * 由于网络请求通常是异步的,函数的return中不能直接获得网络请求的值,因此普通的action是不能实现异步的
    */
    export const netWorkAction = ()=>{
    new Promise((resolve, reject) => {
    setTimeout(()=>{
    resolve("oww")
    },5000)
    }).then(res=>{
    return{
    type:actionType.CHANGE_NAME,
    name:res
    }
    })
    return {}
    }

    redux-thunk中间件技术

    • 安装redux-thunk

      1
      npm install react-thunk --save
    • 设置react可以使用thunk,thunk允许dispatch一个函数,之后会自动执行这个函数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      import { applyMiddleware, combineReducers, createStore } from "redux";
      import countReducer from "./countStore/reducer";
      import homeReducer from "./homeStore/reducer";
      import thunk from "redux-thunk";
      const reducer = combineReducers({
      age:countReducer,
      name:homeReducer
      })

      // combineReducers的底层实现
      function reducerByUs(state = {}, action){
      return {
      /**
      * 第一次执行时(createStore),传入undefined,得到的是默认值对象
      * 后续执行时,每次传入上次的state,得到正确值
      */
      age:countReducer(state.age, action),
      name:homeReducer(state.name,action)
      }
      }
      // 包裹中间件
      export const store = createStore(reducer,applyMiddleware(thunk))
      export default store

      action代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      export const netWorkAction = ()=>{
      console.log("dispatch foo")
      const foo = (dispatch, getState)=>{
      new Promise((resolve) => {
      setTimeout(()=>{
      resolve("oww")
      },5000)
      }).then(res=>{
      dispatch(changeNameAction(res))
      })
      }
      return foo
      }

      jsx中发起dispatch代码

      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
      49
      50
      51
      52
      53
      import React from "react";
      import { connect } from "react-redux";
      import { addAgeAction } from "./store/countStore";
      import { netWorkAction } from "./store/homeStore";
      //编写组件
      class App extends React.PureComponent{
      constructor(){
      super()
      }
      addAgeByButton(){
      // store.dispatch(addAgeAction(1))
      this.props.addAge(1)
      this.props.changeName()
      }

      render(){
      return (
      <div>
      <button onClick={()=>{this.addAgeByButton()}}>changeinfo</button>
      <div>
      <h2>{this.props.name}</h2>
      <h3>{this.props.age}</h3>
      </div>
      </div>
      )
      }
      }

      // 相当于添加监听,会把stero中的state中的对应属性添加到props中
      function mapStateToProps(state){
      // 需要用哪些就使用哪些,会将这个返回对象和本来的props合并
      return{
      age:state.age.age,
      name:state.name.name
      }
      }
      // 设置dispatch,会把return中的函数放到this.pros中去,通过调用这个函数可以发送action给stero
      const mapDispatchToProps = (dispatch)=>{
      return{
      addAge(age){
      dispatch(addAgeAction(age))
      },
      changeName(){
      dispatch(netWorkAction())
      }
      }
      }
      // connect()返回值是一个高阶函数
      /**
      * connect()参数1:store中的哪些数据需要映射到这个组件的props——函数
      * connect()参数2:
      */
      export default connect(mapStateToProps,mapDispatchToProps)(App)

redux代码拆分

不拆分的reducer:

  • 一个reducer处理多个页面的数据
  • 将所有状态都放到reducer中进行管理,随着项目的日趋庞大,会造成代码臃肿

对reducer进行拆分:

  • 将对不同页面的数据或造成抽取为不同的reducer
  • 将多个reducer合并为一个

combineReducers函数

事实上,redux给我们提供了一个combineReducers函数可以方便的让我们对多个reducer进行合并

页面一store代码:

  • actionCreator.js

    1
    2
    3
    4
    5
    6
    import * as actionType from "./constants.js"

    export const changeNameAction = (name)=>({
    type:actionType.CHANGE_NAME,
    name
    })
  • constants.js

    1
    export const CHANGE_NAME = "change_name"
  • index.js

    1
    2
    3
    4
    5
    6
    import homeReducer from "./reducer"

    // 统一当初当前模块的数据
    export default homeReducer
    export * from "./actionCreator"
    export * from "./constants"
  • reducer.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    import * as actionType from "./constants.js"

    const initialState = {
    name:"ouwenwu",
    age:22
    }
    // 定义reducer函数:纯函数
    /**
    * 参数1:store中目前报错的state
    * 参数2:传入的action
    * 返回值:返回值会作为store之后存储的state
    */
    function homeReducer(state=initialState,action){
    console.log("reducer",state,action)
    // 这里使用switch更好
    switch(action.type){
    case actionType.CHANGE_NAME:
    return {...state,name:action.name}
    default:
    return state
    }
    }
    export default homeReducer

页面一store代码:

  • actionCreator.js

    1
    2
    3
    4
    5
    6
    import * as actionType from "./constants.js"

    export const changeNameAction = (name)=>({
    type:actionType.CHANGE_NAME,
    name
    })
  • constants.js

    1
    export const CHANGE_NAME = "change_name"
  • index.js

    1
    2
    3
    4
    5
    6
    import homeReducer from "./reducer"

    // 统一当初当前模块的数据
    export default homeReducer
    export * from "./actionCreator"
    export * from "./constants"
  • reducer.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    import * as actionType from "./constants.js"

    const initialState = {
    name:"ouwenwu",
    age:22
    }
    // 定义reducer函数:纯函数
    /**
    * 参数1:store中目前报错的state
    * 参数2:传入的action
    * 返回值:返回值会作为store之后存储的state
    */
    function homeReducer(state=initialState,action){
    console.log("reducer",state,action)
    // 这里使用switch更好
    switch(action.type){
    case actionType.CHANGE_NAME:
    return {...state,name:action.name}
    default:
    return state
    }
    }
    export default homeReducer

页面二store代码:

  • actionCreator.js

    1
    2
    3
    4
    5
    6
    import * as actionType from "./constants.js"

    export const addAgeAction = (age)=>({
    type:actionType.ADD_AGE,
    age
    })
  • constants.js

    1
    export const ADD_AGE = "add_age"
  • index.js

    1
    2
    3
    4
    5
    import countReducer from "./reducer"

    export default countReducer
    export * from "./actionCreator"
    export * from "./constants"
  • reducer.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    import * as actionType from "./constants.js"

    const initialState = {
    age:22
    }
    // 定义reducer函数:纯函数
    /**
    * 参数1:store中目前报错的state
    * 参数2:传入的action
    * 返回值:返回值会作为store之后存储的state
    */
    function countReducer(state=initialState,action){
    console.log("reducer",state,action)
    // 这里使用switch更好
    switch(action.type){
    case actionType.ADD_AGE:
    console.log(state.age,action.age)
    return {...state,age:state.age+action.age}
    default:
    return state
    }
    }
    export default countReducer

合并两个store的代码:

index.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { combineReducers, createStore } from "redux";
import countReducer from "./countStore/reducer";
import homeReducer from "./homeStore/reducer";

const reducer = combineReducers({
age:countReducer,
name:homeReducer
})

// combineReducers的底层实现
function reducerByUs(state = {}, action){
return {
/**
* 第一次执行时(createStore),传入undefined,得到的是默认值对象
* 后续执行时,每次传入上次的state,得到正确值
*/
age:countReducer(state.age, action),
name:homeReducer(state.name,action)
}
}
export const 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
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 React from "react";
import { connect } from "react-redux";
import { addAgeAction } from "./store/countStore";
//编写组件
class App extends React.PureComponent{
constructor(){
super()
}
addAgeByButton(){
// store.dispatch(addAgeAction(1))
this.props.addAge(1)
}
render(){
return (
<div>
<button onClick={()=>{this.addAgeByButton()}}>age+1</button>
<div>
<h3>{this.props.age}</h3>
</div>
</div>
)
}
}
// 相当于添加监听,会把stero中的state中的对应属性添加到props中
function mapStateToProps(state){
// 需要用哪些就使用哪些,会将这个返回对象和本来的props合并
return{
// 这里获取到的stero为合并后的state,而合并后的state.age是一个reducer对象,因此要再加一个.age才能获取到真实值
age:state.age.age
}
}
// 设置dispatch,会把return中的函数放到this.pros中去,通过调用这个函数可以发送action给stero
const mapDispatchToProps = (dispatch)=>{
return{
addAge(age){
dispatch(addAgeAction(age))
}
}
}
// connect()返回值是一个高阶函数
/**
* connect()参数1:store中的哪些数据需要映射到这个组件的props——函数
* connect()参数2:
*/
export default connect(mapStateToProps,mapDispatchToProps)(App)

ReduxToolkit的使用(RTK)

  • 安装

    1
    npm install @reduxjs/toolkit react-redux
  • Redux Toolkit包旨在成为编写Redux逻辑的标准方式,从而解决上面提到的问题

Redux Toolkit的核心API主要是如下几个:

  • configureStore:包装createStore以提供简化的配置选项和良好的默认值。它可以自动组合你的 slice reducer,添加你提供 的任何 Redux 中间件,redux-thunk默认包含,并启用 Redux DevTools Extension

  • createSlice:接受reducer函数的对象、切片名称和初始状态值,并自动生成切片reducer,并带有相应的actions

  • createAsyncThunk: 接受一个动作类型字符串和一个返回承诺的函数,并生成一个pending/fulfilled/rejected基于该承诺分 派动作类型的 thunk

createSlice代码

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import { createSlice } from "@reduxjs/toolkit";
import { createAsyncThunk } from "@reduxjs/toolkit";
// @reduxjs/toolkit的异步操作1
/**
* payload是在jsx中dispatch时传入的额外参数
*/
export const getNameAction1 = createAsyncThunk("age/changeName",async(payload, stero)=>{
const {dispatch, getState} = stero
new Promise((resolve)=>{
setTimeout(()=>{
resolve("ouwenwu")
},5000)
}).then(res=>[
dispatch(changeName(res))
])
})
// @reduxjs/toolkit的异步操作2
export const getNameAction = createAsyncThunk("age/changeName",async(payload, stero)=>{
const {dispatch, getState} = stero
const res = await new Promise((resolve)=>{
setTimeout(()=>{
resolve("ouwenwu")
},2000)
})
console.log(res)
return res
})
const ageSlice = createSlice({
name:"ageSlice", // 用户后面标记当前操作类型,与react中action的type类似——但是不再需要写出来各种type,而自动实现
// 初始值
initialState:{
name:"oww",
age:22
},
// 相当于之前的reducer函数,可以添加很多的函数,每一个函数都相当于一个原来的case
reducers:{
addAge(state, action){
state.age += action.payload
console.log(action)
},
subAge(state, action){
state.age -= action.payload
console.log(action)
},
changeName(state,action){
state.name = action.payload
console.log(action)
}
},
// 监听异步dispatch
extraReducers:{
/**
* getNameAction一个有三个状态
* pending:action被发出,但是还没有最终的结果
* fulfilled:获取到最终的结果(有返回值的结果)
* rejected:执行过程中有错误或者抛出了异常
*/
[getNameAction.fulfilled](state, action){
state.name = action.payload
console.log(action)
}
}
})
// createSlice是一个把参数穿进去,返回一个对象,包含之前的reducer、action等,使用方法和之前使用相同
export const {addAge, subAge, changeName} = ageSlice.actions
const ageReucer = ageSlice.reducer
export default ageReucer

configureStore代码

1
2
3
4
5
6
7
8
9
10
import { configureStore } from "@reduxjs/toolkit"
import ageReucer from "./countStore/ageSlice"


const store = configureStore({
reducer:{
age: ageReucer
}
})
export default store

jsx代码

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
49
50
51
52
53
54
55
56
57
58
59
60
import React from "react";
import { connect } from "react-redux";
import { addAge, subAge, getNameAction } from "./store/countStore/ageSlice";
//编写组件
class App extends React.PureComponent{
constructor(){
super()
}
addAgeByButton(){
this.props.addAge(1)
}
subAgeByButton(){
this.props.subAge(1)
}
changeNameByButton(){
this.props.changeName()
}
render(){
return (
<div>
<button onClick={()=>{this.addAgeByButton()}}>addAge</button>
<button onClick={()=>{this.subAgeByButton()}}>subAge</button>
<button onClick={()=>{this.changeNameByButton()}}>changeName</button>
<div>
<h2>{this.props.name}</h2>
<h3>{this.props.age}</h3>
</div>
</div>
)
}
}

// 相当于添加监听,会把stero中的state中的对应属性添加到props中
function mapStateToProps(state){
// 需要用哪些就使用哪些,会将这个返回对象和本来的props合并
return{
name:state.age.name,
age:state.age.age
}
}
// 设置dispatch,会把return中的函数放到this.pros中去,通过调用这个函数可以发送action给stero
const mapDispatchToProps = (dispatch)=>{
return{
addAge(age){
dispatch(addAge(age))
},
subAge(age){
dispatch(subAge(age))
},
changeName(){
dispatch(getNameAction("aaa"))
}
}
}
// connect()返回值是一个高阶函数
/**
* connect()参数1:store中的哪些数据需要映射到这个组件的props——函数
* connect()参数2:
*/
export default connect(mapStateToProps,mapDispatchToProps)(App)

React中的过渡动画

  • 动画插件:react-transition-group
  • npm install react-transition-group —save

Transition

与框架无关,常见的CSS动画使用方法:

1
2
3
4
5
6
7
8
.start{
position: relative;
left:10px;
}
.end{
left:20px;
transition:left 1s ease;
}

CSSTransition

CSSTransition具有三种状态

  • 开始状态,对应的类是-appear、-enter、-exit
  • 执行动画,对应的类是-appera-active、-enter-active、-exit-active
  • 执行结束,对应的类是-appera-done、-enter-done、-exit-done

CSSTransition常见属性:

  • in:触发进入或退出状态

    • 如果添加了unmountOnExit={true},那么该组件会在执行退出动画结束后被移除掉
    • 当in为true时,触发进入状态,会添加-enter、-enter-acitve的class开始执行动画,当动画执行结束后,会移除两个class, 并且添加-enter-done的class
    • 当in为false时,触发退出状态,会添加-exit、-exit-active的class开始执行动画,当动画执行结束后,会移除两个class,并 且添加-enter-done的class
  • classNames:动画class的名称

    • 决定了在编写css时,对应的class名称:比如card-enter、card-enter-active、card-enter-done
  • timeout:动画过渡的时间,指的是动画执行后添加-enter-done的时间,动画真实执行时间由transition决定

  • appear:是否在初次进入添加动画(appear和in都要为true、同时要设置-appear类)

  • CSSTransition的钩子函数:

    • onEnter:在进入动画之前触发
    • OnEntering:在进入动画时触发
    • OnEntered:在应用进入动画结束时触发
    • ……

css代码:

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
 /*设置初始状态,不发生动画的状态,可以用appear的三个类替代*/
h2{
position: relative;
opacity: 0;
left: 0;
}
.h2show-enter{
opacity: 0;
left: 0;
}
.h2show-enter-active{
left: 100px;
opacity: 1;
transition: all 2s ease;
}
/*不设置done那么在动画执行完成那一刻会立即恢复到之前的状态,看是否需要恢复编写此类*/
.h2show-enter-done{
opacity: 1;
left: 100px;
}
.h2show-exit{
opacity: 1;
left: 100px;
}
.h2show-exit-active{
left: 0;
opacity: 0;
transition: all 2s ease;
}
/*不设置done那么在动画执行完成那一刻会立即恢复到之前的状态,看是否需要恢复编写此类*/
.h2show-exit-done{
left: 0;
opacity: 0;
}

jsx代码:

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
import React from "react";
import { CSSTransition } from "react-transition-group";
import "./index.css"
//编写组件
class App extends React.PureComponent{
constructor(){
super()
this.state ={
isShow:false
}
}
change(){
this.setState({
isShow:!this.state.isShow
})
}
render(){
const {isShow} = this.state
console.log(isShow)
return (
<div>
<button onClick={()=>{this.change()}}>变化</button>
<CSSTransition in={isShow} classNames="h2show" timeout={2000} unmountOnExit={true}>
<h2>哈哈哈</h2>
</CSSTransition>
</div>
)
}
}
export default App

SwitchTransition

  • 控件两种状态之间的切换:on和off指定两种状态

  • mode属性:

    • in-out:新组件先进入,旧组件再移除

    • out-in:旧组件先移除,新组件再进入

  • SwitchTransition组件里面要有CSSTransition或者Transition组件,不能直接包裹要切换的组件

  • SwitchTransition里面的CSSTransition或Tasnsition组件不再像之前那样接受属性来判断元素状态,取而代之的是key属性

css代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
h2{
position: relative;
left: 100px;
}
.h2show-enter{
left: 0;
}
.h2show-enter-active{
left: 100px;
transition: all 2s ease;
}
.h2show-enter-done{
left: 100px;
}
.h2show-exit{
left: 100px;
}
.h2show-exit-active{
left: 0;
transition: all 2s ease;
}
.h2show-exit-done{
left: 0;
}
  • 起始状态可以没有,那么刚开始就是默认状态
  • out-in模式:先制性exit代码,再执行enter代码,没有h2show-enter-done,动画执行结束会回归初始状态,可以没有h2show-exit-done
  • in-out模式:先执行enter代码,再执行exit代码,没有h2show-exit-done,动画结束会回归初始状态,可以没有h2show-enter-done

jsx代码:

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
import React from "react";
import { CSSTransition,SwitchTransition } from "react-transition-group";
import "./index.css"
//编写组件
class App extends React.PureComponent{
constructor(){
super()
this.state ={
isShow:false
}
this.nodeRef = React.createRef()
}
change(){
this.setState({
isShow:!this.state.isShow
})
}
render(){
const {isShow} = this.state
console.log(isShow)
return (
<div>
<button onClick={()=>{this.change()}}>变化</button>
<SwitchTransition mode="in-out">
<CSSTransition
nodeRef = {this.nodeRef}
key={isShow?"exit":"login"}
classNames="h2show"
timeout={2000}
>
<h2 ref={this.nodeRef}>{isShow?"哈哈哈":"哈你妹"}</h2>
</CSSTransition>
</SwitchTransition>
</div>
)
}
}
export default App

TransitionGroup:

  • 有一组动画时,可以将这些CSSTransition放入到TransitionGroup中

有些搞不懂,官网看教程吧

React中CSS的编写

CSS组件化!!!:

  • 可以编写局部css:css具备自己的局部作用域,不会污染其他组件的元素
  • 可以编写动态css:可以获取当前组件的一些状态,根据状态生成不同的css样式
  • 支持所有的css特性:伪类、动画、媒体查询等
  • 编写起来简单方便,符合一贯的css风格特点

内联样式

  • style接受小驼峰命名属性的JavaScript对象,而不是字符串
  • 可以接受state中的状态来设置相关的格式

内联样式优点:

  • 样式之间不会有冲突
  • 可以动态获取当前state中的状态

内联样式缺点:

  • 都需要驼峰标识
  • 某些样式没有提示
  • 大量样式,代码混乱
  • 某些样式无法编写,比如伪类/伪元素

普通CSS

  • 将CSS编写到一个单独的文件,之后进行引入

  • 和普通网页开发中的编写方式一致

    • 按照普通的网页标准去写,那么不会有太大的问题

    • 但是组件化开发中我们总是希望组件是一个独立的模块,样式只在自己内部生效,不会相互影响

    • 但是普通的CSS都属于全局的CSS,样式组件会相互影响
  • 这种编写方式的最大问题是样式之间会相互层叠掉

CSS modules

  • 将.css文件修改为.module.css看就可以引用并且局部使用
  • 但是类名不能使用(.home-title),因为JavaScript不识别-
  • 所有的className都必须使用{style.className}的形式来编写
  • 不方便动态的修改某些样式,依然需要使用内联样式的方式

CSS in Js

CSS in Js是一种模式,其中CSS有javaScript生成而不是在外部文件中定义

  • 传统的前端开发中,HTML、CSS、JavaScript通常分类
  • React认为逻辑和UI是无法分离的,所以才会有了JSX的语法
  • JSX是Html in Js,加上CSS in Js在完整的体现的逻辑和UI无法分离,因此React被称为All in JS

CSS in JS库:styled-components:

  • CSS-in-JS使用JavaScript为CSS赋予一些能力,可以类似于CSS预处理器一样的样式嵌套、函数定义、逻辑复用、动态修改
  • CSS预处理器也具备某些能力,但获取动态状态依然是一个不好处理的点

ES6标签模板字符串

image-20230329161529306

即函数可以通过字符串的方式来调用

  • 第一个参数是数组,是被模块字符串拆分后的组合
  • 后面的元素是一个个模块字符串传入的内容
  • 注意是反引号

styled-components的使用

基本使用

jsx代码:

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";
import { AppWrapper, StrongWrapper} from "./style";
//编写组件
class App extends React.PureComponent{
constructor(){
super()
}
render(){
return (
<AppWrapper>
<div className="container">
<span>哈哈哈</span>
<StrongWrapper>我是strong</StrongWrapper>
<div className="context">
<span>嘿嘿嘿</span>
</div>
<div className="context1">
<span>嗯嗯嗯</span>
</div>
</div>
</AppWrapper>
)
}
}
export default App

style.js代码:

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 styled from "styled-components";
export const AppWrapper = styled.div`
.container>span{
width:100%;
color: red;
}
.container{
.context1{
color: orange;
}
}
.container .context{
color: blue;
}
.container .context:hover{
color: #cccc;
}
&:hover{
text-decoration: line-through;
}
`
export const StrongWrapper =styled.strong`
color:skyblue
`
  • 样式js的命名不能和组件名称相同,例如不能用App.js
  • 可以像正常CSS一样编写CSS代码
  • 可以用&来获取当前元素
  • 可以通过设置styled.__来设置包裹的类型
  • styled返回的是一个组件,可以按照组件的方法使用

props\attrs属性

  • 获取props需要通过${}传入一个插值函数,props会作为该函数的参数;
  • 这种方式可以有效的解决动态样式的问题

CSS代码:

1
2
3
4
5
6
7
8
9
10
11
12
import styled from "styled-components";
export const AppWrapper = styled.div.attrs({
tcolor:props=>(props.color||"blue")
})`
.container{
width:100%;
color: ${props=>{
return props.tcolor
}};
}

`

jsx代码:

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 React from "react";
import { Children } from "react";
import { AppWrapper, StrongWrapper} from "./style";
//编写组件
class App extends React.PureComponent{
constructor(){
super()
this.state = {
checked:true
}
}
render(){
const {checked} = this.state
return (
<AppWrapper color={checked?"red":""}>
<div className="container">
<h2>哈哈哈</h2>
</div>
<button onClick={()=>{this.setState({checked:!this.state.checked})}}>改变颜色</button>
</AppWrapper>
)
}
}
export default App

注:

  • tcolor不能与color同名,要不然会循环拿取
  • tcolor传入的是一个函数,括号可以省略,会自动调用生成值
  • tcolor:props=>(props.color||”blue”)是为了处理不传值时给定默认值,没有默认值时可以在下面直接取

styled的继承

CSS代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
import styled from "styled-components";
export const AppWrapper = styled.div`
display: flex;
`
export const Button1 = styled.button`
border-radius: 5px;
border:1px solid red;
background-color: gray;
opacity: 0.5;
`
export const Button2 = styled(Button1)`
color: skyblue;
`

jsx代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React from "react";
import { Children } from "react";
import { AppWrapper, Button1, Button2} from "./style";
//编写组件
class App extends React.PureComponent{
constructor(){
super()
}
render(){
return (
<AppWrapper>
<Button1>父Button</Button1>
<Button2>子Button</Button2>
</AppWrapper>
)
}
}
export default App

image-20230329184407490

image-20230329184548981

一、二叉树种类

满二叉树

完全二叉树

二叉搜索树

左节点小于中间节点,右节点大于中间节点

平衡二叉搜索树

左节点小于中间节点,右节点大于中间节点,且左右子树的高度绝对值小于1

c++中map、set、multimap、multiset的底层是通过平衡二叉树实现,那么在插入节点,查询节点的时间复杂度是O(logn)

map中的key和set中的元素都是有序的

二、二叉树存储方式

链式存储

线式存储

左孩子:i*2+1

右孩子:i*2+2

三、二叉树的遍历

深度优先搜索

前序、中序、后序遍历——迭代法、递归法

广度优先搜索

层序遍历——迭代法

对象属性操作的控制

  • Object.defineProperty可以对对象的属性加以限制,例如不能通过delete删除,不能通过for-in/Object.keys遍历
  • Object.defineProperty方法会直接在一个对象上定义一个新的属性,或者修改一个对象的现有属性,并返回该对象

Object.defineProperty参数:Object.defineProperty(obj, prop, descriptor)

  • obj:要定义的对象
  • prop:要定义或修改的属性的名称或Symbol
  • descriptor:要定义或修改的属性描述符

属性描述符分类

image-20230327092658898

Configuratble:属性是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将它修改为存取属性描述符

  • 通过对象定义某个属性,其Configurable为true
  • 通过属性描述符定义属性时,属性的Configurable默认为false

Enumerable:属性是否可以通过for-in或者Object.keys()返回属性

  • 通过对象定义时,属性的Enumerable为true
  • 通过属性描述符定义属性时,属性的Enumerable默认为false

Writable:属性是否可以修改值

  • 通过对象定义时,属性的Writable为true
  • 通过属性描述符定义属性时,属性的Writable默认为false

value:属性的value值,读取属性会返回该值,修改属性时,会对其进行更改

  • 默认情况下这个值是undefined

get:获取属性时会执行的函数,默认为undefined

set:设置属性时会执行的函数,默认为undefined

注:get/set与value/Writable不可共存

补充:

同时定义多个属性:Object.defineProperties()

image-20230327093656890

获取对象的属性描述符:

  • getOwnPropertyDescriptor
  • getOwnPropertyDescriptors

禁止对象扩充新属性:preventExtensions Object.preventExtensions(obj)

  • 给一个对象添加新的属性会失败

密封属性,不允许配置和删除属性:seal Object.seal(obj)

  • 实际上是调用preventExtensions
  • 将现有属性的Configurable设置为false

冻结属性,不允许修改现有属性:freeze Object.freeze(obj)

实际上是调用seal

将现有属性的Writable设置为false

一、柯里化函数

柯里化:

把接收多个参数的函数,变成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数,而且返回 结果的新函数的技术

只传递给函数一部分参数来调用它,让它返回一个函数去处理剩余的参数

实现多数字加法的柯里化

加法函数:

1
2
3
var add0 = function(x,y,z){
return x + y + z
}

柯里化函数:

1
2
3
4
5
6
7
var add1 = function(x){
return function(y){
return function(z){
return x + y + z
}
}
}

箭头函数优化:

1
var add2 = x=>y=>z=>x+y+z

考虑会同时传递进去1个或2个参数

1
2
3
4
5
6
7
var add3 = (...args)=>{
if(args.length===3) return args.reduce((prev,curr)=>prev+curr)
return (...other)=>{
return add3(...args,...other)
}
}
// 未考虑传入参数大于3个时的边界情况

如果我们想得到可以获取不同个参数的柯里化函数呢,自动柯里化:

1
2
3
4
5
6
7
8
9
10
11
12
13
var fn = function(x,y,z,g){
return x+y+z+g
}
var curryMyFn= function(fn){
return curryFn = function(...args){
if(args.length>=fn.length) return fn(...args)
return (...newArgs)=>{
return curryFn(...args.concat(newArgs))
}
}
}
var bar = curryMyFn(fn)
console.log(bar(10,20)(40,50))

二、apply、call、bind的手写

this绑定

  • this绑定共4种,默认绑定、隐式绑定、显示绑定、new绑定
  • 实现显示绑定即是通过其余简单绑定方式实现,而默认绑定的this一般为windows,所以这里只能用隐式绑定实现

apply与call的手写实现

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
// 通过symbol实现apply
Function.prototype.myApply = function(thisArgs, args){
thisArgs = (thisArgs===null || thisArgs ===undefined)?window:Object(thisArgs)
let symbol = Symbol()
thisArgs[symbol] = this
let fn = thisArgs[symbol](...args)
delete thisArgs[symbol]
return fn
}
// 通过symbol实现call
Function.prototype.myCall = function(thisArgs, ...args){
thisArgs = (thisArgs===null || thisArgs ===undefined)?window:Object(thisArgs)
let symbol = Symbol()
thisArgs[symbol] = this
let fn = thisArgs[symbol](...args)
delete thisArgs[symbol]
return fn
}
// 不通过symbol、定义属性不可枚举
Function.prototype.myApply1 = function(thisArgs, args){
thisArgs = (thisArgs===null || thisArgs ===undefined)?window:Object(thisArgs)
Object.defineProperty(thisArgs, "temp",{
configurable:true,
Enumerable:false,
value:this
})
let fn = thisArgs["temp"](...args)
delete thisArgs["temp"]
return fn
}
// 不通过symbol且不定义属性不可枚举
Function.prototype.myApply2 = function(thisArgs, args){
thisArgs = (thisArgs===null || thisArgs ===undefined)?window:Object(thisArgs)
thisArgs.temp = this
let fn = thisArgs.temp(...args)
delete thisArgs.temp
return fn
}
  • 首先要排除thisArgs为null或undefined的情况,同时对数字或字符串包装为对象(Object(object)还是object)
  • 方法添加到Function的原型上,使得所有函数都可以直接调用
  • 使用symbol是因为Symbol()可以创建出一个独一无二的值,那么肯定不会覆盖掉原来对象中可能存在的值,同时Symbol属性不可以通过简单的属性获取得到,在后续的逻辑编写中不会出现错误
  • 如果不适用Symbol,则需要通过Object.defineProperty使其不可枚举Object.defineProperty

测试:

1
2
3
4
5
6
7
8
let bar = function(name,age){
console.log(this, Object.keys(this),name,age)
}

bar.myApply({method:"myApply-symbol"},["ouwenwu",18])
bar.myApply1({method:"myApply-没有symbol-不枚举"},["ouwenwu",18])
bar.myApply2({method:"myApply-没有symbol-枚举"},["ouwenwu",18])
bar.apply({method:"apply-js自带"},["ouwenwu",18])

image-20230326222135494

综上:直接使用symbol属性来完成手写

bind的手写实现

根据上面的经验,这里直接用symbol

bind与apply、call的区别主要是返回一个函数

1
2
3
4
5
6
7
Function.prototype.myBind = function(thisArgs,...args){
let symbol = Symbol()
thisArgs[symbol] = this
return function(...args){
return thisArgs[symbol](...args)
}
}

三、es5继承的实现

继承:子类继承父类的函数与属性

注:类具有显示原型、对象具有隐式原型

1.原型链继承

父类的实例化对象作为子类的显示原型——就可以通过原型链去访问得到父类的属性与方法

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
<!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>
<script src="./jquery.js"></script>
<style>
</style>
</head>
<body>
<script>
function Person(name,age){
this.name = name,
this.age = age
}
Person.prototype.eating = function(){
console.log("eating")
}
const person = new Person("oww", 23)
function Student(sno){
this.sno = sno
}
Student.prototype = person
Student.prototype.studying = function(){
console.log("studying")
}
const student = new Student("111")
student.eating()
student.studying()
</script>
</body>
</html>

image-20230405183113993

有一个很大的弊端:某些属性其实是保存在p对象上的:

  • 我们通过直接打印对象是看不到这个属性的
  • 这个属性会被多个对象共享,如果这个对象是一个引用类型,那么就会造成问题——其他子类修改会同时改变这边
  • 不能给Person传递参数(让每个stu有自己的属性),因为这个对象是一次性创建的(没办法定制化)——子类的name、age在student中写死了

2.借用构造函数继承

原型链继承子类中的属性是通过父类实例定义的,没有自己的属性,不能传入值更改,借用构造函数可以解决这个问题

  • 通过apply()和call()方法也可以在新创建的对象上执行父类构造函数