简介
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。Redux不耦合任何框架,在React、Vue、Angular、jQuery等都可以使用。
为什么使用Redux呢?
- 随着项目的越来越庞大和复杂,组件之间状态难以维护。
- Redux是为了解决state的数据管理难问题。
- 在React开发中由于单项数据流,两个非父子组件通讯麻烦。
Redux工作原理
简述上图的一个工作:
ComponentA派发一个动作dispatch(action)(动作意思是改变state颜色为🧱色),reducer接受到这个动作后改变state颜色,然后通知所有的订阅者,ComponentB和ComponentC监听到state有变化,拿到最新的state,改变组件颜色为🧱色。
store是应用的集中管理的地方,也可以称之为仓库。state里保存我们的数据。reducer处理器,接受到动作之后修改state状态。dispatch派发一个动作通知reducer要修改的state,然后循环调用缓存函数,subscribe回掉函数触发拿到新状态。
action是一个普通对象,描述想要进行什么操作。组件修改state,必须要通过Reducer处理器,不能直接操作state。
话不多说直接上代码:
自己动手试一下吧🌹
模拟redux
一、 mini版Redux,实现下面计数器的功能,使用原生js开发,不依赖任何框架。
1.1 新建index.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Redux</title>
</head>
<body>
<div>
<p id='text'>0</p>
<button id='add_btn'>+</button>
<button id='minus_btn'>-</button>
</div>
<script src="./createStore.js"></script>
<script src="./index.js"></script>
</body>
</html>
1.2 在同级目录创建createStore.js
// createStore 创建一个仓库
function createStore(reducer, initialState) {
let state = initialState;
let listeners = [];
function getState() {
return state;
}
function dispatch(action) {
state = reducer(action, state);
listeners.forEach(l=>l()); // 遍历调用listener函数
}
function subscribe(listener) {
listeners.push(listener); //缓存监听函数
return function unsubscribe() {
const index = listeners.indexOf(listener);
if (index > -1) listeners.splice(index, 1);
};
}
return { dispatch, getState, subscribe }
}
1.3 在同级目录创建index.js
const text = document.getElementById('text');
const addBtn = document.getElementById('add_btn');
const minusBtn = document.getElementById('minus_btn');
const initalState = 0;
const Add = 'Add';
const MINUS = 'MINUS';
function reducer(action, state) {
switch (action.type) {
case Add:
return state + 1;
case MINUS:
return state - 1;
default:
return state;
}
}
const store = createStore(reducer, initalState); //创建仓库
function reder() {
text.innerHTML = store.getState();
}
reder();
store.subscribe(()=>{ //观察state,如果有改动就会执行
reder();
});
addBtn.addEventListener('click', function(){
store.dispatch({ type: Add }) //派发递增的动作
})
minusBtn.addEventListener('click', function() {
store.dispatch({ type: MINUS }) // 派发递减动作
})
这里只是简单的模拟了
redux,还有很多功能没做,如果有兴趣的话不妨自己去完善,运用到react项目中,代码我会贴在文章最后。
二、 实现Redux和React-Redux核心APi,实现上述计数器功能。
2.1 Redux
接下来主要实现APi有createStore, combineReducers, bindActionCreators。
createStore:
createStore函数接受两个参数reducer和initialState,返回dispatch, getState, subscribe。
reducer是一个函数,当我们调用dispatch方法会触发reducer,返回新状态赋值给state。
getState获取最新的状态
subscribe接受一个函数,当调用dispatch方法时,传入subscribe的函数会被调用,在回掉函数内通过getState拿到最新的state 。
export default function createStore(reducer, initialState) {
// 这个state是唯一的,所有的获取修改都是这个state
let state = initialState;
let listeners = [];
function getState() {
return state;
}
function dispatch(action) {
// console.log('-----',reducer);
// reducer函数的目的就是返回更新后状态 然后赋值给state, 这样再去执行getState()拿到的就是新的状态
state = reducer(state, action);
listeners.forEach(l=>l());
}
function subscribe(listener) {
listeners.push(listener);
return function () {
const index = listeners.indexOf(listener);
if (index > -1) listeners.splice(index, 1);
};
}
dispatch({type: '@xx/init'});
return { dispatch, getState, subscribe }
}
combineReducers 合并reducer:
为了便于维护reducer我们要拆分,比如useReducer,sysReducer,countReducer等,由于只允许一个reducer,所以我们需要进行合并。
// 无论几个reducer其最终目的是返回一个新状态,更替老状态
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers);
return function(state={}, action){
const newState = {};
for (const key of reducerKeys) { //遍历所有的reducerKeys, 拿到所有reducer方法
// 获取reducer方法
const reduer = reducers[key];
// 获取老状态
const previousState = state[key];
// 调用reducer, 得到最新的状态,并且赋值给 合并后的大状态(newState),key对应的 reducers对象的key
newState[key] = reduer(previousState , action);
}
return newState;
}
}
参数: 传入的是reducers对象,通过遍历调用每个reducer方法,获取的新状态,然后合并到newState对象上。
const reducers = {
counter,
counter1
}
const rootReducers = combineReducers(reducers);
bindActionCreators 创建actions封装:
export default function bindActionCreators(actions, dispatch) {
if (typeof actions === 'function') {
return function(){
dispatch(actions(...arguments))
}
}
const boundActionCreators = {};
for (const key in actions) {
const action = actions[key];
if (actions.hasOwnProperty(key) && typeof action === 'function') {
boundActionCreators[key] = function(){
dispatch(action(...arguments))
}
}
}
return boundActionCreators;
}
2.2 React-Redux
React-Redux目的是为了关联React和Redux,核心api是 Provider和connect。
Provider:
通过使用方式可以看出,Provider其实就是一个组件,在最高层使用通过绑定store prop,子组件通过context可以获取到store。
<Provider store={store}><Provider>
实现:
context.js
import React, { createContext } from 'react';
const ReactReduxContext = createContext(null); //创建ReactRedux上下文
ReactReduxContext.displayName = 'ReactRedux';
export default ReactReduxContext;
Provider.js
export default function Provider(props){
return(
// 这里的就是高层组件传入的store 有{ dispatch, getState, subscribe }等方法
<ReactReduxContext.Provider value={props.store}>
{props.children}
</ReactReduxContext.Provider>
)
}
connect.js :返回一个有prop的新组件,新组件通过props,拿到state也可以dispatch派发动作
参数:
mapStateToProps: 类型是一个函数,把state映射要props上,通过props可以得到值。
mapDispatchToProps:类型是一个函数或则action对象, 把dispatch(action)动作映射要props上,通过props可以直接拿到方法,进行派发动作。
WrappedComponent:类型是一个老的组件,把所有的值通过props挂载到新的组件上。
export default function (mapStateToProps, mapDispatchToProps) {
return function (WrappedComponent) { //高阶函数为了方便扩展
return function (props) {
// 获取上下文,拿到value对象
const { dispatch, getState, subscribe } = useContext(ReactReduxContext);
// 获取状态
const [state, setstate] = useState(mapStateToProps(getState()));
const boundActionCreator = useMemo(()=>{
return mapDispatchToPropsIsType(mapDispatchToProps , dispatch);
},[]);
useEffect(() => {
// 监听数据改动
const unsubscribe = subscribe(()=>{
setstate(mapStateToProps(getState()));
})
return ()=>{
unsubscribe();
}
}, []);
return (
<WrappedComponent {...state} {...boundActionCreator} />
)
}
}
}
// 根据mapDispatchToProps类型,做相应处理
function mapDispatchToPropsIsType(mapDispatchToProps, dispatch) {
if(typeof mapDispatchToProps === 'function'){
return mapDispatchToProps(dispatch);
}
if(typeof mapDispatchToProps === 'object') {
return bindActionCreators(mapDispatchToProps , dispatch);
}
}
使用:
const mapStateToProps = state => {
return {
counter1: state.counter1
}
}
export default connect(
mapStateToProps,
actions
)(Counter1)
周边
总结
所有的代码都在这,记得给个star哦!
THK!!!