redux 是个状态管理工具,提供了一种良好的结构来管理状态和数据流。不依赖于某个库,可以在vue、react等项目中使用
Redux 的核心概念包括:
-
Store(存储): 一个包含应用状态的对象。整个应用的状态被保存在单一的 store 中。
-
Action(动作): 描述发生了什么的对象。Action 是一个普通的 JavaScript 对象,其中必须包含一个 type 字段用于描述动作的类型。
-
Reducer(处理器): 一个纯函数,接收当前状态和一个 action,并返回一个新的状态。Reducer 定义了状态的变化逻辑。
-
Dispatch(分发): 一个函数,用于将 action 发送到 reducer,从而触发状态的变化。在 Redux 中,通过 store.dispatch(action) 来分发 action。
-
Middleware(中间件): 一个函数,它可以拦截并处理 dispatch 的过程。中间件可以用于执行异步操作、日志记录、错误处理等。(这里最常用的就是redux-thunk,使redux支持异步的dispatch)
一、在node环境中使用redux
0、安装redux
npm i redux
1、创建store(创建一个index.js文件,在这里初始化store)
const { createStore } = require("redux");
const reducer = require("./reducer");
// createStore 有两个参数:
// 1、传入一个reducer: 就是执行action描述的方法
// 2、传入额外的中间件:这个后边在处理异步action中会使用
const store = createStore(reducer);
module.exports=store;
2、创建reducer(reducer.js)
const initalState = { // 初始化对象
counter: 0
};
function reducer(state = initalState, actions) {
switch (actions.type) {
case "counter_changer":
return { ...state, counter: state.counter + actions.num };
default: return state;
}
}
// 这个reducer 将会传给createStore函数。也就是当某个界面发出某个修改store的指令时,
// redux 帮我们执行这个reducer,并执行订阅的回调
module.exports = reducer;
3、创建测试文件(test.js)
const store = require("./index.js");
// store 提供了这个获取state函数,可以得到仓库中state当前的状态
console.log(store.getState()); // { counter: 0}
// store提供了一个订阅方法,当state中值被修改时就会执行回调
// 返回值为一个取消订阅的函数
const unSubscribe = store.subscribe(() => {
console.log("值被修改了!")
})
// 当我们想修改store 中某个值的状态时,需要用到dispatch这个方法
// dispatch接受一个对象为参数,这个对象就是action,相当于指令的描述
store.dispatch({type: "counter_changer", num: 12});
console.log(store.getState()); // { counter: 12 }
unSubscribe() // 取消订阅
4、actionCreators的封装
在步骤3中的dispatch(action),action可能被多个地方使用,而类型是相同的,只有num不会变。
所以项目中常常有一个叫做actionCreator的函数,传入num,返回action。
const store = require("./index.js");
const number_changer_action = (num) => ({type: "counter_changer", num});
// 这样使用这个action时执行这个函数,返回一个action对象
store.dispatch(number_changer_action(12))
5、异步的处理
问题:我们如果想将一个接口回调拿到的数据加入到action的payload中,需要在actionCreator返回action之前异步进行。但是dispatch 不能立马拿到想要的action。这里使用最多的是redux-thunk中间件,它使我们可以给dispatch传入一个异步函数,帮我们执行回调后修改state的内容。
npm i redux-thunk
对index.js进行改造
const { createStore, applyMiddleware } = require("redux");
const thunk = require("redux-thunk").thunk;
const reducer = require("./reducer");
// createStore 有两个参数:
// 1、传入一个reducer: 就是执行action描述的方法
// 2、传入额外的中间件:这时候将我们redux-thunk中间件就行插入
const store = createStore(reducer, applyMiddleware(thunk));
module.exports=store;
来测试一下支持异步的action生成器
const store = require("./index.js");
const number_changer_action = (num) => ({type: "counter_changer", num});
const number_changer_action_async = () => {
// 这里我们的action可以是一个函数,在thunk内部,他帮我们执行这个函数,
// 他会给我们传出两个参数分别是dispatch, getState
// 我们通过dispatch调用同步的action,也可以通过getState获取仓库状态
return (dispatch, getState) => {
// 模拟一个请求
new Promise((resolve) => {
setTimeout(() => {
resolve(100);
}, 1000);
}).then(res => {
dispatch(number_changer_action(res));
});
};
};
// 这样使用这个action时执行这个函数,返回一个action对象
store.dispatch(number_changer_action_async())
redux-thunk 大概原理:
export const reduxThunk = function (store) {
const _cache = store.dispatch;
async function newDispatch(action) {
if (action instanceof Function) {
return action(newDispatch, store.getState);
}
_cache(action);
};
store.dispatch = newDispatch;
};
6、分模块
在项目中通常只有一个store,但是可能有不同类型的数据,比如用户界面样式风格、用户登录信息、以及业务方面的数据。这时候可以分成不同的模块,便于管理。
目录结构如下图:
这时候就会有很多的reducer,我们需要修改index.js文件来将他们合并。
const { createStore, applyMiddleware, combineReducers } = require("redux");
const thunk = require("redux-thunk").thunk;
const { reducer } = require("./counter/reducer");
const combinedReducer = combineReducers({
counter: reducer
});
// createStore 有两个参数:
// 1、传入一个reducer: 就是执行action描述的方法
// 2、传入额外的中间件:这时候将我们redux-thunk中间件就行插入
const store = createStore(combinedReducer, applyMiddleware(thunk));
module.exports=store;
这样的当我们访问counter模块中的counter字段时,需要再加一层
// 第一个counter为执行combineReducers函数传入的对象中与counter模块对应的键名
// 第二个counter就是state中的counter
store.getState().counter.counter
以上就是redux使用的基本过程了。
使用redux 有三个原则:
-
单一数据源(Single Source of Truth):
- Redux 应用的整个状态(state)被存储在单一的 JavaScript 对象中,称为 “state” 或 “store”。
- 这个单一数据源使得整个应用的状态可追踪、可预测,并且便于调试。
-
状态是只读的(State is Read-Only):
- Redux 中的状态是只读的,唯一改变状态的方式是通过触发一个 action。
- 这确保了状态的变化是可控的,且只能通过特定的方式进行。
-
通过纯函数进行状态修改(Changes are Made with Pure Functions):
- 通过编写纯函数的方式来描述状态的变化,这些纯函数被称为 “reducers”。
- Reducer 接收旧的状态和一个 action 作为参数,返回一个新的状态。纯函数不会改变原始状态,而是创建并返回一个新的状态。
- 这种通过纯函数进行状态修改的方式提供了可预测性、可测试性和可维护性。
redux 处理数据流程
操作触发dispatch -> dispatch发送action描述 -> redux帮我们执行reducer更新state -> 执行订阅的回调
二、在react中的使用
其实在react 中使用过程跟上述过程基本一致,只是添加了state数据与视图绑定过程。react-redux给我们提供了state与视图绑定的便利操作。
1、在react 项目中安装redux 与 react-redux
npm i redux react-redux
2、使用react-redux 提供的Provider 组件包裹需要共享state数据的组件
这个的Provider 正是用了react 为我们提供的Context特性。
import { PureComponent } from "react";
import Prefile from "./Profile";
import Home from "./Home";
import { Provider } from "react-redux";
import store from "./store";
class App extends PureComponent {
render() {
return (
<div>
// 这里传入的store 正是我们创建的store对象
<Provider store={store}>
<h1>profile:</h1> <Prefile></Prefile>
<div>=======================</div>
<h1>home:</h1>
<Home></Home>
</Provider>
</div>
);
}
}
export default App;
3、在组件中使用state的数据或者修改数据(connect函数的使用)
import { PureComponent } from "react";
import {
asyncChangerAction,
counterChangeAction,
} from "./store/counter/actionCreators";
// react-redux 提供了connect 高阶函数
// 这个函数执行传入两个参数,返回一个高阶组件,我们再将需要使用state数据的组件传入这个高阶组件
// 具体调用在下面
import { connect } from "react-redux";
class Home extends PureComponent {
render() {
return (
<div>
<div>counter: {this.props.counter}</div>
<button onClick={() => this.props.changeCounter()}>
dispatch-async
</button>
<button onClick={() => this.props.changeCounter_sync()}>
dispatch-sync
</button>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
// 在Home 中可以使用this.props.counter 来访问state中的counter数据
counter: state.counter.counter,
};
};
const mapDispatchToProps = (dispatch) => {
return {
// 在Home 中可以使用this.props.changeCounter来执行dispatch
changeCounter: () => {
dispatch(asyncChangerAction());
},
changeCounter_sync: () => {
dispatch(counterChangeAction(100));
},
};
};
// connect 传入两个参数
// 1、state中的数据与props的绑定
// 2、在组件中需要用到的dispatch与props绑定
// 这时候connect 对上面两个对应关系已经做了缓存,并返回一个高阶组件
// 我们执行高阶组件,将Home 传入,这时对应关系注入到了Home组件的props中。
export default connect(mapStateToProps, mapDispatchToProps)(Home);
三、尝试自己实现一个react-redux
connect.js文件
import { PureComponent } from "react";
import StoreContext from "./storeContext";
function connect(mapStateToProps, mapDispatchToProps) {
return function (WrapperComponent) {
class HComponent extends PureComponent {
constructor(props, context) {
super(props);
this.context = context;
this.state = mapStateToProps(context.getState());
}
componentDidMount() {
this.unSubscribe = this.context.subscribe(() => {
this.setState(mapStateToProps(this.context.getState()));
});
}
componentWillUnmount() {
this.unSubscribe();
}
render() {
return <WrapperComponent {...this.props} {...this.state} {...mapDispatchToProps(this.context.dispatch)}></WrapperComponent>;
}
}
HComponent.contextType = StoreContext;
return HComponent;
};
}
export default connect;
StoreContext.js文件
import { createContext } from "react";
export default createContext();
以上就是对我 redux 和react-redux 的学习记录。