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

对 React 状态管理的理解及方案对比 #36

Open
7 tasks
sunyongjian opened this issue Jan 14, 2018 · 21 comments
Open
7 tasks

对 React 状态管理的理解及方案对比 #36

sunyongjian opened this issue Jan 14, 2018 · 21 comments

Comments

@sunyongjian
Copy link
Owner

sunyongjian commented Jan 14, 2018

引入

在组内做了一次 React 状态管理的分享,总结了一些 redux 的问题,安利了一下 mobx 更方便的地方。整理成文字,便是此文。注:本文未整理完,大部分都是列的大纲,预计周二前整理完。ps: 应该整理不完

目录

  • React 状态、通信

  • Flux —> Redux —> Mobx

  • redux,mobx 对比

React 为什么需要状态管理

不妨先回忆一下 react 的特点。

React 特点

  • 专注 view 层

React 官网是这么简介的。JavaScript library for building user interfaces.专注 view 层 的特点决定了它不是一个全能框架,相比 angular 这种全能框架,React 功能较简单,单一。比如说没有前端路由,没有状态管理,没有一站式开发文档等。

  • f(state) = view

react 组件是根据 state (或者 props)去渲染页面的,类似于一个函数,输入 state,输出 view。不过这不是完整意义上的 MDV(Model Driven View),没有完备的 model 层。顺便提一句,感觉现在的组件化和 MDV 在前端开发中正火热,大势所趋...

  • state 自上而下流向、Props 只读

从我们最开始写 React 开始,就了解这条特点了。state 流向是自组件从外到内,从上到下的,而且传递下来的 props 是只读的,如果你想更改 props,只能上层组件传下一个包装好的 setState 方法。不像 angular 有 ng-model, vue 有 v-model, 提供了双向绑定的指令。React 中的约定就是这样,你可能觉得这很繁琐,不过 state 的流向却更清晰了,单向数据流在大型 spa 总是要讨好一些的。

这些特点决定了,React 本身是没有提供强大的状态管理功能的,那 React 组件是如何通信呢?原生大概是三种方式。

React 组件通信

之前有总结过 React 的通信问题,再次简单提一下,并说明它们存在的问题。其实大概就是两种方式,其一是状态提升,即把需要通信的 state 提升到两者共同的父组件,实现共享和 Reaction。不过状态提升又分为 Continer 组件定义和使用 Context 属性传递,这两者虽然都算是状态提升,但是实际不太一样,所以我们会分为三种方式。
tx1

如图。A,B 组件下的 A1,B1 要实现 state 通信。

  • Continer

tx3

在 A,B 之上增加一个 Container 组件,并把 A1,B1 需要共享的状态提升,定义到 Container,通过 props 传递 state 以及 changeState 的方法。
此方式存在的一个问题是,以后如果有一个 C 组件的 state,与 A 要做通信,就会再添加一个 Container 组件,如果是 A 的 state 要跟 C 共享,更是毁灭性打击,之前提升到 Container 的 state,还要再提升一层。这种无休止的状态提升问题,后期的通信成本非常高,几乎是重写。

  • Context
    image

乍一看,Context 像是一个好的方案,它解决了无限状态提升的问题,都统一放到定义 Context 的组件就好了。不过 React 官网倒是不建议用它 Why Not To Use Context,理由是它一个实验性的特性,未来可能移除。不过我个人觉得已经不太可能移除了,因为基本 React 的 Provider 组件都是基于此做的。

下面是 Context 定义的一段代码:
image

真正不要使用 Context 的原因是,它在状态更新通知组件方面存在缺陷。Context 的机制是这样的,假如 context 中定义存在变化的值,比如上图的 value,Context 组件会重新渲染(执行 SCU,willReceivedProps),重新生成 context 对象。此举会使得 Context 下面的所有组件都重新 render,才可以接收到最新的 context 对象。

假如某一层,比如组件 A,没有 re-render,我设置了 shouldComponentUpdate = false,或者是一个 pureComponent,都会阻碍 context 对象的传递,相当于在流向中加了一堵墙。

而解决这个问题的办法,就是使用发布订阅去改造。context 对象不再发生改变,而且子组件只第一次构建的时候接收 context 对象,这意味着 context 对象要内部实现订阅和发布的功能,即组件使用的时候订阅,对象属性变化的时候通知组件 render。关于此详细内容可以到如何安全的使用 Context查看(需要全局翻墙)

  • 发布订阅

