经典诠释永恒 發表於 2019-7-25 16:02:00

基于react-app搭建react-router+redux项目

<h2 id="前言">前言</h2>
<p><strong>总括:</strong>&nbsp;本文采用react+redux+react-router+less+es6+webpack,以实现一个简易备忘录(todolist)为例尽可能全面的讲述使用react全家桶实现一个完整应用的过程。</p>
<ul>
<li>代码地址:React全家桶实现一个简易备忘录</li>
<li>原文博客地址:React全家桶实现一个简易备忘录</li>
<li>知乎专栏&amp;&amp;简书专题:前端进击者(知乎)&amp;&amp;前端进击者(简书)</li>
<li>博主博客地址:Damonare的个人博客</li>
</ul>
<p><strong>人生不失意,焉能暴己知。</strong></p>
<h3 id="技术说明">技术说明</h3>
<blockquote>
<p>技术架构:本备忘录使用react+react-router+redux+less+ES6+webpack实现;</p>
</blockquote>
<blockquote>
<p>页面UI参照:TodoList官网实现;</p>
</blockquote>
<blockquote>
<p>在线演示地址:Damonare的备忘录;</p>
</blockquote>
<h3 id="功能说明">功能说明</h3>
<ul>
<li>支持回车添加新事项;</li>
<li>支持删除事项(点击X符号);</li>
<li>支持状态转换具体包括:
<ul>
<li>新建事项-&gt;正在进行(点击checkbox选项)</li>
<li>正在进行-&gt;已完成(点击文字内容本身)</li>
<li>正在进行-&gt;新建事项(点击checkbox选项)</li>
<li>已完成-&gt;正在进行(点击文字本身)</li>
</ul>
</li>
<li>支持判断输入空字符,过长字符(20个汉字以内);</li>
<li>支持搜索;</li>
<li>支持本地化存储;</li>
<li>支持状态的展开隐藏(点击标题)</li>
<li>兼容手机端(iPhone6及以上)</li>
<li>支持路由切换</li>
</ul>
<h1>1. React浅谈</h1>
<h4 id="组件化">1.1 组件化</h4>
<p>当谈到<code>React</code>的时候不能避免的会提到组件化思想。React刚开始想解决的问题是UI层面的问题,view层面的问题。后来越滚越大,从最早的UI引擎变成了一整套前后端通吃的 Web App 解决方案。一个完整的页面是由大大小小的组件堆叠而成,组件套组件组成了用户所能看到的完整的页面。</p>
<h4 id="jsx语法糖">1.2 JSX语法糖</h4>
<p>​ 使用<code>React</code>,不一定非要使用<code>JSX</code>语法,可以使用原生的JS进行开发。但是<code>React</code>作者强烈建议我们使用<code>JSX,如下:</code></p>
<p><img src="https://img2018.cnblogs.com/blog/21899/201907/21899-20190723214704657-1262879711.png" alt=""></p>
<pre class="jsx"></pre>
<p>简单明了!!!具体的<code>JSX语法</code>不多说了,学习更多戳这:JSX&nbsp;</p>
<h4 id="virtual-dom">1.3 Virtual DOM</h4>
<p>虚拟DOM的概念并不是FB首创却在FB的手上大火了起来。</p>
<p>页面对应了一个DOM树,在传统页面的开发模式中,每次需要更新页面时,都需要对DOM进行更新,DOM操作十分昂贵,为减少对于真实DOM的操作,诞生了<code>Virtual DOM</code>的概念,也就是用javascript把真实的DOM树描述了一遍,使用的也就是我们刚刚说过的<code>JSX</code>语法。</p>
<p>对比如下:</p>
<p><img src="https://img2018.cnblogs.com/blog/21899/201907/21899-20190723234029678-368256102.png" alt=""></p>
<p>&nbsp;</p>
<p>每次数据更新之后,重新计算<code>Virtual DOM</code>,并和上一次的<code>Virtual DOM</code>对比,对发生的变化进行批量更新。React也提供了<code>shouldComponentUpdate</code>生命周期回调,来减少数据变化后不必要的<code>Virtual DOM</code>对比过程,提升了性能。</p>
<p><code>Virtual DOM</code>虽然渲染方式比传统的DOM操作要好一些,但并不明显,因为对比DOM节点也是需要计算的,最大的好处在于可以很方便的和其它平台集成,比如<code>react-native</code>就是基于<code>Virtual DOM</code>渲染出原生控件。具体渲染出的是<code>Web DOM</code>还是<code>Android</code>控件或是<code>iOS</code>控件就由平台决定了。这一点上让我们多平台的编码相当方便。</p>
<h4 id="函数式编程">1.4 函数式编程</h4>
<p>​ 过去编程方式主要是以命令式编程为主。电脑生硬的执行指令,给电脑下达命令,电脑去执行,现在主要的编程语言(比如:Java,C,C++等)都是由命令式编程构建起来的。</p>
<p>​ 而函数式编程就不一样了,这是模仿我们人类的思维方式发明出来的。例如:操作某个数组的每一个元素然后返回一个新数组,如果是计算机的思考方式,会这样想:创建一个新数组=&gt;遍历旧数组=&gt;给新数组赋值。如果是人类的思考方式,会这样想:创建一个数组方法,作用在旧数组上,返回新数组。这样此方法可以被重复利用。而这就是函数式编程了。</p>
<h4 id="数据流">1.5 数据流</h4>
<div>
<div>在React中,数据是单向流动的,是从上向下的方向,即从父组件到子组件的方向。<br>
state和props是其中重要的概念,如果顶层组件初始化props,那么React会向下遍历整颗组件树,重新渲染相关的子组件。其中state表示的是每个组件中内部的的状态,这些状态只在组件内部改变。<br>
把组件看成是一个函数,那么他接受props作为参数,内部由state作为函数的内部参数,返回一个虚拟dom的实现。</div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"><span style="font-size: 16px"><strong>state</strong></span>
state是组件内部的状态,当组件内部使用内置的setState方法,该组件会重新进行渲染。
注意一下,setState方法是一个异步的方法,在一个生命周期中的所有setState方法会合并操作。
<span style="font-size: 16px">props</span>
props是React中用来让组件之间相互联系的一种机制,props的传递过程对React是非常直观的,React的单向数据流主要的流动管道就是props,props本身是不可变的,当我们试图改变props的原始值,React会报类型错误的警告。组件的props一定是来源于默认指定的属性或者是从父组件传入的。
React为props提供了默认配置,通过defaultProps静态变量的方式来定义,当组件被调用的时候,默认值保证渲染后始终有值。
在React中有一个内置的props</span>--children,代表的是子组件的集合,根据子组件的数量,<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.props.children的数据类型而且不一致,当没有子组件的时候为undefined,当有一个的时候为object,当有多个的时候为array。
<span style="font-size: 16px">propTypes</span>
propTypes是用来规范props的类型和必须的状态,如果组件定义了propTypes,那么在开发环境下会对props进行检查,在生产环境下是不会进行检查的。</span></pre>
</div>
<p>&nbsp;</p>
<h4 id="数据流">1.6 React主要就下图之中的这些内容,掌握了这个图就算入门了。</h4>
<p><img src="https://img2018.cnblogs.com/blog/21899/201907/21899-20190723225740597-735514222.png" alt=""></p>
<p>&nbsp;</p>
<h1 id="react-router">2. React-router</h1>
<p>官方例子:<strong>react-router-tutorial。</strong></p>
<p>完事以后可以再看一下阮一峰老师的教程,主要是对一些API的讲解:React Router 使用教程。</p>
<h1 id="redux">3. Redux&nbsp;</h1>
<h4 id="简介">3.1 简介</h4>
<p>随着 JavaScript 单页应用开发日趋复杂,<strong>JavaScript 需要管理比任何时候都要多的 state (状态)</strong>。 这些 state 可能包括服务器响应、缓存数据、本地生成尚未持久化到服务器的数据,也包括 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等。如果一个 model 的变化会引起另一个 model 变化,那么当 view 变化时,就可能引起对应 model 以及另一个 model 的变化,依次地,可能会引起另一个 view 的变化。乱!</p>
<p>这时候<code>Redux</code>就强势登场了,现在你可以把<code>React</code>的model看作是一个个的子民,每一个子民都有自己的一个状态,纷纷扰扰,各自维护着自己状态,太乱了,我们需要一个King来领导大家,我们就可以把<code>Redux</code>看作是这个King。网罗所有的组件组成一个国家,掌控着一切子民的状态!防止有人叛乱生事!</p>
<p>这个时候就把组件分成了两种:容器组件(King或是路由)和展示组件(子民)。</p>
<ul>
<li>容器组件:即<code>redux</code>或是<code>router</code>,起到了维护状态,出发action的作用,其实就是King高高在上下达指令。</li>
<li>展示组件:不维护状态,所有的状态由容器组件通过<code>props</code>传给他,所有操作通过回调完成
<ul>
<li>。</li>
</ul>
<table>
<thead>
<tr class="header"><th>&nbsp;</th><th>展示组件</th><th>容器组件</th></tr>
</thead>
<tbody>
<tr class="odd">
<td>作用</td>
<td>描述如何展现(骨架、样式)</td>
<td>描述如何运行(数据获取、状态更新)</td>
</tr>
<tr class="even">
<td>直接使用 Redux</td>
<td>否</td>
<td>是</td>
</tr>
<tr class="odd">
<td>数据来源</td>
<td>props</td>
<td>监听 Redux state</td>
</tr>
<tr class="even">
<td>数据修改</td>
<td>从 props 调用回调函数</td>
<td>向 Redux 派发 actions</td>
</tr>
<tr class="odd">
<td>调用方式</td>
<td>手动</td>
<td>通常由 React Redux 生成<br><br></td>










</tr>










</tbody>










</table>









</li>









</ul>









</div>
<p>Redux三大部分:<code>store</code>,<code>action</code>,<code>reducer</code>。相当于King的直系下属。</p>
<p>那么也可以看出<code>Redux</code>只是一个状态管理方案,完全可以单独拿出来使用,这个King不仅仅可以是React的,去Angular,Ember那里也是可以做King的。在React中维系King和组件关系的库叫做<code>&nbsp;react-redux</code>。</p>
<p>, 它主要有提供两个东西:<code>Provider</code>&nbsp;和<code>connect</code>,具体使用文后说明。</p>
<p>提供几个Redux的学习地址:官方教程-中文版,Redux 入门教程(一):基本用法</p>
<h4 id="store">3.2 Store</h4>
<p>Store 就是保存数据的地方,它实际上是一个<code>Object tree</code>。整个应用只能有一个 Store。这个Store可以看做是King的首相,掌控一切子民(组件)的活动(state)。</p>
<p>Redux 提供<code>createStore</code>这个函数,用来生成 Store。</p>
<pre class="javascript"><code class="hljs"><span class="hljs-keyword">import { createStore } <span class="hljs-keyword">from <span class="hljs-string">'redux';
<span class="hljs-keyword">const store = createStore(func);</span></span></span></span></code></pre>
<p>createStore接受一个函数作为参数,返回一个Store对象(首相诞生记)</p>
<p>我们来看一下Store(首相)的职责:</p>
<ul>
<li>维持应用的 state;</li>
<li>提供&nbsp;<code>getState()</code>&nbsp;方法获取 state;</li>
<li>提供&nbsp;<code>dispatch(action)</code>&nbsp;方法更新 state;</li>
<li>通过&nbsp;<code>subscribe(listener)</code>&nbsp;注册监听器;</li>
<li>通过&nbsp;<code>subscribe(listener)</code>&nbsp;返回的函数注销监听器。</li>
</ul>
<h4 id="action">3.3 action</h4>
<p>State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。即store的数据变化来自于用户操作。action就是一个通知,它可以看作是首相下面的邮递员,通知子民(组件)改变状态。它是 store 数据的<strong>唯一</strong>来源。一般来说会通过&nbsp;<code>store.dispatch()</code>&nbsp;将 action 传到 store。</p>
<p>Action 是一个对象。其中的<code>type</code>属性是必须的,表示 Action 的名称。</p>
<div class="cnblogs_code">
<pre>const action =<span style="color: rgba(0, 0, 0, 1)"> {
type: </span>'ADD_TODO'<span style="color: rgba(0, 0, 0, 1)">,
payload: </span>'Learn Redux'<span style="color: rgba(0, 0, 0, 1)">
};</span></pre>
</div>
<p>&nbsp;</p>
<p><strong>Action创建函数</strong></p>
<p><strong>Action 创建函数</strong>&nbsp;就是生成 action 的方法。“action” 和 “action 创建函数” 这两个概念很容易混在一起,使用时最好注意区分。</p>
<p>在 Redux 中的 action 创建函数只是简单的返回一个 action:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> addTodo(text) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> {
    type: ADD_TODO,
    text
}
}</span></pre>
</div>
<p>这样做将使 action 创建函数更容易被移植和测试。</p>
<p>&nbsp;</p>
<h4>3.4 reducer</h4>
<p><strong>Action</strong>&nbsp;只是描述了<strong>有事情发生了</strong>这一事实,并没有指明应用如何更新 state。而这正是 reducer 要做的事情。也就是邮递员(action)只负责通知,具体你(组件)如何去做,他不负责,这事情只能是你们村长(reducer)告诉你如何去做。</p>
<p>专业解释:&nbsp;<strong>Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。</strong></p>
<p>Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。</p>
<div class="cnblogs_code">
<pre>const reducer = <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> (state, action) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ...</span>
<span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> new_state;
};</span></pre>
</div>
<h4 id="数据流-1">3.5 数据流</h4>
<p>下图展示了Redux带来的变化。</p>
<p><img src="https://img2018.cnblogs.com/blog/21899/201907/21899-20190723232604781-283081426.png" alt=""></p>
<p>&nbsp;</p>
<p>Redux 应用中数据的生命周期遵循下面 4 个步骤:</p>
<ul>
<li><strong>调用</strong>&nbsp;<code>store.dispatch(action)</code>。</li>
<li><strong>Redux store 调用传入的 reducer 函数。</strong></li>
<li><strong>根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树。</strong></li>
<li><strong>Redux store 保存了根 reducer 返回的完整 state 树</strong>。</li>
</ul>
<p>工作流程图如下:</p>
<p><img src="https://img2018.cnblogs.com/blog/21899/201907/21899-20190723232038079-956913712.png" alt=""></p>
<p>&nbsp;component -&gt; action -&gt; store -&gt; reducer -&gt; state 的单向数据流,概括的说就是:React组件里面获取了数据之后(比如ajax请求),然后创建一个action通知store我有这个想改变state的意图,然后reducers(一个action可能对应多个reducer,可以理解为action为订阅的主题,可能有多个订阅者)来处理这个意图并且返回新的state,接下来store会收集到所有的reducer的state,最后更新state。</p>
<h4 id="connect">3.6 Connect</h4>
<p>这里需要再强调一下:Redux 和 React 之间没有关系。Redux 支持 React、Angular、Ember、jQuery 甚至纯 JavaScript。</p>
<p>尽管如此,Redux 还是和&nbsp;React&nbsp;和&nbsp;Deku&nbsp;这类框架搭配起来用最好,因为这类框架允许你以 state 函数的形式来描述界面,Redux 通过 action 的形式来发起 state 变化。</p>
<p>Redux 默认并不包含&nbsp;React 绑定库,需要单独安装。&nbsp;</p>
<div class="cnblogs_code">
<pre>npm install --save react-redux</pre>
</div>
<p>&nbsp;</p>
<p>&nbsp;<code>React-Redux</code>&nbsp;提供<code>connect</code>方法,用于从 UI 组件生成容器组件。<code>connect</code>的意思,就是将这两种组件连起来。&nbsp;</p>
<div class="cnblogs_code">
<pre>import { connect } from 'react-redux'<span style="color: rgba(0, 0, 0, 1)">;
const TodoList </span>= connect()(Memos);</pre>
</div>
<p>&nbsp;</p>
<p>&nbsp;上面代码中<code>Memos</code>是个UI组件,<code>TodoList</code>就是由 React-Redux 通过<code>connect</code>方法自动生成的容器组件。</p>
<p>而只是纯粹的这样把Memos包裹起来毫无意义,完整的connect方法这样使用:&nbsp;</p>
<div class="cnblogs_code">
<pre>import { connect } from 'react-redux'<span style="color: rgba(0, 0, 0, 1)">
const TodoList </span>=<span style="color: rgba(0, 0, 0, 1)"> connect(
mapStateToProps
)(Memos)</span></pre>
</div>
<p>&nbsp;</p>
<p>&nbsp;上面代码中,<code>connect</code>方法接受两个参数:<code>mapStateToProps</code>和<code>mapDispatchToProps</code>。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将<code>state</code>映射到 UI 组件的参数(<code>props</code>),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。</p>
<p>&nbsp;</p>
<h4 id="provider">3.7 Provider</h4>
<p>&nbsp;这个Provider 其实是一个中间件,它是为了解决让容器组件拿到King的指令(<code>state</code>对象)而存在的。</p>
<div class="cnblogs_code">
<pre>import { Provider } from 'react-redux'<span style="color: rgba(0, 0, 0, 1)">
import { createStore } from </span>'redux'<span style="color: rgba(0, 0, 0, 1)">
import todoApp from </span>'./reducers'<span style="color: rgba(0, 0, 0, 1)">
import App from </span>'./components/App'<span style="color: rgba(0, 0, 0, 1)">
let store </span>=<span style="color: rgba(0, 0, 0, 1)"> createStore(todoApp);
render(
</span>&lt;Provider store={store}&gt;
    &lt;App /&gt;
&lt;/Provider&gt;,
document.getElementById('root'<span style="color: rgba(0, 0, 0, 1)">)
)</span></pre>
</div>
<p>&nbsp;</p>
<h1 id="实战备忘录">4.实战备忘录</h1>
<p>讲解之前可以先看一下github上的代码,你可以clone下来学习,也可以在线给我提issue,欢迎戳这:React全家桶实现简易备忘录</p>
<p><strong>&nbsp;4.1目录结构</strong></p>
<p><strong>&nbsp;<img src="https://img2018.cnblogs.com/blog/21899/201907/21899-20190723234316882-2128899605.png" alt=""></strong></p>
<p>接下来,我们只关注app目录就好了。</p>
<h4 id="4-2入口文件">4.2入口文件&nbsp;&nbsp;</h4>
<blockquote>
<p>app/main.jsx&nbsp;</p>
</blockquote>
<div class="cnblogs_code">
<pre>import React from 'react'<span style="color: rgba(0, 0, 0, 1)">;
import ReactDOM from </span>'react-dom'<span style="color: rgba(0, 0, 0, 1)">;
import { Route, IndexRoute, hashHistory, Router } from </span>'react-router'<span style="color: rgba(0, 0, 0, 1)">;
import { Provider } from </span>'react-redux'<span style="color: rgba(0, 0, 0, 1)">;
import App from </span>'./container/App'<span style="color: rgba(0, 0, 0, 1)">;
import AllMemosRoute from </span>'./routes/AllMemosRoute'<span style="color: rgba(0, 0, 0, 1)">;
import TodoRoute from </span>'./routes/TodoRoute'<span style="color: rgba(0, 0, 0, 1)">;
import DoingRoute from </span>'./routes/DoingRoute'<span style="color: rgba(0, 0, 0, 1)">;
import DoneRoute from </span>'./routes/DoneRoute'<span style="color: rgba(0, 0, 0, 1)">;
import configureStore from </span>'./stores'<span style="color: rgba(0, 0, 0, 1)">;
import </span>'./main.less'<span style="color: rgba(0, 0, 0, 1)">;

const store </span>=<span style="color: rgba(0, 0, 0, 1)"> configureStore();
ReactDOM.render(
    </span>&lt;Provider store={store}&gt;
      &lt;Router history={hashHistory}&gt;
            &lt;<span style="color: rgba(0, 0, 0, 1)">Route path</span>="/" <span style="color: rgba(0, 0, 0, 1)">component</span>=<span style="color: rgba(0, 0, 0, 1)">{App}</span>&gt;
                &lt;IndexRoute component={AllMemosRoute} /&gt;
                &lt;Route path="/todo" component={TodoRoute} /&gt;
                &lt;Route path="/doing" component={DoingRoute} /&gt;
                &lt;Route path="/done" component={DoneRoute} /&gt;
            &lt;/Route&gt;
      &lt;/Router&gt;
    &lt;/Provider&gt;,
    document.getElementById('root'<span style="color: rgba(0, 0, 0, 1)">)
);</span></pre>
</div>
<p>&nbsp;</p>
<p>这里我们从<code>react-redux</code>中获取到 Provider 组件,我们把它渲染到应用的最外层。<br>他需要一个属性 store ,他把这个 store 放在context里,给Router(connect)用。</p>
<h4 id="4-3-Store">4.3 Store</h4>
<blockquote>
<p>app/store/index.jsx&nbsp;</p>





</blockquote>
<div class="cnblogs_code">
<pre>import { createStore } from 'redux'<span style="color: rgba(0, 0, 0, 1)">;
import reducer from </span>'../reducers'<span style="color: rgba(0, 0, 0, 1)">;
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> configureStore(initialState) {
const store </span>=<span style="color: rgba(0, 0, 0, 1)"> createStore(reducer, initialState);
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (module.hot) {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Enable Webpack hot module replacement for reducers</span>
    module.hot.accept('../reducers', () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      const nextReducer </span>= require('../reducers'<span style="color: rgba(0, 0, 0, 1)">);
      store.replaceReducer(nextReducer);
    });
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> store;
}</span></pre>
</div>
<p>&nbsp;</p>
<h4 id="4-4-Action-创建函数和常量">4.4 Action 创建函数和常量</h4>
<blockquote>
<p>app/action/index.jsx</p>
</blockquote>
<div class="cnblogs_code">
<pre>'use strict'<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
* @author Damonare 2016-12-10
* @version 1.0.0
* action 类型
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
export const Add_Todo </span>= 'Add_Todo'<span style="color: rgba(0, 0, 0, 1)">;
export const Change_Todo_To_Doing </span>= 'Change_Todo_To_Doing'<span style="color: rgba(0, 0, 0, 1)">;
export const Change_Doing_To_Done </span>= 'Change_Doing_To_Done'<span style="color: rgba(0, 0, 0, 1)">;
export const Change_Done_To_Doing </span>= 'Change_Done_To_Doing'<span style="color: rgba(0, 0, 0, 1)">;
export const Change_Doing_To_Todo </span>= 'Change_Doing_To_Todo'<span style="color: rgba(0, 0, 0, 1)">;
export const Search</span>='Search'<span style="color: rgba(0, 0, 0, 1)">;
export const Delete_Todo</span>='Delete_Todo'<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
* action 创建函数
* @methodaddTodo添加新事项
* @param{String} text 添加事项的内容
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
export </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> addTodo(text) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> {
      type: Add_Todo,
      text
}
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
* @methodsearch 查找事项
* @param{String} text 查找事项的内容
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
export </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> search(text) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> {
      type: Search,
      text
}
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
* @methodchangeTodoToDoing 状态由todo转为doing
* @param{Number} index 需要改变状态的事项的下标
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
export </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> changeTodoToDoing(index) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> {
      type: Change_Todo_To_Doing,
      index
}
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
* @methodchangeDoneToDoing 状态由done转为doing
* @param{Number} index 需要改变状态的事项的下标
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
export </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> changeDoneToDoing(index) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> {
      type: Change_Done_To_Doing,
      index
}
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
* @methodchangeDoingToTodo 状态由doing转为todo
* @param{Number} index 需要改变状态的事项的下标
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
export </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> changeDoingToTodo(index) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> {
      type: Change_Doing_To_Todo,
      index
}
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
* @methodchangeDoingToDone 状态由doing转为done
* @param{Number} index 需要改变状态的事项的下标
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
export </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> changeDoingToDone(index) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> {
      type: Change_Doing_To_Done,
      index
}
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
* @methoddeleteTodo 删除事项
* @param{Number} index 需要删除的事项的下标
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
export </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> deleteTodo(index) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> {
      type: Delete_Todo,
      index
}
}</span></pre>
</div>
<p>&nbsp;</p>
<p>&nbsp;在声明每一个返回 action 函数的时候,我们需要在头部声明这个 action 的 type,以便好组织管理。<br>每个函数都会返回一个 action 对象,所以在 组件里面调用&nbsp;</p>
<div class="cnblogs_code">
<pre>text =&gt;<span style="color: rgba(0, 0, 0, 1)">
dispatch(addTodo(text))</span></pre>
</div>
<p>&nbsp;</p>
<p>&nbsp;就是调用<code>dispatch(action)</code>&nbsp;。</p>
<p>&nbsp;</p>
<h4 id="4-5-Reducers">4.5 Reducers&nbsp;</h4>
<blockquote>
<p>app/reducers/index.jsx<span style="color: rgba(0, 0, 0, 1)">&nbsp;</span><span style="color: rgba(0, 0, 0, 1)">&nbsp;</span></p>
</blockquote>
<div class="cnblogs_code">
<pre>import { combineReducers } from 'redux'<span style="color: rgba(0, 0, 0, 1)">;
import todolist from </span>'./todos'<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> import visibilityFilter from './visibilityFilter';</span>
<span style="color: rgba(0, 0, 0, 1)">
const reducer </span>=<span style="color: rgba(0, 0, 0, 1)"> combineReducers({
    todolist,
});

export </span><span style="color: rgba(0, 0, 255, 1)">default</span> reducer;&nbsp;</pre>
</div>
<blockquote>
<p>app/reducers/todos.jsx</p>
</blockquote>
<p>&nbsp;</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">import {
    ADD_TODO,
    DELETE_TODO,
    CHANGE_TODO_TO_DOING,
    CHANGE_DOING_TO_DONE,
    CHANGE_DOING_TO_TODO,
    CAHNGE_DONE_TO_DOING,
    SEARCH,
} from </span>'../actions'<span style="color: rgba(0, 0, 0, 1)">;

let todos;
(() </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (localStorage.todos) {
      todos </span>=<span style="color: rgba(0, 0, 0, 1)"> JSON.parse(localStorage.todos);
    } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
      todos </span>=<span style="color: rgba(0, 0, 0, 1)"> [];
    }
})();
const todolist </span>= (state = todos, action) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)"> (action.type) {
       </span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
      *添加新的事项
      *并进行本地化存储
      *使用ES6展开运算符链接新事项和旧事项
      *JSON.stringify进行对象深拷贝
      </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
      <span style="color: rgba(0, 0, 255, 1)">case</span><span style="color: rgba(0, 0, 0, 1)"> ADD_TODO:
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> [
                ...state, {
                  todo: action.text,
                  istodo: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
                  doing: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
                  done: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
                }
            ];
      </span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
         * 将todo转为doing状态,注意action.index的类型转换
         </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
      <span style="color: rgba(0, 0, 255, 1)">case</span><span style="color: rgba(0, 0, 0, 1)"> CHANGE_TODO_TO_DOING:
            localStorage.setItem(</span>'todos'<span style="color: rgba(0, 0, 0, 1)">, JSON.stringify([
                ...state.slice(</span>0<span style="color: rgba(0, 0, 0, 1)">, action.index),
                {
                  todo:state.todo,
                  istodo: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
                  doing: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
                  done: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
                },
                ...state.slice(parseInt(action.index) </span>+ 1<span style="color: rgba(0, 0, 0, 1)">)
            ]));
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> [
                ...state.slice(</span>0<span style="color: rgba(0, 0, 0, 1)">, action.index),
                {
                  todo:state.todo,
                  istodo: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
                  doing: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
                  done: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
                },
                ...state.slice(parseInt(action.index) </span>+ 1<span style="color: rgba(0, 0, 0, 1)">)
            ];
      </span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
         * 将doing转为done状态
         </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
      <span style="color: rgba(0, 0, 255, 1)">case</span><span style="color: rgba(0, 0, 0, 1)"> CHANGE_DOING_TO_DONE:
            localStorage.setItem(</span>'todos'<span style="color: rgba(0, 0, 0, 1)">, JSON.stringify([
                ...state.slice(</span>0<span style="color: rgba(0, 0, 0, 1)">, action.index),
                {
                  todo:state.todo,
                  istodo: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
                  doing: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
                  done: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
                },
                ...state.slice(parseInt(action.index) </span>+ 1<span style="color: rgba(0, 0, 0, 1)">)
            ]));
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> [
                ...state.slice(</span>0<span style="color: rgba(0, 0, 0, 1)">, action.index),
                {
                  todo:state.todo,
                  istodo: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
                  doing: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
                  done: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
                },
                ...state.slice(parseInt(action.index) </span>+ 1<span style="color: rgba(0, 0, 0, 1)">)
            ];
      </span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
         * 将done转为doing状态
         </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
      <span style="color: rgba(0, 0, 255, 1)">case</span><span style="color: rgba(0, 0, 0, 1)"> CAHNGE_DONE_TO_DOING:
            localStorage.setItem(</span>'todos'<span style="color: rgba(0, 0, 0, 1)">, JSON.stringify([
                ...state.slice(</span>0<span style="color: rgba(0, 0, 0, 1)">, action.index),
                {
                  todo:state.todo,
                  istodo: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
                  doing: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
                  done: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
                },
                ...state.slice(parseInt(action.index) </span>+ 1<span style="color: rgba(0, 0, 0, 1)">)
            ]));
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> [
                ...state.slice(</span>0<span style="color: rgba(0, 0, 0, 1)">, action.index),
                {
                  todo:state.todo,
                  istodo: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
                  doing: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
                  done: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
                },
                ...state.slice(parseInt(action.index) </span>+ 1<span style="color: rgba(0, 0, 0, 1)">)
            ];
      </span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
         * 将doing转为todo状态
         </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
      <span style="color: rgba(0, 0, 255, 1)">case</span><span style="color: rgba(0, 0, 0, 1)"> CHANGE_DOING_TO_TODO:
            localStorage.setItem(</span>'todos'<span style="color: rgba(0, 0, 0, 1)">, JSON.stringify([
                ...state.slice(</span>0<span style="color: rgba(0, 0, 0, 1)">, action.index),
                {
                  todo:state.todo,
                  istodo: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
                  doing: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
                  done: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
                },
                ...state.slice(parseInt(action.index) </span>+ 1<span style="color: rgba(0, 0, 0, 1)">)
            ]));
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> [
                ...state.slice(</span>0<span style="color: rgba(0, 0, 0, 1)">, action.index),
                {
                  todo:state.todo,
                  istodo: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
                  doing: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
                  done: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
                },
                ...state.slice(parseInt(action.index) </span>+ 1<span style="color: rgba(0, 0, 0, 1)">)
            ];
      </span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
         * 删除某个事项
         </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
      <span style="color: rgba(0, 0, 255, 1)">case</span><span style="color: rgba(0, 0, 0, 1)"> DELETE_TODO:
            localStorage.setItem(</span>'todos'<span style="color: rgba(0, 0, 0, 1)">, JSON.stringify([
                ...state.slice(</span>0<span style="color: rgba(0, 0, 0, 1)">, action.index),
                ...state.slice(parseInt(action.index) </span>+ 1<span style="color: rgba(0, 0, 0, 1)">)
            ]));
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> [
                ...state.slice(</span>0<span style="color: rgba(0, 0, 0, 1)">, action.index),
                ...state.slice(parseInt(action.index) </span>+ 1<span style="color: rgba(0, 0, 0, 1)">)
            ];
      </span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
         * 搜索
         </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
      <span style="color: rgba(0, 0, 255, 1)">case</span><span style="color: rgba(0, 0, 0, 1)"> SEARCH:
            let text </span>=<span style="color: rgba(0, 0, 0, 1)"> action.text;
            let reg </span>= eval("/"+text+"/gi"<span style="color: rgba(0, 0, 0, 1)">);
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> state.filter(item=&gt;<span style="color: rgba(0, 0, 0, 1)"> item.todo.match(reg));
      </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">:
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> state;
    }
}
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> todolist;</pre>
</div>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/tianyamoon/p/11235299.html
頁: [1]
查看完整版本: 基于react-app搭建react-router+redux项目