SegmentFault 前端最新的文章
2016-08-12T19:25:23+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
React性能优化
https://segmentfault.com/a/1190000006254212
2016-08-12T19:25:23+08:00
2016-08-12T19:25:23+08:00
hepeguo
https://segmentfault.com/u/hepeguo
11
<p>当大家考虑在项目中使用 React 的时候,第一个问题往往是他们的应用的速度和响应是否能和非 React 版一样,每当状态改变的时候就重新渲染组件的整个子树,让大家怀疑这会不会对性能造成负面影响。React 用了一些黑科技来减少 UI 更新需要的花费较大的 DOM 操作。</p>
<h2>使用 production 版本</h2>
<p>如果你在你的 React app 中进行性能测试或在寻找性能问题,一定要确定你在使用 <a href="/react/downloads.html">minified production build</a>。开发者版本包括额外的警告信息,这对你在开发你的 app 的时候很有用,但是因为要进行额外的处理,所以它也会比较慢。</p>
<h2>避免更新 DOM</h2>
<p>React 使用虚拟 DOM,它是在浏览器中的 DOM 子树的渲染描述,这个平行的描述让 React 避免创建和操作 DOM 节点,这些远比操作一个 JavaScript 对象慢。当一个组件的 props 或 state 改变,React 会构造一个新的虚拟 DOM 和旧的进行对比来决定真实 DOM 更新的必要性,只有在它们不相等的时候,React 才会使用尽量少的改动更新 DOM。</p>
<p>在此之上,React 提供了生命周期函数 <code>shouldComponentUpdate</code>,在重新渲染机制回路(虚拟 DOM 对比和 DOM 更新)之前会被触发,赋予开发者跳过这个过程的能力。这个函数默认返回 <code>true</code>,让 React 执行更新。</p>
<pre><code class="javascript">shouldComponentUpdate: function(nextProps, nextState) {
return true;
}</code></pre>
<p>一定要记住,React 会非常频繁的调用这个函数,所以要确保它的执行速度够快。</p>
<p>假如你有个带有多个对话的消息应用,如果只有一个对话发生改变,如果我们在 <code>ChatThread</code> 组件执行 <code>shouldComponentUpdate</code>,React 可以跳过其他对话的重新渲染步骤。</p>
<pre><code class="javascript">shouldComponentUpdate: function(nextProps, nextState) {
// TODO: return whether or not current chat thread is
// different to former one.
}</code></pre>
<p>因此,总的说,React 通过让用户使用 <code>shouldComponentUpdate</code> 减短重新渲染回路,避免进行昂贵的更新 DOM 子树的操作,而且这些必要的更新,需要对比虚拟 DOM。</p>
<h2>shouldComponentUpdate 实战</h2>
<p>这里有个组件的子树,每一个都指明了 <code>shouldComponentUpdate</code> 返回值和虚拟 DOM 是否相等,最后,圆圈的颜色表示组件是否需要更新。</p>
<p><img src="/img/bVApam?w=555&h=371" alt="should-component-update.png" title="should-component-update.png"></p>
<p>在上面的示例中,因为 C2 的 <code>shouldComponentUpdate</code> 返回 false,React 就不需要生成新的虚拟 DOM,也就不需要更新 DOM,注意 React 甚至不需要调用 C4 和 C5 的 <code>shouldComponentUpdate</code>。</p>
<p>C1 和 C3 的 <code>shouldComponentUpdate</code> 返回 <code>true</code>,所以 React 需要向下到叶子节点检查它们,C6 返回 <code>true</code>,因为虚拟 DOM 不相等,需要更新 DOM。最后感兴趣的是 C8,对于这个节点,React 需要计算虚拟 DOM,但是因为它和旧的相等,所以不需要更新 DOM。</p>
<p>注意 React 只需要对 C6 进行 DOM 转换,这是必须的。对于 C8,通过虚拟 DOM 的对比确定它是不需要的,C2 的子树和 C7,它们甚至不需要计算虚拟 DOM,因为 <code>shouldComponentUpdate</code>。</p>
<p>那么,我们怎么实现 <code>shouldComponentUpdate</code> 呢?比如说你有一个组件仅仅渲染一个字符串:</p>
<pre><code class="javascript">React.createClass({
propTypes: {
value: React.PropTypes.string.isRequired
},
render: function() {
return <div>{this.props.value}</div>;
}
});</code></pre>
<p>我们可以简单的实现 <code>shouldComponentUpdate</code> 如下:</p>
<pre><code class="javascript">shouldComponentUpdate: function(nextProps, nextState) {
return this.props.value !== nextProps.value;
}</code></pre>
<p>非常好!处理这样简单结构的 props/state 很简单,我门甚至可以归纳出一个基于浅对比的实现,然后把它 Mixin 到组件中。实际上 React 已经提供了这样的实现: <a href="/react/docs/pure-render-mixin.html">PureRenderMixin</a></p>
<p>但是如果你的组件的 props 或者 state 是可变的数据结构呢?比如说,组件接收的 prop 不是一个像 <code>'bar'</code> 这样的字符串,而是一个包涵字符串的 JavaScript 对象,比如 <code>{ foo: 'bar' }</code>:</p>
<pre><code class="javascript">React.createClass({
propTypes: {
value: React.PropTypes.object.isRequired
},
render: function() {
return <div>{this.props.value.foo}</div>;
}
});</code></pre>
<p>前面的 <code>shouldComponentUpdate</code> 实现就不会一直和我们期望的一样工作:</p>
<pre><code class="javascript">// assume this.props.value is { foo: 'bar' }
// assume nextProps.value is { foo: 'bar' },
// but this reference is different to this.props.value
this.props.value !== nextProps.value; // true</code></pre>
<p>这个问题是当 prop 没有改变的时候 <code>shouldComponentUpdate</code> 也会返回 <code>true</code>。为了解决这个问题,我们有了这个替代实现:</p>
<pre><code class="javascript">shouldComponentUpdate: function(nextProps, nextState) {
return this.props.value.foo !== nextProps.value.foo;
}</code></pre>
<p>基本上,我们结束了使用深度对比来确保改变的正确跟踪,这个方法在性能上的花费是很大的,因为我们需要为每个 model 写不同的深度对比代码。就算这样,如果我们没有处理好对象引用,它甚至不能工作,比如说这个父组件:</p>
<pre><code class="javascript">React.createClass({
getInitialState: function() {
return { value: { foo: 'bar' } };
},
onClick: function() {
var value = this.state.value;
value.foo += 'bar'; // ANTI-PATTERN!
this.setState({ value: value });
},
render: function() {
return (
<div>
<InnerComponent value={this.state.value} />
<a onClick={this.onClick}>Click me</a>
</div>
);
}
});</code></pre>
<p>内部组件第一次渲染的时候,它会获取 <code>{ foo: 'bar' }</code> 作为 value 的值。如果用户点击了 a 标签,父组件的 state 会更新成 <code>{ value: { foo: 'barbar' } }</code>,触发内部组件的重新渲染过程,内部组件会收到 <code>{ foo: 'barbar' }</code> 作为 value 的新的值。</p>
<p>这里的问题是因为父组件和内部组件共享同一个对象的引用,当对象在 <code>onClick</code> 函数的第二行发生改变的时候,内部组件的属性也发生了改变,所以当重新渲染过程开始,<code>shouldComponentUpdate</code> 被调用的时候,<code>this.props.value.foo</code> 和 <code>nextProps.value.foo</code> 是相等的,因为实际上 <code>this.props.value</code> 和 <code>nextProps.value</code> 是同一个对象的引用。</p>
<p>因此,我们会丢失 prop 的改变,缩短重新渲染过程,UI 也不会从 <code>'bar'</code> 更新到 <code>'barbar'</code></p>
<h2>Immutable-js 来救赎</h2>
<p><a href="https://link.segmentfault.com/?enc=BxAiWL9HhRHoMrTjQkH%2Bbg%3D%3D.CebRHYVFk5Ycj4h1Nboa2DuG%2BLdySGu2z7%2Bj2gYV1OjDb8%2BYnmYAd%2BkUU7pfM3pd" rel="nofollow">Immutable-js</a> 是 Lee Byron 写的 JavaScript 集合类型的库,最近被 Facebook 开源,它通过<em>结构共享</em>提供<em>不可变持久化</em>集合类型。一起看下这些特性的含义:</p>
<ul>
<li><p><em>Immutable</em>: 一旦创建,集合就不能再改变。</p></li>
<li><p><em>Persistent</em>: 新的集合类型可以通过之前的集合创建,比如 set 产生改变的集合。创建新的集合之后源集合仍然有效。</p></li>
<li><p><em>Structural Sharing</em>: 新的集合会使用尽量多的源集合的结构,减少复制来节省空间和性能友好。如果新的集合和源集合相等,一般会返回源结构。</p></li>
</ul>
<p>不可变让跟踪改变非常简单;每次改变都是产生新的对象,所以我们仅需要对象的引用是否改变,比如这段简单的 JavaScript 代码:</p>
<pre><code class="javascript">var x = { foo: "bar" };
var y = x;
y.foo = "baz";
x === y; // true</code></pre>
<p>尽管 <code>y</code> 被改变,因为它和 <code>x</code> 引用的是同一个对象,这个对比返回 <code>true</code>。然而,这个代码可以使用 immutable-js 改写如下:</p>
<pre><code class="javascript">var SomeRecord = Immutable.Record({ foo: null });
var x = new SomeRecord({ foo: 'bar' });
var y = x.set('foo', 'baz');
x === y; // false</code></pre>
<p>这个例子中,因为改变 <code>x</code> 的时候返回了新的引用,我们就可以安全的认为 <code>x</code> 已经改变。</p>
<p>脏检测可以作为另外的可行的方式追踪改变,给 setters 一个标示。这个方法的问题是,它强制你使用 setters,而且要写很多额外的代码,影响你的类。或者你可以在改变之前深拷贝对象,然后进行深对比来确定是不是发生了改变。这个方法的问题是,深拷贝和深对比都是很花性能的操作。</p>
<p>因此,不可变数据结构给你提供了一个高效、简洁的方式来跟踪对象的改变,而跟踪改变是实现 <code>shouldComponentUpdate</code> 的关键。所以,如果我们使用 immutable-js 提供的抽象创建 props 和 state 模型,我们就可以使用 <code>PureRenderMixin</code>,而且能够获得很好的性能增强。</p>
<h2>Immutable-js 和 Flux</h2>
<p>如果你在使用 <a href="https://link.segmentfault.com/?enc=UPk3RP3VLzf3BYCxkHzPTQ%3D%3D.e5jHzPA0uCXxS48%2BjCayMYbbJtRI%2FZ7lLAFbsjuk%2Byypi%2FzOjADmoN7tE2uwrLUb" rel="nofollow">Flux</a>,你应该开始使用 immutable-js 写你的 stores,看一下 <a href="https://link.segmentfault.com/?enc=VgByVb4WRECHJW1KN%2BNGZg%3D%3D.aq5bo9JEAF0um%2FTuUfdkG5szEifaR9JwAA%2B1e59P7Vi8Mt3Un6WfRGTbWvVSo1C%2B" rel="nofollow">full API</a>。</p>
<p>让我们看一个可行的方式,使用不可变数据结构来给消息示例创建数据结构。首先我们要给每个要建模的实体定义一个 <code>Record</code>。Records 仅仅是一个不可变容器,里面保存一系列具体数据:</p>
<pre><code class="javascript">var User = Immutable.Record({
id: undefined,
name: undefined,
email: undefined
});
var Message = Immutable.Record({
timestamp: new Date(),
sender: undefined,
text: ''
});</code></pre>
<p><code>Record</code> 方法接收一个对象,来定义字段和对应的默认数据。</p>
<p>消息的 <em>store</em> 可以使用两个 list 来跟踪 users 和 messages:</p>
<pre><code class="javascript">this.users = Immutable.List();
this.messages = Immutable.List();</code></pre>
<p>实现函数处理每个 <em>payload</em> 类型应该是比较简单的,比如,当 store 看到一个代表新消息的 payload 时,我们就创建一个新的 record,并放入消息列表:</p>
<pre><code class="javascript">this.messages = this.messages.push(new Message({
timestamp: payload.timestamp,
sender: payload.sender,
text: payload.text
});</code></pre>
<p>注意:因为数据结构不可变,我们需要把 push 方法的结果赋给 <code>this.messages</code>。</p>
<p>在 React 里,如果我们也使用 immutable-js 数据结构来保存组件的 state,我门可以把 <code>PureRenderMixin</code> 混入到我门所有的组件来缩短重新渲染回路。</p>
<p><em>这篇文章是翻译React官方文档</em></p>
iOS app SmartCost 开源
https://segmentfault.com/a/1190000006223419
2016-08-10T10:31:13+08:00
2016-08-10T10:31:13+08:00
hepeguo
https://segmentfault.com/u/hepeguo
0
<blockquote><p>一个极简的生活消费记录 app。</p></blockquote>
<p>使用 swift 开发,并在 app store 上架,没有做任何推广(懒),全球总下载3000+。目前因 iOS 开发者账号费用问题(穷),已经从 app store 下架(哭)。现在开源出来<a href="https://link.segmentfault.com/?enc=OQalbJYkDr53AVYdj4tEfw%3D%3D.jhiWZSa34cerI2mPafGaTp%2BU0XInZ6ohxF3%2BT7ESIUij8oFhsHRax9yA3zecmnVC" rel="nofollow">https://github.com/hepeguo/SmartCost</a>,给 iOS 初学者或 swift 初学者一些参考(至少可以参考 iOS 某些 api 的用法)。下面先看下 UI。</p>
<p><img src="/img/bVAg9H" alt="clipboard.png" title="clipboard.png"></p>
<h2>主要的东西</h2>
<ul>
<li><p>自定义数字键盘</p></li>
<li><p>自定义UITableView</p></li>
<li><p>iCloud</p></li>
<li><p>coreData</p></li>
<li><p>UserDefault</p></li>
<li><p>邮件</p></li>
<li><p>动画</p></li>
<li><p>日历</p></li>
<li><p>UIScrollView</p></li>
<li><p>导出csv文件</p></li>
</ul>
React-Redux源码剖析
https://segmentfault.com/a/1190000006196949
2016-08-07T18:54:47+08:00
2016-08-07T18:54:47+08:00
hepeguo
https://segmentfault.com/u/hepeguo
6
<p>React-Redux是用在连接React和Redux上的。如果你想同时用这两个框架,那么React-Redux基本就是必须的了。为了能够更好的使用这个工具,今天就对它进行一下源码剖析。</p>
<h2>Provider</h2>
<p>一个React组件,一般你的rootApp要放倒这个组件内部渲染。它很简单,最关键的作用就是在context中放入Redux的store,方便子组件获取。关键代码:</p>
<pre><code>getChildContext() {
return { store: this.store }
}
Provider.childContextTypes = {
store: storeShape.isRequired
}
</code></pre>
<p>这样connect的组件就可以获取store,使用store的方法。</p>
<h2>connect</h2>
<p>首选connect是个可以执行两次的柯里化函数,第一次传入的参数相当于一系列的定制化东西,第二次传入的是你要连接的React组件,然后返回一个新的React组件。<br>第一次执行时传入的参数是mapStateToProps, mapDispatchToProps, mergeProps, options这四个。首先会对这几个参数进行处理,代码如下:</p>
<pre><code>//决定组件会不会因state改变而更新
const shouldSubscribe = Boolean(mapStateToProps)
//如果不传递这个参数使用默认state => ({})
const mapState = mapStateToProps || defaultMapStateToProps
//mapDispatchToProps的处理,最后的情况实际是使用bindActionCreators处理
let mapDispatch
if (typeof mapDispatchToProps === 'function') {
mapDispatch = mapDispatchToProps
} else if (!mapDispatchToProps) {
mapDispatch = defaultMapDispatchToProps
} else {
mapDispatch = wrapActionCreators(mapDispatchToProps)
}
//不传递就使用默认值
const finalMergeProps = mergeProps || defaultMergeProps
const { pure = true, withRef = false } = options
</code></pre>
<p>第二次执行函数接收的参数是个React组件:WrappedComponent,之后返回一个新的React组件Connect。</p>
<pre><code>return hoistStatics(Connect, WrappedComponent)
</code></pre>
<p>把WrappedComponent的非React属性拷贝到Connect上。下面详细说下Connect。</p>
<h2>Connect</h2>
<p>一个React组件</p>
<pre><code>Connect.contextTypes = {
store: storeShape
}
</code></pre>
<p>所以它可以从context中获取Provider放的store。</p>
<h4>constructor</h4>
<p>在constructor中:</p>
<pre><code>//获取store
this.store = props.store || context.store
const storeState = this.store.getState()
//把store的state作为组件的state,后面通过更新state更新组件
this.state = { storeState }
//清除组件的状态,内部是一系列的标示还原
this.clearCache()
</code></pre>
<h4>render</h4>
<p>然后是render方法,在挂载的时候,会经过一系列的判断和计算,比如使用mapState计算nextStateProps,并和this.stateProps对比是否发生改变,如果发生改变:</p>
<pre><code>nextDispatchProps = mapState(store.getState(), [props])
this.stateProps = nextDispatchProps
</code></pre>
<p>使用mapDispatch计算nextDispatchProps,并和this.dispatchProps对比是否发生改变,如果发生改变:</p>
<pre><code>nextMergedProps = mapDispatch(dispatch, [props])
this.dispatchProps = nextMergedProps
</code></pre>
<p>如果上面的两个对比有一个发生改变,就会继续使用finalMergeProps来计算最终的数据合并结果nextMergedProps,并和this.mergedProps对比是否发生改变,如果发生改变:</p>
<pre><code>nextMergedProps = finalMergeProps(this.stateProps, this.dispatchProps, this.props)
this.mergedProps = nextMergedProps
</code></pre>
<p>如果上面的对比确定发生改变</p>
<pre><code>if (withRef) {
this.renderedElement = createElement(WrappedComponent, {
...this.mergedProps,
ref: 'wrappedInstance'
})
} else {
this.renderedElement = createElement(WrappedComponent,
this.mergedProps
)
}
return this.renderedElement
</code></pre>
<p>如果withRef等于true就会增加ref属性,然后可以通过getWrappedInstance方法获取DOM。如果前面说的这些对比的结果都是false,就会直接返回this.renderedElement,组件不进行任何更新。当然组件挂载的时候前面的对比都会返回true。</p>
<h4>componentDidMount</h4>
<p>它内部的关键代码是:</p>
<pre><code>if (shouldSubscribe && !this.unsubscribe) {
this.unsubscribe = this.store.subscribe(this.handleChange.bind(this))
this.handleChange()
}
</code></pre>
<p>在不指定mapStateToProps的时候shouldSubscribe等于false,这就意味着React-Redux的源码剖析到此结束,谢谢观看!当然如果指定了mapStateToProps剖析就还得继续。看到代码没有,竟然使用subscribe,意味着只要执行dispatch,handleChange就会执行。至此组件已经挂载完毕,后面的代码执行需要有外界因素了,比如父组件传递新的props、执行dispatch。</p>
<h4>componentWillReceiveProps</h4>
<p>组件还实现了componentWillReceiveProps这个React生命周期中的方法:</p>
<pre><code>componentWillReceiveProps(nextProps) {
if (!pure || !shallowEqual(nextProps, this.props)) {
this.haveOwnPropsChanged = true
}
}
</code></pre>
<p>看到pure的重要性了吧,如果pure被设置为false就意味着不管属性是否浅相等this.haveOwnPropsChanged总是会被设置为true,而这会导致后面一系列的为了更新而进行的计算,所以pure为true是可以给你的性能带来帮助的,不过它默认就是true。这里设置this.haveOwnPropsChanged等于true是给通过直接通过父组件传递props更新组件带来可能,当然需要配合mapStateToProps, mapDispatchToProps, mergeProps这三个函数,如果它们都没有利用ownProps,最终组件还是不能通过这种方式更新。</p>
<h4>handleChange</h4>
<p>下面假定触发了一次dispatch,这个时候handleChange就会执行,如果state没有发生改变,并且pure为true,就什么都不做直接返回,pure又在性能上立功了。如果state发生了改变会再做一些计算对比,比如计算this.stateProps。最后是在要更新的时候会:</p>
<pre><code>this.hasStoreStateChanged = true
this.setState({ storeState })
</code></pre>
<p>调用setState来触发组件更新。这里其实意味着只要store的state发生改变,所有的mapStateToProps、 mapDispatchToProps、mergeProps都会执行。</p>
<h4>shouldComponentUpdate</h4>
<p>这个时候会调用它内部实现的shouldComponentUpdate,用来提高性能。</p>
<pre><code>shouldComponentUpdate() {
return !pure || this.haveOwnPropsChanged || this.hasStoreStateChanged
}
</code></pre>
<p>但是怎么感觉这个并没有什么用呢?可能是我理解不深,因为无论是父组件更新props还是state改变这里总是返回true,而不管改变的是不是这个组件关心的数据。没办法又进入了render方法。</p>
<p>好了,源码剖析到此结束,谢谢观看!</p>
Redux源码剖析
https://segmentfault.com/a/1190000006183994
2016-08-05T17:06:09+08:00
2016-08-05T17:06:09+08:00
hepeguo
https://segmentfault.com/u/hepeguo
3
<p>前面写了<a href="https://segmentfault.com/a/1190000006100489">《React组件性能优化》</a><a href="https://segmentfault.com/a/1190000006110864">《Redux性能优化》</a><a href="https://segmentfault.com/a/1190000006120707">《React-Redux性能优化》</a>,但是都没有从这些框架的实现上讲为什么?这次就从源码上来分析一下这些框架的实现原理,以更深入的理解这些框架,并更好的使用它们。</p>
<p>Redux的api很简单,下面一个一个的分析。</p>
<h2>createStore</h2>
<p>首先说下它的三个参数reducer、preloadedState、enhancer。reducer是唯一必传的参数,它很重要,因为它决定了整个state。preloadedState就是state的初始值。第三个参数不是特别常用,它是个函数,如果它存在的情况下,会执行下面的语句:</p>
<pre><code>enhancer(createStore)(reducer, preloadedState)
</code></pre>
<p>很显然enhancer和middleware作用很像,用来增强store。</p>
<p>createStore内部维护了currentReducer(当前的reducer)和currentState(当前的state),初始化的时候:</p>
<pre><code>currentReducer = reducer
currentState = preloadedState
</code></pre>
<p>这两个变量很重要,因为很多操作都和它们相关。</p>
<h4>subscribe</h4>
<p>在createStore内部维护了两个数组currentListeners、nextListeners。nextListeners的存在是为了避免在listeners执行过程中,listeners发生改变,导致错误。listeners的添加或删除都是对nextListeners进行操作的。</p>
<pre><code>nextListeners = currentListeners.slice()
</code></pre>
<p>保证nextListeners的修改不会影响currentListeners。</p>
<p>subscribe(listener)的返回值是个函数,执行这个函数就会unsubscribe(listener),取消监听。</p>
<h4>dispatch</h4>
<p>很重要,因为只能通过它修改state,它只接收一个参数<code>action</code>,action必须是简单对象,而且必须有type属性。在dispatch内部关键代码是:</p>
<pre><code>currentState = currentReducer(currentState, action)
var listeners = currentListeners = nextListeners
for (var i = 0; i < listeners.length; i++) {
listeners[i]()
}
</code></pre>
<p>第一行用来调用reducer,并将执行的结果赋给currentState。这样currentState中的数据就总是最新的,即reducer处理完action之后返回的数据。没有条件,执行dispatch后reducer总会执行。</p>
<p>后面3行代码是用来调用subscribe传进来的listeners,按顺序执行它们,没有任何条件判断,也就是说只要执行dispatch,所有的listeners都会执行,不管state有没有发生改变,而且listeners执行的时候是没参数的。</p>
<p>Redux内部有个变量</p>
<pre><code>ActionTypes = {
INIT: '@@redux/INIT'
}
</code></pre>
<p>在创建store(即调用<code>createStore(reducer, preloadedState, enhancer)</code>)的时候会执行</p>
<pre><code>dispatch({ type: ActionTypes.INIT })
</code></pre>
<p>所以reducer和listeners在store创建的时候都会被执行一遍,listeners没有什么特别要关注的。reducer执行必须关注,因为它的执行结果影响你的数据。比如createStore的时候你没有传入preloadedState,在reducer内的state有默认参数,正常情况下你的reducer会使用default分支处理这个action,而且一般default分支会直接返回state,所以这种情况下store创建完后,使用<code>getState()</code>获取的值就是默认参数组成的state。</p>
<h4>getState</h4>
<p>获取store中的state,很简单,主要代码:</p>
<pre><code>return currentState
</code></pre>
<p>在没有dispatch正在执行的情况下,直接返回前面说很重要的currentState。</p>
<h4>replaceReducer</h4>
<p>参数是nextReducer,也很简单,关键代码:</p>
<pre><code>currentReducer = nextReducer
dispatch({ type: ActionTypes.INIT })
</code></pre>
<p>直接拿nextReducer替换掉前面说很重要的currentReducer,后面再执行dispatch,action就会被nextReducer处理,处理的结果赋值给currentState。替换之后会执行一遍初始化action。</p>
<h2>combineReducers</h2>
<p>代码虽然看起来很长,但是大多都是用来处理校验reducer的。它接收的参数reducers是个对象,对象的value不能是undefined,必须是function。符合这个标准的reducer会被放入finalReducers中。<br>然后再对finalReducers进行校验,reducer必须有default处理,不能处理Redux内部的action type,比如<code>@@redux/INIT</code>。然后返回一个函数<code>combination(state = {}, action)</code>,它也是一个reducer,可以被再次和其他reducer combine。一般combination等同于currentReducer,它的返回结果会赋给state,combination的关键代码如下:</p>
<pre><code>var finalReducerKeys = Object.keys(finalReducers)
var hasChanged = false
var nextState = {}
for (var i = 0; i < finalReducerKeys.length; i++) {
var key = finalReducerKeys[i]
var reducer = finalReducers[key]
var previousStateForKey = state[key]
var nextStateForKey = reducer(previousStateForKey, action)
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
</code></pre>
<p>中间省略了reducer返回结果的校验。reducer就是通过这种方式从state中拿到对应的state,然后把返回的数据组装到state的对应位置,很巧妙!</p>
<h2>bindActionCreators</h2>
<p>它接收两个参数actionCreators和dispatch。如果actionCreators是函数,就直接返回:</p>
<pre><code>(...args) => dispatch(actionCreator(...args))
</code></pre>
<p>直接给这个返回的函数传actionCreator的参数就可以直接触发dispatch,这也是bindActionCreators的目的,简化操作,弱化dispatch的存在感。<br>如果actionCreators是个对象会进行另外的操作,返回一个对象,下面是关键代码。</p>
<pre><code>var keys = Object.keys(actionCreators)
var boundActionCreators = {}
for (var i = 0; i < keys.length; i++) {
var key = keys[i]
var actionCreator = actionCreators[key]
boundActionCreators[key] = (...args) => dispatch(actionCreator(...args))
}
return boundActionCreators
</code></pre>
<p>其实里面的处理和单个function的处理是一样的。</p>
<h2>applyMiddleware</h2>
<p>一个可以被执行三次的柯里化函数,代码虽然简单,但是给Redux带来却是无限可能。它的第一次执行的时候参数是一系列的middlewares,第二次执行参数是createStore,返回的是一个和createStore接收同样参数的函数,再次被执行的话,就会返回dispatch强化之后的store。关键代码如下:</p>
<pre><code>var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
const last = chain[funcs.length - 1]
const rest = chain.slice(0, -1)
dispatch = rest.reduceRight((composed, f) => f(composed), last(store.dispatch))
</code></pre>
<p>这里只写了多个middleware的情况,单个middlewares更简单。代码很简单,逻辑也很简单,作用很大。比如使用thunk可以做异步action。</p>
<p>以上基本是Redux的全部关键代码剖析,Redux简单而强大。</p>
index作为key是反模式
https://segmentfault.com/a/1190000006152449
2016-08-03T09:46:47+08:00
2016-08-03T09:46:47+08:00
hepeguo
https://segmentfault.com/u/hepeguo
0
<p><a href="https://link.segmentfault.com/?enc=33xWqVLz3Kjpc7j2lSjXuQ%3D%3D.jf1pW%2BdqQ0taE1ROu8AifbFG2cu7%2BsSyrn5nwGgtuBJ9Vb3j2vw5OUMRjVwz%2Fsixuh5Il46K%2BDmpOiCif3vXNQSzh%2F2bl7bh7QOmeyFaYd0dTUjiO6tjZnW0w1kXxjCt" rel="nofollow">原文:Index as a key is an anti-pattern</a></p>
<p>我曾多次看到开发者在渲染列表的时候把列表项的index作为它的key。</p>
<pre><code>{todos.map((todo, index) =>
<Todo {...todo}
key={index} />
)}
</code></pre>
<p>这看起来很优雅,而且能够解决警告(这才是“真”问题,对吧?)的问题,这样做有什么危险呢?</p>
<blockquote><p>It may break your application and display wrong data!</p></blockquote>
<p>让我来解释,key是React唯一用来确定DOM元素的东西,如果你想列表增加一项或移除中间的某项,会发生什么事?如果key和之前一个一样React就会假定这个DOM元素和之前对应的组件是一个,但是它们可能并不是同一个了。</p>
<p>为了证明潜在的危险我创建了一个<a href="https://link.segmentfault.com/?enc=ErEWvWshqogdeoCXBTeAIQ%3D%3D.%2BOZnt9T1X1%2FtQUBs8U8dngIkol%2FPlY9bC2UMYxDB28M%3D" rel="nofollow">简单示例</a></p>
<p><img src="/img/bVzYGU" alt="图片描述" title="图片描述"></p>
<p>这表明,如果不指定key的时候React会使用index,因为这是那个时候最好的猜测,而且它会警告你说这不是最优解(它通过令人困惑的语句表述这个意思)。如果你主动提供了它,React就认为你知道你在干什么。记住这个示例,它能产生不可预测的结果。</p>
<h2>比较好</h2>
<p>像这样的应该都有一个永久的唯一的属性,当列表项创建的时候它是最合适被设置为key的,显然我是在说id,我们可以用下面的方式使用它:</p>
<pre><code>{todos.map((todo) =>
<Todo {...todo}
key={todo.id} />
)}
</code></pre>
<p>另外的实现方式是把编号递增移动到抽象方法中,使用一个全局的index来确保任何两个列表项的id不同。</p>
<pre><code>todoCounter = 1;
function createNewTodo(text) {
return {
completed: false,
id: todoCounter++,
text
}
}
</code></pre>
<h2>更好</h2>
<p>一个产品级别的方案应该是一个更健壮的方法,能够处理分散创建列表项。因此,我推荐使用<a href="https://link.segmentfault.com/?enc=RFHEawv44LDK%2FHityV4Cew%3D%3D.LqbcRmP%2Fj3JlLPIYi0yP0wx7IoHXhTNsTT7GceReKpf19poDmvEnOnDmoOKyYLQ8" rel="nofollow">shortid</a>。它能够快速生成“短 无序 url友好 唯一”的id,代码像下面这样:</p>
<pre><code>var shortid = require('shortid');
function createNewTodo(text) {
return {
completed: false,
id: shortid.generate(),
text
}
}
</code></pre>
<p>TL;DR:为每个列表项生成一个唯一的id,并在渲染列表的时候使用它作为key。</p>
<h2>参考和相关文章</h2>
<ul>
<li><p><a href="https://link.segmentfault.com/?enc=hu0kCTTOcST3SvoMiz%2BKiA%3D%3D.uflwMWhn4G8xz4gQoo6YTwqIh2NWRmzjI5ArA5659YflzZrvcjk%2F5t%2BPW5Ku5CO7P92p%2BURfYetS9VjwVpA0noQREGSPpJVKw2HX8Zi1z7o%3D" rel="nofollow">Dynamic Children</a> and <a href="https://link.segmentfault.com/?enc=5J4HZyel1XYjQ7KoJH%2BWBQ%3D%3D.YU8zz6Wr%2BxhIx03B5USKyTwTdbLOxSGmPMKe5nURvRrvf%2BqdSSxXxL3sIgnkDXWkwRvJTEe7vlkdzzxHFYW%2Bdw%3D%3D" rel="nofollow">Keyed Fragments</a> in React Docs</p></li>
<li><p><a href="https://link.segmentfault.com/?enc=OVhufnUzjTFn8ZIyJydjhw%3D%3D.8IaF5nqYU5svrKjk2e5fGXDOhuxR5D1sP176MBNsNwRAgSdwZjOKFzYfiKSBL0KpIt2VWzLLe6B6H6rj85ZL4rUEDYbVvzaL4lecxXB8zak%3D" rel="nofollow">Explanation from Paul O’Shannessy</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=jqR3QV9NweXFj3unssPZaA%3D%3D.d0uxx6QecX5Q7301w9zNrG%2BAB8uI98l7DzYoyncXUl1KTveDXjBJiE6H%2BmES0TTANUBapklCa9e0RiBLpn2DZSKiOzUrv1NtzKvAzyZBbqg%3D" rel="nofollow">The importance of component keys in React.js</a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=0HT13EjT%2FMRvz31Y8iQ13Q%3D%3D.%2B%2BI5uq3ux1Aird%2BDCT0yDv9G%2BGzLy20YJCGPZVU254Iq2erEo3vsasHs4mBCoXmFeVJBvm%2FJkThs79xT2q%2Bj46CqMGmV3PRFYiScN7DXhqTVyJOdvTI09tgBZr5DgcKb" rel="nofollow">React.js and Dynamic Children — Why the Keys are Important</a></p></li>
</ul>
[译] React 组件中绑定回调
https://segmentfault.com/a/1190000006133727
2016-08-01T13:40:32+08:00
2016-08-01T13:40:32+08:00
hepeguo
https://segmentfault.com/u/hepeguo
9
<p><a href="https://link.segmentfault.com/?enc=q2x1LVFVurSqIswGOwI%2Bvw%3D%3D.CPyn%2BqubCPTC2jc5I7suCoIpossSKnI6sUVeYMHyHoW%2FLnZXjL%2Bx3DBTrs6%2FnufZEgD9oVZVFkJm7u7AwAm4zfK3Hm3Df2Bk%2B25lZUiwQe%2FAPOHB3HxR%2B%2F30TVPBwlBl" rel="nofollow">原文:Binding callbacks in React components</a></p>
<p>在组件中给事件绑定处理函数是很常见的,比如说每当用户点击一个button的时候使用<code>console.log</code>打印一些东西。</p>
<pre><code>class DankButton extends React.Component {
render() {
return <button onClick={this.handleClick}>Click me!</button>
}
handleClick() {
console.log(`such knowledge`)
}
}
</code></pre>
<p>很好,这段代码会满足你的需求,那现在如果我想在<code>handleClick()</code>内调用另外一个方法,比如<code>logPhrase()</code></p>
<pre><code>class DankButton extends React.Component {
render() {
return <button onClick={this.handleClick}>Click me!</button>
}
handleClick() {
this.logPhrase()
}
logPhrase() {
console.log('such gnawledge')
}
}
</code></pre>
<p>这样竟然不行,会得到如下的错误提醒</p>
<pre><code>TypeError: this.logPhrase is not a function at handleClick (file.js:36:12)
</code></pre>
<p>当我们把<code>handleClick</code>绑定到 <code>onClick</code>的时候我们传递的是一个函数的引用,真正调用<code>handleClick</code>的是事件处理系统。因此<code>handleClick</code> 的<code>this</code>上下文和我门想象的<code>this.logPhrase()</code>是不一样的。</p>
<p>这里有一些方法可以让<code>this</code>指向DankButton组件。</p>
<h4>不好的方案 1:箭头函数</h4>
<p>箭头函数是在ES6中引入的,是一个写匿名函数比较简洁的方式,它不仅仅是包装匿名函数的语法糖,箭头函数没有自己的上下问,它会使用被定义的时候的<code>this</code>作为上下文,我们可以利用这个特性,给<code>onClick</code>绑定一个箭头函数。</p>
<pre><code>class DankButton extends React.Component {
render() {
// Bad Solution: An arrow function!
return <button onClick={() => this.handleClick()}>Click me!</button>
}
handleClick() {
this.logPhrase()
}
logPhrase() {
console.log('such gnawledge')
}
}
</code></pre>
<p>然而,我并不推荐这种解决方式,因为箭头函数定义在<code>render</code>内部,组件每次重新渲染都会创建一个新的箭头函数,在React中渲染是很快捷的,所以重新渲染会经常发生,这就意味着前面渲染中产生的函数会堆在内存中,强制垃圾回收机制清空它们,这是很花费性能的。</p>
<h4>不好的方案 2:<code>this.handleClick.bind(this)</code>
</h4>
<p>另外一个解决这个问题的方案是,把回调绑定到正确的上下问<code>this</code></p>
<pre><code>class DankButton extends React.Component {
render() {
// Bad Solution: Bind that callback!
return <button onClick={this.handleClick.bind(this)}>Click me!</button>
}
handleClick() {
this.logPhrase()
}
logPhrase() {
console.log('such gnawledge')
}
}
</code></pre>
<p>这个方案和箭头函数有同样的问题,在每次<code>render</code>的时候都会创建一个新的函数,但是为什么没有使用匿名函数也会这样呢,下面就是答案。</p>
<pre><code>function test() {}
const testCopy = test
const boundTest = test.bind(this)
console.log(testCopy === test) // true
console.log(boundTest === test) // false
</code></pre>
<p><code>.bind</code>并不修改原有函数,它只会返回一个指定执行上下文的新函数(boundTest和test并不相等),因此垃圾回收系统仍然需要回收你之前绑定的回调。</p>
<h4>好的方案:在构造函数(constructor)中bind handleClick</h4>
<p>仍然使用 <code>.bind</code> ,现在我们只要绕过每次渲染都要生成新的函数的问题就可以了。我们可以通过只在构造函数中绑定回调的上下问来解决这个问题,因为构造函数只会调用一次,而不是每次渲染都调用。这意味着我们没有生成一堆函数然后让垃圾回收系统清除它们。</p>
<pre><code>class DankButton extends React.Component {
constructor() {
super()
// Good Solution: Bind it in here!
this.handleClick = this.handleClick.bind(this)
}
render() {
return <button onClick={this.handleClick}>Click me!</button>
}
handleClick() {
this.logPhrase()
}
logPhrase() {
console.log('such gnawledge')
}
}
</code></pre>
<p>很好,现在我们的函数被绑定到正确的上下文,而且不会在每次渲染的时候创建新的函数。</p>
<p>如果你使用的是<code>React.createClass</code>而不是ES6的classes,你就不会碰到这个问题,<code>createClass</code>生成的组件会把它们的方法自动绑定到组件的<code>this</code>,甚至是你传递给事件回调的函数。</p>
React-Redux性能优化
https://segmentfault.com/a/1190000006120707
2016-07-30T15:38:13+08:00
2016-07-30T15:38:13+08:00
hepeguo
https://segmentfault.com/u/hepeguo
12
<p>前面写了两篇文章<a href="https://segmentfault.com/a/1190000006100489">《React组件性能优化》</a><a href="https://segmentfault.com/a/1190000006110864">《Redux性能优化》</a>,分别针对React和Redux在使用上的性能优化给了一些建议。但是React和Redux一起使用还需要一个工具<a href="https://link.segmentfault.com/?enc=NT%2BdkfGhE9eMbd23FDAImA%3D%3D.pfNG0ZPQ1FFhkoLlAllgF8J6b4hjVh%2B8x3w3utG2n77UC%2BHg1Dj2vbpzpepdPkaA" rel="nofollow">React-Redux</a>,这一篇就说一下React-Redux在使用上的一些性能优化建议。</p>
<blockquote><p>React-Redux是官方的React和Redux链接工具</p></blockquote>
<h2>Provider</h2>
<p>一个很简单的React组件,它主要的作用是把store放到context中,connect就可以获取store,使用store的方法,比如dispatch。其实没有被connect的组件通过声明contextTypes属性也是可以获取store,使用store的方法的,但是这个时候,如果使用dispatch修改了store的state,React-Redux并不能把修改后的state作为props给React组件,可能会导致UI和数据不同步,所以这个时候一定要清楚自己在做什么。</p>
<h2>connect</h2>
<p>一个柯里化函数,函数将被调用两次。第一次是设置参数,第二次是组件与 Redux store 连接。connect 函数不会修改传入的 React 组件,返回的是一个新的已与 Redux store 连接的组件,而且你应该使用这个新组件。connect的使用方式是<code>connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])(Component)</code>,第一次调用的时候4个参数都是可选。</p>
<ol>
<li><p>mapStateToProps在store发生改变的时候才会调用,然后把返回的结果作为组件的props。</p></li>
<li><p>mapDispatchToProps主要作用是弱化Redux在React组件中存在感,让在组件内部改变store的操作感觉就像是调用一个通过props传递进来的函数一样。一般会配合Redux的bindActionCreators使用。如果不指定这个函数,dispatch会注入到你的组件props中。</p></li>
<li><p>mergeProps用来指定mapStateToProps、mapDispatchToProps、ownProps(组件自身属性)的合并规则,合并的结果作为组件的props。如果要指定这个函数,建议不要太复杂。</p></li>
<li><p>options里面主要关注pure,如果你的组件仅依赖props和Redux的state,pure一定要为true,这样能够避免不必要的更新。</p></li>
<li><p>Component就是要被连接的React组件,组件可以是任意的,不一定是AppRoot。一般会是需要更新store、或者是依赖store中state的最小组件。因为被连接的组件在Redux的state改变后会更新,大范围的更新对性能不友好,而且其中有些组件可能是没必要更新也会更新,所以要尽量拆分、细化,connect仅仅要更新store或依赖store的state的最小组件。</p></li>
</ol>
<h2>Reselect</h2>
<p>mapStateToProps也被叫做selector,在store发生变化的时候就会被调用,而不管是不是selector关心的数据发生改变它都会被调用,所以如果selector计算量非常大,每次更新都重新计算可能会带来性能问题。<a href="https://link.segmentfault.com/?enc=ubErfjaGT1j%2FDhVy9UNFaA%3D%3D.UkoV%2Fthp7bg%2FT68WsY1yzYTjk17voW0JlQFz3RBFL%2Fkce8djGTjRKEe5h3Hu%2Bb%2Bh" rel="nofollow">Reselect</a>能帮你省去这些没必要的重新计算。<br>Reselect 提供 createSelector 函数来创建可记忆的 selector。createSelector 接收一个 input-selectors 数组和一个转换函数作为参数。如果 state tree 的改变会引起 input-selector 值变化,那么 selector 会调用转换函数,传入 input-selectors 作为参数,并返回结果。如果 input-selectors 的值和前一次的一样,它将会直接返回前一次计算的数据,而不会再调用一次转换函数。这样就可以避免不必要的计算,为性能带来提升。</p>
<h2>总结</h2>
<p><strong><em>谨慎使用context中的store</em></strong></p>
<p><strong><em>被connect组件更新的时候影响范围尽量小,避免不必要更新</em></strong></p>
<p><strong><em>使用Resselect避免不必要的selector计算</em></strong></p>
<h2>参考</h2>
<p><a href="https://link.segmentfault.com/?enc=PEUYOKCuzTHotWTATAvyvA%3D%3D.1QbRG87r4QyrTSQvk5dIkHij9VSMqzVVxOJ2bbVytoKYA7urmKBnasabN0rwzDO5" rel="nofollow">React-Redux</a></p>
<p><a href="https://link.segmentfault.com/?enc=gKcXOo9jzgtCetkhbu84Kw%3D%3D.7usIRWDR9LBqe4JEgxb2h%2B%2B3S5eW%2Fub8aloH1uCXWY6T1IjHOirfrRWbmxclc87y" rel="nofollow">Reselect</a></p>
Redux性能优化
https://segmentfault.com/a/1190000006110864
2016-07-29T14:04:32+08:00
2016-07-29T14:04:32+08:00
hepeguo
https://segmentfault.com/u/hepeguo
6
<blockquote><p>Redux is a predictable state container for JavaScript apps.</p></blockquote>
<p>简单的说就是Redux能够管理js app的状态,状态是由数据维护的,也就是说Redux是管理数据的。那么Redux是怎么管理数据的呢?</p>
<h2>store</h2>
<p>一个Redux app中只有一个store,所有的数据都在这个store中,而通过<code>createStore(reducer, [initState])</code>,initState是可选参数,也就是说决定store的是reducer,reducer决定store中存放什么样的数据、处理什么样的数据、处理数据的方式。</p>
<p>store在创建的时候内部会执行<code>dispatch({ type: ActionTypes.INIT })</code>,用来初始化整个store的数据结构,同时获取reducer中的默认数据。之所以能拿到全部的数据结构,是因为在<code>dispatch({ type: ActionTypes.INIT })</code>的时候,所有的reducer都会执行,并根据reducer的combine结构生成数据。在Redux内,每执行一次dispatch,所有的reducer都会执行。</p>
<h2>reducer</h2>
<p>所以这里就有个问题,如果reducer比较多的时候,性能是不是就会出问题。大家可能会想到减少reducer,这也是一个办法,但是如果刻意减少reducer的话,可能会导致某个reducer内过于复杂,后期难以维护。</p>
<pre><code>const dispatcher = (state={}, action) {
switch (action.type) {
case "TYPE1":
case "TYPE2":
return reducerFirst(state, action)
case "TYPE3":
return reducerSecond(state, action)
default:
return state
}
}
</code></pre>
<p>通过这样简单的过滤就可以实现只让对action感兴趣的reducer执行,简单,方便,快捷。</p>
<h2>action</h2>
<p>action是个object,它必须有个type属性,一般是个常量,来标示action类型,方便reducer处理。除去type剩下的一般就是要处理的数据,如果数据比较简单可以直接使用<code>Object.assign({}, state, action.data)</code>来处理,这种方法仅适合简单数据。</p>
<p>如果是复杂数据,有较深的层级,就要使用深度拷贝,这时候你可以使用<a href="https://link.segmentfault.com/?enc=SMI6X5C%2FvAgdoYu9pOUOaw%3D%3D.yh1E6F2mIQJYdBkrwa4JjN7vShyJ5GlaSsV0gmLyRvk%3D" rel="nofollow">lodash</a>的<a href="https://link.segmentfault.com/?enc=qOGZmouEt4U8Y8Nr5zDq9g%3D%3D.0dkcgpXd3C2v4sNYdCyZcptVJnuCdAFygo0l2Doev5qeJPxQwCS1up86o6%2By1jwI" rel="nofollow">cloneDeep</a>进行深度拷贝。</p>
<pre><code>var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// ➜ false
</code></pre>
<h2>immutable</h2>
<p>但是复杂数据的深度拷贝是很花性能的,这个时候就可以使用<strong><a href="https://link.segmentfault.com/?enc=KKt5TnQzg%2FEKx4%2Fx8feTIQ%3D%3D.uUgOnd0mJVNtP86THbsCp5mR8mC3YToCj5PSBuJFgcBjvJoLZQgCin5MgwFTdfXr" rel="nofollow">immutable.js</a></strong>来解决这个问题。immutable不可改变的意思,在Object-C中是原生提供这种数据类型的。对immutable.js生成的数据进行操作之后总是返回一个新的数据,原有的数据不会改变。</p>
<pre><code>var Immutable = require('immutable');
var map1 = Immutable.Map({a:1, b:2, c:3});
var map2 = map1.set('b', 50);
map1.get('b'); // 2
map2.get('b'); // 50
</code></pre>
<p>immutable.js通过结构共享来解决的数据拷贝时的性能问题,数据被set的时候,immutable.js会只clone它的父级别以上的部分,其他保持不变,这样大家可以共享同样的部分,可以大大提高性能。如图</p>
<p><img src="/img/remote/1460000006770743" alt="immutable-js-share.gif" title="immutable-js-share.gif"></p>
<h2>subscribe</h2>
<p>每次执行dispatch,通过subscribe注册的listener都会被执行,如果listener较多,或者listener内部处理比较复杂也会对性能产生影响, 而且在listener内部很难区分哪个数据被改变了,如果在listener内部继续dispatch,而没有处理好执行dispatch条件的话,很容易造成dispatch->listener->dispatch死循环。所以建议通过middleware的方式来处理,而且在middleware内部可以知道action是什么,就可以只处理关心的action。</p>
<h2>总结</h2>
<p><strong><em>预分配reducer、精简reducer</em></strong></p>
<p><strong><em>精简action数据或使用immutable.js</em></strong></p>
<p><strong><em>使用middleware处理特殊需求(reducer中不方便处理的需求)</em></strong></p>
<h2>参考</h2>
<p><a href="https://link.segmentfault.com/?enc=L3Ue%2Ft8s8J1DMhY90rtO2A%3D%3D.8Kg%2F9rUWDhX0elvzZl46AZa%2Fqw6frr2VzbytQzL67AA%3D" rel="nofollow">Redux</a></p>
<p><a href="https://link.segmentfault.com/?enc=W8dbJI8CCbD9ffhsCnlwzg%3D%3D.8%2FgUcTIJRvb89lU4njb71Hx0FBa%2By0UvYa6OPJTUKuidvlNPZvawxa09WhnHlQcu" rel="nofollow">immutable.js</a></p>
<p><a href="https://link.segmentfault.com/?enc=FHWiEYDRu1ZeQyWhmzZoEQ%3D%3D.tCNhzQnOZGnCctqfX%2FfONpMqnRpl2kFHP6sZDGsDogHXRiw8Ldkt%2B3j3CANsETYj" rel="nofollow">cloneDeep</a></p>
React组件性能优化
https://segmentfault.com/a/1190000006100489
2016-07-28T14:25:10+08:00
2016-07-28T14:25:10+08:00
hepeguo
https://segmentfault.com/u/hepeguo
6
<blockquote><p>React: 一个用于构建用户界面的JAVASCRIPT库.</p></blockquote>
<p>React仅仅专注于UI层;它使用虚拟DOM技术,以保证它UI的高速渲染;它使用单向数据流,因此它数据绑定更加简单;那么它内部是如何保持简单高效的UI渲染呢?</p>
<p>React不直接操作DOM,它在内存中维护一个快速响应的DOM描述,render方法返回一个DOM的描述,React能够计算出两个DOM描述的差异,然后更新浏览器中的DOM。</p>
<p>就是说React在接收到props或者state更新时,React就会通过前面的方式更新UI。就算重新使用<code>ReactDOM.render(<Component />, mountNode)</code>,它也只是当作props更新,而不是重新挂载整个组件。所以React整个UI渲染是比较快的。</p>
<h5>但是,这里面有几个问题</h5>
<p><strong>1. 如果更新的props和旧的一样,这个时候很明显UI不会变化,但是React还是要进行虚拟DOM的diff,这个diff就是多余的性能损耗,而且在DOM结构比较复杂的情况,整个diff会花费较长的时间。</strong></p>
<p><strong>2. 既然React总是要进行虚拟DOM的diff,那么它的diff规则是什么?怎么利用?</strong></p>
<h2>PureRenderMixin</h2>
<p>针对第一个问题React给我们提供了 PureRenderMixin。<br>如果React组件是纯函数的,就是给组件相同的props和state组件就会展现同样的UI,可以使用这个Minxin来优化React组件的性能。</p>
<pre><code>var PureRenderMixin = require('react-addons-pure-render-mixin');
React.createClass({
mixins: [PureRenderMixin],
render: function() {
return <div className={this.props.className}>foo</div>;
}
});
</code></pre>
<p>ES6中的用法是</p>
<pre><code>
import PureRenderMixin from 'react-addons-pure-render-mixin';
class FooComponent extends React.Component {
constructor(props) {
super(props);
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
}
render() {
return <div className={this.props.className}>foo</div>;
}
}</code></pre>
<p>PureRenderMixin的原理就是它实现了shouldComponentUpdate,在shouldComponentUpdate内它比较当前的props、state和接下来的props、state,当两者相等的时候返回false,这样组件就不会进行虚拟DOM的diff。</p>
<p><strong>这里需要注意:</strong><br><em>PureRenderMixin内进行的仅仅是浅比较对象。如果对象包含了复杂的数据结构,深层次的差异可能会产生误判。仅用于拥有简单props和state的组件。</em></p>
<h2>shouldComponentUpdate</h2>
<p>React虽然提供简单的PureRenderMixin来提升性能,但是如果有更特殊的需求时怎么办?如果组件有复杂的props和state怎么办?这个时候就可使用shouldComponentUpdate来进行更加定制化的性能优化。</p>
<pre><code>boolean shouldComponentUpdate(object nextProps, object nextState) {
return nexprops.id !== this.props.id;
}</code></pre>
<p>在React组件需要更新之前就会调用这个方法,如果这个方法返回false,则组件不更新;如果返回true,则组件更新。在这个方法内部可以通过nextProps和当前props,nextState和当前state的对比决定组件要不要更新。</p>
<p>如果对比的数据结构比较复杂,层次较深,对比的过程也是会有较大性能消耗,又可能得不偿失。<br>这个时候<strong><a href="https://link.segmentfault.com/?enc=Cj%2BD5KHBdfLtUORoS5Xchg%3D%3D.Sej9QZ1iUIpILMxII3OueVXI%2FeA1YDXd%2Be%2F7VBx5dsgU3Wxz2Y0fJikO1lhEXZGo" rel="nofollow">immutable.js</a></strong>就要登场了,也是fb出品,有人说这个框架的意义不亚于React,但是React光芒太强。它能解决复杂数据在deepClone和对比过程中性能损耗。</p>
<p><strong>注意:</strong>shouldComponentUpdate在初始化渲染的时候不会调用,但是在使用forceUpdate方法强制更新的时候也不会调用。</p>
<h2>render</h2>
<p>PureRenderMixin和shouldComponentUpdate的关注点是UI需不需要更新,而render则更多关注虚拟DOM的diff规则了,如何让diff结果最小化、过程最简化是render内优化的关注点。</p>
<p>React在进行虚拟DOM diff的时候假设:</p>
<p><strong>1、拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。</strong><br><strong>2、可以为元素提供一个唯一的标志,该元素在不同的渲染过程中保持不变。</strong></p>
<p><em>DOM结构</em> </p>
<pre><code>renderA: <div />
renderB: <span />
=> [removeNode <div />], [insertNode <span />
</code></pre>
<p><em>DOM属性</em></p>
<pre><code>renderA: <div id="before" />
renderB: <div id="after" />
=> [replaceAttribute id "after"]
</code></pre>
<p><em>之前插入DOM</em></p>
<pre><code>renderA: <div><span>first</span></div>
renderB: <div><span>second</span><span>first</span></div>
=> [replaceAttribute textContent 'second'], [insertNode <span>first</span>]</code></pre>
<p><em>之前插入DOM,有key的情况</em></p>
<pre><code>renderA: <div><span key="first">first</span></div>
renderB: <div><span key="second">second</span><span key="first">first</span></div>
=> [insertNode <span>second</span>]
</code></pre>
<p>由于依赖于两个预判条件,如果这两个条件都没有满足,性能将会大打折扣。</p>
<p><strong>1、diff算法将不会尝试匹配不同组件类的子树。如果发现正在使用的两个组件类输出的 DOM 结构非常相似,你可以把这两个组件类改成一个组件类。</strong></p>
<p><strong>2、如果没有提供稳定的key(例如通过 Math.random() 生成),所有子树将会在每次数据更新中重新渲染。</strong></p>
<h2>总结</h2>
<p>使用PureRenderMixin、shouldComponentUpdate来避免不必要的虚拟DOM diff,在render内部优化虚拟DOM的diff速度,以及让diff结果最小化。</p>
<p>使用immutable.js解决复杂数据diff、clone等问题。</p>
<h2>参考</h2>
<p><strong><a href="https://link.segmentfault.com/?enc=RIzcg%2FKYN0q8ka19Nps%2FJA%3D%3D.2koCjnJ13%2B%2BshWDDRi2B%2B%2F%2FNxaFoXZCbbbL7KpjC7Kls2Uak9VL1DVjHVoBkBN8Z" rel="nofollow">immutable.js</a></strong></p>
<p><strong><a href="https://link.segmentfault.com/?enc=dqAMIYtw7WUoKF5Zqorf3Q%3D%3D.RwGw8R%2FxcqARy3xsPif8jeYJ2tpEvtsb2RMkNx1JLzOTa4ouWG9U36m8f15Ade5CclI0Qh5OtcnCqOzzH1ITZw%3D%3D" rel="nofollow">reconciliation</a></strong></p>
<p><strong><a href="https://link.segmentfault.com/?enc=x%2F4rruSoFWueWm97AOWGgA%3D%3D.ulbIBpiMN8eLfe08ebysYg%2BkzTzAK%2F8r%2FPrsewgOC6AjfMBOiGP3sfoHTDitcFGpqw3%2FR6OWdnE%2BzBDXJ9gfYg%3D%3D" rel="nofollow">pure-render-mixin</a></strong></p>