【React系列】Redux(一)管理状态

本文来自#React系列教程:https://mp.weixin.qq.com/mp/appmsgalbum?__biz=Mzg5MDAzNzkwNA==&action=getalbum&album_id=1566025152667107329)

在React的开发过程中,Redux对于我们是非常重要的。
但是对于很多人来说,初次接触redux会感觉redux的学习坡度非常陡峭,难度比较大。
在接下来,我会通过一个系列的方式循循渐进、层层深入的方式来讲解Redux:从redux的独立使用过程、到redux的结构划分、到redux结合react的过程、到redux中异步操作、到复杂数据的结构处理、到结合immutable的使用。
整个过程,都是在现有知识的基础之上层层递增、一步步掌握redux的使用!

一. 认识Redux

1.1. JavaScript纯函数

函数式编程中有一个概念叫纯函数,JavaScript符合函数式编程的范式,所以也有纯函数的概念;

在React中,纯函数的概念非常重要,在接下来我们学习的Redux中也非常重要,所以我们有必须来回顾(如果你之前没有学过,那么你就是学习)一下纯函数。

纯函数的维基百科定义:

在程序设计中,若一个函数符合以下条件,那么这个函数被称为纯函数:

  • 此函数在相同的输入值时,需产生相同的输出。函数的输出和输入值以外的其他隐藏信息或状态无关,也和由I/O设备产生的外部输出无关。
  • 该函数不能有语义上可观察的函数副作用,诸如“触发事件”,使输出设备输出,或更改输出值以外物件的内容等。

当然上面的定义会过于的晦涩,所以我简单总结一下:

  • 确定的输入,一定会产生确定的输出;
  • 函数在执行过程中,不能产生副作用;

那么我们来看几个函数是否是纯函数:

案例一:

function sum(num1, num2) {return num1 + num2;
}
  • 很明显,上面的函数是一个纯函数;
  • 它的输出是依赖我们的输入内容,并且中间没有产生任何副作用;

案例二:

let foo = 5;function add(num) {return foo + num;
}console.log(add(5));
foo = 10;
console.log(add(5));
  • add函数不是一个纯函数;
  • 函数依赖一个外部的变量,变量发生改变时,会影响:确定的输入,产生确定的输出;
  • 能否改进成纯函数呢?const foo = 5; 即可

案例三:

function printInfo(info) {console.log(info.name, info.age);info.name = "哈哈哈";
}
  • printInfo不是一个纯函数;
  • 虽然无论输入什么,最终输出都是undefined,但是它产生了副作用,修改了传入的对象;

当然纯函数还有很多的变种,但是我们只需要理解它的核心就可以了。

为什么纯函数在函数式编程中非常重要呢?

  • 因为你可以安心的写安心的用
  • 你在写的时候保证了函数的纯度,只是实现自己的业务逻辑即可,不需要关心传入的内容或者依赖其他的外部变量;
  • 你在用的时候,你确定你的输入内容不会被任意篡改,并且自己确定的输入,一定会有确定的输出;

React 非常灵活,但它也有一个严格的规则:

  • 所有的 React 组件(无论是函数组件还是class声明的组件),都必须像纯函数一样,保护它们的props不被修改。

在之后学习redux中,reducer也被要求是一个纯函数。

1.2. 认识Redux

1.2.1. 为什么需要redux?

JavaScript开发的应用程序,已经变得越来越复杂了:

  • JavaScript需要管理的状态越来越多,越来越复杂;
  • 这些状态包括服务器返回的数据、缓存数据、用户操作产生的数据等等,也包括一些UI的状态,比如某些元素是否被选中,是否显示加载动效,当前分页;

管理不断变化的state是非常困难的:

  • 状态之间相互会存在依赖,一个状态的变化会引起另一个状态的变化,View页面也有可能会引起状态的变化;
  • 当应用程序复杂时,state在什么时候,因为什么原因而发生了变化,发生了怎么样的变化,会变得非常难以控制和追踪;

