React 之 Redux - 状态管理
![](https://img-blog.csdnimg.cn/img_convert/cf068ac63b104db4a9b6e65590778367.jpeg)
一、前言
1. 纯函数
函数式编程中有一个非常重要的概念叫纯函数,JavaScript符合函数式编程的范式,所以也有纯函数的概念
-
确定的输入,一定会产生确定的输出
-
函数在执行过程中,不能产生副作用
2. 副作用
表示在执行一个函数时,除了返回函数值之外,还对调用函数产生了附加的影响,
比如修改了全局变量,修改参数或者改变外部的存储
-
纯函数在执行的过程中就是不能产生这样的副作用
-
副作用往往是产生bug的 “温床”
3. 纯函数的案例
对数组操作的两个函数:
slice就是一个纯函数,不会修改数组本身,而splice函数不是一个纯函数
-
slice:slice截取数组时不会对原数组进行任何操作,而是生成一个新的数组
-
splice:splice截取数组, 会返回一个新的数组, 也会对原数组进行修改
![](https://img-blog.csdnimg.cn/img_convert/84fc744c074e4b86a2fd3747bf5423a1.png)
4. 判断是否纯函数
![](https://img-blog.csdnimg.cn/img_convert/e81021fa0b874aaf845fdd6d17e05bf6.png)
5. 纯函数的作用和优势
纯函数在函数式编程中非常重要 :
-
可以安心的编写和安心的使用
-
在写的时候保证了函数的纯度,只是单纯实现业务逻辑即可,不需要关心传入的内容是如何获得的或者依赖其他的外部变量是否已经发生了修改
-
在用的时候,确定的输入内容不会被任意篡改,并且确定的输入,一定会有确定的输出
6. 在React中
React中就要求无论是函数还是class声明一个组件,这个组件都必须像纯函数一样,保护它们的props不被修改
![](https://img-blog.csdnimg.cn/img_convert/934950b1ae074ec7844d49f797542e27.png)
7. 为啥使用Redux
![](https://img-blog.csdnimg.cn/img_convert/1f09088def59419595e94b36ee970288.png)
二、Redux的核心理念
1. 核心理念 - store
store : 用于存储共享数据的仓库
2. 核心理念 - action
action : store中所有数据的变化,必须通过派发(dispatch)action来更新
action是一个普通的JavaScript对象,用来描述这次更新的type和content
3. 核心理念 - reducer
reducer : reducer是一个纯函数,将state和action联系在一起
reducer做的事情就是将传入的state和action结合起来生成一个新的state
4. 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的基本使用
1. 测试项目搭建
01. 创建一个新的项目文件夹: learn-redux
02. 进入文件夹后进行项目初始化
npm init -y
03. 安装redux
npm install redux
04. 创建src目录,以及其中的index.js文件
05. 修改package.json可以执行index.js
也可不修改,直接控制台输入 node src/index.js
"scripts": {// node v13.2.0之后,添加属性:"type": "module",就可以在node中对ES6模块化的支持"type": "module","start": "node src/index.js"
},
06. 在src中创建目录store,并创建index.js
// 需要配置下type,否则的话直接使用commonjs也是可以的
import { createStore } from 'redux';// 1. 定义初始化数据
const initialState = {name: 'coder',age: 18,counter: 100
};// 2. 定义reducer纯函数
// 2.1 reducer函数的第一个参数是state,第二个参数是action
// 2.2 reducer函数必须返回一个新的state,这个state会覆盖原来的state
// 2.3 reducer函数中,不能修改原来的state,必须返回一个新的state// 第一次调用reducer函数的时候,state是undefined,所以给state设置默认值,只会在第一次调用的时候生效
const reducer = (state = initialState, action) => {// 2.4 在这里,根据action的type,来决定如何修改state,返回一个新的stateswitch (action.type) {case 'xxx' : return {...state,...}}// 2.5 没有数据更新的时候,返回一个新的statereturn state;
};// 3. 创建store
export const store = createStore(reducer);
2. 使用store中的数据
// 1. 导入store
import { store } from './store/index.js';// 2. 拿到store中存储的数据
console.log(store.getState()); // { name: 'coder', age: 18, counter: 100 }
3. 修改store中的数据
执行代码
// 导入store
import { store } from './store/index.js';// 创建一个action对象
const nameAction = { type: 'change_name', name: 'star' };
// 进行派发, 派发的时候,会调用reducer函数,传入state和action
store.dispatch(nameAction)// 创建一个action对象
const counterAction = { type: 'increment_counter', number: 10 };
// 进行派发
store.dispatch(counterAction);console.log(store.getState()); // { name: 'star', age: 18, counter: 110 }
在reducer函数中新增判断
const reducer = (state = initialState, action) => {// action => { type: 'change_name', name: 'star' }switch (action.type) {case 'change_name':return {// 先对原来staet进行解构...state,// 再对name进行覆盖name: action.name};case 'increment_counter':return { ...state, counter: state.counter + action.number };default:// 如果没有匹配到action.type,就返回原来的statereturn state;}
};
4. 订阅store中的数据
// 导入store
import { store } from './store/index.js';// 监听state的变化, 只要state发生了变化,就会调用回调函数
// 返回值是一个函数,调用这个函数,就可以取消监听
const unSubscribe = store.subscribe(() => {console.log('state发生了变化');console.log(store.getState());
});// 派发action => 可以监听到
store.dispatch({ type: 'change_name', name: 'star' });// 取消监听
unSubscribe();// 派发action => 不会监听到,但是state会发生变化,因为store.dispatch会调用reducer函数
store.dispatch({ type: 'increment_counter', number: 10 });
5. 优化
动态生成action
// 导入store
import { store } from './store/index.js';store.subscribe(() => {console.log('state发生了变化', store.getState());
});// 动态生成action : actionCreator => 创建action的函数, 返回一个action对象
const changeNameAction = (name) => ({ type: 'change_name', name });// store.dispatch({ type: 'change_name', name: 'star' });
// store.dispatch({ type: 'change_name', name: 'coderstar' });
store.dispatch(changeNameAction('star'));
store.dispatch(changeNameAction('coderstar'));
目录结构优化
如果将所有的逻辑代码写到一起,那么当redux变得复杂时代码就难以维护
对代码进行拆分,将store、reducer、action、constants拆分成一个个文件
创建store/index.js文件
import { createStore } from 'redux';import reducer from './reducer.js';// 创建store
export const store = createStore(reducer);
创建store/reducer.js文件
import { CHANGE_NAME, INCREMENT_COUNTER } from './constants.js';// 1. 定义初始化数据
const initialState = {name: 'coder',age: 18,counter: 100
};// 2. 定义reducer
export const reducer = (state = initialState, action) => {switch (action.type) {case CHANGE_NAME:return {// 先对原来staet进行解构...state,// 再对name进行覆盖name: action.name};case INCREMENT_COUNTER:return { ...state, counter: state.counter + action.number };default:// 如果没有匹配到action.type,就返回原来的statereturn state;}
};
创建store/actionCreators.js文件 => 用于生成action
// Description: Action Creatorsimport { CHANGE_NAME, INCREMENT_COUNTER } from './constants.js';// 修改name名字的action
export const changeNameAction = (name) => ({ type: CHANGE_NAME, name });// 修改counter的action
export const incrementCounterAction = (number) => ({ type: INCREMENT_COUNTER, number });
创建store/constants.js文件 => 用于定义type常量
// Desc: constants for actionsexport const CHANGE_NAME = 'change_name';
export const INCREMENT_COUNTER = 'increment_counter';
6. Redux使用流程图
四、React结合Redux
1. 安装
npm install redux
2. 基本使用
store文件夹
index.js
import { createStore } from 'redux';
import { reducer } from './reducer';export const store = createStore(reducer);
constants.js
export const CHANGE_COUNTER = 'change_counter';export const CHANGE_BANNER = 'change_banner';
reducer.js
import { CHANGE_BANNER, CHANGE_COUNTER } from './constants';const initialState = {counter: 20,bannerList: []
};export const reducer = (state = initialState, action) => {switch (action.type) {case CHANGE_COUNTER:return { ...state, counter: state.counter + action.counter };case CHANGE_BANNER:return { ...state, bannerList: [...state.bannerList, ...action.bannerList] };default:return state;}
};
actionCreatores.js
import { CHANGE_COUNTER, CHANGE_BANNER } from './constants';// 修改counter的action
export const changeCounterAction = (counter) => ({ type: CHANGE_COUNTER, counter });// 修改bannerList的action
export const changeBannerAction = (bannerList) => ({ type: CHANGE_BANNER, bannerList });
page文件夹
Home.jsx
import React, { PureComponent } from 'react';
import { store } from '../store';
import { changeCounterAction } from '../store/actionCreatores.js';export class Home extends PureComponent {constructor(porps) {super(porps);this.state = {// 从store中获取counter的值,赋予初始化值counter: store.getState().counter};}componentDidMount() {// 监听store的变化store.subscribe(() => {this.setState({counter: store.getState().counter});});}changeCounter(num) {// 改变store中的值,通过dispatch派发actionstore.dispatch(changeCounterAction(num));}render() {const { counter } = this.state;return (<><h2>Home counter : {counter}</h2><button onClick={(e) => this.changeCounter(5)}>+5</button>{' '}<button onClick={(e) => this.changeCounter(10)}>+10</button></>);}
}export default Home;
Profily.jsx
import React, { PureComponent } from 'react';
import { store } from '../store';
import { changeCounterAction } from '../store/actionCreatores.js';export class Profily extends PureComponent {constructor(porps) {super(porps);this.state = {// 从store中获取counter的值,赋予初始化值counter: store.getState().counter};}componentDidMount() {// 监听store的变化store.subscribe(() => {this.setState({counter: store.getState().counter});});}changeCounter(num) {// 改变store中的值,通过dispatch派发actionstore.dispatch(changeCounterAction(num));}render() {const { counter } = this.state;return (<><h2>Profily counter : {counter}</h2><button onClick={(e) => this.changeCounter(-5)}>-5</button>{' '}<button onClick={(e) => this.changeCounter(-10)}>-10</button></>);}
}export default Profily;
App.jsx
import React, { PureComponent } from 'react';
import Home from './page/Home';
import Profily from './page/Profily';import { store } from './store';export class App extends PureComponent {constructor(porps) {super(porps);this.state = {// 从store中获取counter的值,赋予初始化值counter: store.getState().counter};}componentDidMount() {// 监听store的变化store.subscribe(() => {this.setState({counter: store.getState().counter});});}render() {const { counter } = this.state;return (<div style={{ textAlign: 'center', marginTop: '100px' }}><h2>App counter : {counter}</h2><hr /><hr /><hr /><Home /><hr /><hr /><hr /><Profily /></div>);}
}export default App;
3. react-redux的使用
-
redux和react没有直接的关系,完全可以在React, Angular, Ember, jQuery, or vanilla
JavaScript中使用Redux -
redux依然是和React库结合的更好
-
redux官方帮助我们提供了 react-redux 的库,可以直接在项目中使用,并且实现的逻辑会更加的严谨和高效
将组件和store连接
安装 => npm install react-redux
1. 修改index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
// 引入Provider组件, 用于给整个项目提供一个公共的store
import { Provider } from 'react-redux';
import { store } from './store';const root = ReactDOM.createRoot(document.querySelector('#root'));root.render(<React.StrictMode>{/* 给整个项目提供一个公共的store */}<Provider store={store}><App /></Provider></React.StrictMode>
);
2. 组件中使用
需要进行映射,state、dispatch都需要映射
import React, { PureComponent } from 'react';
// 1. 引入connect,用于连接组件和redux,返回一个高阶组件
import { connect } from 'react-redux';
import { changeCounterAction } from '../store/actionCreatores';export class About extends PureComponent {changeCounter(num) {// 4. 从props中取出changeCounter,不再从state中取,因为state中的值已经被映射到props中了const { changeCounter } = this.props;// 执行了下面定义的方法,相当于调用了dispatch(changeCounterAction(num))changeCounter(num);}render() {// 3. 从props中取出counter,不再从state中取,因为state中的值已经被映射到props中了const { counter } = this.props;return (<><h2>About : {counter}</h2><button onClick={(e) => this.changeCounter(8)}>+8</button>{' '}<button onClick={(e) => this.changeCounter(-8)}>-8</button></>);}
}/*** connect => connect()()返回一个高阶组件,第一个()传入两个参数,第二个()传入一个组件,返回一个新的 高阶组件🌟* 第一个()传入两个参数,第一个参数是一个函数,第二个参数是一个对象* 第一个参数函数,函数的参数是state,返回一个对象,对象的属性是state,值是state* 第二个参数对象,对象的属性是一个函数,函数的参数是dispatch,返回一个对象,对象的属性是函数,函数的参数是参数,返回一个action* 第二个()传入一个组件,返回一个新的组件*/
// 2. 注入store中的state和dispatch到组件的props中// 用来映射state中的值到组件的props中,可以设置取其中需要的数据
const mapStateToProps = (state) => ({ counter: state.counter });
// 用来映射dispatch到组件的props中,可以设置取其中需要的方法
const mapDispatchToProps = (dispatch) => ({changeCounter: (num) => dispatch(changeCounterAction(num))
});export default connect(mapStateToProps, mapDispatchToProps)(About);
3. 组件中异步操作
About组件 => 请求banners数据
Category组件 => 展示banners数据
效果
About组件
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { changeCounterAction, changeBannerAction } from '../store/actionCreatores';
import axios from 'axios';export class About extends PureComponent {changeCounter(num) {const { changeCounter } = this.props;changeCounter(num);}async getData() {const res = await axios.get('http://xxxxxx');const bannerList = res?.data?.data?.banner?.list || [];const { changeBanner } = this.props;// 请求数据后,改变store中的值changeBanner(bannerList);}render() {const { counter } = this.props;return (<><h2>About : {counter}</h2><button onClick={(e) => this.changeCounter(8)}>+8</button>{' '}<button onClick={(e) => this.changeCounter(-8)}>-8</button>{' '}<button onClick={(e) => this.getData()}>请求数据</button></>);}
}const mapStateToProps = (state) => ({ counter: state.counter });
const mapDispatchToProps = (dispatch) => ({changeCounter: (num) => dispatch(changeCounterAction(num)),changeBanner: (bannerList) => dispatch(changeBannerAction(bannerList))
});export default connect(mapStateToProps, mapDispatchToProps)(About);
Category组件
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';export class Category extends PureComponent {render() {const { bannerList } = this.props;return (<><h2>Category</h2><ul>{bannerList.map((item, index) => {return <li key={index}>{item.title}</li>;})}</ul></>);}
}const mapStateToProps = (state) => ({ bannerList: state.bannerList });export default connect(mapStateToProps, null)(Category);
4. redux中异步操作
上面的代码有一个缺陷
- 必须将网络请求的异步代码放到组件的生命周期中来完成
- 事实上,网络请求到的数据也属于状态管理的一部分,更好的一种方式应该是将其也交给redux来管理
redux中如何可以进行异步的操作 :
- 答案就是使用中间件(Middleware)
- 学习过Express或Koa框架的童鞋对中间件的概念一定不陌生
- 在这类框架中,Middleware可以帮助我们在请求和响应之间嵌入一些操作的代码,比如cookie解析、日志记录、文件压缩等操作
中间件
redux也引入了中间件(Middleware)的概念:
- 这个中间件的目的是在dispatch的action和最终达到的reducer之间,扩展一些自己的代码
- 比如日志记录、调用异步接口、添加代码调试功能等等
- 发送异步的网络请求,可以使用中间件是 redux-thunk
redux-thunk是如何做到让我们可以发送异步的请求呢
- 我们知道,默认情况下的dispatch(action),action需要是一个JavaScript的对象
- redux-thunk可以让dispatch(action函数),action可以是一个函数
- 该函数会被调用,并且会传给这个函数一个dispatch函数和getState函数
- dispatch函数用于我们之后再次派发action
- getState函数考虑到我们之后的一些操作需要依赖原来的状态,用于让我们可以获取之前的一些状态
redux-thunk的使用
安装 => npm install redux-thunk
store/index.js
// applyMiddleware => 应用中间件
import { createStore, applyMiddleware } from 'redux';import thunk from 'redux-thunk';
import { reducer } from './reducer';// 正常情况下,store.dispatch只能接收对象 store.dispatch(对象object)// 想要派发函数store.dispatch(函数function),需要对store进行增强,增强的方式是使用中间件
// 但是如果使用了redux-thunk中间件,那么store.dispatch就可以接收函数 store.dispatch(函数function)// 1. 引入中间件 applyMiddleware(thunk, logger, ...) => 可以传入多个中间件
const enhancer = applyMiddleware(thunk);
// 2. 使用中间件
export const store = createStore(reducer, enhancer);
actionCreatores.js
import axios from 'axios';
import { CHANGE_COUNTER, CHANGE_BANNER } from './constants';// 修改counter的action
export const changeCounterAction = (counter) => ({ type: CHANGE_COUNTER, counter });// 修改bannerList的action
export const changeBannerAction = (bannerList) => ({ type: CHANGE_BANNER, bannerList });// 异步获取bannerList的action
export const getBannerListAction = () => {// 如果是普通的action,那么返回的是一个对象, {type: 'xxx', payload: 'xxx'}// 问题:对象中不能直接拿到从服务器获取的数据// return {}// 如果是函数,那么redux是不支持的,需要使用中间件redux-thunk// 一旦派发来到这个函数,那么redux-thunk就会自动执行这个函数// 这个函数的参数有来两个,第一个是dispatch => store.dispatch dispatch(xxx) , 第二个是getState => 获取store中的数据 getState().counterreturn async (dispatch, getState) => {const res = await axios.get('http://123.207.32.32:8000/home/multidata');const bannerList = res?.data?.data?.banner?.list || [];// 派发action对象dispatch(changeBannerAction(bannerList));};
};
About.jsx
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { changeCounterAction, getBannerListAction } from '../store/actionCreatores';export class About extends PureComponent {changeCounter(num) {const { changeCounter } = this.props;changeCounter(num);}getData() {const { getBannerList } = this.props;// 在这里调用store中的action发送异步请求,获取数据getBannerList();}render() {const { counter } = this.props;return (<><h2>About : {counter}</h2><button onClick={(e) => this.changeCounter(8)}>+8</button>{' '}<button onClick={(e) => this.changeCounter(-8)}>-8</button>{' '}<button onClick={(e) => this.getData()}>请求数据</button></>);}
}const mapStateToProps = (state) => ({ counter: state.counter });
const mapDispatchToProps = (dispatch) => ({changeCounter: (num) => dispatch(changeCounterAction(num)),getBannerList: () => dispatch(getBannerListAction())
});export default connect(mapStateToProps, mapDispatchToProps)(About);
5. react调试工具 - redux-devtools
redux可以方便的让我们对状态进行跟踪和调试
- redux官网为我们提供了redux-devtools的工具
- 利用这个工具,我们可以知道每次状态是如何被修改的,修改前后的状态变化等等
步骤一
在对应的浏览器中安装相关的插件
比如Chrome浏览器扩展商店中搜索Redux DevTools即可
步骤二
在redux中继承devtools的中间件
import { createStore, applyMiddleware, compose } from 'redux';import thunk from 'redux-thunk';
import { reducer } from './reducer';// redux-devtools-extension,用于在浏览器中查看redux的状态 => 为了安全,只在开发环境中使用
// 生产环境
let composeEnhancers = compose;
if (process.env.NODE_ENV === 'development') {// 开发环境composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ trace: true }) || compose;
}
const enhancer = applyMiddleware(thunk);export const store = createStore(reducer, composeEnhancers(enhancer));
6. Reducer文件拆分
拆分
actionCreatores.js
import { CHANGE_COUNTER } from './constants';// 修改counter的action
export const changeCounterAction = (counter) => ({ type: CHANGE_COUNTER, counter });
constants.js
export const CHANGE_COUNTER = 'change_counter';
index.js
// 文件做统一的导出export { reducer as counterReducer } from './reducer';export * from './actionCreatores';
reducer.js
import { CHANGE_COUNTER } from './constants';const initialState = {counter: 20
};export const reducer = (state = initialState, action) => {switch (action.type) {case CHANGE_COUNTER:return { ...state, counter: state.counter + action.counter };default:return state;}
};
store => 下的index.js文件,用来合并各个模块的reducer
combineReducers函数可以方便的让我们对多个reducer进行合并
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';import thunk from 'redux-thunk';import { counterReducer } from './counter';
import { homeReducer } from './home';// 合并reducer
const reducer = combineReducers({// 相当于模块名称counter: counterReducer,home: homeReducer
});// redux-devtools-extension,用于在浏览器中查看redux的状态 => 为了安全,只在开发环境中使用
// 生产环境
let composeEnhancers = compose;
if (process.env.NODE_ENV === 'development') {// 开发环境composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ trace: true }) || compose;
}
const enhancer = applyMiddleware(thunk);export const store = createStore(reducer, composeEnhancers(enhancer));
使用
直接取数据或者映射数据时,需要加上模块名
派发action还是和以前一样,不需要更改
五、ReduxToolkit
1. 概念
Redux Toolkit 是官方推荐的编写 Redux 逻辑的方法
- 在前面我们学习Redux的时候应该已经发现,redux的编写逻辑过于的繁琐和麻烦
- 并且代码通常分拆在多个文件中(虽然也可以放到一个文件管理,但是代码量过多,不利于管理)
- Redux Toolkit包旨在成为编写Redux逻辑的标准方式,从而解决上面提到的问题
- 在很多地方为了称呼方便,也将之称为“RTK”
安装 => npm install @reduxjs/toolkit react-redux
Redux Toolkit的核心API
- configureStore:包装createStore以提供简化的配置选项和良好的默认值
- 它可以自动组合你的 slice reducer,添加你提供的任何 Redux 中间件,redux-thunk默认包含,并启用 Redux DevTools Extension
- createSlice:接受reducer函数的对象、切片名称和初始状态值,并自动生成切片reducer,并带有相应的actions
- createAsyncThunk: 接受一个动作类型字符串和一个返回承诺的函数,并生成一个pending/fulfilled/rejected基于该承诺分派动作类型的 thunk
2. 基本使用
store中的代码
counter.js => store/peature/counter.js
// 1. 导入创建切片的函数
import { createSlice } from '@reduxjs/toolkit';const counterSlice = createSlice({// 2. 切片名称name: 'counter',// 3. 初始状态initialState: {counter: 88},// 4. reducers => 相当于之前的reducerreducers: {// 5. 相当于之前的case,action.type就是这里的方法名称changeCounterAction(state, action) {state.counter += action.payload;}}
});// 6. 导出actions
export const { changeCounterAction } = counterSlice.actions;// 7. 导出reducer
export default counterSlice.reducer;
index.js => store/index.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counter';
import categoryReducer from './features/category';import thunk from 'redux-thunk';const store = configureStore({// 1. reducer : 将切片的reducer添加到store中reducer: {counter: counterReducer,category: categoryReducer},// 2. devTools : 开启redux-devtools,默认开启,开发环境开启,生产环境关闭devTools: process.env.NODE_ENV === 'development',// 3. middleware : 中间件,默认只有thunk,如果需要添加其他中间件,可以在这里添加middleware: (getDefaultMiddleware) => { return getDefaultMiddleware().concat(thunk);},// 不常用的配置项// 4. enhancers : 增强器,如果需要添加其他增强器,可以在这里添加enhancers: [],// 5. preloadedState : 初始状态,如果需要添加初始状态,可以在这里添加preloadedState: {},// 6. reducerPathInfo : reducer路径信息,如果需要添加reducer路径信息,可以在这里添加reducerPathInfo: {},// 7. middlewareFactories : 中间件工厂,如果需要添加中间件工厂,可以在这里添加middlewareFactories: {},// 8. devToolsEnhancerOptions : devTools增强器选项,如果需要添加devTools增强器选项,可以在这里添加devToolsEnhancerOptions: {},// 9. immutableCheck : 是否开启不可变检查,默认开启immutableCheck: true,// 10. serializableCheck : 是否开启序列化检查,默认开启serializableCheck: true,// 11. middlewareOptions : 中间件选项,如果需要添加中间件选项,可以在这里添加middlewareOptions: {},// 12. thunk : thunk中间件选项,如果需要添加thunk中间件选项,可以在这里添加thunk: {},
});export default store;
主体index.js中的代码
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import { Provider } from 'react-redux';
import store from './store';const root = ReactDOM.createRoot(document.querySelector('#root'));root.render(<React.StrictMode>{/* 给整个项目提供一个公共的store */}<Provider store={store}><App /></Provider></React.StrictMode>
);
page中的代码
About.jsx => page/About.jsx
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { changeCounterAction } from '../store/features/counter';export class About extends PureComponent {changeCounter(num) {// 通过props获取actionconst { changeCounterAction } = this.props;// 调用actionchangeCounterAction(num);}render() {const { counter } = this.props;return (<><h2>About : {counter}</h2><button onClick={(e) => this.changeCounter(8)}>+8</button>{' '}<button onClick={(e) => this.changeCounter(-8)}>-8</button></>);}
}// 映射Redux全局的state到组件的props上 => counter
const mapStateToProps = (state) => ({ counter: state.counter.counter });
// 映射dispatch到props上
const mapDispatchToProps = (dispatch) => ({changeCounterAction: (num) => dispatch(changeCounterAction(num)),
});export default connect(mapStateToProps, mapDispatchToProps)(About);
3. 异步操作
之前通过redux-thunk中间件让dispatch中可以进行异步操作。
Redux Toolkit默认已经继承了Thunk相关的功能:createAsyncThunk
teAsyncThunk创建出来的action被dispatch时,会存在三种状态:
- pending:action被发出,但是还没有最终的结果
- fulfilled:获取到最终的结果(有返回值的结果)
- rejected:执行过程中有错误或者抛出了异常
- 可以在createSlice的entraReducer中监听这些结果
category.js => store/peature/category.js
import axios from 'axios';
import { createSlice } from '@reduxjs/toolkit';
// 1. 导入createAsyncThunk,用于创建异步action
import { createAsyncThunk } from '@reduxjs/toolkit';// 2. 创建异步action
export const getBannerListAction = createAsyncThunk('home/getBannerListAction',async (extraInfo, store) => {// extraInfo => 是调用异步action时传入的参数 => {name: 'coder'}// store => store对象,可以获取到store中的数据和dispatch方法,但是不推荐使用const res = await axios.get('http://123.207.32.32:8000/home/multidata');// 不能直接返回res,不支持序列化会报错,需要返回res.datareturn res.data;}
);const categorySlick = createSlice({name: 'category',initialState: {bannerList: []},reducers: {changeBannerListAction(state, { payload }) {state.bannerList = payload;}},// 3. 添加异步action的处理逻辑extraReducers: {// 不需要监听的话,可以不写[getBannerListAction.pending]: (state, { payload }) => {console.log('pending');},// 4. 在这里拿到异步action的结果,然后修改state[getBannerListAction.fulfilled]: (state, { payload }) => {state.bannerList = payload.data.banner.list;},// 不需要监听的话,可以不写[getBannerListAction.rejected]: (state, { payload }) => {console.log('rejected');}}
});// 6. 导出actions
export const { changeBannerListAction } = categorySlick.actions;// 7. 导出reducer
export default categorySlick.reducer;
Category.jsx => page/Category.jsx
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';import { getBannerListAction } from '../store/features/category';
export class Category extends PureComponent {componentDidMount() {// 2. 在redux中进行数据请求this.props.getBannerListAction();}render() {// 4. 获取redux中的数据const { bannerList } = this.props;return (<><h2>Category</h2><ul>{bannerList.map((item, index) => {return <li key={index}>{item.title}</li>;})}</ul></>);}
}// 3. 映射redux中的数据
const mapStateToProps = (state) => ({ bannerList: state.category.bannerList });// 1. 映射redux中的方法
const mapDispatchToProps = (dispatch) => ({// 这里可以传递参数过去getBannerListAction: () => dispatch(getBannerListAction({ name: 'coder' }))
});
export default connect(mapStateToProps, mapDispatchToProps)(Category);
4. 异步操作的另一种写法
extraReducer还可以传入一个函数,函数接受一个builder参数
/*** extraReducer还可以传入一个函数,函数接受一个builder参数* builder对象中包含了pending、fulfilled、rejected三个属性,分别对应异步action的三种状态* builder对象中的属性值都是函数,函数的第一个参数是state,第二个参数是action* builder对象中的属性值函数的返回值会直接赋值给state*/
extraReducers: (builder) => {builder.addCase(getBannerListAction.pending, (state, { payload }) => {console.log('pending');}).addCase(getBannerListAction.fulfilled, (state, { payload }) => {state.bannerList = payload.data.banner.list;}).addCase(getBannerListAction.rejected, (state, { payload }) => {console.log('rejected');});
}
5. Redux Toolkit的数据不可变性
六、其他补充
1. 自定义实现connect
组件中引入该文件即可,用法一致
实现代码
connect.js => hoc/Category.js
/*** 自定义实现 connect 函数* @param {*} mapStateToProps => 函数* @param {*} mapDispatchToProps => 函数* @returns => 返回值也是函数,是个高级组件,接收一个组件作为参数*/// 1. 导入stroe
import store from '../store';export default function connect(mapStateToProps, mapDispatchToProps) {// 返回一个高级组件return function (WrappedComponent) {class NewComponent extends WrappedComponent {constructor(props) {super(props);// 2. 给state赋初始值this.state = mapStateToProps(store.getState());}componentDidMount() {// 3. 订阅store的变化, 一旦store发生变化,就会执行回调函数this.unSubscribe = store.subscribe(() => {// 4. 更新statethis.setState(mapStateToProps(store.getState()));// 直接调用父类的forceUpdate方法,强制更新, 但是不推荐使用// this.forceUpdate();});}componentWillUnmount() {// 5. 取消订阅this.unSubscribe();}render() {// 6. 拿到stateObjconst stateObj = mapStateToProps(store.getState());// 7. 拿到dispatchObjconst dispatchObj = mapDispatchToProps(store.dispatch);// 8. 合并stateObj和dispatchObj,传递给WrappedComponentreturn <WrappedComponent {...this.props} {...stateObj} {...dispatchObj} />;}}return NewComponent;};
}
优化代码
上面的connect函数有一个很大的缺陷:依赖导入的store
正确的做法是我们提供一个Provider,Provider来自于我们创建的Context,让用户将store传入到value中即可
主体index.js中的代码
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import { Provider } from 'react-redux';
import store from './store';
// 导入StoreContext
import { StoreContext } from './hoc';const root = ReactDOM.createRoot(document.querySelector('#root'));root.render(<React.StrictMode><Provider store={store}>{/* 3. 使用StoreContext.Provider包裹App组件 => 把store传进去 */}<StoreContext.Provider value={store}><App /></StoreContext.Provider></Provider></React.StrictMode>
);
connect.js => hoc/connect.js
/*** 自定义实现 connect 函数* @param {*} mapStateToProps => 函数* @param {*} mapDispatchToProps => 函数* @returns => 返回值也是函数,是个高级组件,接收一个组件作为参数*/// 不直接引入store, 而是从storeContext中获取
// import store from '../store';// 1. 引入storeContext
import { StoreContext } from './index.js';// 3. 这里的context就是store => 因为在index.js中,使用storeContext.Provider包裹了App组件,把store传进去了export function connect(mapStateToProps, mapDispatchToProps) {// 返回一个高级组件return function (WrappedComponent) {class NewComponent extends WrappedComponent {// 这里有两个参数,props和context,context就是storeconstructor(props, context) {super(props);this.state = mapStateToProps(context.getState());}componentDidMount() {this.unSubscribe = this.context.subscribe(() => {this.setState(mapStateToProps(this.context.getState()));});}componentWillUnmount() {this.unSubscribe();}render() {const stateObj = mapStateToProps(this.context.getState());const dispatchObj = mapDispatchToProps(this.context.dispatch);return <WrappedComponent {...this.props} {...stateObj} {...dispatchObj} />;}}// 2. 使用storeContext.Consumer包裹NewComponent组件, 传入storeNewComponent.contextType = StoreContext;return NewComponent;};
}
storeContext.js => hoc/storeContext.js
// 1. 引入createContext, 用于创建上下文
import { createContext } from 'react';// 2. 创建上下文
export const StoreContext = createContext();
index.js => hoc/index.js
// 统一的导出export { connect } from './connect';export { StoreContext } from './storeContext';
2. 打印日志需求
在dispatch之前 : 打印一下本次的action对象
在dispatch完成之后 : 打印一下最新的store state
01 - 手动修改
缺陷非常明显:
- 首先,每一次的dispatch操作,都需要在前面加上这样的逻辑代码
- 其次,存在大量重复的代码,会非常麻烦和臃肿
02 - 修改dispatch
利用一个hack一点的技术:Monkey Patching,利用它可以修改原有的程序逻辑
- 直接修改了dispatch的调用过程
- 在调用dispatch的过程中,真正调用的函数其实是dispatchAndLog
// 导出之前,做一层拦截,派发action之前,做一些事情,比如 => 打印日志
function interceptStoreLog(store) {// 1. 拿到原始的dispatch方法const next = store.dispatch;// 2. 重写dispatch方法 => 在组件中派发action的时候,其实就是调用的这个方法store.dispatch = function dispatchAndLog(action) {console.log('当前派发的action', action);// 3. 调用原始的dispatch方法,派发actionnext(action);console.log('派发之后的结果', store.getState());};
}
interceptStoreLog(store);export default store;
3. 自定义实现thunk中间件
// 自定义实现thunk中间件
function thunkMiddleware(store) {// 1. 拿到原始的dispatch方法const next = store.dispatch;// 2. 重写dispatch方法 => 在组件中派发action的时候,其实就是调用的这个方法store.dispatch = function dispatchAndThunk(action) {// 3. 判断action是不是函数if (typeof action === 'function') {// 4. 如果是函数,就调用这个函数,并且传入dispatch和getState方法 => 使用新的dispatch方法,因为可能又派发了函数action(store.dispatch, store.getState);} else {// 5. 如果是对象,就调用原始的dispatch方法next(action);}};
}
thunkMiddleware(store);
4. 合并自定义的中间件
目录修改
log.js => middleware/log.js
// 导出之前,做一层拦截,派发action之前,做一些事情,比如 => 打印日志
export default function logMiddleware(store) {// 1. 拿到原始的dispatch方法const next = store.dispatch;// 2. 重写dispatch方法 => 在组件中派发action的时候,其实就是调用的这个方法store.dispatch = function dispatchAndLog(action) {console.log('当前派发的action', action);// 3. 调用原始的dispatch方法,派发actionnext(action);console.log('派发之后的结果', store.getState());};
}
thunk.js => middleware/thunk.js
// 自定义实现thunk中间件
export default function thunkMiddleware(store) {// 1. 拿到原始的dispatch方法const next = store.dispatch;// 2. 重写dispatch方法 => 在组件中派发action的时候,其实就是调用的这个方法store.dispatch = function dispatchAndThunk(action) {// 3. 判断action是不是函数if (typeof action === 'function') {// 4. 如果是函数,就调用这个函数,并且传入dispatch和getState方法 => 使用新的dispatch方法,因为可能又派发了函数action(store.dispatch, store.getState);} else {// 5. 如果是对象,就调用原始的dispatch方法next(action);}};
}
index.js => middleware/thunk.js
export { default as logMiddleware } from './log';
export { default as thunkMiddleware } from './thunk';
合并
封装一个函数来实现所有的中间件合并
七、React中的state如何管理
目前主要有三种状态管理方式:
- 方式一:组件中自己的state管理
- 方式二:Context数据的共享状态
- 方式三:Redux管理应用状态
推荐使用 :
- UI相关的组件内部可以维护的状态,在组件内部自己来维护
- 大部分需要共享的状态,都交给redux来管理和维护
- 从服务器请求的数据(包括请求的操作),交给redux来维护
- 根据不同的情况会进行适当的调整
相关文章:
![](https://img-blog.csdnimg.cn/370211db388947aca5d548e2216bfc54.png)
React 之 Redux - 状态管理
一、前言 1. 纯函数 函数式编程中有一个非常重要的概念叫纯函数,JavaScript符合函数式编程的范式,所以也有纯函数的概念 确定的输入,一定会产生确定的输出 函数在执行过程中,不能产生副作用 2. 副作用 表示在执行一个函数时&a…...
![](https://www.ngui.cc/images/no-images.jpg)
集合转数组
首先,我们在看到集合转数组的时候可能第一个想到的就是toArray(),但是我们在调用 toArray()的时候,可能会遇到异常 java.lang.ClassCastException;这是因为 toArray()方法返回的类型是 Obejct[],如果我们将其转换成其他类型&#…...
![](https://img-blog.csdnimg.cn/39f54fc3bcdf48f1a8dc353a65f81005.png)
使用Python将Word文档转换为PDF的方法
摘要: 文介绍了如何使用Python编程语言将Word文档转换为PDF格式的方法。我们将使用python-docx和pywin32库来实现这个功能,这些库提供了与Microsoft Word应用程序的交互能力。 正文: 在现实生活和工作中,我们可能会遇到将Word文…...
![](https://www.ngui.cc/images/no-images.jpg)
Java 判断一个字符串在另一个字符串中出现的次数
1.split实现 package com.jiayou.peis.official.account.biz.utils;public class Test {public static void main(String[] args) {String k"0110110100100010101111100101011001101110111111000101101001100010101" "011101100101011010100011111010111001001…...
![](https://www.ngui.cc/images/no-images.jpg)
设计模式十三:代理(Proxy Pattern)
代理模式是一种结构型设计模式,它允许通过在对象和其真实服务之间添加一个代理对象来控制对该对象的访问。代理对象充当了客户端和真实服务对象之间的中介,并提供了额外的功能,如远程访问、延迟加载、访问控制等。 代理模式的使用场景包括&a…...
![](https://img-blog.csdnimg.cn/5b13eedbaf6f411cb1a5e91710690144.png)
Redis基础 (三十八)
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 一、概述 1.1 NoSQL 1.2 Redis 二、安装 2.1 安装方式 : 三、目录结构 3.1 rpm -ql redis 3.2 /etc/redis.conf 主配置文件 3.3 /var/lib/redis …...
![](https://www.ngui.cc/images/no-images.jpg)
maven中的scope
1、compile:默认值,可省略不写。此值表示该依赖需要参与到项目的编译、测试以及运行周期中,打包时也要包含进去。 2、test:该依赖仅仅参与测试相关的工作,包括测试代码的编译和执行,不会被打包,…...
![](https://img-blog.csdnimg.cn/e1a600d2d03f475f815ae785d1f3c5bc.png)
【网络基础实战之路】实现RIP协议与OSPF协议间路由交流的实战详解
系列文章传送门: 【网络基础实战之路】设计网络划分的实战详解 【网络基础实战之路】一文弄懂TCP的三次握手与四次断开 【网络基础实战之路】基于MGRE多点协议的实战详解 【网络基础实战之路】基于OSPF协议建立两个MGRE网络的实验详解 PS:本要求基于…...
![](https://img-blog.csdnimg.cn/1155249777944b20af420272a301cb1d.png)
CNN(四):ResNet与DenseNet结合--DPN
🍨 本文为🔗365天深度学习训练营中的学习记录博客🍖 原作者:K同学啊|接辅导、项目定制 前面实现了ResNet和DenseNet的算法,了解了它们有各自的特点: ResNet:通过建立前面层与后面层之间的“短路…...
![](https://img-blog.csdnimg.cn/img_convert/7046751de11a706ac05bf59a967f870e.png)
汽车EBSE测试流程分析(四):反思证据及当前问题解决
EBSE专题连载共分为“五个”篇章。此文为该连载系列的“第四”篇章,在之前的“篇章(三)”中已经结合具体研究实践阐述了“步骤二,通过系统调研确定改进方案”等内容。那么,在本篇章(四)中&#…...
![](https://img-blog.csdnimg.cn/2021060109523638.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDc5NzUzOQ==,size_16,color_FFFFFF,t_70)
如何在Spring MVC中使用@ControllerAdvice创建全局异常处理器
文章目录 前言一、认识注解:RestControllerAdvice和ExceptionHandler二、使用步骤1、封装统一返回结果类2、自定义异常类封装3、定义全局异常处理类4、测试 总结 前言 全局异常处理器是一种 🌟✨机制,用于处理应用程序中发生的异常ÿ…...
![](https://img-blog.csdnimg.cn/3b0a5d733f3e4c64b0feb4ce4617dbb7.png)
2023/08/05【网络课程总结】
1. 查看git拉取记录 git reflog --dateiso|grep pull2. TCP/IP和OSI七层参考模型 3. DNS域名解析 4. 预检请求OPTIONS 5. 渲染进程的回流(reflow)和重绘(repaint) 6. V8解析JavaScript 7. CDN负载均衡的简单理解 8. 重学Ajax 重学Ajax满神 9. 对于XML的理解 大白话叙述XML是…...
![](https://www.ngui.cc/images/no-images.jpg)
log_softmax比softmax更好?
多类别分类的一个trick 探讨一下在多类别分类场景,如翻译、生成、目标检测等场景下,使用log_softmax的效果优于softmax的原因。 假设词典大小为10,一个词的ID为9(即词典的最后一个词),使用交叉熵作为损失函…...
![](https://img-blog.csdnimg.cn/156dfb26519a4e9cbf66eb3714f63c2d.png)
[LeetCode - Python]344.反转字符串(Easy);345. 反转字符串中的元音字母(Easy);977. 有序数组的平方(Easy)
1.题目 344.反转字符串(Easy) 1.代码 class Solution:def reverseString(self, s: List[str]) -> None:"""Do not return anything, modify s in-place instead."""# 双指针left,right 0, len(s)-1while left < right:temp s[left]s[…...
![](https://static.oschina.net/uploads/img/202308/04230002_woG6.png)
【SOP】最佳实践之 TiDB 业务写变慢分析
作者: 李文杰_Jellybean 原文来源: https://tidb.net/blog/d3d4465f 前言 在日常业务使用或运维管理 TiDB 的过程中,每个开发人员或数据库管理员都或多或少遇到过 SQL 变慢的问题。这类问题大部分情况下都具有一定的规律可循,…...
![](https://www.ngui.cc/images/no-images.jpg)
带有参数的 PL/SQL 过程/函数从选择查询返回表
技术标签: 【中文标题】带有参数的 PL/SQL 过程/函数从选择查询返回表【英文标题】:PL/SQL Procedure/function with params to return a table from(of) a select query【发布时间】:2020-12-01 11:17:49【问题描述】: 如何创建带参数的 (…...
![](https://img-blog.csdnimg.cn/c13d113b9c52447abf06a00162d9abd8.png)
文件的权限
1、修改文件的所属者和所属组 2、修改文件某一类人(所属者、所属组、其他人)的权限 一、用户对于普通文件的权限 二、用户对于目录文件的权限 三、访问控制列表ACL 四、特殊权限(了解) wuneng创建了几个文件,xiaoming对…...
![](https://www.ngui.cc/images/no-images.jpg)
vue3集成echarts最佳实践
安装 echarts npm install echarts --save 两种引用方式 非虚拟 dom import * as echarts from echarts;var chartDom document.getElementById(mychart); var myChart echarts.init(chartDom); var option;option {title: {text: Referer of a Website,subtext: Fake Da…...
![](https://img-blog.csdnimg.cn/42c2876637e14b6e8d1202d3b5e78b14.gif)
一位年薪40W的测试被开除,回怼的一番话,令人沉思
一位年薪40W测试工程师被开除回怼道:“反正我有技术,在哪不一样” 一技傍身,万事不愁,当我们掌握了一技之长后,在职场上说话就硬气了许多,不用担心被炒,反过来还可以炒了老板,这一点…...
![](https://img-blog.csdnimg.cn/0ca64043f803413b81c7746a154dcc94.png)
网络适配器和MAC地址
点对点信道:由于目的地只有一个选项,所以数据链路层不需要使用地址。 而在广播信道中: 各个主机如何判断信号是不是发给自己的? 当多个主机连接在同一个广播信道上,要想实现两个主机之间的通信,则每个主机都…...
![](https://www.ngui.cc/images/no-images.jpg)
react-player静音不能自动播放问题
现象 移动端不能自动播放 原因 取决于您使用的浏览器,但muted如果您不想与autoplay用户交互,则必须使用视频。 Chrome 的自动播放策略很简单: 始终允许静音自动播放。在以下情况下允许自动播放声音: 用户与域进行了交互&#x…...
![](/Public/Uploads/article/20230724/1690170170@d2b78402da57b11308056062a45d510b.jpg)
培训Java技术要多久才能学会?答案都在这里啦
培训Java技术要多久才能学会?这是想学习Java开发的很多人都会问到的一个问题。而这个问题的答案其实并不是那么简单,因为学Java的时间长短受到众多因素的影响。本文将从个人基础、学习动力和学习效率三个方面来为您解答这个问题。 1. 个人基础 自己的基础对于学习…...
![](https://www.ngui.cc/images/no-images.jpg)
Java中使用HttpPost发送form格式的请求
在Java中使用HttpPost发送form格式的请求,可以使用Apache HttpClient库来实现。以下是一个示例代码: import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client…...
![](https://img-blog.csdnimg.cn/4bc86d3bbcd54fff9a515b00034cceda.png)
C语言----字节对齐
一:字节对齐的概念 针对字节对齐,百度百科的解释如下: 字节对齐是字节按照一定规则在空间上排列,字节(Byte)是计算机信息技术用于计量存储容量和传输容量的一种计量单位,一个字节等于8位二进制数,在UTF-8编…...
![](https://www.ngui.cc/images/no-images.jpg)
Next.js入门介绍(服务端渲染)
Next.js 一 目录 不折腾的前端,和咸鱼有什么区别 目录一 目录二 前言三 设置四 多页面五 链接六 样式七 共享组件八 布局组件九 实战 9.1 目录结构 9.2 UI 组件 9.3 Markdown 内容 9.4 Pages 入口和 API 9.4.1 服务端渲染 9.5 Public 静态资源 9.6 resor…...
![](https://www.ngui.cc/images/no-images.jpg)
模板Plus
文章目录 1.非类型模板参数的引入2.标准库和普通数组3.模板的特化 1.非类型模板参数的引入 //非类型模板参数 -- 常量 template<class T, size_t N 10> class array { private:T _a[N]; };int main() {array<int> a1;array<int, 100> a2;array<double, …...
![](https://www.ngui.cc/images/no-images.jpg)
spring事务和数据库事务是怎么实现
Spring事务的原理 Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。对于纯JDBC操作数据库,想要用到事务,可以按照以下步骤进行: 获取连接 Connection con DriverManag…...
![](https://www.ngui.cc/images/no-images.jpg)
el-date-picker设置默认当前日期
HTMl部分: <el-form-item label"拍摄时间:"><el-date-pickerv-model"searchData.filmingTimeRange"type"daterange"align"right"unlink-panelsrange-separator"至"start-placeholder"…...
![](https://img-blog.csdnimg.cn/1ed0720a63e74f2cb61a4d21de92734f.png)
vue中使用this.$refs获取不到子组件的方法,属性方法都为undefined的解决方法
问题描述 vue2中refs获取不到子组件中的方法?,而获取到的是undefined 原因及解决方案: 第一种、在循环中注册了很多个ref 因为注册了多个ref,获取是不能单单知识refs.xxx,需要使用数组和索引来获取具体一个组件refs[…...
![](https://img-blog.csdnimg.cn/6a3ac5f62f3845d39f11a8bbcf2b1977.png)
Linux命令200例:df用于显示文件系统的磁盘空间使用情况
🏆作者简介,黑夜开发者,全栈领域新星创作者✌。CSDN专家博主,阿里云社区专家博主,2023年6月csdn上海赛道top4。 🏆数年电商行业从业经验,历任核心研发工程师,项目技术负责人。 &…...
![](/images/no-images.jpg)
网站推广流程是/新媒体运营哪个培训机构好
[b]冒泡排序[/b](Bubble Sort,台灣譯為:泡沫排序或氣泡排序)是一種簡單的排序算法。它重複地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重複地進行直到沒…...
![](/images/no-images.jpg)
网店营销活动策划方案/网站seo教程
在不知道密码的情况下修改密码1、不加载权限表,所有用户可登陆并拥有所有权限,可通过flush privileges或reload重新加载权限表[mysqlmysql5 db_master01]$ mysqld --defaults-file/mysqldb/db_master01/conf/master01.cnf --skip-grant-tables2、进入数据…...
![](/images/no-images.jpg)
上海做网站的公/做网站的流程与步骤
最高人民法院《关于适用<中华人民共和国民法典>婚姻家庭编的解释(一)》第二十九条规定:“当事人结婚前,父母为双方购置房屋出资的,该出资应当认定为对自己子女个人的赠与,但父母明确表示赠与双方的除…...
医院网站建设哪家好/百度竞价排名点击软件
一、判断题:T为正确F为错误 C程序的基本组成单位是函数TC程序可以由一个或多个函数组成TC语言可以不用编译就能被计算机识别执行Fmain()函数必须放在其它函数之前F每个C程序中都必须要有一个main()函数T在C程序中main()函数的位置是固定的FC程序中的main()函数必须…...
![](https://img-blog.csdnimg.cn/img_convert/c179e57f968050155d63e618d465773a.png)
青岛做网站公司/网络营销方案的制定
做毕设需要做目标识别的内容,需要GPU进行训练,参考了很多装Cuda的博客和教程,也大致看了一下官方的安装文档,经过了三个月断断续续的摸索,终于把实验室的电脑和自己的电脑成功装上了cuda8.0版本,并在后续装…...
![](https://img-blog.csdnimg.cn/20200426164519882.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2x1emhlbnNtYXJ0,size_16,color_FFFFFF,t_70)
wordpress后台语言设置/深圳搜索引擎优化推广便宜
1:什么是同步队列(CLH) 同步队列 一个FIFO双向队列,队列中每个节点等待前驱节点释放共享状态(锁)被唤醒就可以了。 AQS如何使用它? AQS依赖它来完成同步状态的管理,当前线程如果获取同步状态…...