博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
dva+ts+taro 小程序构建-资料总汇
阅读量:5121 次
发布时间:2019-06-13

本文共 10510 字,大约阅读时间需要 35 分钟。

思路

  1. 技术选型
    taro+dva+typescript
  2. 目录设计
  3. 组件设计
    ui设计规范: 抽象ui组件 组件props注释 页面公共交互行为: 逻辑组件
    页面公共组件: 业务组件
  4. 模块划分 => 尽量解藕
    view划分:模块之间不可相互调用,只可以通过redux事件通信
    model划分: 抽离业务模块公共逻辑 a. 公共节点公用model(全局挂载)
    b. 不共用节点model(对象合并)
  5. 脚本编写 => 将重复的 复制粘贴可完成的代码抽离 编写脚本自动写入 a. model,page,component新建 脚本
    b. actions调用fetch 脚本
  6. utils编写 oss,map,fetch,storage,format(number,sting,date),hooks
  7. types 全局state => 保证ts 静态类型检查
  8. constants常量
    taro方法改写,全局enum
  9. 代码风格统一配置 => 通过代码风格统一保证代码整洁,规范,可维护性和可读性
    .prettierrc ,eslint, tsconfig, readme ,husky

todo

  1. pageModel封装 分页 搜索
  2. 全局type挂载整理
  3. 全局active 态 css调试
  4. Divider model组件更改
  5. moment map 打包有问题
  6. map 单例

难点探索

上传下载图片 oss base64

骨架屏+上拉刷新+下拉加载分页+emptyPage+404Page组件处理

登录拦截和跳转返回

把这些逻辑写在model里,调用只传 logintype

静默登陆