React是在视图层帮助我们解决了DOM的渲染过程,但是State依然是留给我们自己来管理:

  • 无论是组件定义自己的state,还是组件之间的通信通过props进行传递;也包括通过Context进行数据之间的共享;
  • React主要负责帮助我们管理视图,state如何维护最终还是我们自己来决定;

在这里插入图片描述

Redux就是一个帮助我们管理State的容器:Redux是JavaScript的状态容器,提供了可预测的状态管理;

Redux除了和React一起使用之外,它也可以和其他界面库一起来使用(比如Vue),并且它非常小(包括依赖在内,只有2kb)

1.2.2. redux的核心理念

Redux的核心理念非常简单。

比如我们有一个朋友列表需要管理:

const initialState = {friends: [{ name: "why", age: 18 },{ name: "kobe", age: 40 },{ name: "lilei", age: 30 },]
};

如果我们没有定义统一的规范来操作这段数据,那么整个数据的变化就是无法跟踪的;

  • 比如页面的某处通过friends.push的方式增加了一条数据;
  • 比如另一个页面通过friends[0].age = 25修改了一条数据;
  • 整个应用程序错综复杂,当出现bug时,很难跟踪到底哪里发生的变化;

Redux要求我们通过action来更新数据:

  • 所有数据的变化,必须通过派发(dispatch)action来更新;
  • action是一个普通的JavaScript对象,用来描述这次更新的typecontent

比如下面就是几个更新friendsaction

const action1 = { type: "ADD_FRIEND", info: { name: "lucy", age: 20 } }
const action2 = { type: "INC_AGE", index: 0 }
const action3 = { type: "CHANGE_NAME", playload: { index: 0, newName: "coderwhy" } }
  • 强制使用action的好处是可以清晰的知道数据到底发生了什么样的变化,所有的数据变化都是可跟追、可预测的;
  • 当然,目前我们的action是固定的对象,真实应用中,我们会通过函数来定义,返回一个action

但是如何将stateaction联系在一起呢?答案就是reducer

  • reducer是一个纯函数;
  • reducer做的事情就是将传入的stateaction结合起来生成一个新的state
function reducer(state = initialState, action) {switch (action.type) {case "ADD_FRIEND":return { ...state, friends: [...state.friends, action.info] }case "INC_AGE":return {...state, friends: state.friends.map((item, index) => {if (index === action.index) {return { ...item, age: item.age + 1 }}return item;})}case "CHANGE_NAME":return {...state, friends: state.friends.map((item, index) => {if (index === action.index) {return { ...item, name: action.newName }}return item;})}default:return state;}
}

这个reducer有点像 Android 的 HandlerhandleMessage() 方法中根据不同的消息命令进行分发处理的逻辑。

1.2.3. redux的三大原则

1. 单一数据源

整个应用程序的state被存储在一颗object tree中,并且这个object tree只存储在一个 store 中:

  • Redux并没有强制让我们不能创建多个Store,但是那样做并不利于数据的维护;
  • 单一的数据源可以让整个应用程序的state变得方便维护、追踪、修改;

2. State是只读的

唯一修改State的方法一定是触发action,不要试图在其他地方通过任何的方式来修改State

  • 这样就确保了View或网络请求都不能直接修改state,它们只能通过action来描述自己想要如何修改state
  • 这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行,所以不需要担心race condition(竟态)的问题;

3. 使用纯函数来执行修改

通过 reducerstateactions联系在一起,并且返回一个新的State

  • 随着应用程序的复杂度增加,我们可以将reducer拆分成多个小的reducers,分别操作不同state tree的一部分;
  • 但是所有的reducer都应该是纯函数,不能产生任何的副作用;

二. redux的基本使用

2.1. redux使用过程

安装redux

npm install redux --save
# 或
yarn add redux

这里,我通过创建一个简单的js文件,我们先来简单学习一下redux

搭建项目结构

  1. 创建一个新的项目文件夹:learn-redux
# 执行初始化操作
yarn init# 安装redux
yarn add redux
  1. 创建src目录,并且创建index.js文件

  2. 修改package.json可以执行index.js

"scripts": {"start": "node src/index.js"
}

在这里插入图片描述

