





















总括: 本文采用react+redux+react-router+less+es6+webpack,以实现一个简易备忘录(todolist)为例尽可能全面的讲述使用react全家桶实现一个完整应用的过程。
人生不失意,焉能暴己知。
技术架构:本备忘录使用react+react-router+redux+less+ES6+webpack实现;
页面UI参照:TodoList官网实现;
在线演示地址:Damonare的备忘录;
当谈到React的时候不能避免的会提到组件化思想。React刚开始想解决的问题是UI层面的问题,view层面的问题。后来越滚越大,从最早的UI引擎变成了一整套前后端通吃的 Web App 解决方案。一个完整的页面是由大大小小的组件堆叠而成,组件套组件组成了用户所能看到的完整的页面。
使用React,不一定非要使用JSX语法,可以使用原生的JS进行开发。但是React作者强烈建议我们使用JSX,如下:

简单明了!!!具体的JSX语法不多说了,学习更多戳这:JSX
虚拟DOM的概念并不是FB首创却在FB的手上大火了起来。
页面对应了一个DOM树,在传统页面的开发模式中,每次需要更新页面时,都需要对DOM进行更新,DOM操作十分昂贵,为减少对于真实DOM的操作,诞生了Virtual DOM的概念,也就是用javascript把真实的DOM树描述了一遍,使用的也就是我们刚刚说过的JSX语法。
对比如下:

每次数据更新之后,重新计算Virtual DOM,并和上一次的Virtual DOM对比,对发生的变化进行批量更新。React也提供了shouldComponentUpdate生命周期回调,来减少数据变化后不必要的Virtual DOM对比过程,提升了性能。
Virtual DOM虽然渲染方式比传统的DOM操作要好一些,但并不明显,因为对比DOM节点也是需要计算的,最大的好处在于可以很方便的和其它平台集成,比如react-native就是基于Virtual DOM渲染出原生控件。具体渲染出的是Web DOM还是Android控件或是iOS控件就由平台决定了。这一点上让我们多平台的编码相当方便。
过去编程方式主要是以命令式编程为主。电脑生硬的执行指令,给电脑下达命令,电脑去执行,现在主要的编程语言(比如:Java,C,C++等)都是由命令式编程构建起来的。
而函数式编程就不一样了,这是模仿我们人类的思维方式发明出来的。例如:操作某个数组的每一个元素然后返回一个新数组,如果是计算机的思考方式,会这样想:创建一个新数组=>遍历旧数组=>给新数组赋值。如果是人类的思考方式,会这样想:创建一个数组方法,作用在旧数组上,返回新数组。这样此方法可以被重复利用。而这就是函数式编程了。
在React中,数据是单向流动的,是从上向下的方向,即从父组件到子组件的方向。
state和props是其中重要的概念,如果顶层组件初始化props,那么React会向下遍历整颗组件树,重新渲染相关的子组件。其中state表示的是每个组件中内部的的状态,这些状态只在组件内部改变。
把组件看成是一个函数,那么他接受props作为参数,内部由state作为函数的内部参数,返回一个虚拟dom的实现。
state state是组件内部的状态,当组件内部使用内置的setState方法,该组件会重新进行渲染。 注意一下,setState方法是一个异步的方法,在一个生命周期中的所有setState方法会合并操作。 props props是React中用来让组件之间相互联系的一种机制,props的传递过程对React是非常直观的,React的单向数据流主要的流动管道就是props,props本身是不可变的,当我们试图改变props的原始值,React会报类型错误的警告。组件的props一定是来源于默认指定的属性或者是从父组件传入的。 React为props提供了默认配置,通过defaultProps静态变量的方式来定义,当组件被调用的时候,默认值保证渲染后始终有值。 在React中有一个内置的props--children,代表的是子组件的集合,根据子组件的数量,this.props.children的数据类型而且不一致,当没有子组件的时候为undefined,当有一个的时候为object,当有多个的时候为array。 propTypes propTypes是用来规范props的类型和必须的状态,如果组件定义了propTypes,那么在开发环境下会对props进行检查,在生产环境下是不会进行检查的。