// fetch.tsimport Taro from '@tarojs/taro'/** * 上传、下载 和普通请求的判断 todo */import { checkTokenValid, refreshToken, codeMessage, getStorage } from './index'// const loginUrl = process.env.login_url;// const api_url = process.env.api_url;const api_url = 'https://likecrm-api.creams.io/'export interface Options {  header?: HeadersInit  showToast?: boolean  noToken?: boolean  dataType?: String  data?: any  responseType?: String  success?: Function  fail?: Function  complete?: Function  callBack?: Function}export default async function fetch
( urlSuffix: String, method: String = 'GET', options: Options): Promise
{ // 设置token const defaultOptions: any = { header: {}, noToken: false, // 临时不用token showToast: true, data: {}, } const currentOptions = { ...defaultOptions, ...options, } // 如果是游客 不设置token const loginType = await getStorage('loginType'); if (loginType === 'VISITOR') { currentOptions.header.Authorization = ``; return _fetch
(urlSuffix, method, currentOptions) } if (!currentOptions.noToken) { const accessToken = await getStorage('accessToken') currentOptions.header.Authorization = `Bearer ${accessToken}` const tokenValid = await checkTokenValid() // if (tokenValid) {
return _fetch
(urlSuffix, method, currentOptions); // } // return refreshToken
(_fetch, urlSuffix, method, currentOptions) } return _fetch
(urlSuffix, method, currentOptions)}// 设置请求头 不包括 tokenconst addRequestHeader = async function(requestOption) { const methods = ['POST', 'PUT', 'DELETE'] if (methods.includes(requestOption.method)) { // 小程序 没有 FormData 对象 "application/x-www-form-urlencoded" requestOption.header = { Accept: 'application/json', 'content-Type': 'application/json; charset=utf-8', ...requestOption.header, } requestOption.data = JSON.stringify(requestOption.data) } return requestOption}// 过滤请求结果const checkStatusAndFilter = (response): Promise
| undefined => { if (response.statusCode >= 200 && response.statusCode < 300) { return response.data } else { const errorText = codeMessage[response.statusCode] || response.errMsg const error = response.data.error return Promise.reject({ ...response, errorText, error }) }}// 正式请求async function _fetch
( urlSuffix: Request | String, method: String = 'GET', options: Options): Promise
{ const { showToast = true, ...newOption } = options if (showToast) { Taro.showLoading({ title: '加载中', }) } const url = `${api_url}${urlSuffix}` const defaultRequestOption: Object = { url, method, ...newOption, } const requestOption = await addRequestHeader(defaultRequestOption) try { return await Taro.request(requestOption) .then(checkStatusAndFilter) .then(res => { Taro.hideLoading() if (newOption.callBack) { newOption.callBack(res) } return res }) .catch(response => { // if (response.statusCode === 401) { // Taro.hideLoading() // return response // // 登陆可以拦截 // // refreshToken
(_fetch, urlSuffix, method, options); // } else { Taro.hideLoading() if (requestOption.showResponse) { // 自定义 错误结果 return response } Taro.showToast({ title: response.errorText, icon: 'none', duration: 2000, }) return response.data // } }) } catch (e) { Taro.hideLoading() Taro.showToast({ title: '代码执行异常', mask: true, icon: 'none', duration: 2000, }) return Promise.reject() }}复制代码
// checkTokenValid.tsimport Taro from '@tarojs/taro'import { CLIENT_ID, APPROACHING_EFFECTIVE_TIME, TRY_LOGIN_LIMIT } from '@/constants/index'import {  setStorageArray,  getStorageArray,  removeStorageArray,  getStorage,  isError,  Options,} from './index'import {  PostOauth2LoginRefreshTokenQuery,  postOauth2LoginRefreshToken,  postOauth2PlatformLogin,} from '@/actions/crm-user/UserLogin'type IRequest
= (urlSuffix: Request | string, method: String, options?: Options) => Promise
let delayedFetches: any = [] //延迟发送的请求let isCheckingToken = false //是否在检查tokenlet tryLoginCount = 0 // 尝试登陆次数// 检验token是否快过期;const checkTokenValid = async () => { const [tokenTimestamp, oldTimestamp, refreshToken] = await getStorageArray([ 'tokenTimestamp', 'oldTimestamp', 'refreshToken', ]) const nowTimestamp = Date.parse(String(new Date())) // 当前时间 const EffectiveTimes = tokenTimestamp ? tokenTimestamp * 1000 : APPROACHING_EFFECTIVE_TIME // 有效时间 const oldTimes = oldTimestamp ? oldTimestamp : nowTimestamp // 注册时间 const valid = nowTimestamp - oldTimes <= EffectiveTimes - APPROACHING_EFFECTIVE_TIME && refreshToken ? true : false return valid}async function refreshToken
( fetchWithoutToken: IRequest
, urlSuffix: Request | String, method: String, options?: Options): Promise
{ return new Promise(async (resolve, reject) => { delayedFetches.push({ urlSuffix, method, options, resolve, reject, }) if (!isCheckingToken) { isCheckingToken = true const refreshTokenStorage = (await getStorage('refreshToken')) as string const query: PostOauth2LoginRefreshTokenQuery = { clientId: CLIENT_ID, refreshToken: refreshTokenStorage, } postOauth2LoginRefreshToken({ query, noToken: true, showResponse: true, }).then(async data => { const error = isError(data) as any if (error) { // 登陆态失效报401(token失效的话),且重试次数未达到上限 if ( (error.statusCode < 200 || error.statusCode >= 300) && tryLoginCount < TRY_LOGIN_LIMIT ) { // 登录超时 && 重新登录 await removeStorageArray([ 'accessToken', 'refreshToken', 'tokenTimestamp', 'oldTimestamp', 'userId', ]) const loginInfo = await Taro.login() const login = async () => { try { if (tryLoginCount < TRY_LOGIN_LIMIT) { const response = await postOauth2PlatformLogin({ query: { clientId: CLIENT_ID, code: loginInfo.code, loginType: 'OFFICIAL', }, body: {}, noToken: true, }) const userAccessTokenModel = response.userAccessTokenModel const oldTimestamp = Date.parse(String(new Date())) await setStorageArray([ { key: 'accessToken', data: userAccessTokenModel!.access_token, }, { key: 'refreshToken', data: userAccessTokenModel!.refresh_token, }, { key: 'tokenTimestamp', data: userAccessTokenModel!.expires_in, }, { key: 'oldTimestamp', data: oldTimestamp }, ]) tryLoginCount = 0 } else { Taro.redirectTo({ url: '/pages/My/Authorization/index', }) } } catch (e) { tryLoginCount++ login() } login() } } else if (tryLoginCount >= TRY_LOGIN_LIMIT) { Taro.redirectTo({ url: '/pages/My/Authorization/index', }) } else { Taro.showToast({ title: error.errorText, icon: 'none', duration: 2000, complete: logout, }) } return } if (data.access_token && data.refresh_token) { const oldTimestamp = Date.parse(String(new Date())) await setStorageArray([ { key: 'accessToken', data: data.access_token }, { key: 'refreshToken', data: data.refresh_token }, { key: 'tokenTimestamp', data: data.expires_in }, { key: 'oldTimestamp', data: oldTimestamp }, ]) delayedFetches.forEach(fetch => { return fetchWithoutToken(fetch.urlSuffix, fetch.method, replaceToken(fetch.options)) .then(fetch.resolve) .catch(fetch.reject) }) delayedFetches = [] } isCheckingToken = false }) } else { // 正在登检测中,请求轮询稍后,避免重复调用登检测接口 setTimeout(() => { refreshToken(fetchWithoutToken, urlSuffix, method, options) .then(res => { resolve(res) }) .catch(err => { reject(err) }) }, 1000) } })}function logout() { // window.localStorage.clear(); // window.location.href = `${loginUrl}/logout`;}function replaceToken(options: Options = {}): Options { if (!options.noToken && options.header && (options.header as any).Authorization) { getStorage('accessToken').then(accessToken => { ;(options.header as any).Authorization = `Bearer ${accessToken}` }) } return options}export { checkTokenValid, refreshToken }复制代码

bundle大小控制

路由堆栈处理

/** * navigateTo 超过8次之后 强行进行redirectTo 否则会造成页面卡死 */const nav = Taro.navigateToTaro.navigateTo = data => {  if (Taro.getCurrentPages().length > 8) {    return Taro.redirectTo(data)  }  return nav(data)}复制代码

骨架屏

1: 问一下ui 需要多少页面写骨架屏 采用哪种方法

参考项目

  1. 不能动态设置生成Jsx
  2. Taro.pxTransform(10) // 小程序:rpx,H5:rem
    在编译时,Taro 会帮你对样式做尺寸转换操作,但是如果是在 JS 中书写了行内样式,那么编译时就无法做替换了,针对这种情况,Taro 提供了 API Taro.pxTransform 来做运行时的尺寸转换
  3. css module使用必须以 module.scss结尾 // 表示自定义转换,只有文件名中包含 .module. 的样式文件会经过 CSS Modules 转换处理
  4. 开发前请看一遍
  5. 全局process 保存后会报错 not defined 重启一下
  6. static options = { // 继承全局样式 addGlobalClass: true }; 组件要继承权全局样式css才可以生效
  7. css {} 与选择器之间 一定要有空格 不然就会编译失败报错 .cssName{} ❌ .cssName {} ✅ 8.text 放view image等块级元素不生效

参考

转载于:https://juejin.im/post/5d1041436fb9a07eeb13b2fa

你可能感兴趣的文章
latex for wordpress(一)
查看>>
如何在maven工程中加载oracle驱动
查看>>
Flask 系列之 SQLAlchemy
查看>>
aboutMe
查看>>
【Debug】IAR在线调试时报错,Warning: Stack pointer is setup to incorrect alignmentStack,芯片使用STM32F103ZET6...
查看>>
一句话说清分布式锁,进程锁,线程锁
查看>>
python常用函数
查看>>
FastDFS使用
查看>>
服务器解析请求的基本原理
查看>>
[HDU3683 Gomoku]
查看>>
【工具相关】iOS-Reveal的使用
查看>>
数据库3
查看>>
存储分类
查看>>
下一代操作系统与软件
查看>>
【iOS越狱开发】如何将应用打包成.ipa文件
查看>>
[NOIP2013提高组] CODEVS 3287 火车运输(MST+LCA)
查看>>
Yii2 Lesson - 03 Forms in Yii
查看>>
Python IO模型
查看>>
Ugly Windows
查看>>
DataGridView的行的字体颜色变化
查看>>