弹框管理系统简介

一,项目简介。

装饰器包装原生 react 动画插件,弹框队列原理等。

二,技术选型。

  1. git:创库管理代码和版本控制,高效的多人合作。
  2. webpack:静态模块打包器。
  3. react 是强大的构建用户界面的 javascript 库。
  4. redux:应用程序的状态容器,提供可预测的状态管理。
  5. saga:异步处理中间件。

这一套工程化的软件,可实现模块化,组件化,自动化,规范化。

三,Hook 简介,函数式编程优势。

  • class 组件开始很简单,但随着业务的复杂,class 组件也会变得越来越复杂,且难以理解。很多状态逻辑糅合在一起,后序维护变得艰难且容易出错。

  • Hook 使得函数组件也能使用 react state 及生命周期等特性。

  • 逻辑状态可抽离出函数,并且随处复用。

  • 更多函数式编程优势参考阮一峰的博客函数式编程初探

四,自动化的 store

1,reducerssaga 可以写在同一个文件,实现一个模块一个文件,方便管理。那么如何区分 reducerssaga 呢?

/**
 * 格式化
 * @param {Object} actions
 * @returns {Object} 由def,reducers和saga 组成的对象
 */
export function format(actions) {
  const reducers = {}
  const sagas = {}
  let def = {}
  for (let type in actions) {
    const action = actions[type]
    if (type === 'default') {
      // 添加默认参数
      def = action
    } else {
      // 添加action的type
      action.type = type
      // 判断是否generator函数
      if (isGeneratorFunction(action)) {
        sagas[type] = action
      } else {
        reducers[type] = action
      }
    }
  }
}
/**
 * 是否是generator函数
 * @param {*} obj
 * @returns {Boolean} 是否是generator函数
 */

export function isGeneratorFunction({ constructor }) {
  if (!constructor) {
    return fasle
  }
  const { name, displayName } = constructor
  if (name === 'GeneratorFunction' || displayName === 'GeneratorFunction') {
    return true
  }
  const { prototype } = constructor
  return (
    typeof prototype.threw === 'function' &&
    typeof prototype.next === 'function'
  )
}

2. 多个 reducer 通过 combineRducers 组合起来,方便关联到 store 上。

/**
 * 使用combineReducers把所有的reducer函数联合起来
 */
import { combineReducers } from 'redux'
// 导出来的所有reduer和saga函数
import actions from '../actions'

let reducersToCombine = []
for (let stateName in actions) {
  const { reducers, def } = actions[stateName]
  if (reducers) {
    reducersToCombine[stateName] = (state = def, action) => {
      const { type } = action
      const { [type]: reducer } = reducers
      if (reducer) {
        return reducer(state, action)
      } else {
        return state
      }
    }
  }
}
export default combineReducers(reducersToCombine)

3,不用手动监听 saga 函数。

/**
 * 自动化监听多个saga函数
 */
import { call, takeEvery, all } from 'redux-saga/effects'
// 导出来的所有reducer和saga函数
import actions from '../actions'

export default function* () {
  const effects = []
  for (let stateName in actions) {
    const { sagas } = actions[stateName]
    for (let type in sagas) {
      effects.push(
        yield takeEvery(type, function* (action) {
          const { key } = action
          const { [key]: saga } = sagas
          try {
            saga ? yield call(saga, action) : undefined
          } catch (e) {}
        })
      )
    }
  }
  yield all(effects)
}

五,数据逻辑与 UI 的分离

把公用的逻辑提取到 store 中,供全局使用

export function* WHILE() {
  let lock = 1
  while (lock) {
    yield new Promise((resolve) => {
      setTimeout(resolve, 1000)
    })
    lock = 0
  }
}

倒计时事件集中管理

import { put, call, select, all, spawn, delay } from 'redux-saga/effects'
import moment from 'moment'

export default {
  counts: [], //事件对象
  isStart: false, //事件是否循环
}
/**
 * reducer
 * @param {Object} state
 * @param {Object} param1
 * @returns
 */
export function SET_COUNT_DOWN(state = {}, { payload }) {
  return { ...state, ...payload }
}
/**
 * 添加事件函数
 * @param {Object} param0 添加的事件,包括执行函数handler,和剩余时间count
 */
