Redux源码分析
redux是什么
Redux 是 JavaScript 状态容器,提供可预测化的状态管理 redux是一种架构模式,跟react-redux并不是同一个东西。
redux的工作流
- 首先,用户发出action
store.dispatch(action);
- store调用reducer,返回新的state
let nextState = todoApp(previousState, action);
- state一旦有变化,store就会调用监听函数
store.subscribe(listener);
- 重新渲染view
为什么使用redux
某个组件的状态,需要共享 某个状态需要在任何地方都可以拿到 一个组件需要改变全局状态 一个组件需要改变另一个组件的状态
“模块(组件)之间需要共享数据”,和“数据可能被任意修改导致不可预料的结果”之间的矛盾。
学习react团队的做法:提高修改数据的门槛,这个数据并不能直接改,需要执行某些我允许的操作,然后告诉我你要修改。
所有对数据的操作必须通过dispatch函数。
迷你redux的实现
redux的使用
import { createStore } from 'redux'
// reducer处理函数,参数是状态和新的action
function counter(state=0, action) {
console.log(state, action)
switch (action.type) {
case '加1':
return state + 1
case '减1':
return state - 1
default:
return 10
}
}
// 新建保险箱,产生一种我们新定义的数据类型store
const store = createStore(counter)
// store.getState()获取共享状态
const init = store.getState()
console.log(`一开始是${init}`)
function listener(){
const current = store.getState()
console.log(`现在是${current}`)
}
// 订阅,每次state修改,都会执行listener
store.subscribe(listener)
// 提交状态变更的申请,store.dispatch修改状态
store.dispatch({ type: '加1' })
store.dispatch({ type: '加1' })
store.dispatch({ type: '加1' })
store.dispatch({ type: '减1' })
store.dispatch({ type: '减1' })
createStore的实现
export function createStore(reducer) {
let currentState = {}; // 内部状态
let currentListeners = []; // 当前监听
const getState = () => currentState
const subscribe = (listener) => {
currentListeners.push(listener)
}
const dispatch = (action) => {
currentState = reducer(currentState,action)
currentListeners.forEach(v => v())
return action
}
dispatch({type:'@@WINNIE_REDUX/INIT'})
return {getState,subscribe,dispatch}
}
为什么要用react-redux?
以上是redux,跟react没有什么关系,react-redux是把react与redux结合起来,用 Redux 模式帮助管理 React.js 的应用状态
什么是状态提升:当某个状态被多个组件依赖或者影响的时候,就把该状态提升到这些组件的最近公共父组件中去管理,用 props 传递数据或者函数来管理这种依赖或着影响的行为。
问题:如果需求不停变化,共享状态一层层传递或者没完没了的提升,不利于代码的组织和维护
解决:把共享状态放到父组件的context上,这个父组件下所有的组件都可以从context中直接获取到,就不需要一层层地进行传递了
新的问题:直接从 context 里面存放、获取数据增强了组件的耦合性;并且所有组件都可以修改 context 里面的状态就像谁都可以修改共享状态一样,导致程序运行的不可预料。
解决:把 context 和 store 结合起来。store的数据只能通过dispatch去修改,context从store中获取状态。可以追踪更改的过程
最终方案:react-redux
react的context
组件树上某颗子树的全局变量。
某个组件只要往自己的context里放了某些状态,这个组件之下的所有子组件都可以直接访问这个状态而不需要通过中间组件的传递。
一个组件的context只能它的子组件能够访问,它的父组件是不可以访问的。
getChildContext 这个方法就是设置 context 的过程
childContextTypes作用与propsType验证组件props参数的作用类似。不过它是验证getChildContext返回的对象。
为什么要验证呢?因为context是一个危险的特性,按照react团队的想法,就是把危险的事情搞复杂一些,提高使用门槛,人们就不会去用了。如果你要给组件设置context,那么childContextTypes是必须写的。
一个组件可以通过 getChildContext 方法返回一个对象,这个对象就是子树的 context,提供 context 的组件必须提供 childContextTypes 作为 context 的声明和验证。
Redux就是利用这种机制给我们提供便利的状态管理服务
react-redux实现
Provider-为全局提供context。把store放到context里,所有的子元素可以直接取到store。容器组件,会把嵌套内容原封不动为自己的子组件渲染出来
export class Provider extends React.Component {
static childContextTypes = {
store: PropTypes.object
}
getChildContext() {
return {
store: this.store
}
}
constructor(props,context) {
super(props,context)
this.store = props.store
}
render() {
return this.props.children
}
}
connect的实现 为什么要用connect
- 重复逻辑:状态放在了父组件中,子组件使用时需要从context中去取,会有很多重复逻辑
- 依赖context:子组件依赖context,在组件树并没有context事,组件可不复用性
connect参数
- WrapComponent:把这个组件作为参数,包含在一个新的组件里ConnectComponent里,会去context里取出store。然后把store里面的数据取出来通过props传给WrapComponent
但是每个组件需要store里面的数据都不一样的,所以我们需要告诉高级组件我们需要什么数据。
- mapStateToProps:接收store.getState()的结果作为参数,然后返回一个对象,这个对象是根据state生成的。告诉connect应该怎么去store里取数据,然后把返回结果传给被包装的组件
const { store } = this.context
let stateProps = mapStateToProps(store.getState())
- connect监听数据变化然后重新渲染,通过 store.subscribe 监听数据变化重新调用update
store.subscribe(() => this.update())
- mapDispatchToProps:除了需要取数据外,我们还需要store来dispatch,通过mapDispatchToProps来告诉组件如何触发dispatch。接收dispatch,通过dispatch触发特定的action
// connect负责链接组件,给到redux里的数据放到组件的属性里
// 1,负责接收一个组件,把state里的一些数据放进去,返回一个组件
// 2,数据变化的时候,能够通知组件
export const connect = (mapStateToProps = state => state,mapDispatchToProps={}) => (WrapComponent)=> {
return class ConnectComponent extends React.Component {
// 获取context
static contextTypes = {
store: PropTypes.object
}
constructor(props,context) {
super(props);
this.state = {
props:{}
}
}
componentDidMount() {
const {store} = this.context;
store.subscribe(() => this.update())
this.update()
}
update() {
// 获取mapStateToProps和mapDispatchToProps,放入this.props
const {store} = this.context;
const stateProps = mapStateToProps(store.getState());
// 方法不能直接给,因为需要dispatch
const dispatchProps = bindActionCreators(mapDispatchToProps,store.dispatch)
// const dispatchProps = mapDispatchToProps(store.dispatch, this.props)
this.setState({
props: {
...this.state.props,
...dispatchProps,
...stateProps
}
})
}
render() {
return (
<WrapComponent {...this.state.props}/>
)
}
}
}
function bindActionCreator(creator,dispatch) {
return (...args) => dispatch(creator(...args))
}
function bindActionCreators(creators,dispatch) {
let bnound = {};
Object.keys(creators).forEach(v => {
let creator = creators[v];
bnound[v] = bindActionCreator(creator,dispatch)
})
return bnound;
}
mobx和redux的区别
redux将数据保存在单一的store中,mobx将数据保存在分散的多个store中
redux使用plain object保存数据,需要手动处理变化后的操作;mobx适用observable保存数据,数据变化后自动处理响应的操作
redux使用不可变状态,这意味着状态是只读的,不能直接去修改它,而是应该返回一个新的状态,同时使用纯函数;mobx中的状态是可变的,可以直接对其进行修改
mobx相对来说比较简单,在其中有很多的抽象,mobx更多的使用面向对象的编程思维;redux会比较复杂,因为其中的函数式编程思想掌握起来不是那么容易,同时需要借助一系列的中间件来处理异步和副作用
结论:对于一些简单的,规模不大的应用来说,用mobx就足够了。但是一些大的,复杂的应用就不能用mobx吗?我觉得不一定。其实如果能够合理的组织代码的结构,理清依赖关系,一些复杂的场景适用mobx也是可以的。
中间件
用法:
const store = createStore(counter,applyMiddleware(thunk,arrThunk))
applyMiddleware:
export function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args);
let dispatch = store.dispatch;
const midApi = {
getState: store.getState,
dispatch: (...args) => dispatch(...args),
}
// dispatch = middleware(midApi)(store.dispatch)
const middlewareChain = middlewares.map(middleware => middleware(midApi))
dispatch = compose(...middlewareChain)(store.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((res,item) => (...args) => res(item(...args)))
}
thunk:
const thunk = ({dispatch,getState}) => next => action => {
if(typeof action === 'function') {
return action(dispatch,getState)
}
return next(action);
}
export default thunk;
arrThunk:
const arrThunk = ({dispatch,getState}) => next => action => {
if(Array.isArray(action)) {
return action.forEach(v => dispatch(v))
}
return next(action);
}
export default arrThunk;