antd-admin中的状态管理模式:领域驱动设计应用

antd-admin中的状态管理模式:领域驱动设计应用

【免费下载链接】antd-admin An excellent front-end solution for enterprise applications built upon Ant Design and UmiJS 【免费下载链接】antd-admin 项目地址: https://gitcode.com/gh_mirrors/an/antd-admin

在企业级前端应用开发中,状态管理往往是决定应用可维护性和扩展性的关键因素。antd-admin作为基于Ant Design和UmiJS构建的企业级前端解决方案,采用了领域驱动设计(Domain-Driven Design,DDD)思想来组织状态管理逻辑,实现了业务逻辑与UI组件的解耦。本文将深入剖析antd-admin中的状态管理模式,展示如何通过DDD思想构建清晰、可维护的前端状态管理架构。

领域驱动设计在前端的应用

领域驱动设计是一种软件开发方法论,强调将业务领域的概念和规则转化为代码模型。在前端应用中,DDD的核心思想体现为:

  • 按业务领域划分状态模块:将应用状态按照业务功能划分为独立的领域模型
  • 统一的状态管理接口:通过标准化的reducers、effects和subscriptions定义状态操作
  • 业务逻辑封装:将复杂业务逻辑封装在领域模型内部,对外暴露简洁接口

antd-admin的状态管理主要通过UmiJS的model机制实现,每个model文件对应一个业务领域,集中管理该领域的状态、副作用和订阅逻辑。

核心状态管理文件结构

antd-admin采用模块化的状态管理结构,主要的领域模型文件位于src/models/目录下:

这种按业务领域划分的状态管理结构,使得每个模块可以独立开发、测试和维护,符合DDD中"限界上下文"的概念。

领域模型的标准结构

在antd-admin中,每个领域模型遵循统一的结构,包含state、subscriptions、effects和reducers四个部分,形成了标准化的状态管理接口。

状态定义(State)

每个领域模型首先定义该领域的状态结构,清晰描述业务实体的数据结构。以全局应用状态为例:

// src/models/app.js 中的state定义
state: {
  routeList: [
    {
      id: '1',
      icon: 'laptop',
      name: 'Dashboard',
      zhName: '仪表盘',
      router: '/dashboard',
    },
  ],
  locationPathname: '',
  locationQuery: {},
  theme: store.get('theme') || 'light',
  collapsed: store.get('collapsed') || false,
  notifications: [
    {
      title: 'New User is registered.',
      date: new Date(Date.now() - 10000000),
    },
    // ...
  ],
}

这个状态定义清晰地描述了应用级状态应包含的内容:路由列表、当前位置信息、主题设置、侧边栏折叠状态和通知消息等。

副作用处理(Effects)

Effects用于处理异步操作和业务逻辑,通过generator函数实现,是领域模型中的核心业务逻辑载体。以仪表盘模型获取天气信息的effect为例:

// src/pages/dashboard/model.js 中的effects
effects: {
  *queryWeather({ payload = {} }, { call, put }) {
    payload.location = 'shenzhen'
    const result = yield call(queryWeather, payload)
    const { success } = result
    if (success) {
      const data = result.results[0]
      const weather = {
        city: data.location.name,
        temperature: data.now.temperature,
        name: data.now.text,
        icon: `//cdn.antd-admin.zuiidea.com/web/icons/3d_50/${data.now.code}.png`,
      }
      yield put({
        type: 'updateState',
        payload: {
          weather,
        },
      })
    }
  },
}

这段代码展示了如何通过effect处理异步API调用、数据转换和状态更新,完整封装了"获取天气信息"这一业务功能。

状态更新(Reducers)

Reducers是纯函数,负责处理同步状态更新,接收当前状态和动作,返回新的状态。antd-admin中通常定义一个通用的updateState reducer来处理大多数状态更新需求:

// src/models/app.js 中的reducers
reducers: {
  updateState(state, { payload }) {
    return {
      ...state,
      ...payload,
    }
  },
  
  handleThemeChange(state, { payload }) {
    store.set('theme', payload)
    state.theme = payload
  },
  
  handleCollapseChange(state, { payload }) {
    store.set('collapsed', payload)
    state.collapsed = payload
  },
  
  allNotificationsRead(state) {
    state.notifications = []
  },
}

除了通用的updateState,还可以针对特定业务逻辑定义专用的reducer,如主题切换、侧边栏折叠状态更改等。

订阅(Subscriptions)

Subscriptions用于订阅某些数据源,当数据源变化时触发相应的action。常见用途包括监听路由变化、键盘事件等。

// src/models/app.js 中的subscriptions
subscriptions: {
  setup({ dispatch }) {
    dispatch({ type: 'query' })
  },
  
  setupHistory({ dispatch, history }) {
    history.listen(location => {
      dispatch({
        type: 'updateState',
        payload: {
          locationPathname: location.pathname,
          locationQuery: location.query,
        },
      })
    })
  },
  
  setupRequestCancel({ history }) {
    history.listen(() => {
      const { cancelRequest = new Map() } = window
      
      cancelRequest.forEach((value, key) => {
        if (value.pathname !== window.location.pathname) {
          value.cancel(CANCEL_REQUEST_MESSAGE)
          cancelRequest.delete(key)
        }
      })
    })
  },
}

上述订阅功能包括:应用初始化时触发查询、监听路由变化更新状态、以及在路由变化时取消未完成的请求,体现了DDD中"领域事件"的处理思想。

领域模型间的协作