listx

这个没什么好说的,就是一个组件订阅,一个组件发布,了解观察者模式的都清除,这里 也有详细的代码。想指出的是这种方式虽然解决了状态提升带来的问题,但是 state 保存在各个组件中,如果要做通信,需要写很多订阅/发布的代码,而且这部分代码也不好封装。最重要的是,数据流变得很乱,互相之间都有依赖,让代码维护起来比较困难。

其实在这里就会想到,假如说让 Event 中心统一管理状态,统一分发呢,然后每个组件只需要接收,订阅的工作交给 hoc 去处理,我们只需要告诉 hoc 我们要什么 state,它遍从 Event 中心去取。当 state 发生改变,Event 中心 pub 事件,hoc 去处理 re-render 的工作,感觉这就是目前状态管理 library 都在做的,大同小异。

问题

所以随着 spa 的增大,这些方式都会有一些问题,状态无休止提升,context 不好用,发布订阅数据流向混乱,跟组件的耦合性较高,数据流不清晰,
大型 spa 组件通信成本太高。
statetx

所有我们希望有一个独立管理的地方,像这样:
storetx

它可能会有一个 store 的概念,store 去存储数据和状态,还需要可以被订阅,即 store 中的状态发生变化要做到及时的通知。再进一步就是可以跟组件的交互,通信都交给 store 来处理。
还可以区分变化和副作用,做不同的处理。

目前社区中熟知的状态管理库就那几个,接下来按照这几个库的发布顺序介绍一下。

Flux

Flux 是随着 React 的诞生,而提出的一种状态管理的解决方案。由于 MVC 模式在大型前端应用里变得流向复杂,以及 Model 和 View 的双向绑定问题。便提出了这样的结构:
flux

从 flux 开始,就是严格的数据流向,只能通过 actions 改变 store,actions 是借助 Dipatcher.dispatch
这样的 API 发出,然后再修改 store,由 store 去更新 view。

几个主要的概念:

  • Dispatcher,处理动作的分发,修改 Store 上的数据。register,dispatch
  • Store,负责存储数据和处理数据相关的逻辑.addChangeListener
  • Action,驱动 Dipatcher 的 JS 对象
  • VIew,视图部分,展示数据,react 组件的分层组合

flux 在 store 驱动组件这一层,没有做很好的支持,如果要做需要频繁的绑定事件。另外 action 也没有很好的异步方案。这些工作都需要用户自己去摸索处理。所以在 redux 去处理好这些问题,并且提出了更好的思想的时候,flux 很快就被替代了。不过 flux 为 redux 做了很好的借鉴,流程,action 对象,单向数据流。

Redux

redux

如果说 flux 是一种思想的话,redux 就是对 flux 最好的实现。redux 把 flux 的多个 store 概念干掉了,只有一个 store,并且内部由 reducer 计算生成新的 state tree。把 dispatcher 跟 action 解耦,action 就是一个简单的 actionCreator。redux 也基于 koa 的中间件思想,丰富了自己的拓展性。

几个主要的概念:

  • 一个 store,管理 state,并有 dispatch(action) ,subscribe 等方法。
  • Reducers. Old => new state。纯函数,用于计算新的 state。
  • 多了 Provider,store 的订阅者,通过它来与 react 等框架交互,比如 react-redux 中的 Provider。
  • Actions。由 provider 创建并由用户事件触发。

如果你是 flux 的使用者,redux 出现的时候,你基本很容易接受这个思想,除了一些函数式的思维。内容方面在这不过多阐述了,这不是本次的主题,面向的也是使用过的同学。

Redux 生命周期

  • Store.dispatch(action)
  • Reducer 执行
  • 生成 state tree
  • Store.subscribe(listener)

不过在源码里,都是在 createStore 初始化的时候,dispatch 方法里实现的。Redux 的 store,跟我们上面在“发布订阅”设想的一种思想就很契合了,store 不仅作为一个 state 的存储中心,还是事件中心。所有的订阅以及事件的分发,都在 store 里面处理了。而具体如何展示、计算数据(state),是用户传入的 reducer 决定的。关于 store 和 component 的结合,通过 react-redux 中的两个组件,就可以很好的解决了,connect 也的确是 hoc,你只需要告诉它你用什么属性,它就帮你自动处理 reaction 的 re-render。

特点

