基于React和Webpack的Optimize小技巧

公司项目采用使用React16+Webpack3+Graphql来作为技术构建,在图像等方面上使用七牛;
下面是本人将会围绕当前技术来进行优化的体验,理解和使用到的都不算特别复杂,但是点比较多,可能可以启发一下;


React

版本升级(当前是16.8.6)

如果是React15的更应该升级到16,除新的Api外,React本身也进行了优化如Fiber等,体积也更小;

Api更新

1、 React.Memo:LessState组件上采用React.Memo来进行处理,代替原先的PureComponent;
2、 去除依赖库:如果原先有使用一些Lazy库,可以考虑直接使用React.Lazy来实现,减少打包体积,原先的路由懒加载是直接写了一个方法,也可以使用React.Lazy来完成并且Suspense可以实现占位图;

没用React.Lazy前的写法

import React, { Component } from 'react'    
import LoadingViewContainer from '../components/LoadingViewContainer'    

 export default function asyncComponent(importComponent) {    
  return class extends Component {    
    state = {    
      component: null    
    }    
    async componentDidMount() {    
      const { default: component } = await importComponent()    
      this.setState({    
        component: component    
      })    
    }    
    render() {    
      const C = this.state.component    
      return C ? <C {...this.props} /> : <LoadingViewContainer />    
    }    
  }    
}

3、 使用Hooks:Hooks是React16.8之后出的一个新的Api,与以前的Class Component改变很多,如useReducer有人用来实现Redux,至于是否可以真的代替,这个还没有深入了解,按官方说法,因为不使用原先生命周期以及逻辑业务的复用性,可以减少很多代码;具体的不在这里去详写,本身也理解不够透彻,只贴一个自己写的简单的代替原先React-Helmet(Helmet),后续可能会单独写一下Hooks;

import { useEffect } from 'react'

function Helmet({ title, link }){
  useEffect(() => {
    document.title = title
    const node = document.getElementsByTagName('link')
    let isHadLink = false
    for (let i = 0; i < node.length; i++) {
      // NOTE:有link直接修改即可
      if (node[i].rel === linkRel) {
        isHadLink = true
        node[i].href = link
        break
      }
    }
    if (!isHadLink) {
      const newLink = document.createElement('link')
      newLink.rel = linkRel
      newLink.href = link
      document.head.appendChild(newLink)
    }
  }, [title, link])
  return null
}

4、使用Persist

如果有使用redux,建议加上redux-persist,如果是其他的框架应该也有相对应的persist
persist会将该redux的reducer(可加入白名单和黑名单,只保存指定reducer)存到localstorage里面
在用户后续打开应用的时候会直接获取localstorage里面的数据去生成reducer,在用户体验上有提升;

5、用法

1. function的bind写在constructor,不要写在render里面,或者建议直接在创建的时候使用
    handleClick = () => {    
        dosomething
    }
    
 2.在取值的时候不要给元素加data-xxx={xxx},然后用e.target去获取改属性值
    handleClick = e => {
        e.target.xxx    
        // bad
    }
    <div data-value={value} onClick={this.handleClick} /> 
    
    可以直接return一个function来处理
    handleClick = value => () => {    
        value
    }
    <div onClick={this.handleClick(value)} />
    
3.不要在render里面定义常量,减少在render的逻辑
    render函数是渲染界面用的,一些常量和逻辑尽可能放到constructor等地方,避免在组件update时重复创建常量;
    
4.避免传递过多props
    减少<component {...this.props} />,加重了shouldComponentUpdate里面的数据比较;

6、延迟加载外部JS

