redux是一个用于管理状态的前端框架,不仅可以用于react,还可以用于vue。它的源码和设计思想,我们在《颤抖吧!一起手写一个redux框架!》已经讲过,没有看过的小伙伴们可以去看一下。
redux的核心是发明了store,通过dispatch来更改store里的值。
可redux要想在react上使用,需要把两者连接起来,这个时候react-redux就出现了。它的主要作用是:
componentDidMount
时store.subscribe
,这样在store.dispatch
的时候就能回调subscribe
的listener
。import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { ReactReduxContext } from './Context'
class Provider extends Component {
//...
componentDidMount() {
this._isMounted = true
this.subscribe()
}
subscribe() {
const { store } = this.props
this.unsubscribe = store.subscribe(() => {
const newStoreState = store.getState()
if (!this._isMounted) {
return
}
this.setState(providerState => {
//小tip:return null代表不更新state
if (providerState.storeState === newStoreState) {
return null
}
return { storeState: newStoreState }
})
})
// ...
}
}
export default Provider
context
。Provider.js
中声明context
,并把store.getState()
赋值给它。这样其子组件中就可以拿到context
值,只不过要写一些模板代码,这些代码都封装在了connect.js
中。这也就是为什么我们只要把我们的组件传给高阶组件connect
中就可以获取到redux
的state
。mapStateToProp
用来把store中的state传给connect组件,所以在组件中可以通过this.props获取store中state的值。mapDispatchToProps
用来把用dispatch包裹的actionCreator传给组件(bindActionCreators中把dispatch给bind到actionCreator上),所以,在组件中通过props可以直接dispatch一个action。以上大概就是要在react中使用redux要写的代码。
其实,react-redux封装的功能并不一定适合我们的业务场景,相反我们还多写了很多与业务无关的模板代码!
因此,我们可以尝试着自己去封装一个更易用的redux框架。它有如下几个特点:
basePage
就可以了。import React from "react";
import { createStore, applyMiddleware } from "redux";
export default class BasePage {
constructor(props) {
super(props);
this.$actions = null;
this.$store = null;
this.state = {};
}
render() {
let { View } = this;
if (!View) return null;
let pageContext = {
page: this,
state: this.state
};
return <View state={this.state} page={this} />;
}
get store() {
if (this.$store) return this.$store;
this.$store = this.createStore();
return this.$store;
}
get actions() {
if (this.$actions) return this.$actions;
this.store; // trigger createStore
this.$actions = {};
if (!util.isObject(this.model)) return this.$actions;
Object.keys(this.model).forEach(type => {
this.$actions[type] = payload => this.store.dispatch({ type, payload });
});
return this.$actions;
}
createReducer() {
return (state, action) => {
let model = this.model;
let nextState = state;
if (util.isObject(model)) {
let handler = reducer[action.type];
if (util.isFunction(handler)) {
nextState = handler(nextState, action.payload);
}
} else if (util.isFunction(model)) {
nextState = reducer(nextState, action);
}
return nextState;
};
}
createStore() {
const middlewares = [];
if (process.env.NODE_ENV === "development") {
const reduxLogger = require("redux-logger");
const logger = reduxLogger.createLogger();
middlewares.push(logger);
}
const createStoreWithMiddleware = applyMiddleware(...middlewares)(
createStore
);
let store = createStoreWithMiddleware(this.createReducer(), this.state);
store.unsubscribe = store.subscribe(() => {
let nextState = store.getState();
this.setState(nextState);
});
Object.defineProperty(store, "actions", {
get: () => this.actions
});
return store;
}
可以看到,如上框架代码行数并不多,下面我来为大家来讲解一下。
首先,先看render()
,在render
中给每个view
传入了page
:
<View state={this.state} page={this} />
这主要是用来在组件中调用action来更新state。
例如,我们在CounterView
中调用INCRE_BY
。
export default function CounterView({ state, page }) {
const { count } = state;
return (
<View>
<TouchableOpacity onPress={()=> {
page.actions.INCRE_BY
}}>
<Text style={{ backgroundColor: "red" }}>increase</Text>
</TouchableOpacity>
</View>
);
}
此时页面才开始createStore
。。或者说,只有调用页面第一个action来开始createStore
。因为,在调用page.actions.INCRE_BY
时,实际上会先调用
get actions() {
if (this.$actions) return this.$actions;
this.store; // trigger createStore
this.$actions = {};
if (!util.isObject(this.reducer)) return this.$actions;
Object.keys(this.reducer).forEach(type => {
this.$actions[type] = payload => this.store.dispatch({ type, payload });
});
return this.$actions;
}
此时,this.$actions
实际上是空的,所以这个时候就会用this.model
来构建action
。这里的model
不光可以构建action
,还可以构建出reducer
!
我们来看下mode.js是怎样的:
export const INCRE = state => {
let count = state.count + 1;
return {
...state,
count
};
};
export const DECRE = state => {
let count = state.count - 1;
return {
...state,
count
};
};
export const INCRE_BY = (state, aaa = 2) => {
let count = state.count + aaa;
return {
...state,
count
};
};
看到这个model
我相信你大致就能猜到我是如何通过model
来构建reducer
和action
的了——构建action
时,取出model
的key
作为action
名,并把每个function
用dispatch
包裹一下;构建reducer
时,就直接调用model
中相应key
的function
,注意这里每一个function
都return
一个新的state
。
而上面在调用action
时,当发现store
为空时,会接着去createStore
。
createStore() {
const middlewares = [];
if (process.env.NODE_ENV === "development") {
const reduxLogger = require("redux-logger");
const logger = reduxLogger.createLogger();
middlewares.push(logger);
}
const createStoreWithMiddleware = applyMiddleware(...middlewares)(
createStore
);
let store = createStoreWithMiddleware(this.createReducer(), this.state);
store.unsubscribe = store.subscribe(() => {
let nextState = store.getState();
this.setState(nextState);
});
Object.defineProperty(store, "actions", {
get: () => this.actions
});
return store;
}
以上我们就完成了一个比react-redux更轻量级的redux框架,该框架使得我们能尽可能少地在业务代码中写无脑的模板代码。
最后,该框架是参考自@工业聚,他对于技术的态度,给了我很多前进的动力!