aototo / blog

Aototo Blog
344 stars 54 forks source link

React and Redux 性能,框架优化总结 #2

Open aototo opened 7 years ago

aototo commented 7 years ago

React and Redux 性能,框架优化总结


React

  1. 利用React Server Render 提高首屏的渲染速度

    • 利于SEO
    • 加速首屏的渲染时间
    • 前后端共享数据源

    使用Reacr.renderToString, React.renderToStaticMarkup

  2. 请将方法的bind一律置于constructor,避免多次bind。

  3. 请只传递component需要的props ,切勿一股脑的<Component {...props} />

  4. 不需要传入状态的component写成const element的形式,这样能加快这个element的初始渲染速度。

  5. dom上设置可被react识别的同级唯一key,否则情况可能不会重新渲染。

  6. 使用`Stateless Functional Component 无状态组件

    • Class并无必要
    • 没有this关键字
    • 无状态组件写起来代码量更少,
    • 便于测试

React 的无状态组件优雅的实现可复用组件的方式。 栗子如下:

const Pane = (props) => <div>{props.children}</div>;

Pane.propTypes = {
  label: React.PropTypes.string.isRequired,
  children: React.PropTypes.element.isRequired
};
  1. 使用pureRender,避免组件没有意义的渲染,配合immutable,减少渲染。

  2. 使用react-css-modules,解决了命名混乱,全局污染以及依赖管理的问题,多人协同开发有时候难免会发生样式上的冲突。 有个需要注意的地方,下面的2个顺序如果颠倒,就会出错。

    @connect(mapStateToProps, mapDispatchToProps)
    @CSSModules(styles)

    React-router && Webpack

按需加载模块

把这个按需写着这里,本身需要react-router支持,索性就放在这边了。 加载函数:

require.ensure(dependencies, callback, chunkName)

//这里react-router 使用require.ensure,当然了webpack需要配置一下。
<Route path="home" getComponent={(location, callback) => {
  require.ensure([], require => {
    callback(null, require('modules/home'))
  }, 'home')  
}}></Route>

具体看webpack官方 看不懂看这篇webpack 按需打包


Redux

Data

项目数据扁平化,不扁平化带来的问题:

  1. 数据拷贝比较更耗时
  2. 获取数据的时候比较麻烦

通过redux 的combineReducers 可以很好的扁平化数据。如果使用immutable的话整个侵入性非常的强,不仅要修改combineReducers(因为combineReducers实现就是可变的数据),还需要注意获取数据的时候是否是不可变,以免是null。如果使用immutable可以推荐使用redux-immutable。

return {
    ...state,
    newData
}

上面这种写法本身也算是一种immutable,但是要求数据层级不能太深。如果数据相对复杂建议使用immutable。

推荐pure-render-decorator方便的来控制组件渲染。

    import pureRender from 'pure-render-decorator';
    @pureRender
    class ...

使用immutable的时候数据转换如下:

immutableJs Immutable 详解及 React 中实践 redux-immutable seamless-immutable体积更小,兼容相对好。只支持Arrays and Objects。


数据筛选reselect

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

每当store发生改变的时候,connect就会触发重新计算,为了减少重复的不必要计算,减少大型项目的性能开支,需要对selector函数做缓存。推荐使用reactjs/reselect, 缓存的部分实现代码如下。

export function defaultMemoize(func, equalityCheck = defaultEqualityCheck) {
  let lastArgs = null
  let lastResult = null
  const isEqualToLastArg = (value, index) => equalityCheck(value, lastArgs[index])
  return (...args) => {
    if (
      lastArgs === null ||
      lastArgs.length !== args.length ||
      !args.every(isEqualToLastArg)
    ) {
      lastResult = func(...args)
    }
    lastArgs = args
    return lastResult
  }
}

If the values of the input-selectors are the same as the previous call to the selector, it will return the previously computed value instead of calling the transform function.

假如state.todos 中todos 提供的数据没有发生改变,就会return之前计算好的结果,这样就可以少去非常多的计算成本。 具体的实现可以去看https://github.com/reactjs/reselect#creating-a-memoized-selector。

具体用法:以下修改reudx官方的的demo

import { createSelector } from 'reselect'

const getVisibilityFilter = (state) => state.visibilityFilter
const getTodos = (state) => state.todos

export const getVisibleTodos = createSelector(
  [ getVisibilityFilter, getTodos ],
  (visibilityFilter, todos) => {
    switch (visibilityFilter) {
      case 'SHOW_ALL':
        return todos
      case 'SHOW_COMPLETED':
        return todos.filter(t => t.completed)
      case 'SHOW_ACTIVE':
        return todos.filter(t => !t.completed)
    }
  }
)

Batched actions

如果我们需要同时发送很多action,比如:

dispatch(action1)
dispatch(action2)
dispatch(action3)

可以减少不必要的计算,推荐用到redux-batched-actions

dispatch(batchActions[action1, action2, action3])

源码很简单 https://github.com/tshelburne/redux-batched-actions/blob/master/src/index.js#L7


Redux DevTools

开发中使用DevTools,建议使用谷歌的插件,不建议在页面结构中插入DevTools。 redux-devtools-extension

在开发环境以及产品环境中移除devTools,避免不必要性能开销和文件大小。 栗子如下:

if (process.env.NODE_ENV === 'production') {
  module.exports = require('./configureStore.prod');
} else {
  module.exports = require('./configureStore.dev');
}

注意: 需要在webpack中使用DefinePlugin 插件。

new webpack.DefinePlugin({
  'process.env': {
    NODE_ENV: JSON.stringify('production')
  }
}),

相关

以往收集的文章,博客

阮一峰的redux 入门 ReactJS组件间沟通的一些方法 聊一聊基于Flux的前端系统 React 性能工程 聊一聊基于Flux的前端系统 React性能工程-- 深入研究React性能调试 A Better File Structure For React/Redux Applications React服务器端渲染实践小结 dva 项目解决方案 Getting Started with Redux React 实践心得:react-redux 之 connect 方法详解 REACT&REDUX中SCROLL LIST封装实践 redux-axios-middleware axios兼容ie9,提供promise ,很方便。 深入理解 react-router 路由系统 Immutable 详解及 React 中实践 webpack+ react-router 按需加载

大概的整个项目具体的优化就这些,细节的插件和实施大家自己去看文档。后续继续更新,喜欢的朋友star支持一下。

tangkunyin commented 7 years ago

@asd0102433

通过redux 的combineReducers 可以很好的扁平化数据。如果使用immutable的话整个侵入性非常的强,不仅要修改combineReducers(因为combineReducers实现就是可变的数据),还需要注意获取数据的时候是否是不可变,以免是null。如果使用immutable可以推荐使用redux-immutable

这是说引入了redux-immutable替代了redux自己的combineReducers后,还需要手动引入immutablejs库?

aototo commented 7 years ago

@tangkunyin 需要哈!

tangkunyin commented 7 years ago

@asd0102433 三克斯