开始在index.js中编写代码

  1. 创建一个对象,作为我们要保存的状态:
const redux = require('redux');const initialState = {counter: 0
}
  1. 创建Store来存储这个state
  • 创建store时必须创建reducer
  • 我们可以通过 store.getState 来获取当前的state
// 创建reducer
const reducer = (state = initialState, action) => {return state;
}// 根据reducer创建store
const store = redux.createStore(reducer);
console.log(store.getState());
  1. 通过action来修改state
  • 通过dispatch来派发action
  • 通常action中都会有type属性,也可以携带其他的数据;
store.dispatch({type: "INCREMENT"
})store.dispath({type: "DECREMENT"
})store.dispatch({type: "ADD_NUMBER",number: 5
})
  1. 修改reducer中的处理代码
  • 这里一定要记住,reducer是一个纯函数,不需要直接修改state
  • 后面我会讲到直接修改state带来的问题;
const reducer = (state = initialState, action) => {switch (action.type) {case "INCREMENT":return {...state, counter: state.counter + 1};case "DECREMENT":return {...state, counter: state.counter - 1};case "ADD_NUMBER":return {...state, counter: state.counter + action.number}default: return state;}
}
  1. 可以在派发action之前,监听store的变化:
store.subscribe(() => {console.log(store.getState());
})

完成的案例代码如下:

const redux = require('redux');const initialState = {counter: 0
}// 创建reducer
const reducer = (state = initialState, action) => {switch (action.type) {case "INCREMENT":return {...state, counter: state.counter + 1};case "DECREMENT":return {...state, counter: state.counter - 1};case "ADD_NUMBER":return {...state, counter: state.counter + action.number}default: return state;}
}// 根据reducer创建store
const store = redux.createStore(reducer);store.subscribe(() => {console.log(store.getState());
})// 修改store中的state
store.dispatch({type: "INCREMENT"
})
// console.log(store.getState());store.dispatch({type: "DECREMENT"
})
// console.log(store.getState());store.dispatch({type: "ADD_NUMBER",number: 5
})
// console.log(store.getState());

2.2. redux结构划分

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

接下来,我会对代码进行拆分,将store、reducer、action、constants拆分成一个个文件。

注意:node中对ES6模块化的支持

目前我使用的node版本是v12.16.1,从node v13.2.0开始,node才对ES6模块化提供了支持:

node v13.2.0之前,需要进行如下操作:

  • package.json中添加属性:"type": "module"
  • 在执行命令中添加如下选项:node --experimental-modules src/index.js;

node v13.2.0之后,只需要进行如下操作:

  • package.json中添加属性:"type": "module"

注意:导入文件时,需要跟上.js后缀名;

redux结构进行划分

创建store/index.js文件:

import redux from 'redux';
import reducer from './reducer.js';const store = redux.createStore(reducer);export default store;

创建store/reducer.js文件:

import {ADD_NUMBER,SUB_NUMBER
} from './constants.js';const initialState = {counter: 0
}function reducer(state = initialState, action) {switch(action.type) {case ADD_NUMBER:return {...state, counter: state.counter + action.num};case SUB_NUMBER:return {...state, counter: state.counter - action.num};default:return state;} 
}export default reducer;

创建store/actionCreators.js文件:

import {ADD_NUMBER,SUB_NUMBER
} from './constants.js'const addAction = (count) => ({type: ADD_NUMBER,num: count
});const subAction = (count) => ({type: SUB_NUMBER,num: count
})export {addAction,subAction
}

创建store/constants.js文件:

const ADD_NUMBER = "ADD_NUMBER";
const SUB_NUMBER = "SUB_NUMER";export {ADD_NUMBER,SUB_NUMBER
}

2.3. Redux流程图

我们已经知道了redux的基本使用过程,那么我们就更加清晰来认识一下redux在实际开发中的流程:

  1. 全局通常只有一个Store,存储我们的State
  2. Component中在某些情况会派发Action(这些Action是我们提前定义好的);
  3. Reducer会接收到这些Action,并且在Reducer中会返回一个新的State,作为StoreState
  4. State发生更新之后会触发通知,告知订阅者数据发生了改变;
  5. 订阅者拿到最新的数据(在props中),更新到jsx中,界面发生改变;

