设计原则和思想
- 单一数据源,让React的组件之间的通信更加方便,同时也便于状态的统一管理
- 单向数据流,保证的数据的纯净,不被其他操作污染。
- Redux是将整个应用状态存储到到一个地方,称为store,里面保存一棵状态树state
- 组件派发action给store里的reducer, reducer计算并返回新的state, store更新state,并通知订阅它的组件进行重新渲染
- 其它组件可以通过订阅store中的状态(state)来刷新自己的视图
首先用react-cli脚手架来创建一个项目
- 删掉没用的文件
- 安装
redux
npx create-react-app redux-hand
yarn add redux
先用原生的redux实现一个简单的计数器
- 创建
reducer和store - 创建
App组件,在组件挂载时订阅sotre,组件销毁前取消订阅 - 点击
+/-按钮时dispatch一个action(包含type和payload两个属性),修改store里面的状态 - store里面的状态更新完,执行
subscribe函数,更新组件状态
import React from "react";
import ReactDom from "react-dom";
import { createStore } from "redux";
// 派发动作
const INCREMENT = "INCREMENT";
const DECREMENT = "DECREMENT";
// 初始状态
let initState = { number: 0 };
const reducer = (state = initState, action) => {
switch (action.type) {
case INCREMENT:
return { number: state.number + 1 };
case DECREMENT:
return { number: state.number - 1 };
default:
return state;
}
};
// 根据reducer创建store
let store = createStore(reducer);
class App extends React.Component {
constructor(props) {
super(props);
this.state = { number: 0 };
}
componentDidMount() {
// 组件挂载时订阅sotre
this.unsubscribe = store.subscribe(() =>
this.setState({ number: store.getState().number })
);
}
componentWillUnmount() {
// 组件销毁前取消订阅
this.unsubscribe();
}
render() {
return (
<div style={{ margin: "30px" }}>
<h2>{this.state.number}</h2>
<button title="加" onClick={() => store.dispatch({ type: INCREMENT })}>
+
</button>
<button title="减" onClick={() => store.dispatch({ type: DECREMENT })}>
-
</button>
</div>
);
}
}
ReactDom.render(<App />, document.getElementById("root"));
实现Redux中的核心方法
1、实现createStore
createStore用来创建store对象,给外部使用,一般来说一个应用中只有一个storecreateStore方法返回一个对象,包含三个方法getState获取store里的状态,直接将store里的状态返回即可subscribe订阅,会将订阅的函数保存在currentListeners中,然后返回unsubscribe函数取消订阅(就是将订阅的函数从currentListeners中删除)。dispatch派发action,会先执行reducer函数,获取最新状态,然后从依次执行currentListeners中订阅的函数。
import ActionTypes from "./utils/actionTypes";
export default function createStore(reducer, preloadedState) {
let currentReducer = reducer;
let currentState = preloadedState;
let currentListeners = [];
function getState() {
return currentState;
}
function subscribe(listener) {
currentListeners.push(listener);
return function unsubscribe() {
const index = currentListeners.indexOf(listener);
currentListeners.splice(index, 1);
};
}
function dispatch(action) {
currentState = currentReducer(currentState, action);
for (let i = 0; i < currentListeners.length; i++) {
const listener = currentListeners[i];
listener();
}
return action;
}
dispatch({ type: ActionTypes.INIT });
const store = {
dispatch,
subscribe,
getState,
};
return store;
}
上面派发的action是个普通对象,如果我们想派发一个函数,或者做异步处理改怎么办
2、接下来我们看一下bindActionCreators的用法
- 声明
add和minus两个函数,返回值是action对象 - 调用
bindActionCreators方法将actions和store.dispatch方法进行绑定
function add() {
return { type: INCREMENT };
}
function minus() {
return { type: DECREMENT };
}
const actions = { add, minus };
const boundActions = bindActionCreators(actions, store.dispatch);
- 在 render函数中使用
boundActions
render() {
return (
<div style={{ margin: "30px" }}>
<h2>{store.getState().number}</h2>
<button title="加" onClick={() => store.dispatch({ type: INCREMENT })}>
+
</button>
<button title="减" onClick={() => store.dispatch({ type: DECREMENT })}>
-
</button>
<button
title="异步加一"
onClick={() => setTimeout(boundActions.add, 1000)}
>
异步+
</button>
</div>
);
}
3、实现bindActionCreator
- 本质上是将传入的函数用
dispatch方法进行了一次包装,返回了一个新的函数 - 在绑定后的函数中可以直接拿到
dispatch方法进行调用,修改store里的状态
function bindActionCreator(actionCreator, dispatch) {
return function (...args) {
return dispatch(actionCreator.apply(this, args));
};
}
export default function bindActionCreators(actionCreators, dispatch) {
// 传入的是单个函数,直接绑定并返回
if (typeof actionCreators === "function") {
return bindActionCreator(actionCreators, dispatch);
}
const boundActionCreators = {};
for (const key in actionCreators) {
const actionCreator = actionCreators[key];
if (typeof actionCreator === "function") {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
}
}
return boundActionCreators;
}
4、store中只能有一个reducer和state,当我们有多个模块有多个reducer时,需要用combineReducers对reducer进行合并,我们先看一下怎么使用
- 对原有项目按
redux在项目中的真实使用进行改造,最终目录如下
- counter1的reducer,counter2类似
import * as types from "../action-types";
let initialState = { number: 0 };
export default function (state = initialState, action) {
switch (action.type) {
case types.ADD1:
return { number: state.number + 1 };
case types.MINUS1:
return { number: state.number - 1 };
default:
return state;
}
}
- 合并counter1、counter2的
reducer
// src/store/reducers/index.js
import { combineReducers } from "redux";
import counter1 from "./counter1";
import counter2 from "./counter2";
let rootReducer = combineReducers({
counter1,
counter2,
});
export default rootReducer;
- 根据合并后的
reducer创建store
import { createStore } from "../redux";
import reducer from "./reducers";
const store = createStore(reducer);
export default store;
5、实现combineReducers
combineReducers是一个高阶函数,实质上是给store生成一个合并后的总statenextState会将每个reducer的key做为key, 将每个reducer返回的分state作为value存入nextState进行合并,作为返回给store的最终状态- 当
dispatch一个action时,会首先交给combination函数进行处理,combination中会将action传给所有的reducer,返回最新的分state,放在nextState返回给store
function combineReducers(reducers) {
return function combination(state = {}, action) {
let nextState = {};
for (let key in reducers) {
nextState[key] = reducers[key](state[key], action);
}
return nextState;
};
}
export default combineReducers;
将项目中用到的redux切换到自己实现的redux,最终效果是一样的
代码地址
下一节:从1实现react-redux
如果这篇文章对你有帮助,请帮我点个赞吧