Redux复杂应用(一):浅谈状态管理

1,319 阅读7分钟

前言

作者在毕业之后进入了猪厂工作,正是在网易有数产品中认识了redux这个库。从前端开发的角度来说,网易有数作为一个工具型web产品,是一个开发中充满挑战,富含乐趣的产品。

在这之前,作者对于博客的积累可以说微乎其微,决心有所改变,对于redux应用也很感兴趣,于是心中萌生了这么一个念头——写个redux应用系列博客。

这个系列文章会是我在将近两年的redux应用开发中的一些总结和心得,其目的有两个,一个是希望我在写这个系列博客的时候自我反思,自我总结,另一个目的是希望能对于同样喜欢redux应用的同学有所启发,然后可以互相交流学习心得。

整个系列不会对于redux的基础进行讲解,如果你没有使用过redux,可以去redux官网进行一些基础概念的熟悉。

复杂的单模块

步入正题,我们的前端经常自称有数是一个web复杂应用,之所以这么说,不是因为有数有着大量的页面,或者有大量的业务逻辑,而是因为有数的核心逻辑是集中在一个单模块中的,大家可以看看下面这个界面图,这个页面是有数的报告编辑页,也是有数的核心产品页面。

在这个编辑页面中,拥有着PPT式的交互方式、包括图表拖动、resize、画布缩放、复制黏贴、样式设置等等,其交互方式的灵活性、复杂性是我至今遇到的最为复杂的,一个大家可以访问这个产品的试用版玩玩感受一下,网址:www.youdata.163.com

如果没有引入Redux,应用是如何进行状态管理的?

没错,如标题说的,redux就是用来做应用的状态管理的。既然今天的话题是redux,那么我们肯定要看看没有引入redux之前,我们的应用是如何进行状态管理的?

答案很简单,我们的应用的状态管理主要依赖于父子组件之间进行状态传递。

口说无凭,接下来以单向数据流为例子,来讲讲这样怎么进行状态管理以及这样进行状态管理的局限性。(双向绑定的情况类似,大家可以自己思考一下)

注意: 下面的图片是在网上的某一篇博客找到的,忘记了地址,这里没贴来源对不住了。

简单的场景:父子组件树

状态管理:状态作为属性,从父组件传递给子组件。

这种情况是最简单的场景,在下面的代码中,父组件ParentComponent把loading这个状态作为属性传递给了子组件ChildComponent。

ParentComponent.js:

render() {
    const { loading } = this.state;
    return (
        <ChildComponent loading={loading}>
            This is content
        </ChildComponent>
    )
}

稍微复杂的场景:两个兄弟组件共享状态,并在其中一个组件更改这个状态。

状态管理:

  1. 将这个状态和修改这个状态的函数提升到两个组件的共同的祖先组件;
  2. 在子组件中调用作为传下来的函数,修改这个状态;
  3. 父组件中这个状态得到修改;
  4. 父组件本身触发重新update;
  5. 这个父组件的子组件触发update;
  6. 子组件的状态得到更新;

上面的步骤就是解决这个场景的状态管理的基本步骤,简单来说就是状态提升,再向下传递。是不是感觉稍微有点麻烦了呢?如果上面语言描述你觉得难以理解,可以看看下面代码,相信如果你写过React理解起来应该轻而易举。

Parent.js

changeLoading(loading) {
    this.setState({ loading });
}

render() {
    const { loading } = this.state;
    return (
        <div className="m-parent">
            <ChildComponentA 
                loading={loading} 
                onChange={value => this.changeLoading(value)}
            />
            <ChildComponentB loading={loading} />
        </div>
    );
}

ChildComponentA.js

render() {
    const { loading, onChange } = this.props;
    return <div onClick={() => onChange(!loading)}>a loading: {loading}</div>;
}

ChildComponentB.js

render() {
    const { loading } = this.props;
    return <div>b loading: {loading}</div>;
}

更复杂的场景:组件树层级变深

状态管理:

这个组件树相比于上面的场景来说,不仅层级变深了,组件树的分支也变多了,但是,从应用状态的角度来说,核心步骤仍然是上面的场景的思想:状态(函数)提升和向下传递属性,多个状态无非是重复多次这样的过程。

这样会有怎样的弊端?

  • **造成了大量不必要的属性传递。**在状态传递的过程中,由于我们需要把状态提升到共同的祖先组件,然后再层层传递到目标组件,中间必然要经过一些不相关的组件,而这些组件实际上并不需要这些状态,这就造成了一些不必要的浪费。

  • **维护变得困难。**一旦增加状态,或者共享状态的组件发生了变化(这在业务场景中很常见),那么你不但要更改用到这个状态的组件,连带这个属性中间经过的组件都要更改,也许你只是要改个变量名,有十几个组件要改,你烦不烦?所以这样的状态管理维护起来可以说是相当地麻烦。

组件树结构与数据流关系不一致

上面我们介绍了依赖组件树来传递状态的状态管理方法,也看到了这样的管理方法是不方便的,一旦业务复杂起来的时候是难以维护的。

俗话说,知彼知己,百战不殆。

在讨论如何解决这个麻烦之前,我们先来好好剖析一下,这样的状态管理为什么会充满局限性,其根本原因为:

组件树结构与数据流关系不一致

什么意思呢?

例如我们有这么一个组件树结构:

在这个组件树中,B和E都要用到状态state_a,所以state_a必须提升到A组件来进行管理,从数据流的角度来说,相对于A,B和E应该是同级节点,其数据树结构应该为:

如图片,我们就可以很清楚看出其组件树结构和数据树结构是不一致的,这也是问题所在。

引入Redux中介者

知道了上述状态管理混乱的根本原因是组件树结构和数据树结构不一致,那么我们就可以对症下药。既然是要摆脱组件树结构的束缚,那么就应该把状态全部拿出来解耦。

这时候就需要第三方介入,我们才可以把状态从组件树中拿出来交给它,这也就是所谓的中介者模式。

而Redux就是这么一个中介者,当我们引入Redux后,我们的应用的状态管理就会变成:

状态管理:

  • 把状态(修改函数)通通交给Redux来管理,对应redux的概念为state和reducer。
  • 当某个组件想修改状态,这时候这个组件只要通知redux进行修改,这就是dispatch过程。
  • redux在收到action后,对state进行修改,然后把状态丢给想要这个状态的组件,这个过程就是react-redux中的connect和mapStateToProps。

看,是不是就解决了刚才的状态管理的问题,

结束语

今天redux状态管理的话题就到这里,后面会陆陆续续更新redux相关实践和理解,可写的内容不少,如:撤销重做的实现、多人协作的实现、高阶reducer的应用、不可变数据介绍使用、容器组件和展示组件介绍和使用、异步方案选择等等等很多,所以这个系列可以写很久,如果你有比较感兴趣的点,可以留言在评论区,会考虑先写。