基于React生态的保险商城总结

背景

目前新浪保险在微博内的商城是基于Vue的,但是和微博账号体系是绑定的,一定程度上限制了微博端外的拓展渠道,所以希望有单独的一套以便完全脱离微博用于端外拓展以及供给代理人使用。

该项目基于React从0到1,本文只记录开发中遇到的问题以及解决问题的思路和方法,不会过多的介绍某个技术栈的使用(后期会单独总结)和业务相关的内容。

React的优点之一便是组件化。由于每个险种detail有很多相似之处,所以全部抽离成组件,后期每增加一款险种根据自己的业务需求拼装即可,一些个性化的功能以及UI也做了相应的兼容性处理。

技术栈

react、react-dom、redux、react-redux、react-router-dom、scss、css-module、webpack、css-transition-group

实际上在前期搭建项目时还用到了redux中间件redux-saga,但在实际开发中没有使用。我希望在满足业务需求的同时尽可能的让项目简洁,这样无论是后期迭代还是他人维护都易于上手。

第三方UI库

ant-design-mobile

一、 创建项目

本项目基于React提供的脚手架开发。

  1. 全局安装create-react-app
npm i create-react-app -g
  1. 创建项目
create-react-app sina_insurance
  1. 运行
npm start
package.json

项目命令分别执行的是node_modulesreact-scripts/script中的相应文件。

如果要修改webpack的配置,可以通过执行该命令将配置文件弹射出来,需要注意的是,该操作是不可逆的,也就是说一旦弹射出来就无法恢复。

如果不想eject,还可以用react-app-rewired

该项目使用的是eject

  1. 弹射配置文件
npm run eject

执行完成后会多出两个目录config和scripts。config中是webpack相关的配置文件,scripts中是项目命令执行的相应文件。

执行完成后,重新启动项目。

二、 项目需要的依赖

  1. redux
  2. react-redux
  3. react-loadable 懒加载React组件,会将组建切割成单独的js文件
  4. axios
  5. node-sass sass-loader
  6. postcss-plugin-px2rem
  7. sass-resource-loader 全局混入sass需要用到该依赖
  8. babel-plugin-import 按需加载第三方组件
  9. ant-design-mobile 基于React的移动端UI库
  10. less less-loader UI库使用less
  11. classnames

三、 webpack配置

  1. 配置postcss-plugin-px2rem

在postcss-loader选项options的plugins中增加以下配置

require('postcss-plugin-px2rem')({
  rootValue: 750/16,
  unitPrecision: 5,
  propBlackList: ['border', 'border-left', 'border-right', 'border-top', 'border-bottom'],
  exclude: false,
  selectorBlackList: [],
  ignoreIdentifier: false,
  replace: true,
  mediaQuery: false,
  minPixelValue: 0
})

关于border不转rem,一般机型dpr都是2倍或3倍,而且flexible.js中设置了scale为 1/dpr,所以会进行缩放。但是这样做对UI库不是太友好,需要修改antd-mobile/lib/style/thems/default.less 中基本单位修改成2px。

  1. 全局混入scss变量、函数,新增loader

定义sass变量和函数的文件几乎每个组件都会用到,而每次引入不但麻烦而且会造成代码冗余,可以通过webpakc全局混入,在任何地方都可以直接使用。

在getStyleLoaders函数最后新增如下配置:

if (preProcessor) {
  const option = {
    loader: require.resolve(preProcessor),
    options: {
      sourceMap: isEnvProduction && shouldUseSourceMap,
    }
  }

  if (preProcessor === 'less-loader') {
    // less svg
    option.options.javascriptEnabled = true
  }

  loaders.push(option)
}

if (preProcessor === 'sass-loader') {
    // 全局混入sass,一定要在sass-loader之后!!!否则会被sass-loader覆盖!!!
  const sassResourcesLoader = {
    loader: require.resolve('sass-resources-loader'),
    options: {
      resources: [
        path.resolve(__dirname, '../src/assets/style/mixin.scss')
      ]
    }
  }
  loaders.push(sassResourcesLoader)
}

说明:

  • 增加loader

如果是less-loader, 由于项目使用的loader版本是4.1.0,而在less-loader3.x以后的版本需要添加javascriptEnabled: true选项,否则在使用svg的icon时
会报如下错。(项目使用了三方库的loading icon)

Markdown
  • 增加sass-resources-loader

用于在webpack配置中全局混入scss文件,但是一定要在sass-loader之后引入,否则会被sass-loader的配置覆盖。

  1. 添加alias
const resolvePath = (dir) => {
  return path.resolve(__dirname, '../src', dir);
}

alias: {
  'api': resolvePath('api'),
  'assets': resolvePath('assets'),
  'component': resolvePath('component'),
  'views': resolvePath('views')
}
  1. 修改sourceMap配置

简单说,Source map就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置。

主要是便于开发人员debug。因为在开发和生产中,代码都会经过babel编译转换,处理后的代码和源代码差异很大,出现bug很难准确定位到问题的具体位置。其实在生产环境没必要生成该文件,反而会造成打包后的文件体积过大,所以只在生产生成sourceMap文件。修改配置如下:

Markdown
  1. 添加测试环境变量

项目分三种环境,开发、测试、线上。每个环境的Api不同,项目中根据process.env.NODE_ENV仅能区分生产和开发环境。因为提交给QA的代码和生产是一样的,唯一的区别的是Api不同,我想要在打包发布给QA时自动识别出环境,设置相应的Api。

