对 React 状态管理的理解及方案对比

news2024/11/18 15:51:42

1、React 状态、通信

React 为什么需要状态管理
React 特点:
  1. 专注 view 层:专注 view 层 的特点决定了它不是一个全能框架,相比 angular 这种全能框架,React 功能较简单,单一。
  2. UI=render(data)UI=render(data),data就是我们说的数据流,render是react提供的纯函数,所以用户界面的展示完全取决于数据层。
  3. state 自上而下流向、Props 只读React是根据state(或者props)去渲染页面的,state 流向是自组件从外到内,从上到下的,而且传递下来的 props 是只读的。
组件内状态管理
React state

通过 state 管理状态,使用 setState 更新状态,从而更新UI

export default class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: 0,
      otherValue: 0
    };
  }

  handleAdd() {
    this.setState((state, props) => ({
    ...state,
      value: ++state.value
    }));
  }

  render() {
    return (
      <div>
        <p>{this.state.value}</p>
        <button onClick={() => this.handleAdd}>Add</button>
      </div>
    );
  }
}

react自身的状态管理方案缺点:即使只更新state中某个值,也需要带上其他值(otherValue),操作繁琐

React Hooks

React16.8中正式增加了hooks。通过hooks管理组件内状态,简单易用可扩展。

动机:

  • 在组件之间复用状态逻辑很难
  • 复杂组件变得难以理解
  • 难以理解的 class
  • React Hooks 的设计目的,就是加强版函数组件,完全不使用"类"来定义组件,就能写出一个全功能的组件。
import React, { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 1)}>Add</button>
    </div>
  );
}

export default Counter;

对比 Class 组件中,使用react自身的状态管理方法,React Hooks使用useState方法在 Function 组件中创建状态变量、更新状态的方法、赋值初始状态。这样就实现了一个拥有自己的状态的 Function 组件。

React 组件通信

react自身的数据流管理方案有三种:

  • 父子组件通信
  • 兄弟组件通信
  • 跨组件通信
父子组件

父传子:通过props为子组件添加属性数据,即可实现父组件向子组件通信

在这里插入图片描述

实现步骤

  1. 父组件提供要传递的数据 - state

  2. 给子组件标签添加属性值为 state中的数据

  3. 子组件中通过 props 接收父组件中传过来的数据

    1. 类组件使用this.props获取props对象
    2. 函数式组件直接通过参数获取props对象
import React from 'react'

// 函数式子组件
function FSon(props) {
  console.log(props)
  return (
    <div>
      子组件1
      {props.msg}
    </div>
  )
}

// 类子组件
class CSon extends React.Component {
  render() {
    return (
      <div>
        子组件2
        {this.props.msg}
      </div>
    )
  }
}
// 父组件
class App extends React.Component {
  state = {
    message: 'this is message'
  }
  render() {
    return (
      <div>
        <div>父组件</div>
        <FSon msg={this.state.message} />
        <CSon msg={this.state.message} />
      </div>
    )
  }
}

export default App

props说明:

  1. props是只读对象(readonly)根据单项数据流的要求,子组件只能读取props中的数据,不能进行修改
  2. props可以传递任意数据数字、字符串、布尔值、数组、对象、函数、JSX

子传父:父组件给子组件传递回调函数,子组件调用

实现步骤

  1. 父组件提供一个回调函数 - 用于接收数据
  2. 将函数作为属性的值,传给子组件
  3. 子组件通过props调用 回调函数
  4. 将子组件中的数据作为参数传递给回调函数

在这里插入图片描述

import React from 'react'

// 子组件
function Son(props) {
  function handleClick() {
    // 调用父组件传递过来的回调函数 并注入参数
    props.changeMsg('this is newMessage')
  }
  return (
    <div>
      {props.msg}
      <button onClick={handleClick}>change</button>
    </div>
  )
}


class App extends React.Component {
  state = {
    message: 'this is message'
  }
  // 提供回调函数
  changeMessage = (newMsg) => {
    console.log('子组件传过来的数据:',newMsg)
    this.setState({
      message: newMsg
    })
  }
  render() {
    return (
      <div>
        <div>父组件</div>
        <Son
          msg={this.state.message}
          // 传递给子组件
          changeMsg={this.changeMessage}
        />
      </div>
    )
  }
}

export default App
兄弟组件通信

兄弟组件:通过状态提升机制,利用共同的父组件实现兄弟通信

