redux使用和基本实现

redux基本思路

  • redux是用来管理公共状态,公共state都存放在store中。使用createStore()方法新建一个store

  • 直接修改容易引起误操作,需要有条件的操作store,不能直接修改

  • 使用store.getState()来获取state

  • 使用store.dispatch(action)修改state

  • 使用store.subscribe(listener)实现监听,如果有改动,进行对应的响应操作

  • action是一个对象,基本格式{ type: TEST, payload: { name: 'ddd' } }

  • action creatoraction生成函数,根据传入的参数生成对应的action

redux基本使用

  • 新建reducer文件
// action type const COUNT_ADD = "数量增加" const COUNT_MINUS = "数量减少" // initial state const initState = { test: 'test' count: 10 } // reducer export default function(state = initState, action) { switch(action.type) { case COUNT_ADD: return { ...state, count: state.count + 1 } case COUNT_MINUS: return { ...state, count: state.count - 1 } default: return state } } // action creator export function countAdd() { return { type: COUNT_ADD } } export function countMinus() { return { type: COUNT_MINUS } }
  • 如果有多个reducer,可以使用combineReducers方法将多个合并成一个
import { combineReducers } from 'redux' import app from './app' import user from './user' export default combineReducers({ app, user })
  • 使用createStore生成store
import { createStore } from './redux' import rootReducer from './reducers' export default createStore(rootReducer) // 使用中间件 import { createStore, applyMiddleware } from './redux' import thunk from 'redux-thunk' import rootReducer from './reducers' export default createStore(rootReducer, applyMiddleware(thunk)) // 使用中间件并开启devToolsExtension import { createStore, applyMiddleware, compose } from './redux' import thunk from 'redux-thunk' import rootReducer from './reducers' export default createStore(rootReducer, compose( applyMiddleware(thunk), window.devToolsExtension ? window.devToolsExtension() : f=>f ))

单独使用redux

const init = store.getState() console.log(init) function listener() { const { count } = store.getState() console.log(`目前计数为:${count}`) } store.subscribe(listener) // 派发事件 store.dispatch(countAdd()) store.dispatch(countAdd()) store.dispatch(countMinus())

配合react-redux使用

  • react-redux提供了Provider,该组件将store放到context中,方便子孙组件直接使用store
  • react-redux提供了connect(mapStateToProps, mapDispatchToProps)用来将对应的statedispatch放到组件的props
// ... import { Provider } from 'react-redux' import store from './store' ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );
import React from 'react' import { connect } from 'react-redux' import { countAdd, countMinus } from '../../store/reducers/app' import { WingBlank, WhiteSpace,Button } from 'antd-mobile' @connect( ({ app }) => app, { countAdd, countMinus } ) class Demo extends React.Component { render() { return ( <WingBlank style={{marginTop: 50}}> <p>目前计数:{this.props.count}</p> <WhiteSpace /> <Button type="warning" onClick={this.props.countAdd}>+1</Button> <WhiteSpace /> <Button type="primary" onClick={this.props.countMinus}>-1</Button> </WingBlank> ) } } export default Demo

用到的Javascript

箭头函数

// 一个 => const add = (num) => num + 3 // 相当于 // function add(num) { // return num + 3 // } const res = add(3) console.log(res) // 3 + 3 = 6 // 两个 => const add = x => y => x + y + 3 // 相当于 // function add(x) { // return function(y) { // return x + y + 3 // } // } const res = add(2)(3) console.log(res) // 2 + 3 + 3 = 8

…args

function sayHello(...args) { console.log(args) } sayHello('hello', 'React', 'And', 'imooc') // ["hello", "React", "And", "imooc"]

Array.reduce

参考:数组Array属性和方法整理 - Array.reduce()

实现compose

参考:函数式编程 - compose

redux基本实现