总结了几个:

  • CQRS
    命令与查询职责分离,说实话这个概念跟 redux 有关我是在知乎的某个问答看到的。当把 redux 的 store 看做一个 model 的时候,之前对 model 的查询、命令(增删改)对应的就像 redux 里 的 reducer、action 。我们不讨论这两者到底有何内在的联系,编程的思想本来就是相同的。如果你有兴趣,你也可以了解一下 Event Sourcing 和 DDD,都会找到跟 redux 相似的影子。推荐

  • 单向数据流
    从 flux 开始提出,也是目前状态管理比较喜欢采用的,因为简单清晰的数据流向。

  • 很多函数式的思想。比如 Reducer,Immutable Data,Pure Func,Redux 内部实现, compose 等。

  • 通过 state 的引用变化,判断是否更新组件。

    既然 state 的不可变数据,基于此理论基础,使用最简单的比较数据引用,去判断是不是要更新 view。

  • 天生支持时间回溯

    因为每次的 state 都是新的引用,上一次的 state 还可以保存,感觉上是有了基础,实际要做很多工作。可以借鉴 redux-devtools 。

  • 中间件拓展
    redux 最强大的地方便在于中间件的拓展。有一句话叫“好的设计不如好的拓展性”。

一些问题

这是我们要提及的重点。我在学习和使用 redux 的时候,遇到了很多的问题,总结来就是以下四点,相信大家也会碰到...

约定多、理解难

胶水代码

Effects

手动优化

约定

对于初学者来说,一堆的约定看着就头大,也不知道为啥这么写。我大概列举几个:

  • 三大原则,store 唯一,state 只读, reducer 纯函数。

    这个是我们学习 redux 初期就烂熟于心的概念了,几乎所有 redux 相关的入门教程,都会有这样的介绍,总之,就算不明白,我们也记住了。首先,store 设计成唯一的对象,一个对象能带来很多它简单的特点,比如所有的 reducer 计算都合并到这个 store,所有的组件修改,注入都是基于这一个对象;初始化同构应用的状态(服务端渲染)也更方便,服务端直接挂载一个 __ initalStore__ 就可以了。state 只读有函数式的感觉,另外也是单向数据流的关键,你不能直接修改 state 对象,只可以通过 action 去修改,这个跟 flux 是相似的思想。reducer 是纯函数,也来自于 FP。不能有副作用的操作,通常是 state 的一些重组,拷贝和计算。使用 redux,会有很多函数式的思想,所以很多人都会学一波函数式,比如说我... FP-Code

  • 建议我们用单独的文件存放 action;不要定义 action 对象,要用 actionCreator 创建。

    dispatch

    为了不让子组件感受到 redux 的存在,通过 bindActionCreators 生成这种 () => dispatch(actionCreator); 形式的 function。 如上图,最后可能就是 increase 这样的一个函数,然后组件内部去调用此方法,比如用 connect 这样的组件注入 actions 方法。最开始写 redux 的时候,我见过很多人在组件内部调用 dispatch 方法的,因为 connect 的第二个参数 MapDispatchToProps 不传的时候,它会默认把 dispatch 方法传下来。然后就直接在组件内部写 dispatch({ type: ’INCREASE‘}) 这样的代码,这跟使用 bindActionCreator 的就形成了不同的风格。另外,大项目中是不建议组件直接用 dispatch 方法的,redux 建议的是 smart + dumb,即有一个 container 组件跟 redux 做交易,下面的组件要做成可复用的,跟 redux 无强耦合的组件... 所以,我理解的 actionCreator 的作用大概是这样。

  • reducer 强调纯函数,计算。返回新的 state。

    返回新的 state(redux 中的 state 概念理解为 reducer 返回的计算结果),意味着不可变数据,不可变数据操作就设计到一个拷贝的问题。深浅拷贝的方法,深拷贝的性能问题,节约内存等。可能会引入 immutable 这样的库,又增加了代码复杂度,侵入性较强,而且不是 plainObject,存在转换问题,可能会引入 redux-immutable 中的 combineReducers 去处理,内部做转换。。。总之解决的方案总是有,只是有一定的成本。redux 社区才人还是很多的。

  • state 设计的尽量扁平,最多两三层。
    嵌套太多层的话,取值的时候比较麻烦,也需要有容错处理(当然来一个 lodash 就好了)。这只是某个 state 的层级设计问题。还有对整个 store 的设计,比如根据模块划分,共享的状态和私有的区分。谈到这,又引申出什么样的状态该放到 redux 中,大多数情况下,用 redux 很好的处理了复杂的异步逻辑问题,所以复杂的 Effect 推荐放到 redux。另外就是一些需要共享的,比如 userInfo,AppState,特殊的界面状态。