在这里插入图片描述

状态提升

状态提升:即把需要通信的 state 提升到两者共同的父组件,实现共享和 Reaction

在这里插入图片描述

实现内容:A,B 组件下的 A1,B1 要实现 state 通信

在这里插入图片描述

状态提升流程:在 A,B 之上增加一个 Container 组件,并把 A1,B1 需要共享的状态提升,定义到 Container,通过 props 传递 state 以及 changeState 的方法。

此方式存在的一个问题是:以后如果有一个 C 组件的 state,与 A 要做通信,就会再添加一个 Container 组件,如果是 A 的 state 要跟 C 共享,更是毁灭性打击,之前提升到 Container 的 state,还要再提升一层。这种无休止的状态提升问题,后期的通信成本非常高,几乎是重写。

实现步骤

  1. 将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态
    • 提供共享状态
    • 提供操作共享状态的方法
  1. 要接收数据状态的子组件通过 props 接收数据
  2. 要传递数据状态的子组件通过props接收方法,调用方法传递数据
import React from 'react'

// 子组件A
function SonA(props) {
  return (
    <div>
      SonA
      {props.msg}
    </div>
  )
}
// 子组件B
function SonB(props) {
  return (
    <div>
      SonB
      <button onClick={() => props.changeMsg('new message')}>changeMsg</button>
    </div>
  )
}

// 父组件
class App extends React.Component {
  // 父组件提供状态数据
  state = {
    message: 'this is message'
  }
  // 父组件提供修改数据的方法
  changeMsg = (newMsg) => {
    this.setState({
      message: newMsg
    })
  }

  render() {
    return (
      <>
        {/* 接收数据的组件 */}
        <SonA msg={this.state.message} />
        {/* 修改数据的组件 */}
        <SonB changeMsg={this.changeMsg} />
      </>
    )
  }
}

export default App
跨组件Context:

跨组件Context:Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法

实现步骤

1- 创建Context对象 导出 Provider 和 Consumer对象

const { Provider, Consumer } = createContext()

2- 使用Provider包裹上层组件提供数据

<Provider value={this.state.message}>
    {/* 根组件 */}
</Provider>

3- 需要用到数据的组件使用Consumer包裹获取数据

<Consumer >
    {value => /* 基于 context 值进行渲染*/}
</Consumer>

完整代码

import React, { createContext }  from 'react'

// 1. 创建Context对象 
const { Provider, Consumer } = createContext()


// 3. 消费数据
function ComC() {
  return (
    <Consumer >
      {value => <div>{value}</div>}
    </Consumer>
  )
}

function ComA() {
  return (
    <ComC/>
  )
}

// 2. 提供数据
class App extends React.Component {
  state = {
    message: 'this is message'
  }
  render() {
    return (
      <Provider value={this.state.message}>
        <div className="app">
          <ComA />
        </div>
      </Provider>
    )
  }
}

export default App

问题:

  • context只是将状态提升至共有的父组件来实现,看似跨组件,实则还是逐级传递来实现组件间通信、状态同步以及状态共享
  • context也是将底部子组件的状态控制交给到了顶级组件,但是顶级组件状态更新的时候一定会触发所有子组件的re-render,那么也会带来损耗。
跨组件通信会产生的问题:
  • 组件臃肿:当组件的业务逻辑非常复杂时,我们会发现代码越写越多,因为我们只能在组件内部去控制数据流,没办法抽离,Model和View都放在了View层,整个组件显得臃肿不堪,难以维护。
  • 状态不可预知,甚至不可回溯: 当数据流混乱时,一个执行动作可能会触发一系列的setState,整个数据流变得不可“监控”
  • 处理异步数据流:react自身并未提供多种处理异步数据流管理的方案,仅用一个setState已经很难满足一些复杂的异步流场景
  • 状态无休止提升,context 不好用
状态管理定义:

简单来说来说,“状态管理” 就是为了解决组件间的 “跨级” 通信。

  • 一个组件需要和另一个组件共享状态
  • 一个组件需要改变另一个组件的状态

2、Flux —> Redux —> Mobx

Flux架构模式的诞生

Flux是一套架构模式,而不是代码框架。

【单向数据流】:Action -> Dispatcher -> Store -> View页面交互数据流,如用户点击按钮:View -> Create Action -> Dispatcher(由此进入【单向数据流】)

