Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redux中间件、connect以及与flux、mobx的区别 #56

Open
2018212632 opened this issue Dec 4, 2020 · 0 comments
Open

Redux中间件、connect以及与flux、mobx的区别 #56

2018212632 opened this issue Dec 4, 2020 · 0 comments

Comments

@2018212632
Copy link
Owner

Redux中间件、connect以及与flux、mobx的区别

原生的Redux

因此当我们没有使用任何中间件的时候,使用 redux 的大致流程为:

store.js

import { createStore} from 'redux';
import reducers from './reducers';

const store = createStore(reducers);
export default store;

reducer.js

reducer应该是纯函数: 纯函数给定固定的输入,就会有固定的输出,且不会有副作用

副作用:

  • 打印/log
  • 发送一个http请求
  • 可变数据
  • DOM查询

简单一句话, 即只要是与函数外部环境发生交互的都是副作用。

const defaultstate = {
    inputValue: '',
    list: []
}

// reducer可以接受state,但不能直接改变state
// 纯函数给定固定的输入,就会有固定的输出,且不会有副作用
export default (state = defaultstate,action) => {
    if( action.type === 'CHANGE_INPUT_VALUE') {
        const newState = JSON.parse((JSON.stringify(state))); // 对原来的state进行深拷贝
        newState.inputValue = action.value;
        return newState;
    }

    return state;
}

ActionCreators.js

export const getInputChangeAction = (value) => ({
    type: "CHANGE_INPUT_VALUE",
    value
})

component.js

import React, { Component } from 'react';
import store from './store';
import { getInputChangeAction} from './store/ActionCreators';

class Todolist extends Component {
 
    constructor(props){
        super(props);
        this.state = store.getState();
        this.handleStoreChange = this.handleStoreChange.bind(this);
        store.subscribe(this.handleStoreChange)
    }
    componentDidMount()
    {
        const action =getMyListAction()
        store.dispatch(action)
        console.log(action)
    }
    handleStoreChange() {
        this.setState(store.getState())
    }

    render() {
        return (
          <div></div>
        )
    }

}
export default Todolist;

在组件中的的初始化时,绑定store的state到this.state,让后订阅一个函数,一旦store的state变动,那么执行订阅中的函数,订阅中的函数做的事情是让store的state重新赋值给state。

Redux中常用的中间件

如果我们继续采用原始的Redux,通过订阅,getState,手动来更新组件的state,如果action有异步的操作时,代码就会显得特别不清晰。

中间件就是一个函数,对store.dispatch方法进行了改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。

因此通过中间件,我们可以打印日志,以及更方便的异步操作,可以在异步获取到结果后,在一次dispatch,更新store。

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => (next) => (action) => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

applymiddleware:

  const chain = middlewares.map(middleware => middleware(middlewareAPI))
  dispatch = compose(...chain)(store.dispatch)

Redux中applymiddleware 在第一次调用createThunkMiddleware是在chain阶段,
这里的next也就是第二次调用时的store.dispatch, 为了实现同一函数内能执行多次dispatch,我们会判断如果action为函数,则执行action本身并把必要参数传递给它,否则则直接触发dispatch,这样我们就实现了支持action为函数并且支持异步多dispatch的功能了,读到这还是非常感叹其设计的优雅和简洁。

redux-saga

redux-saga和redux-thunk同为中间件,两者都可以处理异步的action,主要区别在于redux-thunk配置较为简单,saga 自己基本上完全弄了一套 asyc 的事件监听机制。虽然好的一方面是扩展了 worker 相关的模块,甚至可以做到 multiple threads 同时执行,但代码量大大增加。如果只是不需要粒度特别小的异步管理流程,以及查看每一个action具体的表现,redux-thunk足够了。

Redux中connect

connect将组件和Redux的store连接起来,允许store上的数据作为props绑定到组件上。

参数:

connect([mapStateToProps], [mapDispatchToProps], [mergeProps],[options])

mapStateToProps:

mapStateToProps(state, ownProps) : stateProps

这个函数的第一个参数就是 Redux 的 store,可以根据组件或页面需求,获取需要的最小属性;通常会通过对应的reducer中找到默认的state,因此在写reducer的时候,名称尽量和组件保持一致,方便书写时查看组件需要store上的属性。第二个参数,组件自身的props,当store的state,或者ownProps变化时,都会执行mapStateToProps,将两个进行merge,随后将生成新的stateProps

mapDispatchToProps

mapDispatchToProps(dispatch, ownProps): dispatchProps

它的功能是,将 action 作为 props 绑定到组件上,也会成为组件的 props。

mergeProps

[mergeProps],[options]

不管是 stateProps 还是 dispatchProps,都需要和 ownProps merge 之后才会被赋给组件。connect 的第三个参数就是用来做这件事。通常情况下,你可以不传这个参数,connect 就会使用 Object.assign 替代该方法。