这里总结了一些使用 redux 的时候,官网和社区推荐的一些规范或者约定。如果遵守约定还好,如果风格不一的话,其实这些约定就失去了原本的意义了。其实我更想表达的是,对于新手来说,学习成本真的挺高的。然后就是 Effects 的一些问题。

Effects

这里的 Effects 主要指异步请求。最开始的方式是引入 redux-thunk,redux-promise 这样的中间件,去改造 store 的原生 dispatch 方法(compose 形成的函数调用链),使得它的参数不再局限于一个 action 对象,而是可以接收 function 或者 promise 对象,通过这一层把 Effect 影响去除,变成原来的 dispatch 一个 action 对象(代码层面就是经过函数调用链,最后执行原来的 dispatch 方法)。如下图:

thunk1

引入 thunk 后的数据流向大概是这样的。组件内部通过事件或者生命周期,去调用 hoc 注入进来的 actions 方法,通常是通过 actionCreator 创建的。由于中间件的机制,我们可以在 actionCreator 中 return 一个新的 function,自然而然的,异步代码和一些逻辑就写在了这里,也就是图中黄色标记的。比如我们 request 一个请求,当 response 的时候,我们再执行 dispatch 方法,把返回的 data 传递到 reducer。reducer 通过计算、拷贝,合并生成新的 state tree,然后会执行 listeners,无论你是通过 connect 还是订阅的方式,总之你的组件会 re-render 了,接收新的 state。大概的流程大家都很清楚了应该,问题在于实际业务中,actionCreator 这里会有很多逻辑代码,开始我们可能把最基本的 fetch,loading,error 的 catch 这样的东西通过 function 封装,去解决最通用的场景。不过涉及到复杂的业务,就会在 then 中出现很多的业务逻辑代码,比如有时候需要轮询的机制,有时候需要 dispatch 多个 action,处理起来不那么顺手,而且actionCreator 中的代码越来越多,不再是一个简单的 function,action 不再纯净,违背了它的原意。所以 saga 出现了。

saga1

saga 全局下跑着 generator,需要 watch 的 actionType,通常是 async 的,用 takeEvery 或者 takeLatest 这样的方法做监听,如 yield* takeEvery('FETCH_REQUESTED', fetchData),当 type 匹配到 FETCH_REQUESTED,就会进入 saga,执行 fetchData(saga 中每个异步任务被定义为 Effect),不匹配的就仍然走到 reducer。跟 thunk 不同的是异步代码写到了 saga 中,也是图中标黄的。整个流程看起来更清晰,每一部分的职责单一, actionCreator 和 Effect、业务逻辑也解耦了。多人开发的大型项目中,这样的代码组织的确是可读性更高,如果你对整个数据流比较清晰,便可以很快定位到代码。当然 saga 的优势不止这一点,它还封装了一些复杂的业务场景。比如某个异步任务调用多次的时候,saga 可以 takeLatest,也就是只保留最新的任务,而 thunk 是比较难处理的,之前的任务不容易取消。另外 saga 也提供了 cancel 的 API 可以取消 task。它还封装了一些 promise.all, promise race 的功能,也默认支持堵塞和非堵塞的调用。总之,异步处理的很多常见它都帮我们想到了,好好看看文档,你便可以很容易并且很优雅的写出相关代码。

不过 saga 也有一些问题。比如错误的 catch,基本需要每个 task 要加一个 try catch,不然最后 saga 抛出的错,你根本不知道是哪里出了问题,调试不尽人意,babel 的 source-map 有时也会定位失误。还有一个问题便是,redux 本来定义的文件就够多了,又出现了一个 saga,感觉像是把 actionCreator 里的异步代码换了个地方而已。另外,最开始的重复的代码也很多,重复的监听 saga 任务,call Effect,put action,便又需要封装一个通用的 fetchSaga 的方法,去处理常见的异步任务。引入 saga 的好处是优雅的处理的异步 action,提供的 API 也很强大。但是引入一方面是学习成本,一方面又是封装的时间成本,这都是项目选型需要考虑的。如果你的项目有大量的读取操作,轮询的状态,比较麻烦的异步任务,那引入 saga 带来的效果是比较明显的。反之,则是增加了开发和维护成本,还是要注意场景。对 saga 的研究也没有特别深入,不再过多赘述了,网上也有很多的文章,推荐几个:
1.redux-saga 实践总结2.