四大核心部分

  • dispatcher:负责两件事,一是分发和处理Action,二是维护Store。
  • Store:数据和逻辑部分。
  • views:页面-React 组件,从 Store 获取状态(数据),绑定事件处理。
  • actions:交互封装为action,提交给Store处理。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Lni0WBa-1687931092907)(C:\Users\changwenqiao\AppData\Roaming\Typora\typora-user-images\image-20230610100446215.png)]

Redux

Redux是进化Flux,它是在Flux架构模式指导下生成的代码框架,也进一步进行架构约束设计

为什么要使用Redux?
  1. 独立于组件,无视组件之间的层级关系,简化通信问题
  2. 单项数据流清晰,易于定位bug
  3. 调试工具配套良好,方便调试
三个原则:

1、数据来源的唯一性:在redux中所有的数据都是存放在store中,store是单一不可变状态树,这样做的目的就是保证数据来源的唯一性。单个状态树也使调试或检查应用程序变得更容易。

2、state只能是只读的状态: state只能是只读的,State 只能通过触发 Action 来更改,在action中可以去取值,但是不能够去改变它,这个时候采取的方式通常是深度拷贝state,并且将其返回给一个变量,然后改变这个变量,最后将值返回出去。而且要去改变数据只能够在的reducer中,reducer是一个描述了对象发生了一个什么样过程的函数过程。 只读状态的好处,确保视图和网络回调都不会直接写入状态。

3、使用纯函数进行改变:reducer的实质其实就是一个纯函数。每次更改总是返回一个新的 State

Redux数据流架构

在这里插入图片描述

  • store:提供了一个全局的store变量,用来存储我们希望从组件内部抽离出去的那些公用的状态;
  • action:提供了一个普通对象,用来描述你想怎么改数据,并且这是唯一的途径;
  • reducer:提供了一个纯函数,根据action的描述更新state
纯Redux实现计数器

核心步骤

  1. 创建reducer函数 在内部定义好action和state的定义关系
  2. 调用Redux的createStore方法传入定义好的reducer函数生成store实例
  3. 通过store实例身上的subscribe方法监控数据是否变化
  4. 点击按钮 通过专门的dispatch函数 提交action对象 实现数据更新
<button id="decrement">-</button>
<span id="count">0</span>
<button id="increment">+</button>

<script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>

<script>
  // 定义reducer函数 
  // 内部主要的工作是根据不同的action 返回不同的state
  function counterReducer (state = { count: 0 }, action) {
    switch (action.type) {
      case 'INCREMENT':
        return { count: state.count + 1 }
      case 'DECREMENT':
        return { count: state.count - 1 }
      default:
        return state
    }
  }
  // 使用reducer函数生成store实例
  const store = Redux.createStore(counterReducer)
  
  // 订阅数据变化
  store.subscribe(() => {
    console.log(store.getState())
    document.getElementById('count').innerText = store.getState().count
    
  })
  // 增
  const inBtn = document.getElementById('increment')
  inBtn.addEventListener('click', () => {
    store.dispatch({
      type: 'INCREMENT'
    })
  })
  // 减
  const dBtn = document.getElementById('decrement')
  dBtn.addEventListener('click', () => {
    store.dispatch({
      type: 'DECREMENT'
    })
  })
</script>
Redux异步处理
import { createSlice } from '@reduxjs/toolkit'
import axios from 'axios'

const channelStore = createSlice({
  name: 'channel',
  initialState: {
    channelList: []
  },
  reducers: {
    setChannelList (state, action) {
      state.channelList = action.payload
    }
  }
})


// 创建异步
const { setChannelList } = channelStore.actions
const url = 'http://geek.itheima.net/v1_0/channels'
// 封装一个函数 在函数中return一个新函数 在新函数中封装异步
// 得到数据之后通过dispatch函数 触发修改
const fetchChannelList = () => {
  return async (dispatch) => {
    const res = await axios.get(url)
    dispatch(setChannelList(res.data.data.channels))
  }
}

export { fetchChannelList }

