{"action":"opened","issue":{"url":"https://api.github.com/repos/jackieli123723/jackieli123723.github.io/issues/30","repository_url":"https://api.github.com/repos/jackieli123723/jackieli123723.github.io","labels_url":"https://api.github.com/repos/jackieli123723/jackieli123723.github.io/issues/30/labels{/name}","comments_url":"https://api.github.com/repos/jackieli123723/jackieli123723.github.io/issues/30/comments","events_url":"https://api.github.com/repos/jackieli123723/jackieli123723.github.io/issues/30/events","html_url":"https://github.com/jackieli123723/jackieli123723.github.io/issues/30","id":269877991,"number":30,"title":"react性能优化","user":{"login":"jackieli123723","id":10346511,"avatar_url":"https://avatars1.githubusercontent.com/u/10346511?v=4","gravatar_id":"","url":"https://api.github.com/users/jackieli123723","html_url":"https://github.com/jackieli123723","followers_url":"https://api.github.com/users/jackieli123723/followers","following_url":"https://api.github.com/users/jackieli123723/following{/other_user}","gists_url":"https://api.github.com/users/jackieli123723/gists{/gist_id}","starred_url":"https://api.github.com/users/jackieli123723/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jackieli123723/subscriptions","organizations_url":"https://api.github.com/users/jackieli123723/orgs","repos_url":"https://api.github.com/users/jackieli123723/repos","events_url":"https://api.github.com/users/jackieli123723/events{/privacy}","received_events_url":"https://api.github.com/users/jackieli123723/received_events","type":"User","site_admin":false},"labels":[{"id":668544066,"url":"https://api.github.com/repos/jackieli123723/jackieli123723.github.io/labels/React.js","name":"React.js","color":"7cb342","default":false}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2017-10-31T08:46:17Z","updated_at":"2017-10-31T08:46:17Z","closed_at":null,"author_association":"OWNER","body":"# react性能优化\r\n\r\n## 如何正确地在React中处理事件\r\n\r\n[参考官网](https://facebook.github.io/react/docs/handling-events.html)\r\n\r\n#### 1、构造器内绑定this\r\n```javascript\r\nclass MyComponent extends React.Component {\r\n    constructor(props) {\r\n        super(props);\r\n        this.state = {\r\n            count: 0\r\n        };\r\n        this.handleClick = this.handleClick.bind(this);\r\n    }\r\n\r\n    handleClick() {\r\n        this.setState({\r\n            count: ++this.state.count\r\n        });\r\n    }\r\n\r\n    render() {\r\n        return (\r\n        <div>\r\n            <div>{this.state.count}</div>\r\n            <button onClick={this.handleClick}>Click</button>\r\n        </div>\r\n        );\r\n    }\r\n}\r\n```\r\n这种方式的好处是每次render，不会重新创建一个回调函数，没有额外的性能损失。需要注意的是，使用这种方式要在构造函数中为事件回调函数绑定this:  this.handleClick = this.handleClick.bind(this)，否则handleClick中的this是undefined。这是因为ES6 语法的缘故，ES6 的 Class 构造出来的对象上的方法默认不绑定到 this 上，需要我们手动绑定。\r\n\r\n#### 2、属性初始化\r\n使用ES7的 property initializers，代码可以这样写：\r\n```javascript\r\nclass MyComponent extends React.Component {\r\n    constructor(props) {\r\n        super(props);\r\n        this.state = {\r\n            count: 0\r\n        };\r\n    }\r\n\r\n    handleClick = () => {\r\n        this.setState({\r\n            count: ++this.state.count\r\n        });\r\n    }\r\n\r\n    render() {\r\n        return (\r\n        <div>\r\n            <div>{this.state.count}</div>\r\n            <button onClick={this.handleClick}>Click</button>\r\n        </div>\r\n        );\r\n    }\r\n}\r\n```\r\n这种方式就不需要手动绑定this了。但是你需要知道，这个特性还处于试验阶段，默认是不支持的。如果你是使用官方脚手架Create React App 创建的应用，那么这个特性是默认支持的。你也可以自行在项目中引入babel的transform-class-properties插件获取这个特性支持。\r\n\r\n#### 3、箭头函数\r\n```javascript\r\nclass MyComponent extends React.Component {\r\n    render() {\r\n        return (\r\n        <button onClick={()=>{console.log('button clicked');}}>\r\n            Click\r\n        </button>\r\n        );\r\n    }\r\n}\r\n```\r\n当事件响应逻辑比较复杂时，如果再把所有的逻辑直接写在onClick的大括号内，就会导致render函数变得臃肿，不容易直观地看出组件render出的元素结构。这时，可以把逻辑封装成组件的一个方法，然后在箭头函数中调用这个方法。如下所示：\r\n```javascript\r\nclass MyComponent extends React.Component {\r\n    constructor(props) {\r\n        super(props);\r\n        this.state = {\r\n            count: 0\r\n        };\r\n    }\r\n    handleClick() {\r\n        this.setState({\r\n            count: ++this.state.count\r\n        });\r\n    }\r\n    render() {\r\n        return (\r\n        <div>\r\n            <div>{this.state.number}</div>\r\n            <button onClick={()=>{this.handleClick();}}>Click</button>\r\n        </div>\r\n        );\r\n    }\r\n}\r\n```\r\n这种方式最大的问题是，每次render调用时，都会重新创建一个事件的回调函数，带来额外的性能开销，当组件的层级越低时，这种开销就越大，因为任何一个上层组件的变化都可能会触发这个组件的render方法。当然，在大多数情况下，这点性能损失是可以不必在意的。这种方式也有一个好处，就是不需要考虑this的指向问题，因为这种写法保证箭头函数中的this指向的总是当前组件。\r\n\r\n#### 4、函数传递参数\r\n事件的回调函数默认是会被传入一个事件对象Event作为参数的。如果我想传入其他参数给回调函数应该怎么办呢？\r\n\r\n使用第一种方式（构造器内绑定this）的话，可以把绑定this的操作延迟到render中，在绑定this的同时，绑定额外的参数：\r\n```javascript\r\n// 代码6\r\nclass MyComponent extends React.Component {\r\n    constructor(props) {\r\n        super(props);\r\n        this.state = {\r\n        list: [1,2,3,4],\r\n        current: 1\r\n        };\r\n    }\r\n\r\n    handleClick(item) {\r\n        this.setState({\r\n            current: item\r\n        });\r\n    }\r\n\r\n    render() {\r\n        return (\r\n        <ul>\r\n            {this.state.list.map(\r\n                (item)=>(\r\n                <li className={this.state.current === item ? 'current':''} \r\n                onClick={this.handleClick.bind(this, item)}>{item}\r\n                </li>\r\n                )\r\n            )}\r\n        </ul>\r\n        );\r\n    }\r\n}\r\n```\r\n使用第二种方式（属性初始化），解决方案和第一种基本一致：\r\n```javascript\r\n// 代码7\r\nclass MyComponent extends React.Component {\r\n    constructor(props) {\r\n        super(props);\r\n        this.state = {\r\n            list: [1,2,3,4],\r\n            current: 1\r\n        };\r\n    }\r\n\r\n    handleClick = (item) =>  {\r\n        this.setState({\r\n            current: item\r\n        });\r\n    }\r\n\r\n    render() {\r\n        return (\r\n        <ul>\r\n            {this.state.list.map(\r\n                (item)=>(\r\n                <li className={this.state.current === item ? 'current':''} \r\n                onClick={this.handleClick.bind(undefined, item)}>{item}\r\n                </li>\r\n                )\r\n            )}\r\n        </ul>\r\n        );\r\n    }\r\n}\r\n```\r\n不过这种方式就有点鸡肋了，因为虽然你不需要通过bind函数绑定this，但仍然要使用bind函数来绑定其他参数。\r\n\r\n使用第三种方式（函数传递参数）的话很简单，直接传就可以了：\r\n```javascript\r\nclass MyComponent extends React.Component {\r\n    constructor(props) {\r\n        super(props);\r\n        this.state = {\r\n            list: [1,2,3,4],\r\n            current: 1\r\n        };\r\n    }\r\n\r\n    handleClick(item,event) {\r\n        this.setState({\r\n            current: item\r\n        });\r\n    }\r\n\r\n    render() {\r\n        return (\r\n        <ul>\r\n            {this.state.list.map(\r\n                (item)=>(\r\n                <li className={this.state.current === item ? 'current':''} \r\n                onClick={(event) => this.handleClick(item, event)}>{item}\r\n                </li>\r\n                )\r\n            )}\r\n        </ul>\r\n        );\r\n    }\r\n}\r\n```\r\n\r\n关于事件响应的回调函数，还有一个地方需要注意。不管你在回调函数中有没有显式的声明事件参数Event，React都会把事件Event作为参数传递给回调函数，且参数Event的位置总是在其他自定义参数的后面。例如，在代码6和代码7中，handleClick的参数中虽然没有声明Event参数，但你依然可以通过arguments[1]获取到事件Event对象。\r\n\r\n总结一下，三种绑定事件回调的方式，第一种有额外的性能损失；第二种需要手动绑定this，代码量增多；第三种用到了ES7的特性，目前并非默认支持，需要Babel插件的支持，但是写法最为简洁，也不需要手动绑定this。推荐使用第二种和第三种方式。\r\n\r\n## Immutable 详解及 React 中实践 (https://github.com/camsong/blog/issues/3)\r\n\r\n## react 实现pure render的时候，bind(this)隐患\r\n```javascript\r\nexport default class Parent extends Component {\r\n...\r\n  render() {\r\n    const {name,age} =this.state;\r\n    return (\r\n      <div>\r\n        <Child name={name} age={age} onClick={this._handleClick.bind(this)}></Child>//bug 所在\r\n      </div>\r\n    )\r\n  }\r\n...\r\n}\r\n```\r\n发现一个问题，对于Child这个子组件来说，在父组件re－render的时候，即使Child得前后两个props都没改变，它依旧会re－render。。即使用immutable.js也不好使。。。原来啊，父组件每次render，_handleClick都会执行bind(this) 这样_handleClick的引用每次都会改。。所以Child前后两次props其实是不一样的。。\r\n那怎么办？把bind（this）去掉？不行 还必须得用。真正的答案是 让父组件每次render 不执行bind（this），直接提前在constructor执行好，修改之后\r\n```javascript\r\nexport default class Parent extends Component {\r\n  constructor(props){\r\n    super(props)\r\n    this._handleClick=this._handleClick.bind(this)//改成这样\r\n  }\r\n  render() {\r\n    const {name,age} =this.state;\r\n    return (\r\n      <div>\r\n        <Child name={name} age={age} onClick={this._handleClick}></Child>\r\n      </div>\r\n    )\r\n  }\r\n...\r\n}\r\n```\r\n## 子组件跟随父组件re－render\r\n想象一下这种场景，一个父组件下面一大堆子组件。然后呢，这个父组件re－render。是不是下面的子组件都得跟着re－render。可是很多子组件里面是冤枉的啊！！很多子组件的props 和 state 然而并没有改变啊！！虽然virtual dom 的diff 算法很快。。但是性能也不是这么浪费的啊！！\r\n```javascript\r\nclass Child  extends Component {\r\n  render() {\r\n    console.log(\"我re-render了\");\r\n    const {name,age} = this.props;\r\n\r\n      return (\r\n        <div>\r\n          <span>姓名:</span>\r\n          <span>{name}</span>\r\n          <span> age:</span>\r\n          <span>{age}</span>\r\n        </div>\r\n      )\r\n  }\r\n}\r\nconst Person = pureRender(Child);\r\n```\r\npureRender其实就是一个函数，接受一个Component。把这个Component搞一搞，返回一个Component看他pureRender的源代码就一目了然\r\n```javascript\r\nfunction shouldComponentUpdate(nextProps, nextState) {\r\n  return shallowCompare(this, nextProps, nextState);\r\n}\r\n\r\nfunction pureRende(component) {\r\n  component.prototype.shouldComponentUpdate = shouldComponentUpdate;\r\n}\r\nmodule.exports = pureRender;\r\n```\r\npureRender很简单，就是把传进来的component的shouldComponentUpdate给重写掉了，原来的shouldComponentUpdate，无论怎样都是return ture，现在不了，我要用shallowCompare比一比，shallowCompare代码及其简单，如下\r\n```javascript\r\nfunction shallowCompare(instance, nextProps, nextState) {\r\n  return !shallowEqual(instance.props, nextProps) || !shallowEqual(instance.state, nextState);\r\n}  \r\n```\r\n一目了然。分别拿现在props&state和要传进来的props&state，用shallowEqual比一比，要是props&state都一样的话，就return false"}}
{"actor":{"display_login":"jackieli123723"}}