胶水代码

redux 的胶水代码也是开发时比较痛苦的地方。

  • actions、reducers、actionTypes 都要定义
    在使用 redux 的项目中,当你要共享状态或者处理异步的时候,假如说放到 redux 中,就不得不定义一堆类似的文件。有的人觉得这是比较好的约束和规范,而有的人就觉得这种形式比较繁琐,这个问题就仁者见仁智者见智了。

  • saga 里面的各种 yield => 封装

关于这一部分,社区也有很多方案,比较成熟的 dva, 封装的已经很好了,把 actions、reducers、actionTypes 以 model 的概念封装,尽管 redux 本来 model 的概念很弱。saga 的也封装到了 effects 中。

手动优化

跟大部分技术栈一样,redux 也需要手动调优,下面是我总结的几个:

  • mapStateToProps 采用 Reselect 做缓存

    当你采用 connect 去给组件注入 state 的时候,需要 mapStateToProps 函数去指定你需要的 state。而这里有个问题,就是可能你这个组件只使用了某个 state,但是其他无关的 action 触发的时候,mapState 函数都会重新执行计算。我在这里就踩过坑,比如我的 mapState 是这样的:

const mapStateToProps = (state) => ({ data: state.data.toJS()  })

在 reducer 中我把 data 通过 immutablejs 处理了,组件中使用的时候需要 toJS 转换成原生对象,而每次执行返回的都是新对象。所以每次包装后的组件做 shadowEqual 的时候,都认为 state 发生变化了,导致无关的 action 也会引发该组件的 re-render。这是由 connect 的机制决定的,hoc 内部调用 store 的 subscribe 函数,注册一个事件,当 dispatch 一个 action,所有 reducer 执行后,会 pub 这个事件,此事件就会对比两次 state 的引用是否变化,决定组件是否更新 state 以及 render。所以这里每次返回的某个 state 如果都是新引用,是有问题,并且 mapStateToProps 函数也不应该存放大量的计算代码。

reselect 就是用来解决上述重复计算的问题的,其实 reselect 可以理解为一个缓存函数,当你做过一次计算或者处理,该结果被缓存到哈希表(就是一个对象)中,下次遇到同样的输入,直接从缓存中调取,就不用重新计算了,也就不会有坑,效率也好一些。一个缓存函数类似于这样:

const memoize = (f) => {
  const cache = {};
  return (str) => {
    if (!cache[str]) {
      cache[str] = f(str);
    }
    return cache[str];
  };
};

const capitalized = (str) => str.slice(0, 1).toUpperCase() + str.slice(1, str.length)

const memoCap = memoize(capitalized);

利用函数式的高阶函数和 js 的闭包就可以实现最基本的缓存函数,简单来说,reselect 就是这样的原理。当你 f 的结果需要的计算越多,执行的代码越多,缓存的意义就越大。

  • 有时候需要 SCU 填 state 设计不合理的坑

    React 组件本身就拥有 shouldUpdate 的能力,比如组件 state 的引用是否变化。另外,React 也提供了 shouldComponentUpdate 这样的函数,让你可以自己控制 update 的细度。
    ------------------------------------------分割线------------------------------------------------

  • pureComponent shallowEqual

  • scu 的简化版,手动作浅层比较。

  • state 的层次设计,拷贝、设值问题

  • 对后端数据拆分

then,dva 去封装的思想或许是更好的选择。try mobx...

mobx1

mobx

  • 非常适合数据驱动的应用。

redux 还是遵循的 setState 一套流程,mobx 推出的时候,一个主张就是干掉 setState 的机制。

image

API

  • 只有五个。
    Observable
    Computed
    Autonrun
    Action
    Observer

数据流

image

依赖收集

  • mobx 核心点。

生命周期

  • 只有一个 componentWillReact,当然也会有 didupdate didmount unmount。

优点

  • 数据订阅颗粒度高,不用刻意维护性能
    oop 好上手,少些代码

