【react】Redux的设计思想与工作原理
Redux 的设计理念
Redux 的设计采用了 Facebook 提出的 Flux 数据处理理念
在 Flux 中通过建立一个公共集中数据仓库 Store 进行管理,整体分成四个部分即: View (视图层)、Action (动作)、Dispatcher (派发器)、Store (数据层)
如下图所示,当我们想要修改仓库的数据时,需要从 View 中触发 Action,由 Dispatcher 派发到 Store 修改数据,从而驱动视图更新
这种设计的好处在于其数据流向是单一的,数据的修改一定是会经过 Action、Dispatcher 等动作才能实现,方便预测、维护状态的流向。
当我们了解了 Flux 的设计理念后,便可以照葫芦画瓢了。
如下图所示,在 Redux 中同样需要维护一个公共数据仓库 Store, 而数据流向只能通过 View 触发 Action、 Reducer更新派发, Store 改变从而驱动视图更新
工作原理
当我们了解了 Redux 的设计理念后,趁热打铁炫一波 Redux 的工作原理,我们知道使用 Redux 进行状态管理的第一步就是需要先创建数据仓库 Store, 也就会需要调用 createStore 方法。那我们就先拿 createStore 开炫。
createStore
从 Redux 源码中我们不难看出,createStore 接收 reducer、初始化state、中间件三个参数,当执行 createStore 时会记录当前的 state 状态,并返回 store 对象,包含 dispatch、subscribe、getState 等属性。
其中
- dispatch: 用来触发 Action
- subscribe: 当 store 值的改变将触发 subscribe 的回调
- getState: 用来获取当前的 state 状态。
getState 比较简单,直接返回当前的 state 状态,接下来我们将着重了解 dispatch 与 subscribe 的实现。
function createStore(reducer, preloadedState, enhancer) {let currentReducer = reducer // 记录当前的 reducerlet currentState = preloadedState // 记录当前的 statelet isDispatching = false // 是否正在进行 dispatchfunction getState() {return currentState // 通过 getState 获取当前的 state}// 触发 actionfunction dispatch(action: A) {}function subscribe(listener: () => void) {}// 初始化 statedispatch({ type: ActionTypes.INIT } as A)// 返回一个 sttoreconst store = {dispatch: dispatch as Dispatch<A>,subscribe,getState}return store
}
dispatch
在 Redux 中, 修改数据的唯一方式就是通过 dispatch,而 dispatch 接受一个 action 对象作为参数,执行 dispatch 方法,将生成新的 state,并触发监听事件。
function dispatch(action) {// 如果已经在触发中,则不允许再次出发 dispatch (禁止套娃)// 例如:在 reducer 中触发 dispatchif (isDispatching) {throw new Error('Reducers may not dispatch actions.')}try {// 上锁isDispatching = true// 调用 reducer,获取新的 statecurrentState = currentReducer(currentState, action)} finally {isDispatching = false}// 触发订阅事件const listeners = (currentListeners = nextListeners)listeners.forEach(listener => {listener()})return action}
subscribe
在 Redux 中, 可以通过 subscribe 方法来订阅 store 的变化, 一旦 store 发生了变化, 就会执行订阅的回调函数
可以看到 subscribe 方法接收一个回调函数作为参数, 执行 subscribe 方法将会返回一个 unsubscribe 函数, 用于取消订阅
function subscribe(listener: () => void) {if (isDispatching) {throw new Error()}let isSubscribed = true // 防止调用多次 unsubscribeensureCanMutateNextListeners() // 确保 nextListeners 是 currentListeners 的快照,而不是同一个引用const listenerId = listenerIdCounter++nextListeners.set(listenerId, listener) //nextListeners 添加订阅事件// 取消订阅事件return function unsubscribe() {if (!isSubscribed) {return}if (isDispatching) {throw new Error()}isSubscribed = falseensureCanMutateNextListeners(); // 如果某个订阅事件执行了 unsubscribe, nextListeners 创建了新的内存地址,而原先的listeners 依然保持不变 (dispatch 方法中的312 行)nextListeners.delete(listenerId)currentListeners = null}}
ensureCanMutateNextListeners 与 currentListeners 的作用
承接上文,在 subscribe 中不管是注册监听还是取消监听都会调用 ensureCanMutateNextListeners 的方法,那么这个方法是做什么的呢?
从函数的逻辑上不难得出答案:
ensureCanMutateNextListeners 确保 nextListeners 是 currentListeners 的快照,而不是同一个引用
function ensureCanMutateNextListeners() {if (nextListeners === currentListeners) { // currentListeners 用来确保循环的稳定性nextListeners = new Map()currentListeners.forEach((listener, key) => {nextListeners.set(key, listener)})}
}
在 dispatch 或者 subscribe 函数中,都是通过 nextListeners 触发监听,那为何还需要使用 currentListeners?
这里就不卖关子了,这里的 currentListeners 用于确保在 dispatch 中 listener 的数量不会发生变化, 确保当前循环的稳定性。
请看下面的例子👇
const a = store.subscribe(() => {/* a */
});
const b = store.subscribe(() => a());
const c = store.subscribe(() => {/*/ c */
});
store.dispatch(action);
上面的代码在 Redux 中是被允许的, 通过 subscribe 注册监听函数 a、b、c,此时 nextListeners 指向 [a, b, c]
当执行 dispatch 时, listener、currentListeners、nextListeners 将指向地址 [a, b, c];
// dispatch 触发监听事件的逻辑
// 触发订阅事件
const listeners = (currentListeners = nextListeners)
listeners.forEach(listener => { listener() })
当执行到 b 监听函数时,将解绑 a 函数的监听事件,如果直接修改 nextListeners, 在循环中操作数组是非常危险的事情, 因此借助 ensureCanMutateNextListeners、currentListeners 为 nextListeners 开辟了新的内存地址,对 nextListeners 的操作将不影响 listener。
实现一个 mini react-redux
上文我们说到,一个组件如果想从 store 存取公用状态,需要进行四步操作:
- import引入store
- getState获取状态
- dispatch修改状态
- subscribe订阅更新
代码相对冗余,我们想要合并一些重复的操作,而 react-redux 就提供了一种合并操作的方案:react-redux提供 Provider
和 connect
两个API, Provider 将 store 放进 this.context 里,省去了 import 这一步, connect将 getState、dispatch 合并进了this.props,并自动订阅更新,简化了另外三步,下面我们来看一下如何实现这两个API:
Provider
Provider
组件比较简单,接收 store 并放进全局的 context
对象,使 store
可用于任何需要访问 Redux store 的嵌套组件
import React, { createContext } from 'react';
let StoreContext;
const Provider = (props) => {StoreContext = createContext(props.store);return <StoreContext.Provider value={props.store}>{ props.children }</StoreContext.Provider>
}
connect
下面我们来思考一下如何实现 connect
,我们先回顾一下connect的使用方法
connect(mapStateToProps, mapDispatchToProps)(App)
connect 接收 mapStateToProps、mapDispatchToProps 两个函数,然后返回一个高阶函数, 最终将 mapStateToProps、mapDispatchToProps 函数的返回值通过 props 形式传递给 App 组件
我们直接放出connect的实现代码,并不复杂:
import React, { createContext, useContext, useEffect } from 'react';
export function connect(mapStateToProps, mapDispatchToProps) {return function (Component) {const connectComponent: React.FC = (props) => {const store = useContext(StoreContext);const [, updateState] = useState();const forceUpdate = useCallback(() => updateState({}), []);const handleStoreChange = () => {// 强制刷新forceUpdate();}useEffect(() => {store.subscribe(handleStoreChange)}, [])return (<Component// 传入该组件的props,需要由connect这个高阶组件原样传回原组件 { ...(props) }// 根据 mapStateToProps 把 state 挂到 this.props 上 { ...(mapStateToProps(store.getState())) }// 根据mapDispatchToProps把dispatch(action)挂到this.props上{ ...(mapDispatchToProps(store.dispatch)) }/>)}return connectComponent;}
}
可以看出 connect 通过 useContext 实现和 store 的链接,将 state 作为第一个参数传给 mapStateToProps、将 dispatch 作为第一个参数传递给 mapDispatchToProps,最终将结果通过 props 形式传递给子组件。
其实 connect 这种设计,是装饰器模式的实现,所谓装饰器模式,简单地说就是对类的一个包装,动态地拓展类的功能。这里的 connect 以及 React 中的高阶组件(HoC)都是这一模式的实现。
对类的装饰常用于拓展类的功能,对类中函数的装饰常用于 AOP 切面
@decorator
class A {}// 等同于class A {}
A = decorator(A) || A;
装饰器只能用于类和类的方法,不能用于函数,因为存在函数提升。 如果一定要装饰函数,可以使用高阶函数
mini react-redux
通过上文,我们了解了 Provider 与 connect 的实现,我们可以写个 mini react-redux 来测试一下
1 创建如下目录结构
2 实现 createStore 函数 创建一个 createStore.ts 文件,createStore 最终将返回 store 对象,包含 getState、dispatch、subscribe
export const createStore = (reducer: Function) => {let currentState: undefined = undefined;const obervers: Array<Function> = [];function getState() {return currentState;}function dispatch(action: { type: string}) {currentState = reducer(currentState, action);obervers.forEach(fn => fn());}function subscribe(fn: Function) {obervers.push(fn);}dispatch({ type: '@@REDUX/INIT' }); // 初始化 statereturn {getState,dispatch,subscribe}
}
3 实现 reducer
createStore 函数接收一个 reducer 方法,reducer 常用来分发 action, 并返回新的 state
// reducer.ts
const initialState = {count: 0
}export function reducer(state = initialState, action: { type: string}) {switch (action.type) {case 'add': return {...state,count: state.count + 1}case 'reduce':return {...state,count: state.count - 1}default:return initialState;}
}
4 实现 Provider 与 connect
/* eslint-disable react-hooks/rules-of-hooks */
//@ts-nocheck
import React, { createContext, useContext, useEffect } from 'react';let StoreContext;
const Provider = (props) => {StoreContext = createContext(props.store);return <StoreContext.Provider value={props.store}>{ props.children }</StoreContext.Provider>
}
export default Provider;
export function connect(mapStateToProps, mapDispatchToProps) {return function (Component) {const connectComponent: React.FC = (props) => { const store = useContext(StoreContext); const [, updateState] = React.useState(); const forceUpdate = React.useCallback(() => updateState({}), []);const handleStoreChange = () => {// 强制刷新forceUpdate();}useEffect(() => {store.subscribe(handleStoreChange)}, [])return (<Component// 传入该组件的props,需要由connect这个高阶组件原样传回原组件 { ...(props) }// 根据 mapStateToProps 把 state 挂到 this.props 上 { ...(mapStateToProps(store.getState())) }// 根据mapDispatchToProps把dispatch(action)挂到this.props上 { ...(mapDispatchToProps(store.dispatch)) }/>)}return connectComponent;}
}
5 修改 main.tsx
// main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import Provider from './react-redux/index.tsx';
import { createStore } from './react-redux/createStore.ts';
import { reducer } from './react-redux/reducer.ts';ReactDOM.createRoot(document.getElementById('root')!).render(<React.StrictMode><Provider store={createStore(reducer)}><App /></Provider></React.StrictMode>,
)
6 修改 App.tsx
// App.tsx
import { useState } from 'react';
import { connect } from './react-redux';const addAction = {type: 'add'
}const mapStateToProps = (state: { count: number }) => {return {count: state.count}
}const mapDispatchToProps = (dispatch: any) => {return {addCount: () => {dispatch(addAction)}}
}interface Props {count: number;addCount: () => void;
}function App(props: Props): JSX.Element {const { count, addCount } = props;return (<div className="App"> { count } <button onClick={ () => addCount() }>增加</button> </div>);
}export default connect(mapStateToProps, mapDispatchToProps)(App);
运行项目,点击增加按钮,如能正确计数,我们整个redux、react-redux的流程就走通了。
中间件
在大部分场景下, 我们需要自定义 dispatch 的行为, 在 Redux 中, 我们可以使用 中间件来拓展 dispatch 的功能
类似于 Express 或者 Koa, 在这些框架中,我们可以使用中间件来拓展 请求 和 响应 之间的功能
而 Redux 中间件的作用是在 action 发出之后, 到达 reducer 之前, 执行一系列的任务
在 Redux 中我们可以通过 applyMiddleware 生成一个强化器 enhancer 作为 createStore 的第二个参数传递。
import { createStore, applyMiddleware } from 'redux'
import rootReducer from './reducer'
import { print1, print2, print3 } from './exampleAddons/middleware' const middlewareEnhancer = applyMiddleware(print1, print2, print3) // Pass enhancer as the second arg, since there's no preloadedState
const store = createStore(rootReducer, middlewareEnhancer) export default store
正如它们的名称所示,每个中间件在调度操作时都会打印一个数字
import store from './store' store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about actions' })
// log: '1'
// log: '2'
// log: '3'
在这个例子中,当触发 dispatch 的内部执行顺序如下:
- The
print1
middleware (which we see asstore.dispatch
) - The
print2
middleware - The
print3
middleware - The original
store.dispatch
- The root reducer inside
store
实现一个中间件
从上文得知, 我们了解了如何使用中间件, 接下来我们将实现一个中间件。
在 Redux 中,中间件其实是由三个嵌套函数组成
function exampleMiddleware(storeAPI) { return function wrapDispatch(next) { return function handleAction(action) { // Do anything here: pass the action onwards with next(action), // or restart the pipeline with storeAPI.dispatch(action) // Can also use storeAPI.getState() here return next(action) } }
}
最外层函数 exampleMiddleware 将会被 applyMiddleware 调用,并传入 storeAPI 对象( 形如 {dispatch, getState} ),
const loggerMiddleware = storeAPI => next => action => { console.log('dispatching', action) let result = next(action) //调用下一个中间件或最终的dispatchconsole.log('next state', storeAPI.getState()) return result
}
在这个中间件中:
storeAPI
是传递给中间件的 store 对象,包含dispatch
和getState
方法。next
是指向下一个中间件的函数,如果没有更多中间件,则指向原始的dispatch
方法。action
是被分发的 action 对象。
写完 logger 中间件后,我们尝试在 Redux 中使用,如下
import { createStore, applyMiddleware } from "redux";const initialState = {count: 0
}function reducer(state = initialState, action: { type: string}) {switch (action.type) {case 'add': return {...state,count: state.count + 1}case 'reduce':return {...state,count: state.count - 1}default:return initialState;}
}
const logger1 = storeAPI => next => action => { console.log('logger1 开始');const result = next(action) console.log('logger1 结束');return result
}const logger2 = storeAPI => next => action => { console.log('logger2 开始');const result = next(action) console.log('logger2 结束');return result
}const logger3 = storeAPI => next => action => { console.log('logger3 开始');const result = next(action) console.log('logger3 结束');return result
}
const middlewares = applyMiddleware(logger1, logger2, logger3);
const store = createStore(reducer, middlewares);
store.dispatch({ type: 'add' });
最终将打印
从打印的记过来看,如果之前有接触过 Express 或者 Koa 的同学,应该可以很快发现,这个是一个洋葱模型
示例2:
在创建store时,使用applyMiddleware方法将中间件应用到store上
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './reducers'; // 假设您有一个根 reducer
import loggerMiddleware from './loggerMiddleware';
// 导入您实现的 logger 中间件const store = createStore(rootReducer,applyMiddleware(loggerMiddleware)
);
applyMiddleware 的实现原理
从上可知,Redux 提供了一个 applyMiddleware 方法用于将中间件拓展到 dispatch 上
具体是如何拓展的呢?
从源码我们不难看出,最终是通过 compose 也就是利用 reduce 方法,将下一个的中间件函数作为参数,在上一个中间件的函数体内执行。
注意这里传入 compose 内的每一个函数都是一个双层嵌套函数。
applyMiddleware
是一个高阶函数,它接收一个或多个中间件作为参数,并返回一个函数。这个函数接收 createStore
的参数(reducer、初始 state 和可选的 store 增强器),并返回一个新的 createStore
函数。新的 createStore
函数创建的 store 有一个增强的 dispatch
方法,该方法能够依次通过所有中间件。
// applyMiddleware 源码
export default function applyMiddleware(...middlewares
) {// 返回一个接收 createStore为入参的函数return createStore => (reducer, preloadedState) => {// 创建 storeconst store = createStore(reducer, preloadedState)let dispatch: Dispatch = () => {throw new Error('Dispatching while constructing your middleware is not allowed. ' +'Other middleware would not be applied to this dispatch.')}/*** middleware 形如:* ({dispatch, getState}) => next => action => { ... return next(action) }*/const middlewareAPI: MiddlewareAPI = {getState: store.getState,dispatch: (action, ...args) => dispatch(action, ...args)}const chain = middlewares.map(middleware => middleware(middlewareAPI))dispatch = compose(...chain)(store.dispatch)return {...store,dispatch}}
}
compose
函数用于将多个中间件函数组合成一个。它使用reduce
方法将中间件函数串联起来,形成一个洋葱模型。applyMiddleware
函数接收中间件数组,并返回一个函数。这个函数接收createStore
函数和它的参数(reducer、初始 state 和可选的增强器),并返回一个新的createStore
函数。- 新的
createStore
函数创建的 store 有一个增强的dispatch
方法,该方法通过所有中间件。
### 中间件在大部分场景下,我们需要自定义 `dispatch` 的行为。
在 Redux 中,我们可以使用中间件来拓展 `dispatch` 的功能。中间件的作用是在 action 发出之后,到达 reducer 之前,执行一系列的任务。
类似于 Express 或 Koa,在这些框架中,我们可以使用中间件来拓展请求和响应之间的功能。#### 实现一个 Logger 中间件```typescript
const loggerMiddleware = storeAPI => next => action => {console.log('Dispatching:', action);let result = next(action); // 调用下一个中间件或最终的 dispatchconsole.log('Next State:', storeAPI.getState());return result;
};
function compose(...funcs) {if (funcs.length === 0) {// infer the argument type so it is usable in inference down the linereturn (arg:) => arg}if (funcs.length === 1) {return funcs[0]}return funcs.reduce((a, b) =>(...args) =>a(b(...args)))
}
模拟洋葱模型
洋葱模型形象地描述了Redux中间件的工作流程。
想象一下,每个中间件都像洋葱的一层皮,action就像一颗子弹,从外向内穿过每一层中间件,然后再由内向外穿出。
在每层中间件中,都可以对action进行处理或修改,然后再传递给下一层。
工作流程
1、action的传递:
当一个action被dispatch时,它会首先进入最外层的中间件。
在这个中间件中,可以对action进行处理,然后调用next(action)
将action传递给下一层中间件。2、逐层处理:
action会依次穿过每一层中间件,每层中间件都可以对action进行处理。
处理完毕后,再调用next(action)
将action传递给下一层。
3、到达reducer:
当action穿过所有中间件后,最终会到达reducer。
Reducer根据action的类型和当前的状态来计算出一个新的状态。
4、逐层返回:
在action被reducer处理后,结果会由内向外逐层返回给每一层中间件。
在每层中间件中,可以对返回的结果进行进一步的处理或修改。
5、最终返回:
当action从最内层中间件返回时,整个中间件链的处理流程结束。
最终的结果会被返回给dispatch函数,完成整个action的处理流程。
// 定义三个中间件
const middleware1 = store => next => action => {console.log('中间件1之前');const result = next(action); // 调用下一个中间件或reducerconsole.log('中间件1之后');return result;
};const middleware2 = store => next => action => {console.log('中间件2之前');const result = next(action); // 调用下一个中间件或reducerconsole.log('中间件2之后');return result;
};const middleware3 = store => next => action => {console.log('中间件3之前');const result = next(action); // 调用reducer(因为没有下一个中间件了)console.log('中间件3之后');return result;
};// 应用中间件到Redux store
const store = createStore(reducer,applyMiddleware(middleware1, middleware2, middleware3)
);// 派发一个action
store.dispatch({ type: 'SOME_ACTION' });
输出结果
中间件1之前
中间件2之前
中间件3之前
// reducer的处理结果(假设有日志输出)
中间件3之后
中间件2之后
中间件1之后
拓展
Redux洋葱模型和Koa洋葱模型的区别
Redux洋葱模型
1、概念:
Redux洋葱模型描述了中间件如何围绕action和reducer进行工作。
每个中间件都像洋葱的一层皮,action从外向内穿过每一层中间件,然后再由内向外穿出。
2、执行流程:
当一个action被dispatch时,它会首先进入最外层的中间件。
中间件可以对action进行处理或修改,然后调用next(action)
将action传递给下一层中间件。
最终,action会到达reducer进行处理,然后结果会由内向外逐层返回给每一层中间件。
3、应用场景:
Redux中间件常用于异步操作、日志记录、错误报告等功能。
例如,redux-thunk、redux-saga等中间件允许在Redux中进行异步操作。
Koa洋葱模型
1、概念:
Koa洋葱模型描述了中间件如何围绕HTTP请求和响应进行工作。
每个中间件都像洋葱的一层皮,请求从外向内穿过每一层中间件,响应则由内向外穿出。
2、执行流程:
当一个HTTP请求到达时,它会首先进入最外层的中间件。
中间件可以对请求进行处理或修改,然后调用next()
将控制权传递给下一个中间件。
一旦所有的中间件都通过next()
调用完成入栈阶段的执行,控制流开始回溯,即从最内部的中间件开始向外部返回响应。
3、应用场景:
Koa中间件常用于日志记录、错误处理、身份验证、数据解析等HTTP请求和响应相关的功能。
Koa的中间件机制允许开发者在请求和响应之间添加额外的逻辑,从而实现对HTTP请求和响应的灵活处理。
区别总结
1、应用场景不同:
Redux洋葱模型主要应用于Redux状态管理库中的中间件处理,特别是与异步操作和日志记录等相关的功能。
Koa洋葱模型则主要应用于Koa Web框架中的中间件处理,特别是与HTTP请求和响应相关的功能。
2、中间件写法不同:
Redux中间件通常是一个函数,它接收store作为参数,并返回一个函数,这个函数再接收next和action作为参数。
Koa中间件则是一个异步函数(在Koa 2.x中使用async/await),它接收ctx(上下文对象)和next作为参数。
3、执行流程细节不同:
在Redux洋葱模型中,action在穿过所有中间件后到达reducer进行处理,然后结果再逐层返回给中间件。
在Koa洋葱模型中,请求在穿过所有中间件后控制权开始回溯,中间件可以在这个过程中处理响应或进行任何清理操作。
码字不易,大佬们点点赞
相关文章:
【react】Redux的设计思想与工作原理
Redux 的设计理念 Redux 的设计采用了 Facebook 提出的 Flux 数据处理理念 在 Flux 中通过建立一个公共集中数据仓库 Store 进行管理,整体分成四个部分即: View (视图层)、Action (动作)、Dispatcher (派发器)、Stor…...
深入解析Android JNI:以Visualizer为例
深入解析Android JNI:以Visualizer为例 引言 Java Native Interface (JNI) 是Java平台提供的一种机制,允许Java代码与本地代码(如C/C++)进行交互。在Android开发中,JNI被广泛用于性能优化、访问底层系统API、以及复用现有的C/C++库。本文将通过Android中的Visualizer类,…...
Java基于SpringBoot的甘肃非物质文化网站的设计与实现,附源码
博主介绍:✌Java老徐、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇&…...
Unity小白工作心得(无限记录)
24届毕业,11月才找到工作在一家小公司。工作两个月了,遇到最大的问题就是探索新知识,每天都是摸索式前进,因为工作面临着交付,你不得不想尽办法解决问题然后交给上面,但其他程序员用的引擎不一样࿰…...
STC单片机内部常见寄存器及其作用
STC单片机内部常见寄存器及其作用 STC单片机是基于8051架构的增强型单片机,广泛应用于嵌入式系统中。其内部有多个特殊功能寄存器(SFR, Special Function Register),用于控制硬件模块(如定时器、串口、中断等…...
【亚马逊云】基于Amazon EC2实例部署 NextCloud 云网盘并使用 Docker-compose 搭建 ONLYOFFICE 企业在线办公应用软件
文章目录 1. 部署EC2实例2. 安装 Docker 服务3. 安装docker-compose4. 创建Docker-compose文件5. 创建nginx.conf文件6. 运行docker-compose命令开始部署7. 访问ONLYOFFICE插件8. 访问NextCloud云盘9. 下载并启用ONLYOFFICE插件10. 上传文件测试11. 所遇问题12. 参考链接 1. 部…...
vim、watch、cp和mv
一、vim使用技巧 vim主配置文件:/etc/vimrc (对所有用户都生效) vim子配置文件:vim ~/.vimrc (只对当前用户生效) 可写入: set nu 显示行号 ts2 tab键长度为两个空格(默认为8个空格…...
【Linux】:线程安全 + 死锁问题
📃个人主页:island1314 🔥个人专栏:Linux—登神长阶 ⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞 1. 线程安全和重入问题&…...
HarmonyOS Next 应用元服务开发-应用接续动态配置迁移保持迁移连续性
保证迁移连续性,由于迁移加载时,目标端拉起的应用可能执行过自己的迁移状态设置命令(如:冷启动时目标端在onCreate中设置了INACTIVE;热启动时对端已打开了不可迁移的页面,迁移状态为INACTIVE等情况…...
重装操作系统后 Oracle 11g 数据库数据还原
场景描述: 由于SSD系统盘损坏,更换硬盘后重装了操作系统,Oracle数据库之前安装在D盘(另一个硬盘),更换硬盘多添加一个盘符重装系统后盘符从D变成E,也就是之前的D:/app/... 变成了现在的 E:/app/...,重新安装…...
《Vue3实战教程》39:Vue3无障碍访问
如果您有疑问,请观看视频教程《Vue3实战教程》 无障碍访问 Web 无障碍访问 (也称为 a11y) 是指创建可供任何人使用的网站的做法——无论是身患某种障碍、通过慢速的网络连接访问、使用老旧或损坏的硬件,还是仅处于某种不方便的环境。例如,…...
Linux-Redis哨兵搭建
环境资源准备 主机名IP端口号角色vm1192.168.64.156379/26379mastervm2192.168.64.166379/26379slavevm3192.168.64.176379/26379slave 6379为redis服务暴露端口号、26379为sentinel暴露端口号。 安装Redis # 包文件下载 wget https://github.com/redis/redis/archive/7.2.2…...
数据分析思维(六):分析方法——相关分析方法
数据分析并非只是简单的数据分析工具三板斧——Excel、SQL、Python,更重要的是数据分析思维。没有数据分析思维和业务知识,就算拿到一堆数据,也不知道如何下手。 推荐书本《数据分析思维——分析方法和业务知识》,本文内容就是提取…...
Go基础之环境搭建
文章目录 1 Go1.1 简介1.1.1 定义1.1.2 特点用途 1.2 环境配置1.2.1 下载安装1.2.2 环境配置1.2.2.1 添加环境变量1.2.2.2 各个环境变量理解 1.2.3 验证环境变量 1.3 包管理工具 Go Modules1.3.1 开启使用1.3.2 添加依赖包1.3.3 配置国内包源1.3.3.1 通过 go env 配置1.3.3.2 修…...
Flume的安装和使用
一、安装Flume 1. 下载flume-1.7.0 http://mirrors.shu.edu.cn/apache/flume/1.7.0/apache-flume-1.7.0-bin.tar.gz 2. 解压改名 tar xvf apache-flume-1.7.0-bin.tar.gz mv apache-flume-1.7.0-bin flume 二、配置Flume 1. 配置sh文件 cp conf/flume-env.sh.template …...
原生js封装ajax请求以及css实现提示效果和禁止点击效果
<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0,user-scalableno"><title>本地模式网络切换</title>&l…...
idea 的 springboot项目spring-boot-devtools 自动编译 配置热部署
1,设置一 2,设置二 设置二(旧版本) CtrlShiftAlt/ 点击弹出框中Registry... 引入(如果报错,换不同的版本) <dependency><groupId>org.springframework.boot</groupId><a…...
算法 class 005 (对数器C语言实现)
对数器的概念: 用来测试你的算法是否正确。 怎么做呢? 1:比如,写个冒泡排序,作为对比的对象 2:生成一个随机数 数组,用来测试 3:用冒泡排序和你想要验证的那个排序算法,同…...
靶场搭建问题(技巧)总结
目录 DVWA靶场问题 待续、更新中...... DVWA靶场 问题 错误信息 Parse error: syntax error, unexpected ‘[’, expecting ‘)’ in C:\Softwares\phpstudy\PHPTutorial\WWW\dvwa\dvwa\includes\dvwaPage.inc.php on line 52 原因 phpstudy2018所使用的php版本过低,…...
Elasticsearch DSL版
文章目录 1.索引库操作创建索引库:删除索引库:查询索引库:修改索引库:总结 2.文档操作创建文档:查询文档:删除文档:全量修改文档:增量修改文档:总结 3.DSL查询语法&#…...
在K8S中,Pod请求另一个Pod偶尔出现超市或延迟,如何排查?
在Kubernetes中,当Pod请求另一个Pod时偶尔出现超时或延迟,可能是由于多种原因造成的。以下是一些建立的排查步骤: 1. 检查网络配置和插件: 确认你的kubernetes集群使用了合适的网络插件(如Calico、Flannel等…...
Echarts实现大屏可视化2
目录 一、效果展示 二、说明 2.1 销售额统计图表 2.2 全国热榜模块 三、边框图片【border-image】 3.1 使用场景 3.2 边框图片切图原理 3.3 边框图片语法【重要】 四、代码展示 4.1 展示页面 4.2 数据概览区域 4.3 监控区域 4.4 点位分布统计区域 4.5 设备数据统…...
HarmonyOS NEXT 应用开发练习:智能视频推荐
一、整体思路 本DEMO展示了如何在HarmonyOS NEXT平台上开发一个智能视频推荐应用。应用通过模拟的用户偏好数据,为用户推荐可能感兴趣的视频。用户可以通过滑动屏幕查看推荐的视频列表,并点击视频封面进入播放页面,本例中仅模拟点击效果&…...
FPGA(二)组成结构基础内容
1. FPGA的基本结构 FPGA主要由以下部分组成: (1)可编程逻辑单元(CLB):CLB是FPGA中最基本的逻辑单元,由查找表(LUT)和触发器组成,可实现任意逻辑功能。查找表…...
linux中执行命令
1.1 命令格式 命令格式: 主命令 选项 参数(操作对象) 命令分为两类: 内置命令( builtin ):由 shell 程序自带的命令 外部命令:有独立的可执行程序文件,文件名即命令…...
《Java 数据结构》
《Java 数据结构》 1. 概述 Java 数据结构是计算机科学中的一种基础概念,它涉及到数据的组织和存储方式,以便能够高效地访问和修改数据。在 Java 中,数据结构通常通过类和接口来实现,这些类和接口提供了用于操作数据的各种方法。…...
C# 设计模式(结构型模式):装饰器模式
C# 设计模式(结构型模式):装饰器模式 在软件开发中,面对需要扩展功能但又不想修改已有代码的情况时,装饰模式(Decorator Pattern)是一个非常有用的设计模式。装饰模式允许我们在不改变对象自身…...
ubuntu初始配置
ubuntu初始配置 vm下ubuntu安装vmtools安装常用工具ubuntu终端美化安装ssh使用apt安装时出现错误解决办法 vm下ubuntu安装vmtools //安装vmtools sudo apt-get install open-vm-tools //桌面组件提供了更好的集成体验,包括拖放文件和共享剪贴板等功能 sudo apt-get…...
Spark Runtime Filter
Runtime Filter 参考链接: https://docs.google.com/document/d/16IEuyLeQlubQkH8YuVuXWKo2-grVIoDJqQpHZrE7q04/edit?tabt.0https://www.modb.pro/db/557718https://issues.apache.org/jira/browse/SPARK-32268https://github.com/apache/spark/pull/35789https…...
《Vue3实战教程》42:Vue3TypeScript 与组合式 API
如果您有疑问,请观看视频教程《Vue3实战教程》 TypeScript 与组合式 API 这一章假设你已经阅读了搭配 TypeScript 使用 Vue 的概览。 为组件的 props 标注类型 使用 <script setup> 当使用 <script setup> 时,defineProps() 宏函数支…...
【Hadoop】Hadoop安全之Knox网关
目录 一、概述 2.1 knox介绍 2.2 版本信息 二、部署 三、验证Knox网关 3.1 Hdfs RESTFULL 3.2 HDFSUI 3.3 YARNUI 3.4 HBASEUI 一、概述 2.1 knox介绍 Apache Knox网关是一个用于与Apache Hadoop部署的REST api和ui交互的应用程序网关。Knox网关为所有与Apache Hadoop…...
UML之关联
关联指两个类之间的各种联系。UML使用各种单实线表示关联,这个单实线可以是直线(垂直的、水平的或者倾斜的)、折线甚至曲线。 事实上,关联也是展示类的属性的另一外的一种形式。例如在下图中,我们通过一条实线连接类B…...
大数据面试笔试宝典之大数据运维面试
1.大数据集群节点规划 1.HDFS 集群节点规划 假如业务系统数据量每天增量 50T ,保留周期为 30 天,那么...
Matlab全局变量用法及其实例分析
⭕⭕ 目 录 ⭕⭕ 一、全局变量使用说明二、全局变量的用法实例演示2.1 实例演示2.2 修改global 一、全局变量使用说明 全局变量是指在不同的工作空间以及基本的工作空间中可以共享的变量。用户只需要在主程序或者任何子程序中声明一个或多个全局变量,则函数和主程序…...
leetcode hot 100 跳跃游戏
55. 跳跃游戏 已解答 中等 相关标签 相关企业 给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标,如果可以,返回 true ;否则…...
CG顶会论文阅读|《科技论文写作》硕士课程报告
文章目录 一、基本信息1.1 论文基本信息1.2 课程基本信息1.3 博文基本信息 二、论文评述(中英双语)2.1 研究问题(Research Problem)2.2 创新点(Innovation/Contribution)2.3 优点(Why this pape…...
短视频矩阵系统搭建开发指导
在数字化营销的广阔天地中,抖音短视频已迅速崛起为一个拥有巨大影响力的社交媒体平台。随着其受众范围的日益扩大,采用有效的搜索引擎优化(SEO)策略以增强视频的曝光度和吸引流量变得至关重要。本文旨在阐述一种专为抖音短视频量身…...
LeetCode热题100-移动零【JavaScript讲解】
题目: 根据本题想思路: 快指针和慢指针同时移动,当遍历的值不为0的时候,将快指针的值赋给慢指针,如果遍历到0,快指针继续移动,慢指针不动等待被覆盖。之后使用fill方法填充0。具体答案放在最后…...
MarkDown 的 mermaid gantt(甘特图)、mermaid sequenceDiagram (流程图) 语法解析和应用
简简单单 Online zuozuo: 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo :本心、输入输出、结果 简简单单 Online zuozuo : 文章目录 MarkDown 的 mermaid gantt、mermaid sequenceDiagram 语法解析和应用前言mermaid gan…...
Flask 与 SocketIO 正确初始化及最佳实践调试
1、问题 我使用Flask和Flask-SocketIO 来做 Websocket 链接。前期正常使用,但是后期布置修改什么导致Websocket连接失败。排查需求,才发现初始化不正常导致。 SocketIO 和 Flask 应用的初始化顺序和引用循环的问题 2、环境 python-engineio4.11.1 py…...
Spring Boot 3 文件上传、多文件上传、大文件分片上传、文件流处理以及批量操作
在 Spring Boot 3 中,可以通过内置的文件处理机制结合 Java 的 IO 流与多线程技术,实现文件上传、多文件上传、大文件分片上传、文件流处理以及批量操作的需求。以下是详细实现步骤: 1. 单文件上传 控制器代码 import org.springframework…...
Springcloud项目-前后端联调(一)
项目采用SpringCloud整体构建,nacos作为注册中心,Mysql和Redis进行数据存储,整体项目类似于平时使用的出行APP,idea2023编写后端,vscode编写前端 后端代码先前已经编写完毕 这部分功能主要是通过前端输入出发地和目的地之后调用…...
树莓派之旅-第一天 系统的烧录和设置
自言自语: 在此记录一下树莓派的玩法。以后有钱了买点来玩啊草 系统的安装烧录 系统下载 树莓派官网:https://www.raspberrypi.com/ 首页点击SoftWare进入OS下载页面 这里是安装工具:安装工具负责将系统镜像安装到sd卡中 点击下载符合自己…...
SQL 中的 EXISTS
我们先从 SQL 中最基础的 WHERE 子句开始。 比如下面这条 SQL 语句: 很显然,在执行这条 SQL 语句的时候,DBMS 会扫描 Student 表中的每一条记录,然后把符合 Sdept IS 这个条件的所有记录筛选出来,并放到结果集里面去…...
WebSocket 的封装使用
import { ElMessage } from "element-plus";// 全局WebSocket实例 let ws null; let isConnected false; let currentWsUrl ; // 用于存储当前的wsUrl let baseURL ws://XXX.com:8081;const initWebSocket (wsUrl, sendData) > {return new Prom…...
[算法] [leetcode-509] 斐波那契数
509 斐波那契数 斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: F(0) 0,F(1) 1 F(n) F(n - 1) F(n - 2),其中 n…...
解决CentOS 8 YUM源更新后报错问题:无法下载AppStream仓库元数据
背景介绍 在尝试更新CentOS 8的YUM源以使用阿里云镜像时,遇到了Failed to download metadata for repo appstream的错误。此错误通常出现在执行yum clean all && yum makecache命令之后,表明系统无法从指定的URL获取AppStream仓库的元数据。本文…...
HTML——49.header和footer标签
<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>header和footer标签</title></head><body><!--header和footer标签:是html5中新标签--><!--header:定义文档的页眉,通常用来定义可见…...
如何在 Spring Boot 中配置数据库?
在 Spring Boot 中配置数据库是一个相对简单的过程,通常涉及到以下几个步骤:添加数据库驱动依赖、配置数据源属性、以及可选的配置 JPA(如果使用)。下面是小编给大家编写的一个详细的指南,以MySQL 数据库为例。 文章目…...
Ant Design Pro搭建react项目
1、使用pro-cli 来快速的初始化脚手架,先全局安装 npm i ant-design/pro-cli -g pro create myapp 2、选择模板类型 pro 是基础模板,只提供了框架运行的基本内容,complete 包含所有区块,不太适合当基础模板来进行二次开发。&a…...