老项目一些优化

写个webplugin 记录了webpack在一次compilation所用的时间

const chalk = require('chalk') /* console 颜色 */
var slog = require('single-line-log'); /* 单行打印 console */

class ConsolePlugin {
    constructor(options){
       this.options = options
    }
    apply(compiler){
        /**
         * Monitor file change 记录当前改动文件
         */
        compiler.hooks.watchRun.tap('ConsolePlugin', (watching) => {
            const changeFiles = watching.watchFileSystem.watcher.mtimes
            for(let file in changeFiles){
                console.log(chalk.green('当前改动文件:'+ file))
            }
        })
        /**
         *  before a new compilation is created. 开始 compilation 编译 。
         */
        compiler.hooks.compile.tap('ConsolePlugin',()=>{
            this.beginCompile()
        })
        /**
         * Executed when the compilation has completed. 一次 compilation 完成。
         */
        compiler.hooks.done.tap('ConsolePlugin',()=>{
            this.timer && clearInterval( this.timer )
            const endTime =  new Date().getTime()
            const time = (endTime - this.starTime) / 1000
            console.log( chalk.yellow(' 编译完成') )
            console.log( chalk.yellow('编译用时:' + time + '秒' ) )
        })
    }
    beginCompile(){
       const lineSlog = slog.stdout
       let text  = '开始编译:'
       /* 记录开始时间 */
       this.starTime =  new Date().getTime()
       this.timer = setInterval(()=>{
          text +=  '█'
          lineSlog( chalk.green(text))
       },50)
    }
}
构建时间如上

webpack 优化项

  1. include 或 exclude 限制 loader 范围。
{
    test: /\.jsx?$/,
    exclude: /node_modules/,
    include: path.resolve(__dirname, '../src'),
    use:['happypack/loader?id=babel']
    // loader: 'babel-loader'
}

2.  happypack多进程编译 和 缓存babel编译过的文件

// 加 pluglin 
new HappyPack({
    id:'babel',
    loaders:['babel-loader?cacheDirectory=true']
})

3. tree Shaking 删除冗余代码

4. 按需加载,按需引入。

.babelrc 增加对 antd 样式按需引入

["import", {
    "libraryName":
    "antd",
    "libraryDirectory": "es",
    "style": true
}]

二 路由懒加载,路由监听器

传统路由

<Switch>
    <Route path={'/index'} component={Index} ></Route>
    <Route path={'/list'} component={List} ></Route>
    <Route path={'/detail'} component={ Detail } ></Route>
    <Redirect from='/*' to='/index' />
</Switch>

或者 list保存路由信息,方便在进行路由拦截,或者配置路由菜单等

const router = [
    {
        'path': '/index',
        'component': Index
    },
    {
        'path': '/list'',
        'component': List
    },
    {
        'path': '/detail',
        'component': Detail
    },
]

asyncRouter懒加载路由,并实现路由监听

我们今天讲的这种react路由懒加载是基于import 函数路由懒加载, 众所周知 ,import 执行会返回一个Promise作为异步加载的手段。我们可以利用这点来实现react异步加载路由

const routerObserveQueue = [] /* 存放路由卫视钩子 */
/* 懒加载路由卫士钩子 */
export const RouterHooks = {
  /* 路由组件加载之前 */
  beforeRouterComponentLoad: function(callback) {
    routerObserveQueue.push({
      type: 'before',
      callback
    })
  },
  /* 路由组件加载之后 */
  afterRouterComponentDidLoaded(callback) {
    routerObserveQueue.push({
      type: 'after',
      callback
    })
  }
}
/* 路由懒加载HOC */
export default function AsyncRouter(loadRouter) {
  return class Content extends React.Component {
    constructor(props) {
      super(props)
      /* 触发每个路由加载之前钩子函数 */
      this.dispatchRouterQueue('before')
    }
    state = {Component: null}
    dispatchRouterQueue(type) {
      const {history} = this.props
      routerObserveQueue.forEach(item => {
        if (item.type === type) item.callback(history)
      })
    }
    componentDidMount() {
      if (this.state.Component) return
      loadRouter()
        .then(module => module.default)
        .then(Component => this.setState({Component},
          () => {
            /* 触发每个路由加载之后钩子函数 */
            this.dispatchRouterQueue('after')
          }))
    }
    render() {
      const {Component} = this.state
      return Component ? <Component {
      ...this.props
      }
      /> : null
    }
  }
}

asyncRouter实际就是一个高级组件,将()=>import()作为加载函数传进来,然后当外部Route加载当前组件的时候,在componentDidMount生命周期函数,加载真实的组件,并渲染组件,我们还可以写针对路由懒加载状态定制属于自己的路由监听器beforeRouterComponentLoadafterRouterComponentDidLoaded,类似vue中 watch $route 功能。接下来我们看看如何使用。