思路:

node执行js是可以带参数的,通过process.argv获取。process是一个全局对象,argv返回的是一组包含命令行参数的数组。
数组第一项为“node”,第二项为执行的js的完整路径,后面是附加在命令行后的参数。

无参数:

无参数

带参数

带参数

解决方式:

  • 新增命令 执行build.js,并添加参数。
"int": "node scripts/build.js qa_env",
  • 在build.js中判断是否有qa_env参数,如果有则修改process.env.NODE_ENV的值为qa_env
build.js

测试环境运行时报如下错误:

Markdown

产生原因:
目前uglifyjs会在混淆代码的同时,更改一些变量、函数名,以减小js文件的体积。而redux利用了这点,判断当前环境。

尝试解决方式:

redux是根据 new webpack.DefinePlugin()判断环境的。

webpack.config.js

而参数的值来自env.js

env.js

在执行npm run int时,process.env.NODE_ENV为'qa_env',redux会根据这个进行打包压缩。通过env.stringified获取的也是'qa_env',但是redux压缩编译时是识别不了该标识的,当成development压缩编译,所以在测试环境打包出来的体积会达到1.7+M,在项目运行时console会提示该警告,意思是包是压缩后的,但是redux是没压缩的,会导致项目运行缓慢。

Markdown
Markdown

太乐观,此种解决方式不可行。在编译时,项目js读取的都是new webpack.DefinePlugin中的prosecc.env中的值。
start.js中通过process.env.NODE_ENV定义的development 是运行时,在开发环境就是development。而打包编译时文件中通过process.env.NODE_ENV读取的是插件中重新生成的值。这样测试环境也是production。

四、 项目问题

  1. a标签添加rel="noopener noreferrer"属性
    在没有rel=“noopener noreferrer"属性的a标签中使用target=”_blank"存在一定的风险”

当一个外部链接使用了target=_blank的方式,这个外部链接会打开一个新的浏览器tab,并且和原始页面占用同一个渲染(UI)进程。新标签页中 window.opener 指向上一个标签页的window。

noopener noreferrer就是告诉浏览器,新打开的子窗口不需要访问父窗口的任何内容,这是为了防止一些钓鱼网站窃取父窗口的信息。
此外,如果这个新页面有任何性能上的问题,比如有一个很高的加载时间,这也将会影响到原始页面的表现。

如果你打开的是同域名(协议、域名、端口相同)的页面,那么你将可以在新页面访问到原始页面的所有内容,包括document对象(window.opener.document)。但是如果你打开的是跨域的页面,即便他们是同一站点(根域名和协议相等),你依然无法通过 opener 来操作父标签页中的 DOM,依然会受到同源策略的限制,但是你依然可以访问到location等对象。

<a rel="noopener noreferrer" target="_blank" href="https://www.baidu.com">

以上 a 链接的 rel 属性值都使用了 noopener 和 noreferrer,通常,将 noopener 的值引入 rel 属性中,就是告诉浏览器通过这个链接打开的标签页中的 opener 值设置为 null,引入 noreferrer 是告诉浏览器,新打开的标签页不要有引用关系(不要在同一浏览器上下文组)。

  1. 组件卸载取消未完成的异步操作

问题:


Markdown

原因:

blur() {
  // 让setState同步更新state
  this.timer = setTimeout(() => {
    this.setState({
      showIcon: false
    })
  }, 10)
}

setTimeout是异步的,在组件卸载时可能仍然处于任务队列中。说明一点:setTimeout是React控制之外的,在其回调中执行的setState是同步的。如果不用setTimeout,setState就是异步更新。在组件卸载时,仍可能有未完成的异步任务。

fix:

componentWillUnmount() {
  // 卸载组件取消未完成的异步事件,防止内存泄漏
  this.setState = () => false
  clearTimeout(this.timer)
}
  1. 阻止默认事件

开发mask组件时,需要在触发mask的touchmove事件时阻止底部body滚动。React要求阻止默认事件使用e.preventDefault(),而非return false。

但实际效果却事与愿违,调试中console不但有警告,而且没起作用。


根据 chrome 的提示,是因为 Chrome 现在默认把通过在 document 上绑定的事件监听器 passive 属性(后面细说)默认置为 true,这样就会导致设置的 e.preventDefault() 被忽视了。当然 Chrome 的这个做法是有道理,是为了提高页面滚动的性能,那么为了防止带来的副作用,官方考虑的很周到,给我们提供了一个 CSS 属性专门用来解决这个问题.

touch-action: none;

但这个属性在ios是不支持的,只能降级处理。

MDN解释:

根据规范,passive 选项的默认值始终为false。但是,这引入了处理某些触摸事件(以及其他)的事件监听器在尝试处理滚动时阻止浏览器的主线程的可能性,从而导致滚动处理期间性能可能大大降低。

为防止出现此问题,某些浏览器(特别是Chrome和Firefox)已将touchstart和touchmove事件的passive选项的默认值更改为true文档级节点 Window,Document和Document.body。这可以防止调用事件监听器,因此在用户滚动时无法阻止页面呈现。

浏览器默认设置 passive 为 true,表示 listener 永远不会调用 preventDefault()。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。

解决方案:

通过原生 addEventListener 给element注册监听事件

this.mask.addEventListener('touchmove', e => {
  e.preventDefault()
}, {
  passive: false
})
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容