export function createStore(reducer) { let currentState = {} let currentListeners = [] // getter function getState() { return currentState } // setter function dispatch(action) { currentState = reducer(currentState, action) // 有改变,触发所有的更新 currentListeners.forEach(v => v()) return action } // 观察者:发布-订阅 function subscribe(listener) { currentListeners.push(listener) } // 先dispatch一下,否则currentState为空 // 这个type,写一个比较特殊的,应该不会重复的 dispatch({ type: '@@redux/INIT_MY' }) return { getState, dispatch, subscribe } }

combineReducers

  • combineReducers用来将多个reducer合并成一个
export const combineReducers = reducers => (state = {}, action) => { return Object.keys(reducers).reduce((nextState, key) => { nextState[key] = reducers[key](state[key], action) return nextState }, {}) }

react-redux基本实现

context

  • 父子组件传值,一般是通过props

  • 如果一个组件的值,传给孙组件,或者更深层次的组件使用,每层都需要通过props传递。

  • 将值放在context中,就不用每层都传了

import React from 'react' import ReactDOM from 'react-dom' import PropTypes from 'prop-types' class Sidebar extends React.Component { render() { return ( <div> <p>侧边栏</p> <Navbar></Navbar> </div> ) } } class Navbar extends React.Component { // 固定写法(静态变量 contextTypes):this.context.user 需要配置类型 static contextTypes = { user: PropTypes.string } render() { console.log(this.context) return ( <div>{this.context.user}的导航栏</div> ) } } class Page extends React.Component { // 固定写法(静态变量 childContextTypes):this.context.user 需要配置类型 static childContextTypes = { user: PropTypes.string } constructor(props) { super(props) this.state = { user: 'admin' } } // 固定的方法 getChildContext getChildContext() { // return { user: 'admin' } // 子孙组件可以使用 this.context 访问 return this.state } render() { return ( <div> <p>我是{this.state.user}</p> <Sidebar></Sidebar> </div> ) } } ReactDOM.render( <Page />, document.getElementById('root') )

Provider

import React from 'react' import PropTypes from 'prop-types' // Provider,把store放到context里,所有的子元素可以直接取到store export class Provider extends React.Component { // 需要声明静态属性childContextTypes来指定context对象的属性,是context的固定写法 static childContextTypes = { store: PropTypes.object } constructor(props) { super(props) this.store = props.store } // 实现getChildContext方法,返回context对象,也是固定写法 getChildContext() { return { store: this.store } } render() { return this.props.children } }

connect

  • connect负责链接组件,把redux里的statedispatch放到组件的属性里

  • connectstate数据变化的时候,能够通知组件

  • connect使用格式:connect(mapStateToProps, mapDispatchToProps)(Comp)

  • mapStateToProps是一个函数,接受state作为参数。就是将state映射到组件的props属性上。执行后应该返回一个对象,里面的每一个键值对就是一个映射

// 参数是 state function mapStateToProps(state) { return { count: state.app.count } } // 可以这么直接将 state.app 取出来 const mapStateToProps = ({ app }) => ({ count: app.count })
  • mapDispatchToProps可以是一个函数,也可以是一个对象

  • mapDispatchToProps如果是函数。接受dispatch作为参数。返回一个对象。键名对应要映射到props的属性,键值是一个方法,根据参数dispatch一个action

const mapDispatchToProps = dispatch => ({ onSwitchColor: (color) => { dispatch({ type: 'CHANGE_COLOR', themeColor: color }) } })
  • mapDispatchToProps如果是对象。键名对应要映射到props的属性,键值是action creator,返回的action会由redux自动发出
const mapDispatchToProps = { countAdd: countAdd, // countAdd 是 action creator countMinus // 简写 }
  • 使用这种格式是装饰器模式的实现。能够兼容ES7的装饰器(Decorator)。使得我们可以用@connect这样的方式来简化代码

  • 装饰器相关知识参考:修饰器Decorator笔记

  • 基本结构

export function connect(mapStateToProps, mapDispatchToProps) { return function(WrapComponent) { return class ConnectComponent extends React.Component { } } } // 使用箭头函数简化 export const connect = (mapStateToProps, mapDispatchToProps) => WrapComponent => { return class ConnectComponent extends React.Component { } }
  • 基本实现
// ... import { bindActionCreators } from './redux' export const connect = (mapStateToProps = state=>state, mapDispatchToProps={}) => WrapComponent => { return class ConnectComponent extends React.Component { static contextTypes = { store: PropTypes.object } constructor(props) { super(props) this.state = { props: props } } componentDidMount() { const { store } = this.context // 监听store的数据变化,及时更新 store.subscribe(() => this.update()) this.update() } update() { // 获取mapStateToProps和mapDispatchToProps,放入this.props const { store } = this.context const stateProps = mapStateToProps(store.getState()) // 方法不能直接给,因为需要dispatch // function countAdd() { // return { type: COUNT_ADD } // } // 直接执行countAdd() 毫无意义 // 要 (参数) => store.dispatch(countAdd(参数)) 才有意义 // 其实就是用dispatch把action creator包了一层 const dispatchProps = bindActionCreators(mapDispatchToProps, store.dispatch) this.setState({ props: { ...this.state.props, ...stateProps, ...dispatchProps } }) } render() { return <WrapComponent {...this.state.props} /> } } } export class Provider extends React.Component { // ... }
  • 这里的bindActionCreatorsredux提供的一个方法,我们也同样实现一下
// countAdd(参数) → dispatch(countAdd(参数)) function bindActionCreator(creator, dispatch) { return (...args) => dispatch(creator(...args)) } // { countAdd, countMinus } // { // countAdd: (参数) => dispatch(countAdd(参数)) // countMinus: (参数) => dispatch(countMinus(参数)) // } export function bindActionCreators(creators, dispatch) { // let bound = {} // Object.keys(creators).forEach(v => { // let creator = creators[v] // bound[v] = bindActionCreator(creator, dispatch) // }) // return bound return Object.keys(creators).reduce((ret, item)=> { ret[item] = bindActionCreator(creators[item], dispatch) return ret }, {}) }

redux中间件

  • 中间件:我们可以理解为拦截器。用于对某些过程进行拦截和处理,且中间件之间能够串联使用

  • redux中,我们中间件拦截的是dispatch提交到reducer这个过程,从而增强dispatch的功能

每次dispatch后手动打印store的内容

  • 每次dispatch后手动打印store的内容
store.dispatch({ type: 'plus' }) console.log('next state', store.getState())

封装dispatch

  • 重新封装一个公用的新的dispatch方法,这样可以减少一部分重复的代码
function dispatchAndLog(store, action) { store.dispatch(action) console.log('next state', store.getState()) }

替换dispatch

  • 经过封装之后,每次使用这个新的dispatch都得从外部引一下,还是比较麻烦

  • 我们直接把dispatch给替换,这样每次使用的时候不就不需要再从外部引用一次了

let next = store.dispatch store.dispatch = function dispatchAndLog(action) { let result = next(action) console.log('next state', store.getState()) return result }

模块化

  • 不同的功能是独立的可拔插的模块

  • 把不同功能的模块拆分成不同的方法。通过在方法内获取上一个中间件包装过的store.dispatch实现链式调用

// 打印日志中间件 function patchStoreToAddLogging(store) { let next = store.dispatch store.dispatch = action => { let result = next(action) console.log('next state', store.getState()) return result } } // 监控错误中间件 function patchStoreToAddCrashReporting(store) { // 这里取到的dispatch已经是被上一个中间件包装过的dispatch, 从而实现中间件串联 let next = store.dispatch store.dispatch = action => { try { return next(action) } catch (err) { console.error('捕获一个异常!', err) throw err } } }
  • 能通过调用这些中间件方法,分别使用、组合这些中间件
patchStoreToAddLogging(store) patchStoreToAddCrashReporting(store)

applyMiddleware

  • 上面代码写的中间件方法都是先获取dispatch,然后在方法内替换dispatch

  • 优化下:不在方法内替换dispatch,而是返回一个新的dispatch,然后让循环来进行每一步的替换

function logger(store) { let next = store.dispatch return action => { let result = next(action) console.log('next state', store.getState()) return result } }
  • redux中增加一个方法applyMiddleware,用于添加中间件
export function applyMiddleware(store, middlewares) { // 浅拷贝数组, 避免下面reserve()影响原数组 middlewares = [ ...middlewares ] // 由于循环替换 dispatch 时,前面的中间件在最里层, 因此需要翻转数组才能保证中间件的调用顺序 middlewares.reverse() // 循环替换dispatch middlewares.forEach(middleware => { store.dispatch = middleware(store) }) }
  • 然后就可以用以下形式添加中间件了
applyMiddleware(store, [ logger, crashReporter ])
  • 创建了三个中间件,分别是logger1thunklogger2测试下中间件
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import { Provider } from './react-redux' import { createStore, applyMiddleware } from './redux' import { reducer } from './reducer' let store = createStore(reducer) function logger(store) { let next = store.dispatch return action => { console.log('logger1') let result = next(action) return result } } function thunk(store) { let next = store.dispatch return action => { console.log('thunk') return typeof action === 'function' ? action(store.dispatch) : next(action) } } function logger2(store) { let next = store.dispatch return action => { console.log('logger2') let result = next(action) return result } } applyMiddleware(store, [ logger, thunk, logger2 ]) ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );
  • 发出异步dispatch
function addCountAction(dispatch) { setTimeout(() => { dispatch({ type: 'plus' }) }, 1000) } dispatch(addCountAction)

纯函数

  • 上面的方法,函数在函数体内修改了store自身的dispatch,产生了所谓的副作用

  • 从函数式编程的规范出发,进行一些改造。把applyMiddleware作为高阶函数,用于增强store,而不是替换dispatch

  • 修改reduxcreateStore()方法,传入enhancerenhancer接收并强化createStore

// ... export function createStore(reducer, enhancer) { // enhancer 是一个高阶函数,用于增强 createStore,返回一个增强后的 createStore // 如果存在 enhancer,则执行增强后的 createStore if (enhancer) { return enhancer(createStore)(reducer) } // ... }
  • 中间件进一步柯里化,让next通过参数传入
const logger = store => next => action => { console.log('log1') let result = next(action) return result } const thunk = store => next => action => { console.log('thunk') const { dispatch, getState } = store return typeof action === 'function' ? action(store.dispatch) : next(action) } const logger2 = store => next => action => { console.log('log2') let result = next(action) return result }
  • 修改reduxapplyMiddleware()方法
export function applyMiddleware(...middlewares) { return createStore => reducer => { const store = createStore(reducer) let { getState, dispatch } = store const midApi = { getState: store.getState, // 这里不要直接 dispatch: dispatch // 因为直接使用 dispatch 会产生闭包,导致所有中间件都共享同一个dispatch // 如果有中间件修改了 dispatch 或者进行异步 dispatch 就可能出错 dispatch: action => dispatch(action) } const middlewareChain = middlewares.map(middleware => middleware(midApi)) dispatch = compose(...middlewareChain)(dispatch) // dispatch = middleware(midApi)(dispatch) return { ...store, dispatch } } } // compose(fn1, fn2, fn3) // fn1(fn2(fn3)) export function compose(...funcs) { if (funcs.length == 0) { return arg => arg } if (funcs.length == 1) { return funcs[0] } return funcs.reduce((ret, item) => (...args) => ret(item(...args))) }

enhancer

  • 通过createStore(reducer)可以生成store
  • createStore(reducer, enhancer)方法多传一个enhancer,用来增强createStore
  • enhancer(createStore)返回一个增加后的createStore,比如叫enhancerdCreateStore
  • 使用enhancerdCreateStore(reducer)生成store返回

中间件

  • const thunk = ({ dispatch, getState }) => next => action => {}

applyMiddleware

  • enhancer == applyMiddleware(...middlewares)
  • 所以applyMiddleware(thunk)应该返回一个enhancer
  • applyMiddleware基本结构应该function applyMiddleware(...middlewares) { return createStore => reducer => { /* ... */} }

完整代码

完整代码下载


创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: