redux基本思路
-
redux
是用来管理公共状态,公共state
都存放在store
中。使用createStore()
方法新建一个store
-
直接修改容易引起误操作,需要有条件的操作
store
,不能直接修改 -
使用
store.getState()
来获取state
-
使用
store.dispatch(action)
修改state
-
使用
store.subscribe(listener)
实现监听,如果有改动,进行对应的响应操作 -
action
是一个对象,基本格式{ type: TEST, payload: { name: 'ddd' } }
-
action creator
是action
生成函数,根据传入的参数生成对应的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)
用来将对应的state
和dispatch
放到组件的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
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
里的state
和dispatch
放到组件的属性里 -
connect
在state
数据变化的时候,能够通知组件 -
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 {
// ...
}
- 这里的
bindActionCreators
是redux
提供的一个方法,我们也同样实现一下
// 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 ])
- 创建了三个中间件,分别是
logger1
、thunk
、logger2
测试下中间件
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
-
修改
redux
的createStore()
方法,传入enhancer
。enhancer
接收并强化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
}
- 修改
redux
的applyMiddleware()
方法
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 => { /* ... */} }
发表评论