完事以后可以再看一下阮一峰老师的教程,主要是对一些API的讲解:React Router 使用教程。
随着 JavaScript 单页应用开发日趋复杂,JavaScript 需要管理比任何时候都要多的 state (状态)。 这些 state 可能包括服务器响应、缓存数据、本地生成尚未持久化到服务器的数据,也包括 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等。如果一个 model 的变化会引起另一个 model 变化,那么当 view 变化时,就可能引起对应 model 以及另一个 model 的变化,依次地,可能会引起另一个 view 的变化。乱!
这时候Redux就强势登场了,现在你可以把React的model看作是一个个的子民,每一个子民都有自己的一个状态,纷纷扰扰,各自维护着自己状态,太乱了,我们需要一个King来领导大家,我们就可以把Redux看作是这个King。网罗所有的组件组成一个国家,掌控着一切子民的状态!防止有人叛乱生事!
这个时候就把组件分成了两种:容器组件(King或是路由)和展示组件(子民)。
redux或是router,起到了维护状态,出发action的作用,其实就是King高高在上下达指令。props传给他,所有操作通过回调完成
| 展示组件 | 容器组件 | |
|---|---|---|
| 作用 | 描述如何展现(骨架、样式) | 描述如何运行(数据获取、状态更新) |
| 直接使用 Redux | 否 | 是 |
| 数据来源 | props | 监听 Redux state |
| 数据修改 | 从 props 调用回调函数 | 向 Redux 派发 actions |
| 调用方式 | 手动 | 通常由 React Redux 生成 |
Redux三大部分:store,action,reducer。相当于King的直系下属。
那么也可以看出Redux只是一个状态管理方案,完全可以单独拿出来使用,这个King不仅仅可以是React的,去Angular,Ember那里也是可以做King的。在React中维系King和组件关系的库叫做 react-redux。
, 它主要有提供两个东西:Provider 和connect,具体使用文后说明。
提供几个Redux的学习地址:官方教程-中文版,Redux 入门教程(一):基本用法
Store 就是保存数据的地方,它实际上是一个Object tree。整个应用只能有一个 Store。这个Store可以看做是King的首相,掌控一切子民(组件)的活动(state)。
Redux 提供createStore这个函数,用来生成 Store。
import { createStore } from 'redux';
const store = createStore(func);
createStore接受一个函数作为参数,返回一个Store对象(首相诞生记)
我们来看一下Store(首相)的职责:
getState() 方法获取 state;dispatch(action) 方法更新 state;subscribe(listener) 注册监听器;subscribe(listener) 返回的函数注销监听器。State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。即store的数据变化来自于用户操作。action就是一个通知,它可以看作是首相下面的邮递员,通知子民(组件)改变状态。它是 store 数据的唯一来源。一般来说会通过 store.dispatch() 将 action 传到 store。
Action 是一个对象。其中的type属性是必须的,表示 Action 的名称。
const action = { type: 'ADD_TODO', payload: 'Learn Redux' };
Action创建函数
Action 创建函数 就是生成 action 的方法。“action” 和 “action 创建函数” 这两个概念很容易混在一起,使用时最好注意区分。
在 Redux 中的 action 创建函数只是简单的返回一个 action:
function addTodo(text) { return { type: ADD_TODO, text } }
这样做将使 action 创建函数更容易被移植和测试。
Action 只是描述了有事情发生了这一事实,并没有指明应用如何更新 state。而这正是 reducer 要做的事情。也就是邮递员(action)只负责通知,具体你(组件)如何去做,他不负责,这事情只能是你们村长(reducer)告诉你如何去做。
专业解释: Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。
Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
const reducer = function (state, action) { // ... return new_state; };
下图展示了Redux带来的变化。

Redux 应用中数据的生命周期遵循下面 4 个步骤:
store.dispatch(action)。工作流程图如下:

component -> action -> store -> reducer -> state 的单向数据流,概括的说就是:React组件里面获取了数据之后(比如ajax请求),然后创建一个action通知store我有这个想改变state的意图,然后reducers(一个action可能对应多个reducer,可以理解为action为订阅的主题,可能有多个订阅者)来处理这个意图并且返回新的state,接下来store会收集到所有的reducer的state,最后更新state。
这里需要再强调一下:Redux 和 React 之间没有关系。Redux 支持 React、Angular、Ember、jQuery 甚至纯 JavaScript。
尽管如此,Redux 还是和 React 和 Deku 这类框架搭配起来用最好,因为这类框架允许你以 state 函数的形式来描述界面,Redux 通过 action 的形式来发起 state 变化。
Redux 默认并不包含 React 绑定库,需要单独安装。
npm install --save react-redux
React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来。
import { connect } from 'react-redux';
const TodoList = connect()(Memos);
上面代码中Memos是个UI组件,TodoList就是由 React-Redux 通过connect方法自动生成的容器组件。
而只是纯粹的这样把Memos包裹起来毫无意义,完整的connect方法这样使用:
import { connect } from 'react-redux'
const TodoList = connect(
mapStateToProps
)(Memos)
上面代码中,connect方法接受两个参数:mapStateToProps和mapDispatchToProps。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。
这个Provider 其实是一个中间件,它是为了解决让容器组件拿到King的指令(state对象)而存在的。
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'
let store = createStore(todoApp);
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
讲解之前可以先看一下github上的代码,你可以clone下来学习,也可以在线给我提issue,欢迎戳这:React全家桶实现简易备忘录
4.1目录结构

接下来,我们只关注app目录就好了。
app/main.jsx
import React from 'react'; import ReactDOM from 'react-dom'; import { Route, IndexRoute, hashHistory, Router } from 'react-router'; import { Provider } from 'react-redux'; import App from './container/App'; import AllMemosRoute from './routes/AllMemosRoute'; import TodoRoute from './routes/TodoRoute'; import DoingRoute from './routes/DoingRoute'; import DoneRoute from './routes/DoneRoute'; import configureStore from './stores'; import './main.less'; const store = configureStore(); ReactDOM.render( <Provider store={store}> <Router history={hashHistory}> <Route path="/" component={App}> <IndexRoute component={AllMemosRoute} /> <Route path="/todo" component={TodoRoute} /> <Route path="/doing" component={DoingRoute} /> <Route path="/done" component={DoneRoute} /> </Route> </Router> </Provider>, document.getElementById('root') );
这里我们从react-redux中获取到 Provider 组件,我们把它渲染到应用的最外层。
他需要一个属性 store ,他把这个 store 放在context里,给Router(connect)用。
app/store/index.jsx
import { createStore } from 'redux';
import reducer from '../reducers';
export default function configureStore(initialState) {
const store = createStore(reducer, initialState);
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
const nextReducer = require('../reducers');
store.replaceReducer(nextReducer);
});
}
return store;
}
app/action/index.jsx
'use strict'; /* * @author Damonare 2016-12-10 * @version 1.0.0 * action 类型 */ export const Add_Todo = 'Add_Todo'; export const Change_Todo_To_Doing = 'Change_Todo_To_Doing'; export const Change_Doing_To_Done = 'Change_Doing_To_Done'; export const Change_Done_To_Doing = 'Change_Done_To_Doing'; export const Change_Doing_To_Todo = 'Change_Doing_To_Todo'; export const Search='Search'; export const Delete_Todo='Delete_Todo'; /* * action 创建函数 * @method addTodo添加新事项 * @param {String} text 添加事项的内容 */ export function addTodo(text) { return { type: Add_Todo, text } } /* * @method search 查找事项 * @param {String} text 查找事项的内容 */ export function search(text) { return { type: Search, text } } /* * @method changeTodoToDoing 状态由todo转为doing * @param {Number} index 需要改变状态的事项的下标 */ export function changeTodoToDoing(index) { return { type: Change_Todo_To_Doing, index } } /* * @method changeDoneToDoing 状态由done转为doing * @param {Number} index 需要改变状态的事项的下标 */ export function changeDoneToDoing(index) { return { type: Change_Done_To_Doing, index } } /* * @method changeDoingToTodo 状态由doing转为todo * @param {Number} index 需要改变状态的事项的下标 */ export function changeDoingToTodo(index) { return { type: Change_Doing_To_Todo, index } } /* * @method changeDoingToDone 状态由doing转为done * @param {Number} index 需要改变状态的事项的下标 */ export function changeDoingToDone(index) { return { type: Change_Doing_To_Done, index } } /* * @method deleteTodo 删除事项 * @param {Number} index 需要删除的事项的下标 */ export function deleteTodo(index) { return { type: Delete_Todo, index } }
在声明每一个返回 action 函数的时候,我们需要在头部声明这个 action 的 type,以便好组织管理。
每个函数都会返回一个 action 对象,所以在 组件里面调用
text =>
dispatch(addTodo(text))
就是调用dispatch(action) 。
app/reducers/index.jsx
import { combineReducers } from 'redux';
import todolist from './todos';
// import visibilityFilter from './visibilityFilter';
const reducer = combineReducers({
todolist,
});
export default reducer;
app/reducers/todos.jsx
import { ADD_TODO, DELETE_TODO, CHANGE_TODO_TO_DOING, CHANGE_DOING_TO_DONE, CHANGE_DOING_TO_TODO, CAHNGE_DONE_TO_DOING, SEARCH, } from '../actions'; let todos; (() => { if (localStorage.todos) { todos = JSON.parse(localStorage.todos); } else { todos = []; } })(); const todolist = (state = todos, action) => { switch (action.type) { /* * 添加新的事项 * 并进行本地化存储 * 使用ES6展开运算符链接新事项和旧事项 * JSON.stringify进行对象深拷贝 */ case ADD_TODO: return [ ...state, { todo: action.text, istodo: true, doing: false, done: false } ]; /* * 将todo转为doing状态,注意action.index的类型转换 */ case CHANGE_TODO_TO_DOING: localStorage.setItem('todos', JSON.stringify([ ...state.slice(0, action.index), { todo:state[action.index].todo, istodo: false, doing: true, done: false }, ...state.slice(parseInt(action.index) + 1) ])); return [ ...state.slice(0, action.index), { todo:state[action.index].todo, istodo: false, doing: true, done: false }, ...state.slice(parseInt(action.index) + 1) ]; /* * 将doing转为done状态 */ case CHANGE_DOING_TO_DONE: localStorage.setItem('todos', JSON.stringify([ ...state.slice(0, action.index), { todo:state[action.index].todo, istodo: false, doing: false, done: true }, ...state.slice(parseInt(action.index) + 1) ])); return [ ...state.slice(0, action.index), { todo:state[action.index].todo, istodo: false, doing: false, done: true }, ...state.slice(parseInt(action.index) + 1) ]; /* * 将done转为doing状态 */ case CAHNGE_DONE_TO_DOING: localStorage.setItem('todos', JSON.stringify([ ...state.slice(0, action.index), { todo:state[action.index].todo, istodo: false, doing: true, done: false }, ...state.slice(parseInt(action.index) + 1) ])); return [ ...state.slice(0, action.index), { todo:state[action.index].todo, istodo: false, doing: true, done: false }, ...state.slice(parseInt(action.index) + 1) ]; /* * 将doing转为todo状态 */ case CHANGE_DOING_TO_TODO: localStorage.setItem('todos', JSON.stringify([ ...state.slice(0, action.index), { todo:state[action.index].todo, istodo: true, doing: false, done: false }, ...state.slice(parseInt(action.index) + 1) ])); return [ ...state.slice(0, action.index), { todo:state[action.index].todo, istodo: true, doing: false, done: false }, ...state.slice(parseInt(action.index) + 1) ]; /* * 删除某个事项 */ case DELETE_TODO: localStorage.setItem('todos', JSON.stringify([ ...state.slice(0, action.index), ...state.slice(parseInt(action.index) + 1) ])); return [ ...state.slice(0, action.index), ...state.slice(parseInt(action.index) + 1) ]; /* * 搜索 */ case SEARCH: let text = action.text; let reg = eval("/"+text+"/gi"); return state.filter(item=> item.todo.match(reg)); default: return state; } } export default todolist;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。