connect首先会在原应用组件上包裹一层,使整个应用成为Provider的子组件,将store作为props传给子组件的connect;connect内部帮我们订阅了store,因此每当store的state变化,组件能够获悉state的变化;它在性能优化,对Component做了一个性能更新优化,当之前的props和当前的props进行浅比较来确定 diff 中component是否更新。

connect是一个高阶函数,首先传入mapStateToProps、mapDispatchToProps,然后返回一个生产Component的函数(wrapWithConnect),然后再将真正的Component作为参数传入wrapWithConnect,这样就生产出一个经过包裹的Connect组件,该组件具有如下特点:

  • 通过props.store获取祖先Component的store
  • props包括stateProps、dispatchProps、parentProps,合并在一起得到nextState,作为props传给真正的Component
  • componentDidMount时,添加事件this.store.subscribe(this.handleChange),实现页面交互
  • shouldComponentUpdate时判断是否有避免进行渲染,提升页面性能,并得到nextState
  • componentWillUnmount时移除注册的事件this.handleChange

引出的问题

业内有许多数据流管理方案,比如flux、Redux、Mobx,为什么选择redux?

flux 将应用分成四个部分:

  • View:视图层
  • Action:视图层发出的消息(比如click事件)
  • Dispatcher(派发器):用来接收Actions、执行回调函数
  • Store(数据层):用来存放应用的状态,一旦发生变动,就提醒Views要更新页面

flux整个流程为:用户访问 View -> View 发出用户的 Action -> Dispatcher 收到 Action,要求 Store 进行相应的更新 -> Store 更新后,发出一个"change"事件 -> View 收到"change"事件后,更新页面

Flux 的最大特点,就是数据的"单向流动",保证了流程的清晰;flux有一个缺点,一个应用可以拥有多个 Store,多个Store之间可能有依赖关系,只要触发了Action就会派发给所有的store,不论这个Action是View触发或者测试触发;Store 封装了数据还有处理数据的逻辑。

Redux 本身流程和 flux 相似:

  • React component: 视图层
  • Action Creators: 组件发出的消息或动作()
  • Store: 单一的数据源
  • Reducer: 纯函数,固定的出入就会有固定的输出,没有副作用

Redux整个流程:用户访问组件 -> 组件触发Action(希望改变store中的state) -> dispatch(action) -> 将action通过reducer进行处理 -> 更新store的数据

在Redux中有三条原则,单一的数据源store;改变state只能通过Action去触发,保证整个数据改变过程是可预测的;使用纯函数来执行修改(如果不是纯函数,引用外部变量发生变化,那么下次经过reducer得到的state就会和预期不符);虽然Redux也是单向数据流,但是和flux相对,Redux的数据源是单向的,flux每次更新只通知相应的view,Redux中的各个子Reduce是由根Reducer统一管理,每个子Reducer的变化最终经过根Reducer的整合。

Mobx 背后的思想任何源自应用状态的东西都应该自动地获得:

  • Actions: 事件调用Actions,Actions是唯一可以修改state的东西,可能带有副作用
  • State: state是可观察的数据,可以数组、引用等等
  • Computed values: 可以用纯函数从state中推导的值,mobx会自动更新它,在不使用时,优化掉
  • Reactions: 会对state的变化做出反应,不会产生一个值,会产生副作用,比如更新UI

mobx与Redux的区别:

  • Mobx观察者模式,其中store是被观察者(observable),组件是观察者(observer);一旦Store有变化,会立刻被组件观察到,从而引发重新渲染,而Redux采用的发布者-订阅模式;观察者模式过于耦合,发布-订阅需要中间层,做一个派发,执行订阅者的 lisnter 函数,是松散的,可以解耦的。

  • mobx中修改数据是直接修改源state,不像redux那样返回新的state;

  • redux中是粗粒度的订阅,如果一个组件订阅了当前的state,如果组件中一个小组件的state更新,就会触发v这个组件相应的reducer的改动,mobx中只是相应state来改变相应的view。

  • 逻辑层的限制,在mobx中,当项目庞大时,mobx没有类似redux-saga这样的中间来处理业务逻辑,那么组件本身既要负责UI、组件通信、还有一些特殊的dom处理,对model的操作会倾向写在view里面,那么维护这些逻辑的成本就会随着迭代急剧上升。也因为逻辑都在一个地方,所以直接使用一个复杂业务逻辑组件就简单很多。如果这时候来一个特殊需求,你就只能在这个组件内写特殊逻辑分支了。在redux内则不会有这个问题。

  • 可以随意操作store:会导致逻辑的封装不严谨。对store的不同操作逻辑可能被放在各个不同的组件内,不能被抽象出来,导致这个组件很可能没办法很好的复用,或是解耦。在多人团队合作的时候,很可能因为高自由度而导致逻辑的混乱。

    最新的 mobx 2.2 加入了 action 的支持。并且在开启 strict mode 之后,就只有 action 可以对数据进行修改,限制数据的修改入口。可以解决这个问题。

参考链接

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant