-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcode_react_redux.html
1 lines (1 loc) · 20.6 KB
/
code_react_redux.html
1
<!doctype html><html lang="zh-CN" class="night"><head><meta charset="utf-8"><meta content="width=device-width,initial-scale=1,maximum-scale=4,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>!function(){var e=-1!==window.location.search.indexOf("theme=night")||"night"===window.localStorage.getItem("edeity-theme_theme"),t=-1!==window.location.search.indexOf("theme=light")||"light"===window.localStorage.getItem("edeity-theme_theme");(new Date).getHours();var n=document.querySelector("html");e?n.classList.add("night"):t?n.classList.remove("night"):n.classList.add("night")}(),document.addEventListener("DOMContentLoaded",function(){null!==document.querySelector("ol.toc")&&(document.querySelector("#nav-bar").style.cssText="display: block")})</script><script async src="https://www.googletagmanager.com/gtag/js?id=G-M3J9QSEE2Z"></script><script>function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config","G-M3J9QSEE2Z")</script><meta name="generator" content="Hexo 5.0.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"><Provider store={store}></span><br><span class="line"> <YourApp/></span><br><span class="line"></Provider></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="class"><span class="keyword">class</span> <span class="title">YourApp</span> <span class="keyword">extends</span> <span class="title">Compoenent</span> </span>{</span><br><span class="line"> render(){<span class="keyword">return</span> ...}</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">mapStateToProps</span>(<span class="params">state</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> xxx: state.xxx</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> connect(mapStateToProps, ...actions)(YourApp)</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="class"><span class="keyword">class</span> <span class="title">MessageList</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>{</span><br><span class="line"> getChildContext() {</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"> render() {</span><br><span class="line"> <span class="keyword">const</span> children = <span class="built_in">this</span>.props.messages.map(<span class="function">(<span class="params">message</span>) =></span></span><br><span class="line"> <Message text={message.text} /></span><br><span class="line"> );</span><br><span class="line"> <span class="keyword">return</span> <span class="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">MessageList.childContextTypes = {</span><br><span class="line"> color: PropTypes.string</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="class"><span class="keyword">class</span> <span class="title">Button</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>{</span><br><span class="line"> render() {</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <button style={{<span class="attr">background</span>: <span class="built_in">this</span>.context.color}}></span><br><span class="line"> {<span class="built_in">this</span>.props.children}</span><br><span class="line"> </button></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="function"><span class="keyword">function</span> <span class="title">createProvider</span>(<span class="params">storeKey = <span class="string">'store'</span>, subKey</span>) </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="class"><span class="keyword">class</span> <span class="title">Provider</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>{</span><br><span class="line"> getChildContext() {</span><br><span class="line"> <span class="keyword">return</span> { [storeKey]: <span class="built_in">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="keyword">constructor</span>(props, context) {</span><br><span class="line"> <span class="built_in">super</span>(props, context)</span><br><span class="line"> <span class="built_in">this</span>[storeKey] = props.store;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> render() {</span><br><span class="line"> <span class="keyword">return</span> Children.only(<span class="built_in">this</span>.props.children)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> Provider.propTypes = {</span><br><span class="line"> store: storeShape.isRequired,</span><br><span class="line"> children: PropTypes.element.isRequired,</span><br><span class="line"> }</span><br><span class="line"> Provider.childContextTypes = {</span><br><span class="line"> [storeKey]: storeShape.isRequired,</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> Provider</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="function"><span class="keyword">function</span> <span class="title">connect</span>(<span class="params">mapStateToProps, ChildCompnent</span>) </span>{</span><br><span class="line"> <span class="keyword">const</span> otherProps = mapStateToProps(<span class="built_in">this</span>.context)</span><br><span class="line"> <span class="keyword">return</span> <span class="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="function"><span class="keyword">function</span> <span class="title">counter</span>(<span class="params">state = <span class="number">0</span>, action</span>) </span>{</span><br><span class="line"> <span class="keyword">switch</span> (action.type) {</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="keyword">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 = createStore(counter);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 可以手动订阅更新,也可以事件绑定到视图层。</span></span><br><span class="line">store.subscribe(<span class="function">() =></span></span><br><span class="line"> <span class="built_in">console</span>.log(store.getState())</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.dispatch({ <span class="attr">type</span>: <span class="string">'INCREMENT'</span> }); <span class="comment">// 1</span></span><br><span class="line">store.dispatch({ <span class="attr">type</span>: <span class="string">'INCREMENT'</span> }); <span class="comment">// 2</span></span><br><span class="line">store.dispatch({ <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-2022 · Powered by Hexo</div></footer><script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script><script src="/public/js/init.js"></script></html>