helios741 / myblog

觉得好请点小星星,欢迎有问题交流(issue/email)
109 stars 21 forks source link

为什么即将使用react-redux #25

Open helios741 opened 5 years ago

helios741 commented 5 years ago

目前在项目中还没有使用任何数据管理工具,目前的状态都是通过放在父组件的state里面。(前面的两篇文章也介绍了项目的主要结构:编写不用redux的React代码Typescript配合React实践)。

不用数据管理工具的原因

一直不舍得在项目中使用数据管理工具的原因有下面两点:

不得不引入数据管理工具

不用数据管理工具能解决问题,但是解决的不优雅

1. 承担路由的组件承载过多代码

我们一般会有一个组件,把一个项目所有的路由都放在里面进行配置,没有过多的逻辑。但是项目要求在展开详情的时候显示面包屑,如下图一下:

image

先看一下路由组件的现在代码:

        <Fragment>
        <Route exact path="/user" component={User} />
        <Route path="/user/:id" render={(props: IProps) =>
          <UserDetailWrapper
            {...props}
            getBCPaths={(bcPaths: IPath[]) => this.getBCPaths(bcPaths)}
          />
        } />
        <Route path="/ldap" render={(props: IRouterProps) =>
          <Ldap
            {...props}
            getBCPaths={(bcPaths: IPath[]) => this.getBCPaths(bcPaths)}
          />} exact />
        <Route path="/ldap/:id" render={(props: IProps) =>
          <LdapDetailWrapper
            {...props}
            getBCPaths={(bcPaths: IPath[]) => this.getBCPaths(bcPaths)}
          />} />
        <Switch>
          <Route path="/k8s-queue" component={K8sQueue} exact />
          <Route path="/k8s-queue/create" component={K8sFormWrapper} />
          <Route path="/k8s-queue/:name" render={(props: IK8sProps) =>
            <K8sDetailWrapper
              {...props}
              getBCPaths={(bcPaths: IPath[]) => this.getBCPaths(bcPaths)}
            />}
          />
        </Switch>
        <Switch>
          <Route path="/yarn" component={YarnList} exact />
          <Route path="/yarn/create" component={YarnFromWrapper} />
          <Route path="/yarn/:id" render={(props: IProps) =>
            <YarnDetailWrapper
              {...props}
              getBCPaths={(bcPaths: IPath[]) => this.getBCPaths(bcPaths)}
            />} />
        </Switch>
        <Route path="/updateLdap/:id" component={LdapForm} />
        <Route path="/createUser" component={CreateUser} />
        <Route path="/monitor" component={ModuleStatusModule} />
        <Route path="/node-list" component={NodeListModule} />
        <Route path="/job-list" component={JobListModule} />
        <Route path="/config" component={ConfigCenter} />
        <Route path="/license" component={LicenseList} />
      </Fragment>

上面只是其中一部分,能明显的看到好多组件中多了个getBCPaths这个方法,这个方法就是在通过父子组件通信的方式得到具体组件的面包屑路径,我们就以上面代码中的UserDetailWrapper这个组件来说,这个组件是个有状态组件,按理说不应该承担这种业务,但是还是不得不在组件的componentDidMount中去回调这个方法来把面包屑的路径传递出去。UserDetailWrapper的代码如下:(本来想都贴上,后来有一百多行就贴个简化版本的把)


import React, { ReactNode, PureComponent } from 'react';
import { RouteComponentProps } from 'react-router';

export interface IProps extends RouteComponentProps<{ id: number }> {
  getBCPaths?: (bcPaths: IPath[]) => void;
}

class UserDetailWrapper extends PureComponent<IProps>{

  componentDidMount() {
    const { getBCPaths } = this.props;
    const bcPaths: IPath[] = [
      {
        name: '用户列表',
        path: '/user',
      },
      {
        name: '详情',
      },
    ];
    getBCPaths && getBCPaths(bcPaths);
  }

  render(): ReactNode {
    const { user, loading, namespaceList, yarnList, workspace, complex } = this.state;
    return (
      <UserDetail
        user={user}
        loading={loading}
        />
    );
  }
}

export default UserDetailWrapper;

如果引入了数据管理工具之后,这些面包屑的地址,都放在配置文件中,然后在在管理路由的中进行简单匹配就可以了,这样就解决了组件和路由之间的强耦合。

2. 跳转路由的时候会多次请求

比如这样的一个场景:在管理员创建用户的时候,可能会给该用户分配了一个资源A,这个资源列表接口和当前的场景无关的。 当我们在查看用户详情的时候,可能会给这个用户修改这个资源,可能把A资源改为B。这个时候在不同的路由页面(创建用户页面和用户详情页面)都会请求一遍获取资源列表这个接口。