export function* ADD_COUNT_DOWN({ payload = {} }) {
  if (!payload.count || !payload.handler) return
  const { counts, isStart } = yield select(({ cd }) => cd)
  const newCounts = [...counts]
  let index = -1
  counts.some((item, i) => {
    if (item.handler === payload.handler) {
      index = i
      return true
    }
    return false
  })
  if (!~index) {
    newCounts.push(payload)
  } else {
    newCounts.splice(index, 1, payload)
  }

  yield put({
    type: SET_COUNT_DOWN.type,
    payload: { counts: newCounts, startTime: new Date() },
  })
  if (!isStart) {
    yield spawn(function* () {
      yield put({
        type: SET_COUNT_DOWN.type,
        payload: { isStart: true },
      })
      yield call(WHILE_COUNT_DOWN)
    })
  }
}
/**
 * 循环函数
 */
export function* WHILE_COUNT_DOWN() {
  while (true) {
    const { counts, startTime } = yield select(({ cd }) => cd)
    let nowTime = new Date()
    const changeTime = Math.floor((nowTime - startTime) / 1000) * 1000
    //把变化的值加入数组
    const countsArr = []
    counts.forEach((item) => {
      const n = moment(item.count).format('x') - changeTime
      if (n >= 0) {
        countsArr.push({ handler: item.handler, count: item.count, change: n })
      }
    })

    const len = countsArr.length

    if (len === 0) {
      yield put({
        type: SET_COUNT_DOWN.type,
        payload: { isWhile: false },
      })
      break
    }
    // 重新设置事件数组
    yield put({
      type: SET_COUNT_DOWN.type,
      payload: {
        counts: countsArr,
      },
    })
    //同时执行所有的函数
    yield all(
      countsArr.map((item) => {
        return call(item.handler, item.change)
      })
    )
    //等待1秒
    const goTime = new Date() - nowTime
    yield delay(1000 - (goTime % 1000))
  }
}

六,使用装饰器包装原生 react 动画插件

react 官方推出的动画插件 react-transition-group详细使用方法参考

我们把动画调用函数抽象到装饰器函数里。js 代码如下

import React from 'react'
import { CSSTransition } from 'react-transition-group'
const Transition = ({ type, children, diraction, popupResolves }) => {
  const item = popupResolves[type] || {}
  const isShow = item.isShow
  const resolve = item.resolve
  return (
    <CSSTransition // 动画插件
      in={isShow} // 是否显示
      classNames={`popup--${diraction}`} // 样式名称
      mountOnEnter // 延迟挂载,才会第一次出动画
      unmountOnExit //退出后卸载
      appear // 第一次挂载立即出动画
      onExited={() => {
        // 卸载完成后触发
        resolve && resolve() //下一节会讲到作用
      }}>
      <div className="popup__box">{children(item)}</div>
    </CSSTransition>
  )
}

// opts为第一次调用时传入的type参数
export const add = function (opts) {
  // WrappedComponent为装饰的函数
  return function (WrappedComponent) {
    // props为react调用时传入的参数
    return function ({ ...props }) {
      // Transition为上面写的动画插件

      // 为了传递动画插件的参数给装饰的函数,多加了一层函数,data为动画插件的item参数对象
      return (
        <Transition {...opts}>
          {(data) => <WrappedComponent {...props} {...data} />}
        </Transition>
      )
    }
  }
}

调用装饰器函数

//用@语法糖调用装饰器函数
// type为弹框的类型,diraction为弹窗出现的方向
@add({ type: 'popup', diraction: 'bottom' })
//被装饰的class类函数
export default class extends Component {
  render() {
    return null
  }
}
//或者直接调用装饰器函数
export default add({ type: 'popup', diraction: 'bottom' })(function () {})

七,弹框队列原理

1. 先用 async 函数发送一个 action,示例代码

import React from 'react'
import { INFO_SHOW } from '../actions'
import store from '../store'

export const showPopup = async ({ ...props }) => {
  await store.dispatch({
    // 调用显示saga
    type: INFO_SHOW.type,
    // 传递数据,包括type和data
    payload: { ...props },
  })
}
// 调用示例
// type为弹框的类型,data为传递给组件的数据
showPopup({ type: 'popup', data })

2. saga 文件接收到这个 action 后,运行 INFO_SHOW 函数

export function* INFO_SHOW({ payload }) {
  // 取出传递过来的type
  const type = payload.type
  let popupResolves = {}
  yield call(
    () =>
      new Promise((resolve) => {
        // 传递resolve函数,利用promise的状态未结束来保持程序。直到第七节动画卸载后onExited回调里执行resolve
        const popupResolves = {
          [type]: {
            resolve,
            ...payload,
            isShow: true,
          },
        }
        // 发送一个action储存数据,并更新store,引起view的刷新,弹出弹框。
        store.dispatch({
          type: SET_DATA.type,
          payload: {
            popupResolves,
          },
        })
      })
  )
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值