思考🤔

  • 什么时候用单例
  • 什么时候用 class
  • 什么时候注入到
  • toJS
  • observer 用的越多,效率越高,但是会强依赖 mobx

Mobx 拓展

Mobx,Redux 比较

社区

image

使用度关注度,redux 更多,社区 redux 也是完胜。

简单对比

  • fp vs oop
  • Elm vs 依赖收集 + 响应式
  • 单一数据源 vs 多个 store
  • Readonly vs read & write
  • Plain objects vs observable data
  • smart + dumb vs smart

总结

  • mobx api 简单,样板代码少
  • redux 需要注意的挺多,mobx 需要对依赖收集理解
  • Redux 规范 state,mobx 不担心嵌套
  • 性能方面。调优过的 redux 项目基本跟 mobx 打平。
  • Effect。saga 封装的业务场景,mobx 没有中间件,不容易拓展。
  • 社区。redux 完胜
    选哪个呢?

最后

虽然我通过一些 redux 的问题,去引出 mobx,但是并不意味着 redux 就是不好的。首先每个技术栈都是根据场景,团队需要去选择的。另外,手动调优的工作不仅仅是 view 层的应用中,而是整个前端的链路都是需要的,也没有完美的、一劳永逸的方案,只有自己不断的学习和优化,我们才能更快的进步。

拓展

https://www.youtube.com/watch?v=xsSnOQynTHs redux 自此开始火

https://zhuanlan.zhihu.com/p/25989654 conf MobX vs Redux

xufei/blog#47 单页数据流方案探索,rxjs

#21 redux 中间件

https://medium.com/@mweststrate/how-to-safely-use-react-context-b7e343eff076

@2016gx
Copy link

2016gx commented Jan 30, 2018

👍

@riskers
Copy link

riskers commented Mar 8, 2018

刚好之前也写过这样的状态管理对比的文章 riskers/blog#32

@sunyongjian
Copy link
Owner Author

@riskers 嗯,👍 还写了 demo

@xcatliu
Copy link

xcatliu commented May 5, 2018

总结的很到位,redux + redux-thunk 还是目前比较成熟的方案

@sunyongjian
Copy link
Owner Author

@xcatliu 额,本来我是想突出 mobx 相对 redux 的一些优势的,结果没搞完 - -

个人感觉 redux 有时候不如 mobx 爽... 唯一的缺点就是异步逻辑不如 saga 优雅

@glbjiiii
Copy link

大佬能帮我写一个使用mobx的react状态管理的例子吗?我主要是不明白inject是怎么用的,看了官方的文档,但是官方的文档没有能直接运行的例子,所以一直整不明白。


比如我有两个组件,两个组件放在任意位置,A组件点击一下,B组件隐藏,再点击一下就显示,像这样的用mobx应该怎么写啊,求大佬给个能运行的例子。

@hemisu
Copy link

hemisu commented Sep 10, 2018

bindActionCreators这里,官方文档中有提到

You might ask: why don't we bind the action creators to the store instance right away, like in classical Flux? The problem is that this won't work well with universal apps that need to render on the server. Most likely you want to have a separate store instance per request so you can prepare them with different data, but binding action creators during their definition means you're stuck with a single store instance for all requests.

应该是考虑同构的问题吧

@huyansheng3
Copy link

牛逼牛逼

@huyansheng3
Copy link

mobx有几点不爽。

  1. 传递到组件的props,到底要不要toJS,不做toJS,感觉混入一点奸细,不够纯对象;做toJS,是递归,会损耗一点性能。
  2. @observer 做细粒度的绑定,保证渲染最小,但react父组件render,会调用组件的 willReceiveProps,除非绑的很细,否则肯定会有浪费。绑的太细,会导致组件不能方便的导出复用,跟mobx耦合太深。但是 redux + immer 配合不可变数据和 pureComponents就可以解决了。

@sunyongjian
Copy link
Owner Author

@huyansheng3
e... 其实还好
第一个,toJS 其实就是在你真正需要用数组的时候,才需要转。比如使用 lodash 的一些数组操作方法之前。Observable 的数组,mobx 本身也提供了一些比较好用的方法,这个文档上有。
第二个,首先 observer(component),是不会调用 cwrp 的,而是直接触发组件的 forceupdate。然后,使用 mobx 的一个核心的思想就是尽可能的观测最小粒度的组件,使用的 observer 越多,更新的效率越高、损耗越小。这个是不同于 react 本身的 props 传递思想,而且一般用了 mobx 的组件,之后抽离出来,封装更公共组件,脱离 mobx 也比较麻烦,当然如果大家都用 mobx 就没啥问题了。

