redux无疑已经是react应用的标配了,也确实好用,这里就redux的基础点说明一下,包括createStore, combineReducers,provider, connect
createStore()
const createStore = (reducer) => {
let state
let listeners = []
const getState = () => state
const dispatch = (action) => {
state = reducer(state, action)
listeners.forEach(listener => listener())
}
const subscribe = (listener) => {
listeners.push(listener)
return () => {
listeners = listeners.filter(l => l !== listener)
}
}
dispatch({}) // to get initial state
return {getState, dispatch, subscribe}
}
// import {createStore} from 'redux'
另外一个class版本的Store, 更接近真实的store, 构造函数传递两个参数,reducers和initialState
const store = new Store(reducers, initialState);
export class Store {
private subscribers: Function[];
private reducers: { [key: string]: Function };
private state: { [key: string]: any };
constructor(reducers = {}, initialState = {}) {
this.subscribers = [];
this.reducers = reducers;
this.state = this.reduce(initialState, {});
}
get value() {
return this.state;
}
subscribe(fn) {
this.subscribers = [...this.subscribers, fn];
fn(this.value);
return () => {
this.subscribers = this.subscribers.filter(sub => sub !== fn);
};
}
dispatch(action) {
this.state = this.reduce(this.state, action);
this.subscribers.forEach(fn => fn(this.value));
}
private reduce(state, action) {
const newState = {};
for (const prop in this.reducers) {
newState[prop] = this.reducers[prop](state[prop], action);
}
return newState;
}
}
和第一种function定义createStore还有一处不同,subscribe(fn)后执行了fn(this.value),就不需要等到dispatch后更新状态,相当于初始化状态
combineReducers()
const combineReducers = (reducers) => {
return (state = {}, action) => {
return Object.keys(reducers).reduce(
(nextState, key) => {
nextState[key] = reducers[key](state[key], action);
return nextState;
}, {})
}
}
// import {combineReducers} from 'redux'
使用 combineReducers
const {combineReducers} = Redux;
const todoApp = combineReducers({todos, visibilityFilter});
Provider
预备知识:context, 使用react的context传递store, 作为全局变量
import PropTypes from 'prop-types';
class Button extends React.Component {
render() {
return (
<button style={{background: this.context.color}}>
{this.props.children}
</button>
);
}
}
/* 接收context */
Button.contextTypes = {
color: PropTypes.string
};
class Message extends React.Component {
render() {
return (
<div>
{this.props.text} <Button>Delete</Button>
</div>
);
}
}
class MessageList extends React.Component {
/* 传递context */
getChildContext() {
return {color: "purple"};
}
render() {
const children = this.props.messages.map((message) =>
<Message text={message.text} />
);
return <div>{children}</div>;
}
}
// 传递context
MessageList.childContextTypes = {
color: PropTypes.string
};
通过添加childContextTypes、getChildContext到MessageList, context 会自动传递到子组件,但只有定义了contextTypes的子组件,context才会作为属性传递
使用context传递store, 封装Provider
class Provider extends React.Component {
/* called by react */
getChildContext () {
return {
store: this.props.store
}
}
render () {
return this.props.children
}
}
Provider.childContextTypes = {
store: PropTypes.object
}
ReactDOM.render(
<Provider store={createStore(todoApp)}>
<TodoApp />
</Provider>,
document.getElementById('root')
)
// import {Provider} from 'react-redux'
class VisibleTodoList extends React.Component {
componentDidMount () {
const {store} = this.context
this.unsubscribe = store.subscribe(() => {
this.forceUpdate()
})
}
componentWillUnmount () {
this.unsubscribe()
}
render () {
const {store} = this.context
const state = store.getState()
return (
<TodoList todos={getVisibleTodos(state.todos, state.visibilityFilter)}
onTodoClick={id => store.dispatch({type: 'TOGGLE_TODO', id})} />
)
}
}
VisibleTodoList.contextTypes = {
store: PropTypes.object
}
Connect
使用react-redux的connect函数将presentation component和redux连接起来
1 省去了context的传递(presentation component的contextTypes的定义)
2 省去了componentDidMount中调用store.subscribe(),componentWillUnmount中调用unsubscribe()
3 传递了dispatch
const mapStateToProps = (state, ownProps) => {
return {
todos: getVisibleTodos(
state.todos,
state.visibilityFilter)
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onTodoClick: id => dispatch({
type: 'TOGGLE_TODO',
id
})
}
}
import {connect} from 'react-redux';
const visibleTodoList = connect(mapStateToProps, mapDispatchToProps)(TodoList);
附上connect.js的代码解释,很详细
https://gist.github.com/gaearon/1d19088790e70ac32ea636c025ba424e
mapDispatchToProps
将action映射为封装的组件的props,可以简写为对象形式,但要保证参数的顺序要一直,比如下面例子中的id
const mapDispatchToTodoListProps = (dispatch) => ({
onTodoClick (id) {
dispatch(toggleTodo(id))
}
})
VisibleTodoList = withRouter(connect(
mapStateToTodoListProps,
actions
)(VisibleTodoList))
// mapDispatchToTodoListProps的简写版
VisibleTodoList = withRouter(connect(
mapStateToTodoListProps,
{onTodoClick: toggleTodo}
)(VisibleTodoList))
MiddleWare
const addLoggingToDispatch = (store) => {
const rawDispatch = store.dispatch
if (!console.group) {
return rawDispatch
}
return (action) => {
console.group(action.type)
console.log('%c prev state', 'color: gray', store.getState())
console.log('%c action', 'color: blue', action)
const returnValue = rawDispatch(action)
console.log('%c next state', 'color: green', store.getState())
console.group(action.type)
return returnValue
}
}
const configureStore = () => {
const persistedState = loadState()
const store = createStore(todoApp, persistedState)
store.dispatch = addLoggingToDispatch(store)
return store
}