在这里插入图片描述

在这里插入图片描述

三. react结合redux

3.1. redux融入react代码

目前redux在react中使用是最大的,所以我们需要将之前编写的redux代码,融入到react当中去。

这里我创建了两个组件:

  • Home组件:其中会展示当前的counter值,并且有一个+1和+5的按钮;
  • Profile组件:其中会展示当前的counter值,并且有一个-1和-5的按钮;
    在这里插入图片描述

home.js代码实现:

import React, { PureComponent } from 'react';import store from '../store';
import {addAction
} from '../store/actionCreators';export default class Home extends PureComponent {constructor(props) {super(props);this.state = {counter: store.getState().counter}}componentDidMount() {store.subscribe(() => {this.setState({counter: store.getState().counter})})}render() {return (<div><h1>Home</h1><h2>当前计数: {this.state.counter}</h2><button onClick={e => this.increment()}>+1</button><button onClick={e => this.addCounter()}>+5</button></div>)}increment() {store.dispatch(addAction(1));}addCounter() {store.dispatch(addAction(5));}
}

Profile.js代码实现:

import React, { PureComponent } from 'react';import store from '../store';
import {subAction
} from '../store/actionCreators';export default class Profile extends PureComponent {constructor(props) {super(props);this.state = {counter: store.getState().counter}}componentDidMount() {store.subscribe(() => {this.setState({counter: store.getState().counter})})}render() {return (<div><hr/><h1>Profile</h1><div><h2>当前计数: {this.state.counter}</h2><button onClick={e => this.decrement()}>-1</button><button onClick={e => this.subCounter()}>-5</button></div></div>)}decrement() {store.dispatch(subAction(1));}subCounter() {store.dispatch(subAction(5));}
}

上面的代码其实非常简单,核心代码主要是两个:

  • componentDidMount 中定义数据的变化,当数据发生变化时重新设置 counter;
  • 在发生点击事件时,调用storedispatch来派发对应的action

不要忘记取消订阅:

在这里插入图片描述

3.2. 自定义 connect 函数

上面的代码是否可以实现react组件和redux结合起来呢?

  • 当然是可以的,但是我们会发现每个使用的地方其实会有一些重复的代码:
  • 比如监听store数据改变的代码,都需要在 componentDidMount中完成;
  • 比如派发事件,我们都需要去先拿到 store, 在调用其 dispatch 等;

能否将这些公共的内容提取出来呢?

我们来定义一个connect函数:

这个connect函数本身接受两个参数:

  • 参数一:里面存放 component 希望使用到的 State 属性;
  • 参数二:里面存放 component 希望使用到的 dispatch动作;

这个connect函数有一个返回值,是一个高阶组件:

  • constructor中的state中保存一下我们需要获取的状态;
  • componentDidMount中订阅store中数据的变化,并且执行 setState操作;
  • componentWillUnmount中需要取消订阅;
  • render函数中返回传入的WrappedComponent,并且将所有的状态映射到其props中;
  • 这个高阶组件接受一个组件作为参数,返回一个class组件;
  • 在这个class组件中,我们进行如下操作:
import React, { PureComponent } from "react";import store from '../store';export default function connect(mapStateToProps, mapDispatchToProps) {return function handleMapCpn(WrappedComponent) {return class extends PureComponent {constructor(props) {super(props);this.state = {storeState: mapStateToProps(store.getState())}}componentDidMount() {this.unsubscribe = store.subscribe(() => {this.setState({storeState: mapStateToProps(store.getState())})})}componentWillUnmount() {this.unsubscribe();}render() {return <WrappedComponent {...this.props} {...mapStateToProps(store.getState())}{...mapDispatchToProps(store.dispatch)}/>}}}
}

homeprops文件中,我们按照自己需要的state、dispatch来进行映射:

比如home.js中进行如下修改:

