阅读 83

Taro + rematch + 云开发实践(二)-添加Hooks

引言

本项目在经历了大半年的时间终于迎来了第二篇文章(。・∀・)ノ゙,文章主要内容为封装页面hooks,让函数组件的用法类似于rematch

可以持续关注本项目 github.com/zhixiaoqian…

一、使用方法

1. 无store时的基本用法

import Taro from '@tarojs/taro'
import PropTypes from 'prop-types'
import { usePage, useMount } from '@/hooks'

import { AtTabBar } from 'taro-ui'

const init = {
  state: {
    url: '/xxx',
    tabList: [],
  },

  reducers: {
    handleClick () {
      // this中挂载了 state、setState、location、reducers & effects 中的方法
      this.setState({ url: '/yyy' })

      this.navigateTo(this.state.url)
    },

    navigateTo (url) {
      console.warn(url)
    },
  },
 
  effects: {
    async initData () {
      const { url } = this.state
      await new Promise((resolve) => setTimeout(() => resolve(url), 500))
    },
  },
}

export default function Footer (props) {
  // state: 最新的state
  // events: 包含reducers及effects里所有的方法
  // loading: effects中的所有方法都会有loading状态,如:loading = { [fnName]: false }
  // error: effects中的所有方法都会有error
  const [state, events, loading, error] = usePage(init)
  useMount(() => {
    events.initData()
  })

  const { current } = props
  const { tabList } = state
  return (
    <AtTabBar
      fixed
      tabList={tabList}
      onClick={events.handleClick.bind(this, tabList)}
      current={current}
    />
  )
}

Footer.propTypes = {
  current: PropTypes.number,
}

复制代码

2. 需要用到store时

import { useSelector, useDispatch } from '@tarojs/redux'

···
const [state, events, loading, error] = usePage(init)

const pageStore = useSelector(({ globalData }) => ({
  globalData,
}))

const dispatch = useDispatch()

console.warn(pageStore, dispatch.globalData)

useMount(() => {
  // 传入dispatch
  events.initData(dispatch)
})
···
复制代码

二、封装hooks

1. common util js:下面将会用到

export const isObject = function (obj) {
  return toString.call(obj) === '[object Object]'
}

export const isBoolean = function (bool) {
  return toString.call(bool) === '[object Boolean]'
}

export const isFunction = function (arg) {
  return toString.call(arg) === '[object Function]'
}
复制代码

2. useState-setState:与class Component的保持一致, 暂无实现回调函数

export function useState (initState) {
  const [state, setState] = Taro.useState(initState)

  const setComboState = newState => {
    if (isObject(newState)) {
      setState(prevState => {
        return { ...prevState, ...newState }
      })
    } else {
      setState(newState)
    }
  }
  return [state, setComboState]
}
复制代码

3. usePage:用法保持与rematch一致

  1. 将接受的数据处理成 state, pageEvents: { ...reducers, ...effects }
    1. effects 特殊处理成 调用是setLoading为true, 完成后设置为false, 并处理error状态
  2. 用一个key去保存某些值 { state, setState, location, loading, ...pageEvents }
  3. 将方法中的this指向 { state, setState, location, loading, pageEvents }
export function useEventEnhancement (initPageState, state, setState) {
  const [loading, setLoading] = useState({})
  const [error, setError] = useState(null)
  const pageEvents = {}
  // 1. 对包含异步的方法做一层封装,获得loading和error状态值
  if (initPageState.effects) {
    const asyncEvents = initPageState.effects
    for (const eventName in asyncEvents) {
      const event = asyncEvents[eventName]
      if (isFunction(event)) {
        pageEvents[eventName] = async function (...args) {
          try {
            setLoading({
              [eventName]: true,
            })
            if (error) {
              console.log('reset error')
              setError(null)
            }
            await event.call(this, ...args)
          } catch (err) {
            // 请求异常在request那里有打印,这里打印非请求异常,比如代码语法错误等
            if (!isBoolean(err.success)) {
              console.log('err', err)
            }
            const title = err.msg ||
                err.error ||
                err.message ||
                '服务器开了小差请稍后重试'
            if (err.proxy) {
              showToast({
                title,
              })
            } else {
              setError(err)
            }
          }
          setLoading({
            [eventName]: false,
          })
        }
      }
    }
  }
  if (initPageState.reducers) {
    const events = initPageState.reducers
    for (const eventName in events) {
      const event = events[eventName]
      if (isFunction(event)) {
        pageEvents[eventName] = event
      }
    }
  }

  initPageState._instance = Object.assign(initPageState._instance, {
    state,
    setState,
    loading,
    ...pageEvents,
  })

  // 2. 将所有方法加入到this环境中,这样就可以直接调用其他方法
  for (const propName in pageEvents) {
    const handler = pageEvents[propName]
    if (isFunction(handler)) {
      pageEvents[propName] = function (...args) {
        // 此处可以收集formId,以及点击事件
        return handler.call(initPageState._instance, ...args)
      }
    }
  }
  return [pageEvents, loading, error]
}

export function usePage (initPageState = {}) {
  if (!initPageState._instance) initPageState._instance = {}
  const [state, setState] = useState(initPageState.state || {})

  // 缓存路由信息
  useRouter(params => {
    initPageState._instance.location = params
  })

  const [events, loading, error] = useEventEnhancement(
    initPageState,
    state,
    setState
  )
  return [state, events, loading, error]
}
复制代码

更详细的内容可前往 github.com/zhixiaoqian…

总结

至此,一个简单的Taro Hook开发方式封装完成,在开发时可根据实际业务进行修改,在接下来的文章里会写关于小程序、云函数等等一些问题的解决方案,喜欢这篇的话请继续关注后续文章。




关注下面的标签,发现更多相似文章
评论