@huyansheng3
Copy link

@sunyongjian 父组件render,会触发子组件调用 willReceiveProps,修正下。

@cllgeek
Copy link

cllgeek commented Oct 1, 2018

@sunyongjian 我想问下,如果context api 一直是采用的,还需要这些状态管理库吗? 我个人理解,是没必要了吧

@sunyongjian
Copy link
Owner Author

@cllgeek 赞同~ 如果只是共享 state,我觉得足够了。但是大部分情况下,我还是会直接用状态管理,有时候页面应用会越来越复杂,直接上 redux 这种,能够解决很多代码组织,异步方案问题,引用最佳实践,减轻自己的工作量。

@cllgeek
Copy link

cllgeek commented Oct 3, 2018

@CocaColf
Copy link

CocaColf commented Jan 7, 2019

1.所以说状态管理库解决的问题是:组件之间的共享数据传递的问题,一个组件自己的非其他组件需要的数据,还是由这个组件自己的state来管理。
请问这个理解正确吗?

我就是因为有:什么时候该使用状态管理,什么时候使用state? 的疑问而google到你这篇博文的。

2.于是我就想到,即使有些数据并不是组件之间用来共享的,而是某个组件自己的,那么我也创建一个 store的话,岂不是整个react项目里我就不需要使用 state 以及 setState了评论完我自己去写了一个demo,有个体会就是state还是需要的,一个组件如果没有state的话,那么会反而增加一些问题。我刚刚实践的场景是: 点击 提交 把一个input的数据存到store数组里去,但是对于input我使用的是onChange方法,如果这个input我没有发生改变的话,那么点击提交,我在store的action里获取不到数据。所以我通过这个体会到state不能完全没有

谢谢。

@sunyongjian
Copy link
Owner Author

@CocaColf

  1. 你这个理解基本是正确的。当组件的某个状态,被其他组件多次、频繁的引用,这时候 state 通信的成本就会很高,而且逻辑会比较混乱,而通过 store 来管理会清晰很多。关于什么时候用 state or store,就仁者见仁了。
  2. 你举的这个例子,我没有体会出 state 跟 store 的区别。onChange 是一回事,submit 是另一回事,就算input 的 value 放 state 就能取到值了吗,还需要做校验。
  3. 通常在项目中,即使是不需要共享的状态,我也会放到 store 里。因为状态管理提供的是一整套的开发规范和流程,比如我的请求就通过 action 触发,我的逻辑就写到 saga 里。而且一个页面的数据,这个页面的子组件经常会使用,通过 store 管理就避免了 props 多层传递的繁琐。
    那什么时候用 state 呢,通用的 react 组件,或者一些页面内部的状态(比如显隐,type)之类的。一个是为了组件的通用性,支持 react 原生使用而不是依赖状态管理,通过 props 可以控制组件;
    内部状态直接用 state 更简洁,更简单,而且确实没必要用状态管理。
    😆 另外 mobx 跟 redux 的思想也有点出入,redux 是不要滥用 store,而 mobx 反其道而行,有替代 state 之势...

@fantasticsoul
Copy link

既然Context有缺陷,那我们就自己维护一个吧😀,欢迎了解concent,一个可预测、零入侵、渐进式、高性能的增强型状态管理方案 https://concentjs.github.io/concent-doc/

@justjavac
Copy link

@fantasticsoul 所有关于 redux 的文章下面都能看到你的评论 😂

@fantasticsoul
Copy link

酒香也怕巷子深呀,需要让更多人了解concent

@awefeng
Copy link

awefeng commented Nov 3, 2019

我认为状态提示其实是不好的解决方案,为什么子组件自己的属性要提升到父组件,就因为父组件需要使用?不应该是子组件管理自己的store,父组件需要使用或者获取的时候发送事件进行通知?

@hemisu
Copy link

hemisu commented Nov 3, 2019

我认为状态提示其实是不好的解决方案,为什么子组件自己的属性要提升到父组件,就因为父组件需要使用?不应该是子组件管理自己的store,父组件需要使用或者获取的时候发送事件进行通知?

可以看下前面的讨论再发表意见哈,我觉得前面的同学有解答了。

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

No branches or pull requests