-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcode_react_redux.html
More file actions
246 lines (212 loc) · 25.1 KB
/
Copy pathcode_react_redux.html
File metadata and controls
246 lines (212 loc) · 25.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
<!doctype html>
<html lang="zh-CN" class="night">
<head>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=4.0, user-scalable=0" name="viewport">
<title>Ede's Blog</title>
<meta name="description" content="Try to be a qualified programmer">
<meta property="og:type" content="website">
<meta property="og:description" content="Try to be a qualified programmer">
<meta property="og:title" content="Ede's Blog">
<meta property="og:site_name" content="Ede's Blog">
<meta property="og:url" content="https://ede.ink">
<meta property="og:image" content="https://edeity.oss-cn-shenzhen.aliyuncs.com/public/edeity_o.png">
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
<link rel="mainfest" href="/mainfest.json">
<link rel="stylesheet" href="/public/css/common.css">
<link rel="stylesheet" href="//at.alicdn.com/t/font_707055_4b9og9sc5lx.css">
<script>
// 是否需要切换黑夜模式(此JS应在CSS加载前执行,否则会造成页面闪烁)
(function toggleNightOrDay() {
var isForceNightTheme = window.location.search.indexOf('theme=night') !== -1
|| window.localStorage.getItem('edeity-theme_theme') === 'night';
var isForceLightTheme = window.location.search.indexOf('theme=light') !== -1
|| window.localStorage.getItem('edeity-theme_theme') === 'light';
var hours = new Date().getHours();
hours = 22;
var html = document.querySelector('html')
if (isForceNightTheme) {
html.classList.add('night');
} else if (isForceLightTheme) {
html.classList.remove('night');
} else {
// 没有强制开关,用时间计算
if (hours < 8 || hours >= 20) {
html.classList.add('night');
} else {
html.classList.remove('night');
}
}
})();
// 切换暗夜模式,需要在CSS渲染前调整,否则重绘时会闪烁
document.addEventListener('DOMContentLoaded', function () {
// 是否需要隐藏左侧导航栏
if (document.querySelector('ol.toc') !== null) {
var bar = document.querySelector('#nav-bar')
bar.style.cssText = 'display: block'
}
});
</script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-M3J9QSEE2Z"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-M3J9QSEE2Z');
</script>
<meta name="generator" content="Hexo 7.3.0"></head>
<body>
<div class="loading"></div>
<div id="switch" data-switch="{"toc":true,"use_pwa":false}"></div>
<header class="fullscreen">
<div class="toolbar">
<i class="iconfont icon-menu"></i>
</div>
<h1>
<a href="/">Ede's Blog</a>
</h1>
<div class="head-link">
<a class="btn waves" href="/">
<span>
<i class="iconfont icon-home">
Home
</i>
</span>
</a>
<a class="btn waves" href="/about/index.html">
<span>
<i class="iconfont icon-me">
About
</i>
</span>
</a>
<a class="btn waves" target="_blank" rel="noopener" href="https://github.com/edeink">
<span>
<i class="iconfont icon-github">
Github
</i>
</span>
</a>
</div>
</header>
<div class="some-link">
<a class="btn" id="light-or-not">
<i class="iconfont icon-light"></i>
</a>
<a style="display: none;" class="btn" id="up-to-top">
<i class="iconfont icon-up"></i>
</a>
</div>
<div id="nav-bar" style="display: none">
<div class="toc">
<ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#context"><span class="toc-number">1.</span> <span class="toc-text">context</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Provider"><span class="toc-number">2.</span> <span class="toc-text">Provider</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#HOC%E9%AB%98%E9%98%B6%E7%BB%84%E4%BB%B6"><span class="toc-number">3.</span> <span class="toc-text">HOC高阶组件</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%90%91%E7%BB%84%E4%BB%B6%E6%B3%A8%E5%85%A5props"><span class="toc-number">3.1.</span> <span class="toc-text">向组件注入props</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#connect"><span class="toc-number">4.</span> <span class="toc-text">connect</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%90%8E%E7%BB%AD%EF%BC%882018-07-10%EF%BC%89"><span class="toc-number">5.</span> <span class="toc-text">后续(2018.07.10)</span></a></li></ol>
</div>
</div>
<main id="content-main" class="section">
<div class="list-item">
<h1 class="post-title">
<a id="react-redux实现原理" class="article-link" href="">
react-redux实现原理
</a>
</h1>
<div class="post-meta">
<time class="meta published">
Mar 14, 2018
</time>
</div>
<!-- 文章声明 -->
<div class="warn">
文章未成熟,请绕路
</div>
<div class="article">
<div class="post-excerpt markdown-body">
<p>易知,<code>react-redux</code>是将<code>react</code>组与redux关联的类库。</p>
<blockquote>
<p>Provider是顶层组件,将store作为上下文提供给全局共享,而Connect组件是局部组件,将某个react组件包装起来,传递指定的state和props给该组件访问</p>
</blockquote>
<p>基本代码如下:</p>
<figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line"><<span class="title class_">Provider</span> store={store}></span><br><span class="line"> <span class="language-xml"><span class="tag"><<span class="name">YourApp</span>/></span></span></span><br><span class="line"></<span class="title class_">Provider</span>></span><br></pre></td></tr></table></figure>
<p>在<code><YourApp/></code>或后面的组件中,会通过<code>connect</code>方法从store中抽取部分状态(一般为该组件需要的最小状态集),注入到该组件的<code>props</code>,代码如下</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">YourApp</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Compoenent</span> {</span><br><span class="line"> <span class="title function_">render</span>(<span class="params"></span>){<span class="keyword">return</span> ...}</span><br><span class="line">}</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">mapStateToProps</span>(<span class="params">state</span>) {</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> <span class="attr">xxx</span>: state.<span class="property">xxx</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title function_">connect</span>(mapStateToProps, ...actions)(<span class="title class_">YourApp</span>)</span><br></pre></td></tr></table></figure>
<p>所以,我比较好奇以下几点:</p>
<ol>
<li>Provider如何提供全局store</li>
<li>如何通过connect向props注入属性</li>
</ol>
<h2 id="context"><a href="#context" class="headerlink" title="context"></a>context</h2><p>在React中,有一种隐藏的神奇东西,名为<code>context</code>,参见<a target="_blank" rel="noopener" href="https://reactjs.org/docs/context.html#how-to-use-context">文档</a>,其作用便是:假若最外层的组件(一般为根节点)实现了<code>getChildContext</code>和<code>childContextTypes</code>,后续的组件都能通过<code>context</code>获得<code>getChildContext</code>中声明并返回的属性,官方示例:</p>
<ul>
<li>父组件实现<code>getChildContext</code>,并返回<code>color</code>属性</li>
</ul>
<figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MessageList</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> {</span><br><span class="line"> <span class="title function_">getChildContext</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">return</span> {<span class="attr">color</span>: <span class="string">"purple"</span>}; <span class="comment">// </span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="title function_">render</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">const</span> children = <span class="variable language_">this</span>.<span class="property">props</span>.<span class="property">messages</span>.<span class="title function_">map</span>(<span class="function">(<span class="params">message</span>) =></span></span><br><span class="line"> <span class="language-xml"><span class="tag"><<span class="name">Message</span> <span class="attr">text</span>=<span class="string">{message.text}</span> /></span></span></span><br><span class="line"> );</span><br><span class="line"> <span class="keyword">return</span> <span class="language-xml"><span class="tag"><<span class="name">div</span>></span>{children}<span class="tag"></<span class="name">div</span>></span></span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="title class_">MessageList</span>.<span class="property">childContextTypes</span> = {</span><br><span class="line"> <span class="attr">color</span>: <span class="title class_">PropTypes</span>.<span class="property">string</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<ul>
<li>所有子组件均能读取<code>this.context.color</code>的值<figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Button</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> {</span><br><span class="line"> <span class="title function_">render</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="language-xml"><span class="tag"><<span class="name">button</span> <span class="attr">style</span>=<span class="string">{{background:</span> <span class="attr">this.context.color</span>}}></span></span></span><br><span class="line"><span class="language-xml"> {this.props.children}</span></span><br><span class="line"><span class="language-xml"> <span class="tag"></<span class="name">button</span>></span></span></span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li>
</ul>
<p>其中<code>color</code>作为<code>MessageList.childContextTypes</code>定义的属性,通过<code>getChildContext</code>被返回去了;只要Button被包含在MessageList内(无视depth层级),都能通过<code>this.context.color</code>获得<code>purple</code>的颜色。</p>
<h2 id="Provider"><a href="#Provider" class="headerlink" title="Provider"></a>Provider</h2><p><code>Provider</code>只需实现<code>context</code>基本接口,即可随心所欲地暴露内部的属性。源码如下:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">createProvider</span>(<span class="params">storeKey = <span class="string">'store'</span>, subKey</span>) {</span><br><span class="line"> <span class="keyword">const</span> subscriptionKey = subKey || <span class="string">`<span class="subst">${storeKey}</span>Subscription`</span></span><br><span class="line"> <span class="keyword">class</span> <span class="title class_">Provider</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Component</span> {</span><br><span class="line"> <span class="title function_">getChildContext</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">return</span> { [storeKey]: <span class="variable language_">this</span>[storeKey], [subscriptionKey]: <span class="literal">null</span> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="title function_">constructor</span>(<span class="params">props, context</span>) {</span><br><span class="line"> <span class="variable language_">super</span>(props, context)</span><br><span class="line"> <span class="variable language_">this</span>[storeKey] = props.<span class="property">store</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="title function_">render</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="title class_">Children</span>.<span class="title function_">only</span>(<span class="variable language_">this</span>.<span class="property">props</span>.<span class="property">children</span>)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="title class_">Provider</span>.<span class="property">propTypes</span> = {</span><br><span class="line"> <span class="attr">store</span>: storeShape.<span class="property">isRequired</span>,</span><br><span class="line"> <span class="attr">children</span>: <span class="title class_">PropTypes</span>.<span class="property">element</span>.<span class="property">isRequired</span>,</span><br><span class="line"> }</span><br><span class="line"> <span class="title class_">Provider</span>.<span class="property">childContextTypes</span> = {</span><br><span class="line"> [storeKey]: storeShape.<span class="property">isRequired</span>,</span><br><span class="line"> [subscriptionKey]: subscriptionShape,</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="title class_">Provider</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>Provide通过实现<code>getChildContext</code>将<code>store</code>作为<code>context</code>传递给所有子孙组件。</p>
<p>注:</p>
<ul>
<li>虽然日常开发中用到context的地方不多,但几个常用的api是可以获得context的,如:<code>constructor(props,context)</code>、<code>componentWillReceiveProps(nextProps, nextContext)</code>、<code>shouldCompoentUpdate(nextProps, nextState, nextContext)</code>、<code>componentWillUpdate(nextProps, ,nextState, nextContent)</code></li>
<li>React组件中,假若<code>state</code>或<code>props</code>没有改变,<code>shouldComponentUpdate</code>会终止子组件的更新。也就是说,只要<code>state</code>或<code>props</code>没有变更,即使<code>context</code>变更了,后续子组件也不会重新渲染。因此,<code>context</code>并不具备实时性和一致性。<strong>context应作为只读属性传递</strong>。(观点源自:<a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/28037267">文档</a>)</li>
</ul>
<h2 id="HOC高阶组件"><a href="#HOC高阶组件" class="headerlink" title="HOC高阶组件"></a>HOC高阶组件</h2><p><code>connect</code>这个方法有什么用?既然能在任何的地方访问到<code>Provider</code>的<code>store</code>,为什么要声明在<code>mapToProps</code>中,又是如何绑定到<code>props上</code>的?</p>
<p>为此,不得不提<code>高阶组件</code>这个概念,参考<a target="_blank" rel="noopener" href="https://reactjs.org/docs/higher-order-components.html">文档</a>。高阶组件是FB推荐的一种组件形式,我暂将它归类于<code>装饰者模式</code><small>(后续:ES7的<code>注解</code>会简化这一定义)</small>。通过传入不同的参数,返回相似的组件。好处是,<strong>以非继承的方式获得并增强原组件的能力,又不修改原组件的内部属性</strong>。与面向对象中的重载或重写不同,FB就高阶组件给出了一下几种建议或约定:</p>
<ol>
<li>不改变原有属性</li>
<li>传递不相关的props</li>
<li>最大化使用组合</li>
<li>命名上应区分高阶组件和一般组件</li>
</ol>
<p>注意事项:</p>
<ol>
<li>不要在<code>render</code>中调用</li>
<li>拷贝静态方法</li>
<li><code>refs</code>属性不能传递</li>
</ol>
<p>我认为,FB给出这些建议或约定,原因在于:高阶组件作为原组件的拓展,应尽可能不影响原组件,类似于纯函数。</p>
<h3 id="向组件注入props"><a href="#向组件注入props" class="headerlink" title="向组件注入props"></a>向组件注入props</h3><p>因<code>Provider</code>的存在,只需把<code>mapStateToProps</code>中声明的字段,通过<code>context</code>传递给<code>ChildComponent</code>,并返回该<code>高阶组件</code>,即可达到同样的效果,推断代码如下:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">connect</span>(<span class="params">mapStateToProps, ChildCompnent</span>) {</span><br><span class="line"> <span class="keyword">const</span> otherProps = <span class="title function_">mapStateToProps</span>(<span class="variable language_">this</span>.<span class="property">context</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="language-xml"><span class="tag"><<span class="name">ChildComponent</span> {<span class="attr">...otherProps</span>}/></span></span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>那这样的实现方式有什么弊端?在Provider小节中,已经提及,因<code>shouldComponent</code>的关系,不能保证<code>context</code>的实时性。也即是说,假如如此实现高阶组件,并不能保证组件状态的实时更新。如何来保证context和props或state一样,具备实时性呢?</p>
<h2 id="connect"><a href="#connect" class="headerlink" title="connect"></a>connect</h2><p>还记得<code>reducer</code>吗?</p>
<p>一个reducer为一个纯函数,传入旧的state和action,生成新的state。</p>
<p>redux的<a target="_blank" rel="noopener" href="https://cn.redux.js.org/">官方例子</a>:</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">counter</span>(<span class="params">state = <span class="number">0</span>, action</span>) {</span><br><span class="line"> <span class="keyword">switch</span> (action.<span class="property">type</span>) {</span><br><span class="line"> <span class="keyword">case</span> <span class="string">'INCREMENT'</span>:</span><br><span class="line"> <span class="keyword">return</span> state + <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="string">'DECREMENT'</span>:</span><br><span class="line"> <span class="keyword">return</span> state - <span class="number">1</span>;</span><br><span class="line"> <span class="attr">default</span>:</span><br><span class="line"> <span class="keyword">return</span> state;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建 Redux store 来存放应用的状态。</span></span><br><span class="line"><span class="comment">// API 是 { subscribe, dispatch, getState }。</span></span><br><span class="line"><span class="keyword">let</span> store = <span class="title function_">createStore</span>(counter);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 可以手动订阅更新,也可以事件绑定到视图层。</span></span><br><span class="line">store.<span class="title function_">subscribe</span>(<span class="function">() =></span></span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(store.<span class="title function_">getState</span>())</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 改变内部 state 惟一方法是 dispatch 一个 action。</span></span><br><span class="line"><span class="comment">// action 可以被序列化,用日记记录和储存下来,后期还可以以回放的方式执行</span></span><br><span class="line">store.<span class="title function_">dispatch</span>({ <span class="attr">type</span>: <span class="string">'INCREMENT'</span> }); <span class="comment">// 1</span></span><br><span class="line">store.<span class="title function_">dispatch</span>({ <span class="attr">type</span>: <span class="string">'INCREMENT'</span> }); <span class="comment">// 2</span></span><br><span class="line">store.<span class="title function_">dispatch</span>({ <span class="attr">type</span>: <span class="string">'DECREMENT'</span> }); <span class="comment">// 1</span></span><br></pre></td></tr></table></figure>
<blockquote>
<p> 在redux中,通过disptach分发 action。这是触发 state 变化的唯一途径</p>
</blockquote>
<p>官方图解如下:</p>
<p><img src="https://edeity.oss-cn-shenzhen.aliyuncs.com/2018/redux.jpeg" alt="redux流程"></p>
<p><code>component->action->reducer->store</code>的过程是<code>订阅-分发</code>设计模式的具体应用,可参考<a target="_blank" rel="noopener" href="https://www.cnblogs.com/lovesong/p/5272752.html">文章(含源码实现)</a>。在此不再展开。</p>
<p>组件的<code>shouldUpdate</code>不仅仅依赖于<code>props</code>和<code>state</code>,也依赖于上述设计模式中分发的<code>context</code>。在React原有基础上,通过分发来保证在store中声明的<code>context</code>的实时性,并触发更新。(具体代码可参考:<code>connect.js</code>以及<code>connectAdavance.js</code>)</p>
<h2 id="后续(2018-07-10)"><a href="#后续(2018-07-10)" class="headerlink" title="后续(2018.07.10)"></a>后续(2018.07.10)</h2><p>基于这篇文章:<a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/39289157">react-redux源码分析</a></p>
<p>大致的实现原理和我的论(yi)述(yin)相差不大,但是包含了很多细节。交代了触发subscribe后,通过<code>forceUpdate</code>和<code>setState</code>触发重绘,以及子孙容器的重绘机制是基于父HOC重绘机制listener优化方式。</p>
</div>
</div>
</div>
<div class="more section">
<div class="pre">
<a class="article-link" href="/game_for_parkour.html">
<i class="iconfont icon-right"></i>
<span>跑酷小游戏-总结</span>
</a>
</div>
<div class="next">
<a class="article-link" href="/https_your_blog.html">
Https你的博客
<i class="iconfont icon-right"></i>
</a>
</div>
</div>
</main>
</body>
<footer class="section fullscreen">
<div class="footer-desc">
Edeink © 2015-2025 · Powered by Hexo
</div>
</footer>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
<!--<script src="https://lab.hakim.se/zoom-js/js/zoom.js"></script>-->
<script src="/public/js/init.js"></script>
</html>