必须要使用的依赖库,可通过webpack打包进去,但一些第三方的统计、地图等,可以在需要用到的场景,再调用方法进行加载;
或者该依赖库在npm上有,也可以npm install进去用webpack分包;
export default function loadJs(url, loadSuccess) {
  if (!url) {
    return null
  }
  let script = document.createElement('script')
  script.type = 'text/javascript'
  if (script.readyState) {
    script.onreadystatechange = () => {
      const state = script.readyStat
      if (state === 'loaded' || state === 'complete') {
        script.onreadystatechange = null
        loadSuccess && loadSuccess()
      }
    }
  } else {
    script.onload = () => {
      loadSuccess && loadSuccess()
    }
  }
  script.src = url
  document.body.appendChild(script)
}

经验技巧

1、 图片在需要用到的地方require,不要在文件开头直接import进来;
2、 使用import { xxx } from 'xxx',避免import整个文件,注重导包;
3、 一些必须要的依赖可以将它放到需要触发的条件下import,如项目用到的验证码验证,所以将下面代码放到Click里面进行处理;

handleClick = () => {
    import('hash.js/lib/hash/sha/256').then(({ default: sha256 }) => {
      //do something
    })
}

4、 在同级元素显示可以使用<></>来包裹,<React.Fragment>也是和<>一样,可不渲染该标签,如下面则不会出现<div>;

<div>
    <a />
    <b />
</div>
可以采用
return (
    <>
       <a />
       <b />
    </>
)
或者
return [
    <a key="a"/>,
    <b key="b" />
]

5、动画方面采取json来实现,不使用mp4等,减少体积;
6、一切需要使用的依赖都考虑小而优的,如

  1. 日期方面可以采用date-fns;
  2. 轮播使用react-id-swiper来代替swiper;
  3. 视频采取lottie-web库(lottie_light);

Webpack

版本升级(当前是4.32.2)

webpack4在打包上升级了不少,如果是使用creat-react-app的,直接使用脚手架的config即可,直接会减少打包的体积;

1、浏览器语法兼容

使用polyfill.io来处理语法兼容,不再引入core-js、object-assign、promise、raf、whatwg-fetch;
直接在html文件上加载该文件即可,它会直接根据浏览器的UA进行判断下载需要用到的兼容补丁;

2、使用的第三方库按需加载来减少打包体积

如说提到的date-fns,babel-plugin-date-fns在webpack的配置中

{
   test: /\.(js|mjs|jsx|ts|tsx)$/,
   include: paths.appSrc,
   loader: require.resolve('babel-loader'),
   options: {
     customize: require.resolve('babel-preset-react-app/webpack-overrides'),
     plugins: [
       [require.resolve('babel-plugin-date-fns')],
        xxxxplugins
     ],
     cacheDirectory: true,
     cacheCompression: isEnvProduction,
     compact: isEnvProduction
  }
}
在代码中import { format } from 'date-fns',不直接import dateFns from 'date-fns',
同理如lodash、swiper等第三方库同样可以进行这样处理;

3、图片处理

1:webpack的config
    {
      test: [
        /\.bmp$/,
        /\.gif$/,
        /\.jpe?g$/,
        /\.png$/,
        /\.svg$/,
        /\.(eot|ttf|woff|woff2|otf)$/
      ],
      loader: require.resolve('url-loader'),
      options: {
        limit: 100, //注意这个limit限制,不要设置太大,容易把大图打包进去js里面
        name: 'static/media/[name].[hash:8].[ext]'
      }
    }
2:图片格式
    图片采取了webp格式,部分图片直接使用七牛的api进行切割等,减少加载的体积;
3:Icon复用
    可以使用一些Icon库构建UI,如果公司有整体Icon要求,建议处理好Icon的分类和组件,避免多次加载相同的Icon;
4:CDN
    将图片以及CSS和JS走CDN,可以加快加载速度;

4、引入过量

在Home(首页的container)里面,我们经常会import Actions(全部的action),而Actions可能又包含了很多的query(如graphql的query字符串),但在首页其实并不需要加载全部的actions,对于一些只在某些页面会调用的action(如加载数据)可以进行分离,不放入到actions里面,actions里面只保留多处会进行调用的action,特有的直接该页面import XXXActions即可;

相关推荐