const mapStateToProps = state => {return {counter: state.counter}
}const mapDispatchToProps = dispatch => {return {addNumber: function(number) {dispatch(addAction(number));}}
}export default connect(mapStateToProps, mapDispatchToProps)(Home);
  • mapStateToProps:用于将state映射到一个对象中,对象中包含我们需要的属性;
  • mapDispatchToProps:用于将dispatch映射到对象中,对象中包含在组件中可能操作的函数;当调用该函数时,本质上其实是调用dispatch(对应的Action)

Profile中也是类似的操作。

有了connect函数,我们之后只需要关心从statedispatch中映射自己需要的状态和行为即可;

3.3. store的context处理

但是上面的connect函数有一个很大的缺陷:依赖导入的store

如果我们将其封装成一个独立的库,需要依赖用于创建的store,我们应该如何去获取呢?
难道让用户来修改我们的源码吗?不太现实;

正确的做法是我们提供一个ProviderProvider来自于我们创建的Context,让用户将store传入到value中即可;

创建一个context.js文件:

import { createContext } from 'react';export const StoreContext = createContext();

修改connect函数中class组件部分的代码:

  • 注意下面我们将class组件的名称明确的定义出来,并且给它的contextType进行了赋值;
  • 在组件内部用到store的地方,统一使用this.context代替(注意:constructor中直接使用第二个参数即可)
import React, { PureComponent } from "react";import { StoreContext } from './context';export default function connect(mapStateToProps, mapDispatchToProps) {return function handleMapCpn(WrappedComponent) {class ConnectCpn extends PureComponent {constructor(props, context) {super(props);this.state = {storeState: mapStateToProps(context.getState())}}componentDidMount() {this.unsubscribe = this.context.subscribe(() => {this.setState({storeState: mapStateToProps(this.context.getState())})})}componentWillUnmount() {this.unsubscribe();}render() {return <WrappedComponent {...this.props}{...mapStateToProps(this.context.getState())}{...mapDispatchToProps(this.context.dispatch)} />}}ConnectCpn.contextType = StoreContext;return ConnectCpn;}
}

在入口的index.js中,使用Provider并且提供store即可:

import { StoreContext } from './utils/context';
import store from './store';ReactDOM.render(<StoreContext.Provider value={store}><App /></StoreContext.Provider>,document.getElementById('root')
);

测试代码,依然可以正常运行。

四. react-redux使用

4.1. react-redux的使用

开始之前需要强调一下,redux和react没有直接的关系,你完全可以在React, Angular, Ember, jQuery, or vanilla JavaScript中使用Redux。

尽管这样说,redux依然是和React或者Deku的库结合的更好,因为他们是通过state函数来描述界面的状态,Redux可以发射状态的更新,让他们作出相应。

虽然我们之前已经实现了connectProvider这些帮助我们完成连接reduxreact的辅助工具,但是实际上redux官方帮助我们提供了 react-redux 的库,可以直接在项目中使用,并且实现的逻辑会更加的严谨和高效。

安装react-redux

yarn add react-redux

使用connect函数:

  • 将之前使用的connect函数,换成react-reduxconnect函数;
import React, { PureComponent } from 'react';
import { connect } from "react-redux";// import connect from '../utils/connect2';export default connect(mapStateToProps, mapDispatchToProps)(Home);

使用Provider

  • 将之前自己创建的ContextProvider,换成react-reduxProvider组件:
  • 注意:这里传入的是store属性,而不是value属性(待会儿可以在源码中查看);
import { Provider } from 'react-redux';import store from './store';ReactDOM.render(<Provider store={store}><App /></Provider>,document.getElementById('root')
);

2.2. react-redux的源码

这里我简单带着大家看一下react-redux的源码:

  • 但是第一因为这个教程不是讲源码为主的教程(穿插讲解部分源码),所以源码只会阅读核心的部分;
  • 另外我经常会说,整个社区在hooks出现后大量的库转向了hooks,所以在源码中会出现大量的hooks代码;
  • 因为目前还没有讲解hooks相关的API,所以某些hooks的作用在这里也不方便解释(可以学习完hooks之后再详细阅读);

首先,我们简单看一下Provider的源码:

在这里插入图片描述

  • 使用了一个useMemo来返回一个contextValue的对象;
  • 这里使用useMemo的原因是为了进行性能的优化;
  • 在依赖的store不改变的情况下,不会进行重新计算,返回一个新的对象;
  • 在下面的ContextProvider中就会将其赋值给value属性;

ReactReduxContext来自另外一个文件:

在这里插入图片描述

connect函数的依赖比较复杂:

  • 调用createConnect来返回一个connect函数:

在这里插入图片描述

createConnect函数的调用:

在这里插入图片描述

connect函数最终调用的是connectHOC

  • connectHOC其实是connectAdvanced的函数;
  • connectAdvanced函数最终返回的是wrapWithConnect函数;

在这里插入图片描述

wrapWithConnect函数:

在这里插入图片描述
在这里插入图片描述

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

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

相关文章

基于SSM的人事档案管理系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

1.大数据概述

目录 概述hadoophadoop 模块hadoop 发行版apache社区版本CDP(CDHHDP)其它云产商框架选择 hadoop 安装 结束 概述 先了解几个常用的网站 apache 官网hadoop 官网hadoop githubhttps://github.com/apache/xxx [https://github.com/apache/spark (example)] hadoop hadoop 模块…

ssm基于web的网络游戏交易平台信息管理系统的设计与实现论文

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装网络游戏交易平台软件来发挥其高效地信息处理的作用&#x…

基于帝国主义竞争算法优化的Elman神经网络数据预测 - 附代码

基于帝国主义竞争算法优化的Elman神经网络数据预测 - 附代码 文章目录 基于帝国主义竞争算法优化的Elman神经网络数据预测 - 附代码1.Elman 神经网络结构2.Elman 神经用络学习过程3.电力负荷预测概述3.1 模型建立 4.基于帝国主义竞争优化的Elman网络5.测试结果6.参考文献7.Matl…

【Java 进阶篇】Nginx 使用详解:搭建高性能的 Web 服务器

在互联网的世界里&#xff0c;Web 服务器是我们访问网站、获取信息的入口。Nginx&#xff08;发音"engine x"&#xff09;作为一款轻量级、高性能的 Web 服务器和反向代理服务器&#xff0c;因其出色的性能和可扩展性而备受推崇。本文将围绕 Nginx 的使用进行详解&am…

数据结构学习 jz56数组中数字出现的次数

关键词&#xff1a;位运算 异或性质 虽然有两道题&#xff0c;但是其实应该分成三个级别的题目。 题目一&#xff1a; 一个整型数组 sockets 里除 一个 数字之外&#xff0c;其他数字都出现了两次。 思路&#xff1a;异或的性质 复杂度计算&#xff1a; 时间复杂度O(n) 空…

Python字符串

目录 1 创建字符串的三种方式2 字符串的转义3 字符串的格式化输出4 字符串的索引5 字符串的切片6 字符串的拼接7 计算字符串的长度8 判断字符串是否存在 字符串是编程中经常使用到的概念&#xff0c;熟悉字符串的常见用法是掌握编程的必经之路&#xff0c;本篇介绍一下字符串的…

ASP.NET Core高级之认证与授权(一)--JWT入门-颁发、验证令牌

阅读本文你的收获 了解认证和授权的作用了解在ASP.NET Core中实现身份认证的技术都有哪些学习基于JWT认证并学会颁发和验证JWT令牌 一、重要的前置概念 在一个系统中&#xff0c;不是所有的功能和资源都能够被自由地访问&#xff0c;比如你存在银行系统里面的资金&#xff0c…

网络故障问题一般性检查排查思路

一、基本连通性检查 在网络中ping是一个十分强大的TCP/IP工具。它可以用来检测网络的连通情况和分析网络速度、也可以ping网址根据域名得到服务器IP、同时我们根据ping返回的TTL值来判断对方所使用的操作系统及数据包经过路由器数量。 ping 网址&#xff0c;有几种输出情况&a…

Linkage Mapper 工具参数详解——Centrality Mapper

