仅个人理解,有不当的地方还请批评指正。
Redux
store(贮存): 就是存储state的地方
action: 就是一个普通的对象,用来描述更新的type和content。所有数据的变化,都必须通过派发(dipatch)action来更新。相当于action只是一条指令,真正要执行的话需要通过reducer。
例如:const action1 = { type: "ADD_FIEND", info: {name: "lucy", age: 20} }
reducer: 是一个纯函数,将传入的state和action结合起来生成(返回)一个新的state。
举个例子,便于个人理解
store 就好比一家服装加工厂,采购了各种不同的服饰,这个服饰就可以类比成一个个state;
服装厂建成了,工厂开始接收订单了,不同的服饰会有不同的订制需求,比如说这家要染色,那家要裁剪,这些不同的订单就可以类比成 action。
用什么根据订单进行服装加工呢?->机器!对于染色的订单,需要专门的染色器;对于裁剪的订单,则需要专门的裁剪器,这些机器就可以类比成 reducer。对于染色订单,送过来什么衣服,需要染什么色,则对应reducer中传入的state 和 action 。一个工程可以有多个机器 ,即一个store 可以有多个 reducer(一般来说一个reducer对应一个state)。工厂在创建时就要采购好各种机器,对应store创建时需要传入reducer。
下面是一个非常简单的例子:
// state,类比成 需要裁剪的衣服
const initialState = {
counter: 0
}
// reducer,类比成 裁剪器
function reducer(state = initialState, action){
switch(action.type){
case "INCREMENT":
return {...state, counter: state.counter + 1};
}
}
// store(创建时需要传入一个reducer)
// 类比成 工厂采购裁剪器
const store = redux.createStore(reducer);
// actions,类比成需要裁剪的订单
const action = { type: "INCREMENT" };
const action2 = { type: "ADD_NUMBER", num: 5 };
// 订阅store的修改
store.subscribe(() => {
console.log("counter: ", store.getState().counter);
})
// 派发action,类比成工厂接收订单
// 调用dispatch后,内部会执行reducer。
store.dispatch(action);
不加connect
一般来说使用redux时候都会创建一个文件夹,将store相关的流程拆开放进去。分别有:
- actionCreater.js:定义 action 并导出,
- constants.js,:定义类型常量的
- index.js:创建store,并绑定reducer,
- reducer.js:定义并导出 reducer。
如果在组件内要用到 redux,就需要这样写:
import React, { PureComponent } from 'react';
import store from '../store';
import {
subAction
} from "../store/actionCreators";
export default class About extends PureComponent {
constructor(props) {
super(props);
this.state = {
counter: store.getState().counter
}
}
componentDidMount() {
this.unsubscribue = store.subscribe(() => {
this.setState({
counter: store.getState().counter
})
})
}
componentWillUnmount() {
this.unsubscribue();
}
render() {
return (
<div>
<hr/>
<h1>About</h1>
<h2>当前计数: {this.state.counter}</h2>
<button onClick={e => this.decrement()}>-1</button>
<button onClick={e => this.subNumber(5)}>-5</button>
</div>
)
}
decrement() {
store.dispatch(subAction(1));
}
subNumber(num) {
store.dispatch(subAction(num));
}
}
使用connect抽离公共部分
上述方法是可以优化的,例如constructor,componentDidMount这些都是可以抽离出来的。所以新建一个connect.js,用来接收state和dispatch需要执行的函数,并将它们作为参数传给已定义好的组件,返回新的组件。
connect.js:
import { PureComponent } from "react";
import store from '../store';
export function connect(mapStateToProps, mapDispachToProp) {
return function enhanceHOC(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())}
{...mapDispachToProp(store.dispatch)}/>
}
}
}
}
组件使用:
import React from 'react';
import { connect } from '../utils/connect';
import {
decAction,
subAction
} from "../store/actionCreators";
function About(props) {
return (
<div>
<hr/>
<h1>About</h1>
<h2>当前计数: {props.counter}</h2>
<button onClick={e => props.decrement()}>-1</button>
<button onClick={e => props.subNumber(5)}>-5</button>
</div>
)
}
const mapStateToProps = state => {
return {
counter: state.counter
}
};
const mapDispatchToProps = dispatch => {
return {
decrement: function() {
dispatch(decAction());
},
subNumber: function(num) {
dispatch(subAction(num))
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(About);
添加 context 进行优化&react-redux
上面就是redux包大体的封装方式,当然了,实际肯定更加复杂,因为上面connect代码依赖了具体业务的store。因此引入 context,在使用的时候将用户的store通过context传过去即可。
react-redux 包提供了这些功能,使用起来也是非常简单:
在根用Provider包裹一下:
import React from 'react';
import ReactDOM from 'react-dom';
import store from './store';
// import { StoreContext } from './utils/context';
import { Provider } from 'react-redux';
import App from './App';
ReactDOM.render(
// 本质上还是跟context用法一样传的value,只是加了一层封装,传入store
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
组件内使用的时候,将import { connect } from '../utils/connect';
改成import { connect } from 'react-redux';
即可,其他的和上小部分一样使用。
react-redux 在内部实现时候用到了大量hooks。
更加详细的使用(添加了redux-thunk):
安装:yarn add redux react-redux redux-thunk
store/index.js: 用于创建store
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducer';
// 用于浏览器redux插件
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, composeEnhancers(
// 使用中间件thunk,用于在redux中发送异步网路请求
applyMiddleware(thunk)
));
export default store;
store/reducer.js: 用于将store和action联系,不同的模块单独配置一个reducer,然后合并起来传给index
import { combineReducers } from "redux-immutable";
import { reducer as recommendReducer } from "../pages/discover/c-pages/recommend/store";
const cReducer = combineReducers({
recommend: recommendReducer,
});
export default cReducer;
App.js中需要用Provider包裹组件,组件就可以通过connect获取store、dispatch等
import { Provider } from "react-redux";
import store from "./store";
export default memo(function App() {
return (
<Provider store={store}>
<HashRouter>
<SAppHeader />
{renderRoutes(routes)}
<SAppFooter />
</HashRouter>
</Provider>
);
});
现在就要创建子模块(以recommend为例)对应的redux,用于向reducer.js中传入recommendReducer。
四个文件分别是actionCreator.js,constants.js,index.js,reducer.js。
constants.js:
export const CHANGE_TOP_BANNERS = "recommend/CHANGE_TOP_BANNERS";
export const CHANGE_HOT_RECOMMEND = "recommend/CHANGE_HOT_RECOMMEND";
....
reducer.js:
import * as actionTypes from "./constants";
const defaultState = {
topBanners: [],
};
function reducer(state = defaultState, action) {
switch (action.type) {
case actionTypes.CHANGE_TOP_BANNERS:
return state.set("topBanners", action.topBanners);
case .....
default:
return state;
}
}
export default reducer;
index.js:
import reducer from './reducer';
export {
reducer
}
actionCreator.js: