React进阶—性能优化
React性能优化思路软件的性能优化思路就像生活中去看病,大致是这样的:
React性能优化的特殊性看过《高性能JavaScript》这本书的小伙伴都知道,JavaScipt的语言特性、数据结构和算法、浏览器机理、网络传输等都可能导致性能问题。同样是web实现,跟传统的技术(如原生js、jQuery)相比,react的性能优化有什么不同呢? 使用jQuery时,要考虑怎么使用选择器来提高元素查找效率、不要在循环体内进行DOM操作、使用事件委托呀等等。到了React这里,这些东西好像都用不上了。是的,因为React有一个很大的不同点,它实现了虚拟DOM,并且接管了DOM的操作。你不能直接去操作DOM来改变UI,你只能通过改变数据源(props和state)来驱动UI的变化。 说起React的性能分析,还得从它的生命周期和渲染机制说起: React组件生命周期
当 props 和 state 发生变化时,React会根据shouldComponentUpdate方法来决定是否重新渲染整个组件。 React组件树渲染机制
父亲组件的props 和 state发生变化时,它和它的子组件、孙子组件等所有后代组件都会重新渲染。 综上所述,可以得出React的性能优化就是围绕shouldComponentUpdate方法(SCU)来进行的,无外乎两点:
React 性能分析工具Web通用工具:Chrome DevTools最常用到的是Chrome DevTools的Timeline和Profiles。
React特色工具:PerfPerf 是react官方提供的性能分析工具。Perf最核心的方法莫过于 有童鞋开发了Chrome扩展程序“React Perf”(戳这里)。相比自己在代码中插入Perf方法进行分析,这个小工具更加灵活方便,墙裂推荐! 案例分析:TodoListTodoList的功能很简单,就是对待办事项进行增加和删除操作: import React,{PropTypes,Component} from 'react'; class TodoItem extends Component { static propTypes = { deleteItem: PropTypes.func.isRequired,item: PropTypes.shape({ text: PropTypes.string.isRequired,id: PropTypes.number.isRequired,}).isRequired,}; deleteItem = ()=>{ let id = this.props.item.id; this.props.deleteItem(id); }; render() { return ( <div> <button style={{width: 30}} onClick={this.deleteItem}>X</button> <span>{this.props.item.text}</span> </div> ); } } class Todos extends Component { // 构造 constructor(props) { super(props); // 初始状态 this.state = { items: this.props.initialItems,text: '',}; } static propTypes = { initialItems: PropTypes.arrayOf(PropTypes.shape({ text: PropTypes.string.isRequired,}).isRequired).isRequired,}; addTask = (e)=> { e.preventDefault(); this.setState({ items: [{id: ID++,text: this.state.text}].concat(this.state.items),}); }; deleteItem = (itemId)=> { this.setState({ items: this.state.items.filter((item) => item.id !== itemId),}); }; render() { return ( <div> <h1>待办事项</h1> <form onSubmit={this.addTask}> <input value={this.state.text} onChange={(v)=>{this.setState({text:v.target.value});}}/> <button>添加</button> </form> {this.state.items.map((item) => { return ( <TodoItem key={item.id} item={item} deleteItem={this.deleteItem}/> ); })} </div> ); } } let ID = 0; const items = []; for (let i = 0; i < 1000; i++) { items.push({id: ID++,text: '事项' + i}); } class TodoList extends Component { render() { return ( <Todos initialItems={items}/> ); } } export default TodoList; 在待办事项输入框里输入一个字母,接下来我们以这个行为为例来进行性能分析和优化。 第一次优化使用Chrome开发者工具的Timeline记录下这个过程: 重点关注出现的红色块,代表这个行为存在性能问题。从上图我们可以看出,耗时的 接着,我们使用Profiles来进一步分析脚本问题: 对Total Time进行降序排列,发现耗时最长的是dispatchEvent,来自react源码。这时,我们就可以确定是react这一层出现了性能问题。 嗯,轮到Perf出场了: 上图表示,有1000次不必要的渲染发生在TodoItem组件上. 打开react面板,我们来看看组件的层次和相应的state、props值: TodoItem是Todos的子组件,当我们在输入框输入字母“s”时,Todos的state值发生改变时,文章开头所说的react的渲染机制导致Todos下的1000个TodoItem组件都会重新渲染一次。但是,TodoItem的展现其实没有任何变化。 所以,我们应该优化下TodoItem的SCU方法: class TodoItem extends Component { ... //在props没有变化的时候返回false,不重新渲染 shouldComponentUpdate(nextState,nextProps) { if(this.props.item == nextProps.item && this.props.deleteItem == nextProps.deleteItem){ return false; } return true; } render() { ... } } (PS: TodoItem中的SCU方法,使用的是浅比较,也可以使用PureComponent代替。实际项目中,往往需要使用复杂的深比较,可以考虑使用Immutable.js) 验证下优化效果,使用Perf测试,发现1000个多余的渲染被干掉了! 疗效还不错! 第二次优化通过SCU返回false,我们避免了无谓的渲染。但是,我们还是调用了1000次TodoItem的SCU方法,这也是一笔不小的性能开支。 是否可以不用调用呢?通过合理地规划组件粒度,可以做到: //将增加待办事项抽象成一个组件 class AddItem extends Component{ constructor(props) { super(props); this.state = { text:"" }; } static PropTypes = { addTask:PropTypes.func.isRequired }; addTask = (e)=>{ e.preventDefault(); this.props.addTask(this.state.text); }; render(){ return ( <form onSubmit={this.addTask}> <input value={this.state.text} onChange={(v)=>{this.setState({text:v.target.value});}}/> <button>添加</button> </form> ); } } class Todos extends Component{ constructor(props) { super(props); this.state = { items: this.props.initialItems,}; addTask = (text)=>{ this.setState({ items: [{id: ID++,text:text}].concat(this.state.items),}); }; deleteItem = (itemId)=>{ this.setState({ items: this.state.items.filter((item) => item.id !== itemId),}); }; render() { return ( <div> <h1>待办事项V3</h1> <AddItem addTask={this.addTask}/> {this.state.items.map((item) => { return ( <TodoItem key={item.id} item={item} deleteItem={this.deleteItem}/> ); })} </div> ); } } 把增加待办事项抽象成一个AddItem组件。这样一来,组件树从原来的 变成
输入信息时触发变化的text这个state值,被下放到AddItem组件来管理,因此不会导致兄弟组件(TodoItem)的重新渲染。 再次运行Timeline测试,这时 至此,性能优化完毕~ (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- Mac OS Sierra:检查C编译器是否正常工作……没有
- arrays – Swift泛型数组函数,用于查找不匹配项的元素的所有
- 天津政府应急系统之GIS一张图(arcgis api for flex)讲解(
- cocos2dx中的Director类中的getInstance()方法解析,即单
- postgresql – 标签中的最大字符(表名,列等)
- ios – 如何调用sqlite3_errmsg来了解sqlite3_prepare_v2失
- 黑马day18 juery的高级特性&Ajax的$.get()/post()方法
- DOJO 配置
- Flash Builder 开发相关链接
- 使用自动生成的C代码对大型C dll进行性能损失