可能这样场景会有很多。当然我们也可以通过上面在面包屑的方式,把数据存在最顶层组件(配置路由组件),但是这样带来危害和不可维护性可能比调用多次接口的影响要大。

因为这个场景很有画面感,就不上代码了。

3. 组件层次比较深的时候,中间很多透传

当组件的之间的嵌套层级很深的时候,可能父组件的数据要到子子子组件才能用,中间有过多的透传代码是没必要的。这个也是很有画面感的。

4. 多个地方管理一份数据

比如说管理用户这个操作(不知道叫操作合不合适,也可以说把操作改为界面),可以由多个地方去管理,在A这个地方能进行管理,在B这个地方也能进行管理。暂且不说这种方式好不好,或者说这样的产品设计的好不好,但是就真的是遇到了场景如下:

先解释以下工作区的概念,可以认为是多个用户共同使用同一份东西。

以前我们工作区的入口只有一个,如下图: image

进去之后是这个样子的: image 应该是除了难看以外还是很平常的。

现在不一样了,不仅从这个地方能进如工作区管理,还在只有管理员能进的控制台增加了个一模一样的,如下图: image

这样堪比Apple的设计,随之而来的就带来了以下两个问题:

  1. 路由怎么设计
  2. 像面包屑,创建成功后跳转,创建取消等后退跳转操作以前是写死的,现在如果跳回原来的地方可能就遇到错误。

对待第一个问题,我的方案就是瞎设计。以前叫workspaces现在叫ws-list

对待第二个问题: 只能把以前的路由copy一份改改,放在现在的路由里面咯: 以前的路由:

             <Route key="list-active" path="/workspaces" component={WorkspaceList} exact />
              <Route
                path="/workspaces/create"
                render={(props: IRouterProps) =>
                  <WorkspaceForm
                    {...props}
                    getBCPaths={(bcPaths: IPath[]) => this.getBCPaths(bcPaths)}
                  />
                }
              />
              <Route
                path="/workspaces/:id"
                render={(props: IRouterProps) =>
                  <WorkspaceDetailWrapper
                    {...props}
                    getBCPaths={(bcPaths: IPath[]) => this.getBCPaths(bcPaths)}
                  />
                }
                exact
              />

现在变了这样,只是多了一个参数:

           <Route path="/ws-list" render={(props: IProps) =>
            <WorkspaceList
              {...props}
              baseUrl="ws-list"
            />
          } exact />
          <Route
            path="/ws-list/create"
            render={(props: IRouterProps) =>
              <WorkspaceForm
                {...props}
                baseUrl="ws-list"
                getBCPaths={(bcPaths: IPath[]) => this.getBCPaths(bcPaths)}
              />
            } />
          <Route path="/ws-list/:id" render={(props: IProps) =>
            <WorkspaceDetailWrapper
              {...props}
              baseUrl="ws-list"
              getBCPaths={(bcPaths: IPath[]) => this.getBCPaths(bcPaths)}
            />
          } />

然后在相应的组件里面只用this.props.baseUrl默认值是workspaces。 当前也就只能这样解决了,如果使用redux虽然不能解决大问题,但是还是能解决解决在配置路由的组件中减少逻辑代码。

使用哪种数据管理工具

最近也看了一下比较流行的mbox是基于响应式编程的,引入的概念也不像redux那样很多,总的来说是同不错的。但是还是不像做公司第一个吃螃蟹的,还是安安稳稳的使用redux了。

redux方案

这玩意儿要啥方案,直接干就完了

使用redux-action

使用redux-action可以有效的简化actionCreatorsreducers的代码。具体了解请看使用 Redux 打造你的应用

所有redux相关的东西放在一个文件下

为了防止redux的代码平铺使得开发人员变得烦躁,所有都一个文件夹目录下然后通过index导出,如下:


home
├── components
│   ├── list.js
│   ├── recommend.js
│   ├── topic.js
│   └── writer.js
├── containers
│   └── header
│       └── index.jsx
├── index.js
├── store
│   ├── actionCreators.js
│   ├── actionTypes.js
│   ├── index.js
│   └── reducer.js
└── style.js

store目录下面的index.js如下:

import reducer from './reducer'
import * as actionTypes from './actionTypes'
import * as actionCreators from './actionCreators'

export { reducer, actionCreators, actionTypes }

全局状态放在app目录下

redux分为全局状态和局部状态,上一步我们说的是局部状态,如果有比如说loading等全局状态的时候要放在app目录下面使得所有组件都能获取到