【小白一学就会无需其他教程】此文档用于解析使用Linkage Mapper 各输入输出参数详情以及可能的影响&#xff0c;并介绍了如何解释模型输出结果和输出参数&#xff0c;适合刚入手的人。篇幅很长很啰嗦&#xff0c;是因为每个参数都解释的万分细致。 从以下链接中获取内容&…

软件测试基础理论学习-常见软件开发模型

瀑布模型 背景 瀑布模型的概念最早在1970年由软件工程师Winston W. Royce在其论文《Managing the Development of Large Software Systems》中提出。Royce虽然没有明确提出“瀑布模型”这个术语&#xff0c;但他描述了一种线性的、阶段性的开发流程&#xff0c;各个阶段之间具…

Windows系统中Wireshark抓包工具的安装使用

在使用Windows服务器时&#xff0c;如果我们发现网络流量异常或存在异常的外发数据包行为&#xff0c;我们可以利用抓包工具来捕获网络流量包&#xff0c;并对这些流量包进行特征分析&#xff0c;以查看其来源和目的地。通过这些信息&#xff0c;我们可以进一步诊断问题。 以下…

Linux下NUMA 内存管理初步理解

LINUX 系统在多核下,以及NUMA架构技术下 如何管理物理内存? 经过初步了解 发现系统对内存有以下工作 1 映射 2 内存碎片 3 内存回收 4 内存池 5 冷热页 6 水位线和保留内存 7 支持内存条热插拔 8 大页管理 这些功能给人感觉,尤其是DBA感觉是重新实现了数据缓存池的功…

EG网关网口连接触控一体机操作说明V1.0 20210809

前言&#xff1a;工业现场中&#xff0c;很多情况下都会使用触摸屏连接PLC这一套设备&#xff0c;此一体机是将触摸屏和PLC两款设备集成到一起&#xff0c;可以简化现场设备结构&#xff0c;方便维护。同时&#xff0c;使用蓝蜂 EG系列网关&#xff0c;可以实现远程在线监控数据…

代码随想录算法训练营第十五天| 二叉树 513. 找树左下角的值 112. 路径总和 106.从中序与后序遍历序列构造二叉树

513. 找树左下角的值 层序遍历 本题用层序遍历可以直接秒了&#xff0c;直接提取每一层中最左边的元素&#xff08;i0&#xff09;&#xff0c;然后保存到最后一层即可。 class Solution { public:int findBottomLeftValue(TreeNode* root) {queue<TreeNode*> que;int…

Java学习苦旅(十七)——栈和队列

本篇博客将详细讲解Java中的栈和队列。 文章目录 栈概念Java中Stack常用方法代码实现 队列概念队列常用方法对比QueueDeque 代码实现Queue 结尾 栈 概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一…

惊呆:RocketMQ顺序消息,是“4把锁”实现的(顺序消费)

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格&#xff0c;遇到很多关于RocketMQ 的、很重要的面试题&#xff1a; 如何保证RocketMQ消息有序&#xff1f; Ro…

应用层网络协议

tags: [“计算机网络”] descripution: “学习应用层的一些常用协议” 网络协议&#xff1a;约定的信息传输的格式&#xff0c;如几个字节是消息头、消息头记录什么信息之类的&#xff1b;c/s架构&#xff1a;不一定是两台计算机&#xff0c;而是两个应用、两个端口工具&#…

poium测试库之JavaScript API封装原理

为什么要封装JavaScript的API&#xff1f; 因为有些场景下Selenium提供的API并不能满足我们需求。比如&#xff0c;滑动浏览滚动条&#xff0c;控制元素的显示/隐藏&#xff0c;日历控件的操作等&#xff0c;都可以通过JavaScrip实现&#xff0c;而且Selenium为我们提供了 exe…

教你直接在 Midjourney 网站上创建图像

经过一年多的工作和使用 Midjourney 的乐趣&#xff0c;我们终于能够直接在网站上生成图像。许多以前没有使用过 Discord 的人对于只能在 Discord 上创建图像并不满意。现在&#xff0c;几乎所有人的等待终于结束了。 直接在 Midjourney 网站上生成图像 David Holz 在 Discor…