const channelReducer = channelStore.reducer
export default channelReducer
import { useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { fetchChannelList } from './store/channelStore'

function App () {
  // 使用数据
  const { channelList } = useSelector(state => state.channel)
  useEffect(() => {
    dispatch(fetchChannelList())
  }, [dispatch])

  return (
    <div className="App">
      <ul>
        {channelList.map(task => <li key={task.id}>{task.name}</li>)}
      </ul>
    </div>
  )
}

export default App
什么时候应该使用 Redux?

当需要处理复杂的应用状态,且 React 本身无法满足时. 比如:

  • 需要持久化应用状态, 这样可以从本地存储或服务器返回数据中恢复应用
  • 需要实现撤销重做这些功能
  • 实现跨页面的用户协作
  • 应用状态很复杂时
  • 数据流比较复杂时
  • 许多不相关的组件需要共享和更新状态
  • 外置状态
redux的缺点:
  • 繁重的代码模板:修改一个state可能要动四五个文件;
  • store里状态残留:多组件共用store里某个状态时要注意初始化清空问题;
  • 无脑的发布订阅:每次dispatch一个action都会遍历所有的reducer,重新计算connect,这无疑是一种损耗;
  • 交互频繁时会有卡顿:如果store较大时,且频繁地修改store,会明显看到页面卡顿;
  • 不支持typescript;
Mobx

一个可以和React良好配合的集中状态管理工具,和Redux解决的问题相似,都可以独立组件进行集中状态管理

Mobx 提供了一个类似 Vue 的响应式系统,相对 Redux 来说 Mobx 的架构更容易理解。

Mobx数据流

在这里插入图片描述

响应式数据. 首先使用@observable 将数据转换为‘响应式数据’,类似于 Vue 的 data。这些数据在一些上下文(例如 computed,observer 的包装的 React 组件,reaction)中被访问时可以被收集依赖,当这些数据变动时相关的依赖就会被通知. 响应式数据带来的两个优点是 ① 简化数据操作方式(相比 redux 和 setState); ② 精确的数据绑定,只有数据真正变动时,视图才需要渲染,组件依赖的粒度越小,视图就可以更精细地更新

核心:

动作改变状态,状态的改变会更新所有受影响的视图

基础使用

初始化mobx

初始化步骤

  1. 定义数据状态state
  2. 在构造器中实现数据响应式处理 makeAutoObservble
  3. 定义修改数据的函数action
  4. 实例化store并导出
import { makeAutoObservable } from 'mobx'

class CounterStore {
  count = 0 // 定义数据
  constructor() {
    makeAutoObservable(this)  // 响应式处理
  }
  // 定义修改数据的方法
  addCount = () => {
    this.count++
  }
}

const counter = new CounterStore()
export default counter

React使用store

实现步骤

  1. 在组件中导入counterStore实例对象
  2. 在组件中使用storeStore实例对象中的数据
  3. 通过事件调用修改数据的方法修改store中的数据
  4. 让组件响应数据变化
// 导入counterStore
import counterStore from './store'
// 导入observer方法
import { observer } from 'mobx-react-lite'
function App() {
  return (
    <div className="App">
      <button onClick={() => counterStore.addCount()}>
        {counterStore.count}
      </button>
    </div>
  )
}
// 包裹组件让视图响应数据变化
export default observer(App)

计算属性(衍生状态)

有一些状态根据现有的状态计算(衍生)得到

在这里插入图片描述

实现步骤

  1. 生命一个存在的数据
  2. 通过get关键词 定义计算属性
  3. 在 makeAutoObservable 方法中标记计算属性
import { computed, makeAutoObservable } from 'mobx'

class CounterStore {
  list = [1, 2, 3, 4, 5, 6]
  constructor() {
    makeAutoObservable(this, {
      filterList: computed
    })
  }
  // 修改原数组
  changeList = () => {
    this.list.push(7, 8, 9)
  }
  // 定义计算属性
  get filterList () {
    return this.list.filter(item => item > 4)
  }
}

const counter = new CounterStore()

export default counter
// 导入counterStore
import counterStore from './store'
// 导入observer方法
import { observer } from 'mobx-react-lite'
function App() {
  return (
    <div className="App">
      {/* 原数组 */}
      {JSON.stringify(counterStore.list)}
      {/* 计算属性 */}
      {JSON.stringify(counterStore.filterList)}
      <button onClick={() => counterStore.changeList()}>change list</button>
    </div>
  )
}
// 包裹组件让视图响应数据变化
export default observer(App)

异步数据处理

实现步骤:

  1. 在mobx中编写异步请求方法 获取数据 存入state中
  2. 组件中通过 useEffect + 空依赖 触发action函数的执行
// 异步的获取

import { makeAutoObservable } from 'mobx'
import axios from 'axios'

class ChannelStore {
  channelList = []
  constructor() {
    makeAutoObservable(this)
  }
  // 只要调用这个方法 就可以从后端拿到数据并且存入channelList
  setChannelList = async () => {
    const res = await axios.get('http://geek.itheima.net/v1_0/channels')
    this.channelList = res.data.data.channels
  }
}
const channlStore = new ChannelStore()
export default channlStore
import { useEffect } from 'react'
import { useStore } from './store'
import { observer } from 'mobx-react-lite'
function App() {
  const { channlStore } = useStore()
  // 1. 使用数据渲染组件
  // 2. 触发action函数发送异步请求
  useEffect(() => {
    channlStore.setChannelList()
  }, [])
  return (
    <ul>
      {channlStore.channelList.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  )
}
// 让组件可以响应数据的变化[也就是数据一变组件重新渲染]
export default observer(App)

模块化

在这里插入图片描述

实现步骤

  1. 拆分模块js文件,每个模块中定义自己独立的state/action
  2. 在store/index.js中导入拆分之后的模块,进行模块组合
  3. 利用React的context的机制导出统一的useStore方法,给业务组件使用

1- 定义task模块

import { makeAutoObservable } from 'mobx'

class TaskStore {
  taskList = []
  constructor() {
    makeAutoObservable(this)
  }
  addTask () {
    this.taskList.push('vue', 'react')
  }
}

const task = new TaskStore()


export default task

2- 定义counterStore

import { makeAutoObservable } from 'mobx'

class CounterStore {
  count = 0
  list = [1, 2, 3, 4, 5, 6]
  constructor() {
    makeAutoObservable(this)
  }
  addCount = () => {
    this.count++
  }
  changeList = () => {
    this.list.push(7, 8, 9)
  }
  get filterList () {
    return this.list.filter(item => item > 4)
  }
}

const counter = new CounterStore()

export default counter

3- 组合模块导出统一方法

import React from 'react'

import counter from './counterStore'
import task from './taskStore'


class RootStore {
  constructor() {
    this.counterStore = counter
    this.taskStore = task
  }
}


const rootStore = new RootStore()

// context机制的数据查找链  Provider如果找不到 就找createContext方法执行时传入的参数
const context = React.createContext(rootStore)

const useStore = () => React.useContext(context)
// useStore() =>  rootStore  { counterStore, taskStore }

export { useStore }

4- 组件使用模块中的数据

import { observer } from 'mobx-react-lite'
// 导入方法
import { useStore } from './store'
function App() {
  // 得到store
  const store = useStore()
  return (
    <div className="App">
      <button onClick={() => store.counterStore.addCount()}>
        {store.counterStore.count}
      </button>
    </div>
  )
}
// 包裹组件让视图响应数据变化
export default observer(App)
mobx的缺陷
  • 没有状态回溯能力:mobx是直接修改对象引用,所以很难去做状态回溯;(这点redux的优势就瞬间体现出来了)
  • 没有中间件:和redux一样,mobx也没有很好地办法处理异步数据流,没办法更精细地去控制数据流动
  • store太多:随着store数的增多,维护成本也会增加,而且多store之间的数据共享以及相互引用也会容易出错
  • 副作用:mobx直接修改数据,和函数式编程模式强调的纯函数相反,这也导致了数据的很多未知性
Mobx和Redux的对比

redux

  • redux将数据保存在单一的store中
  • redux使用plain object保存数据,需要手动处理变化后的操作
  • redux使用的是不可变状态,意味着状态只是只读的,不能直接去修改它,而是应该返回一个新的状态,同时使用纯函数
  • redux会比较复杂,因为其中的函数式编程思想掌握起来不是那么容易,同时需要借助一系列的中间件来处理异步和副作用
  • 数据流:dispatch(action) - > 在store中调用reducer,将当前state和收到的action做对比,计算出新的state -> state发生变化,store监听到变化 -> 出发重新渲染

mobx

  • mobx将数据保存在多个独立的store中,按模块应用划分
  • mobx使用observable保存数据,数据变化后自动处理响应的操作。
  • mobx中的状态是可变的,可以直接对其进行修改
  • mobx相对来说比较简单,在其中有很多的抽象,mobx使用的更多的是面向对象的思维
  • 数据流:action -> 修改state -> 触发变化

总结

  • 如果是小型的项目且没有多少状态需要共享,那么不需要状态管理,react 本身的 props 或者 context 就能实现需求
  • 如果需要手动控制状态的更新,单向数据流是合适的选择,例如:redux
  • 如果需要简单的自动更新,双向绑定的状态管理是不二之选,例如:mobx
  • 在小型项目或者少量开发人员的项目中,可以采用 MobX,效率会更高一点。
  • 大型项目或者多人协助的项目,考虑采用 Redux,后续维护成本更低。
  • 如果是两个或多个组件之间简单的数据共享,那么原子化或许是合适的选择:,例如:jotai,recoil
  • 如果状态有复杂数据流的处理,请用 rxjs
  • 如果管理的是复杂的业务状态,那么可以使用有限状态机做状态的跳转管理,例如:xstate
  • 如果有在非react上下文订阅、操作状态的需求,那么 jotai、recoil 等工具不是好的选择。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/694757.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Keras-深度学习-神经网络-人脸识别模型

目录 模型搭建 模型训练 模型搭建 ①导入所需的库&#xff0c;导入了 Keras 和其他必要的库&#xff0c;用于构建和处理图像数据。 from keras.models import Sequential from keras.layers import Dense, Flatten, Conv2D, MaxPooling2D import os from PIL import Image …

《C++高级编程》读书笔记(十二:利用模板编写泛型代码)

1、参考引用 C高级编程&#xff08;第4版&#xff0c;C17标准&#xff09;马克葛瑞格尔 2、建议先看《21天学通C》 这本书入门&#xff0c;笔记链接如下 21天学通C读书笔记&#xff08;文章链接汇总&#xff09; 1. 模板概述 模板将参数化的概念推进了一步&#xff0c;不仅允许…

用Python搭建监控平台详解

概要 监控和运维&#xff0c;是互联网工业链上非常重要的一环。监控的目的就是防患于未然。通过监控&#xff0c;我们能够及时了解到企业网络的运行状态。一旦出现安全隐患&#xff0c;你就可以及时预警&#xff0c;或者是以其他方式通知运维人员&#xff0c;让运维监控人员有时…

oracle 自定义类型(type)的用法

emp表数据如下所示 定义object类型 create or replace type typeof_userinfo_row as object(user_id varchar2(50),user_name varchar2(50) )创建函数并将此类型作为返回值类型 create or replace function FUN_TEST return typeof_userinfo_row isFunctionResult typeof_use…

互联网业务全球化互通组网

随着互联网业务的快速发展&#xff0c;越来越多的企业开始全球化扩张业务&#xff0c;并需要在全球范围内建立互联网组网以实现业务数据的高效传输。在这个过程中&#xff0c;如何建立高效、稳定的全球互联网组网方案&#xff0c;是每个企业都需要考虑的问题。 一种可行的方案…

springboot 上传文件

在Spring Boot中&#xff0c;可以使用RequestParam注解来接收文件。 在你的控制器方法中&#xff0c;使用RequestParam注解来声明一个MultipartFile类型的参数来接收上传的文件 MultipartFile是spring类型&#xff0c;代表HTML中form data方式上传的文件&#xff0c;包含二进制…

【C语言操作符优先级】

C语言操作符优先级 C语言操作符1、操作符的优先级序表2、操作符的属性2.1、操作符优先级运算例程12.2、操作符优先级运算例程2 C语言操作符 前言&#xff1a; &#xff08;1&#xff09;在我们熟悉的数学加减乘除运算中&#xff0c;都知道先乘除再加减&#xff0c;有括号的先算…

Error: Cannot find module ‘webpack‘ 问题解决办法

这句话的意思是&#xff1a;没有找到webpack模块。 就算之前你装了webpack&#xff0c;那肯定是非全局安装 所以要全局安装 npm install --save-dev webpack 问题解决

数据库管理-第八十六期 19c OCM之路-第一堂(01)(20230628)

数据库管理 2023-06-28 第八十六期 19c OCM之路-第一堂&#xff08;01&#xff09;1 环境补充说明2 第一堂-01考点1&#xff1a;Create a pluggable database 创建PDB考点2&#xff1a;Create tablespace 创建表空间考点3&#xff1a;Managing undo 管理undo 总结 第八十六期 1…

【无名管道】无名管道(Pipe)与进程通信:快速上手

目录 0. 管道概述&#xff1a; 1. 管道特点 2. 管道创建&#xff1a;pipe函数 3. 管道的读写特点 4. 通过fcntl函数设置文件的阻塞特性 5. 查看管道缓冲区命令 总结&#xff1a; 0. 管道概述&#xff1a; 管道也叫无名管道&#xff0c;它是是 UNIX 系统 IPC&#xff08;…

019、数据库管理之备份恢复管理(BR)

备份的重要性 数据库恢复审计和分析典型DBA任务 备份的类型 热备&#xff0c;允许应用程序完全访问数据。冷备&#xff0c;不允许应用程序访问年数据温备&#xff0c;允许应用程序读取&#xff0c;但不能修改 热备份 热备份是在读取和修改数据时进行的&#xff0c;几乎不会…

怎么高效批量调整视频的尺寸大小

大家平时在剪辑视频的过程中&#xff0c;是否会遇到视频尺寸大小不合适的情况&#xff0c;当我们遇到这种情况时&#xff0c;如何才能快速批量地解决呢&#xff1f;有没有什么方法可以快速批量修改视频尺寸大小呢&#xff1f;跟着小编一起来看看我平时批量修改视频尺寸的方法。…

联邦学习的架构思想

目录 联邦学习介绍&#xff08;非常详细&#xff09; 联邦学习的由来 联邦学习的发展历程 1) 机器学习 2) 分布式机器学习 3) 隐私保护技术 4) 联邦学习 联邦学习的规范与标准 联邦学习的架构思想 联邦学习的社区与生态 联邦学习介绍&#xff08;非常详细&#xff09…

Lenovo联想笔记本电脑 小新 Air-14 2020 Intel平台IIL版(81YJ)原装Win10系统恢复原厂OEM预装专用系统

Lenovo联想笔记本电脑&#xff0c;小新 Air-14 2020 Intel平台IIL版(81YJ)出厂Windows10系统原装系统镜像&#xff0c;恢复原厂状态 系统自带所有驱动、出厂主题壁纸LOGO、Office办公软件、联想电脑管家等预装程序 所需要工具&#xff1a;16G或以上的U盘 文件格式&#xff1…

基于微信小程序的上课签到系统(数据库+报告+文档+演示视频)

系统技术语言介绍&#xff1a; 本系统采用了SSM (Spring Spring MVC Mybatis&#xff09;架构&#xff0c; MySQL作为基础数据库&#xff0c;微信开发工具作为前端基础&#xff0c;前端采用了 wxml的设计语言&#xff0c;Idea作为后台的开发工具。 功能列表&#xff1a; 1.…

从数字图像到音视频学习:我的学习之旅

数字图像是一门广泛应用于计算机视觉、图像处理和计算机图形学等领域的学科&#xff0c;而音视频学习则涵盖了音频和视频的处理、分析和应用。 如果你最开始接触数字图像&#xff0c;可能会学习一些基本概念&#xff0c;例如像素、分辨率、色彩空间和图像处理算法等。这可能涉…

java项目之药源购物网站ssm源码+文档

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的药源购物网站。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xff1a;风歌&#xff…

隔离变压器和浪涌保护器SPD有哪些不同作用

浪涌保护装置&#xff08;SPD&#xff09;和隔离变压器的作用经常混淆。为了保护敏感的电子设备和设备免受瞬态事件的影响&#xff0c;必须考虑各种因素。 无论瞬态来自外部还是内部&#xff0c;信号噪声、共模瞬变、差模瞬变和接地电位上升都可能导致保护不力的设备出现问题。…

vue中使用mock.js

安装mock npm install mockjs --save-dev或者在ui中选择依赖 查看安装是否成功 进入package.json文件 配置mock 在src目录下新建mock文件夹&#xff0c;在mock文件夹下建立index.js 配置模拟请求数据 index.js import Mock from mockjsconst loginData Mock.mock(http://l…

【 云原生 kubernetes 】- 单点登录Authelia + OpenLdap

文章目录 简介AutheliaOpenLdap属性介绍 Helm部署openldap配置调整数据持久化部署使用命令行Web Authelia 页面访问反向代理进行集成流程 ⚡️: OpenLDAP是轻量级目录访问协议&#xff08;LDAP&#xff09;的开源实现&#xff0c;它提供了一种存储和访问关于用户、组、计算机和…