import AsyncRouter ,{ RouterHooks }  from ‘./asyncRouter.js’
const { beforeRouterComponentLoad} = RouterHooks
const Index = AsyncRouter(()=>import(‘../src/page/home/index’))
const List = AsyncRouter(()=>import(‘../src/page/list’))
const Detail = AsyncRouter(()=>import(‘../src/page/detail’))
const index = () => {
  useEffect(()=>{
/* 增加监听函数 */
    beforeRouterComponentLoad((history)=>{
      console.log(‘当前激活的路由是’,history.location.pathname)
    })
  },[])
  return <div >
    <div >
      <Router  >
      <Meuns/>
      <Switch>
          <Route path={‘/index’} component={Index} ></Route>
          <Route path={‘/list’} component={List} ></Route>
          <Route path={‘/detail’} component={ Detail } ></Route>
          <Redirect from=’/*’ to=’/index’ />
       </Switch>
      </Router>
    </div>
  </div>
}

受控性组件颗粒化 ,独立请求服务渲染单元

不是所有状态都应该放在组件的 state 中. 例如缓存数据。如果需要组件响应它的变动, 或者需要渲染到视图中的数据才应该放到 state 中。这样可以避免不必要的数据变动导致组件重新渲染.

颗粒化控制可控性组件 / 独立的请求渲染单元

1 可以避免父组件的冗余渲染 ,react的数据驱动,依赖于 state 和 props 的改变,改变state 必然会对组件 render 函数调用,如果父组件中的子组件过于复杂,一个自组件的 state 改变,就会牵一发动全身,必然影响性能,所以如果把很多依赖请求的组件抽离出来,可以直接减少渲染次数。

2 可以优化组件自身性能,无论从class声明的有状态组件还是fun声明的无状态,都有一套自身优化机制,无论是用shouldupdate 还是用 hooks中 useMemo useCallback ,都可以根据自身情况,定制符合场景的渲条 件,使得依赖数据请求组件形成自己一个小的,适合自身的渲染环境。

3 能够和redux ,以及redux衍生出来 redux-action , dva,更加契合的工作,用 connect 包裹的组件,就能通过制定好的契约,根据所需求的数据更新,而更新自身,而把这种模式用在这种小的,需要数据驱动的组件上,就会起到物尽其用的效果。

shouldComponentUpdate ,PureComponent 和 React.memo ,immetable.js 助力性能调优

我们用react-redux来简单举一个例子,如下所示 数据都已经被 immetable.js处理。

import { is  } from 'immutable'
const GoodItems = connect(state =>
    ({ GoodItems: filter(state.getIn(['Items', 'payload', 'list']), state.getIn(['customItems', 'payload', 'list'])) || Immutable.List(), })
    /* 此处省略很多代码~~~~~~ */
)(memo(({ Items, dispatch, setSeivceId }) => {
   /*  */
}, (pre, next) => is(pre.Items, next.Items)))

规范写法,合理处理细节问题

绑定事件尽量不要使用箭头函数

react更新来大部分情况自于props的改变(被动渲染),和state改变(主动渲染)。当我们给未加任何更新限定条件子组件绑定事件的时候,或者是PureComponent 纯组件, 如果我们箭头函数使用的话。

<ChildComponent handerClick={()=>{ console.log(666) }}  />

每次渲染时都会创建一个新的事件处理器,这会导致 ChildComponent 每次都会被渲染。

解决问题

function index(){
    const handerClick1 = useMemo(()=>()=>{
       console.log(777)
    },[])  /* [] 存在当前 handerClick1 的依赖项*/
    const handerClick = useCallback(()=>{ console.log(666) },[])  /* [] 存在当前 handerClick 的依赖项*/
    return <div>
        <ChildComponent handerClick={ handerClick }  />
        <div onClick={ handerClick1 }  >hello,world</div>
    </div>
}

循环正确使用key

错误用法一:用index做key

错误用法二:用index拼接其他的字段

正确用法:用唯一id作为key

无状态组件hooks-useMemo 避免重复声明

错误用法

function Index(){
    const [ number , setNumber  ] = useState(0)
    const handerClick1 = ()=>{
        /* 一些操作 */
    }
    const handerClick2 = ()=>{
        /* 一些操作 */
    }
    const handerClick3 = ()=>{
        /* 一些操作 */
    }
    return <div>
        <a onClick={ handerClick1 } >点我有惊喜1</a>
        <a onClick={ handerClick2 } >点我有惊喜2</a>
        <a onClick={ handerClick3 } >点我有惊喜3</a>
        <button onClick={ ()=> setNumber(number+1) } > 点击 { number } </button>
    </div>
}

正确写法

function Index(){
    const [ number , setNumber  ] = useState(0)
    const [ handerClick1 , handerClick2  ,handerClick3] = useMemo(()=>{
        const fn1 = ()=>{
            /* 一些操作 */
        }
        const fn2 = ()=>{
            /* 一些操作 */
        }
        const  fn3= ()=>{
            /* 一些操作 */
        }
        return [fn1 , fn2 ,fn3]
    },[]) /* 只有当数据里面的依赖项,发生改变的时候,才会重新声明函数。 */
    return <div>
        <a onClick={ handerClick1 } >点我有惊喜1</a>
        <a onClick={ handerClick2 } >点我有惊喜2</a>
        <a onClick={ handerClick3 } >点我有惊喜3</a>
        <button onClick={ ()=> setNumber(number+1) } > 点击 { number } </button>
    </div>
}

懒加载 Suspense 和 lazy

const LazyComponent = React.lazy(() => import('./LazyComponent'));

function demo () {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
        <LazyComponent1 /> //Suspense 可以包裹多个懒加载组件
      </Suspense>
    </div>
  )
}

多种方式避免重复渲染

批量更新

class index extends React.Component{
    constructor(prop){
        super(prop)
        this.state = {
            a:1,
            b:2,
            c:3,
        }
    }
    handerClick=()=>{
        const { a,b,c } :any = this.state
        this.setState({ a:a+1 })
        this.setState({ b:b+1 })
        this.setState({ c:c+1 })
    }
    render= () => <div onClick={this.handerClick} />
}

点击事件发生之后,会触发三次 setState,但是不会渲染三次,因为有一个批量更新batchUpdate批量更新的概念。三次setState最后被合成类似如下样子




    const  [ a , setA ] = useState(1)
    const  [ b , setB ] = useState({})
    const  [ c , setC ] = useState(1)
    const handerClick = () => {
        setB( { ...b } ) 
        setC( c+1 ) 
        setA( a+1 )
    }

Leave a Reply

Your email address will not be published. Required fields are marked *