在复杂应用中,不同领域模型之间需要进行协作。antd-admin通过以下方式实现模型间通信:

  1. 状态依赖:一个模型的effect可以依赖另一个模型的状态
  2. 跨模型dispatch:通过dispatch触发其他模型的action
  3. 全局服务:将通用功能封装为全局服务,如src/services/api.js

以仪表盘模型为例,它依赖于全局应用模型的路由信息,当路由切换到仪表盘时,自动加载数据:

// src/pages/dashboard/model.js
subscriptions: {
  setup({ dispatch, history }) {
    history.listen(({ pathname }) => {
      if (
        pathToRegexp('/dashboard').exec(pathname) ||
        pathToRegexp('/').exec(pathname)
      ) {
        dispatch({ type: 'query' })
        dispatch({ type: 'queryWeather' })
      }
    })
  },
}

实际业务场景分析

用户认证与权限控制

用户认证和权限控制是企业应用的核心功能,antd-admin在src/models/app.js中实现了这一领域逻辑:

// src/models/app.js 中的用户认证effect
*query({ payload }, { call, put, select }) {
  // store isInit to prevent query trigger by refresh
  const isInit = store.get('isInit')
  if (isInit) {
    goDashboard()
    return
  }
  const { locationPathname } = yield select(_ => _.app)
  const { success, user } = yield call(queryUserInfo, payload)
  if (success && user) {
    const { list } = yield call(queryRouteList)
    const { permissions } = user
    let routeList = list
    
    // 根据用户角色过滤路由权限
    if (
      permissions.role === ROLE_TYPE.ADMIN ||
      permissions.role === ROLE_TYPE.DEVELOPER
    ) {
      permissions.visit = list.map(item => item.id)
    } else {
      routeList = list.filter(item => {
        const cases = [
          permissions.visit.includes(item.id),
          item.mpid ? permissions.visit.includes(item.mpid) || item.mpid === '-1' : true,
          item.bpid ? permissions.visit.includes(item.bpid) : true,
        ]
        return cases.every(_ => _)
      })
    }
    
    // 保存权限和路由信息到本地存储
    store.set('routeList', routeList)
    store.set('permissions', permissions)
    store.set('user', user)
    store.set('isInit', true)
    goDashboard()
  } else if (queryLayout(config.layouts, locationPathname) !== 'public') {
    // 未认证用户重定向到登录页
    history.push({
      pathname: '/login',
      search: stringify({
        from: locationPathname,
      }),
    })
  }
}

这段代码展示了如何基于DDD思想实现复杂的权限控制逻辑:

  • 从API获取用户信息和路由列表
  • 根据用户角色过滤路由权限
  • 保存认证状态到本地存储
  • 处理未认证用户的重定向

仪表盘数据聚合

仪表盘页面需要聚合展示多种业务数据,src/pages/dashboard/model.js实现了这一领域逻辑:

// src/pages/dashboard/model.js
effects: {
  *query({ payload }, { call, put }) {
    const data = yield call(queryDashboard, parse(payload))
    yield put({
      type: 'updateState',
      payload: data,
    })
  },
  
  *queryWeather({ payload = {} }, { call, put }) {
    payload.location = 'shenzhen'
    const result = yield call(queryWeather, payload)
    const { success } = result
    if (success) {
      const data = result.results[0]
      const weather = {
        city: data.location.name,
        temperature: data.now.temperature,
        name: data.now.text,
        icon: `//cdn.antd-admin.zuiidea.com/web/icons/3d_50/${data.now.code}.png`,
      }
      yield put({
        type: 'updateState',
        payload: {
          weather,
        },
      })
    }
  },
}

仪表盘模型通过两个effect分别获取业务数据和天气信息,然后通过updateState统一更新到状态中,最后在UI组件中展示这些数据。

最佳实践与扩展建议

状态设计原则

基于antd-admin的实践,前端状态管理应遵循以下原则:

  1. 单一职责:每个model专注于一个业务领域
  2. 最小知识原则:模型间通信通过明确的接口进行,避免直接访问其他模型的状态
  3. 不可变性:状态更新应保持不可变性,避免副作用
  4. 持久化策略:区分临时状态和需要持久化的状态,如src/models/app.js中使用store持久化主题设置

性能优化建议

  1. 状态拆分:避免创建过大的单一状态对象,按领域拆分
  2. 按需加载:通过UmiJS的动态导入功能,实现模型的按需加载
  3. 合理使用订阅:避免过度使用subscriptions导致性能问题
  4. 取消未完成请求:如src/models/app.js中的请求取消机制

总结

antd-admin通过领域驱动设计思想,构建了清晰、可维护的前端状态管理架构。其核心特点是:

  • 按业务领域划分状态模型:每个model专注于一个业务领域
  • 标准化的状态管理接口:统一的state、effects、reducers和subscriptions结构
  • 业务逻辑封装:将复杂业务逻辑封装在effect中,对外暴露简洁接口
  • 模型间松耦合协作:通过明确的接口实现模型间通信

这种状态管理模式特别适合中大型企业应用,能够有效降低代码复杂度,提高团队协作效率,值得在类似项目中借鉴和应用。通过深入理解antd-admin的状态管理模式,开发者可以构建出更加健壮、可维护的前端应用。

【免费下载链接】antd-admin An excellent front-end solution for enterprise applications built upon Ant Design and UmiJS 【免费下载链接】antd-admin 项目地址: https://gitcode.com/gh_mirrors/an/antd-admin

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值