总括: 本文采用react+redux+react-router+less+es6+webpack,以实现一个简易备忘录(todolist)为例尽量全面的讲述使用react全家桶实现一个完整应用的过程。javascript
人生不失意,焉能暴己知。html
技术架构:本备忘录使用react+react-router+redux+less+ES6+webpack实现;前端
页面UI参照:TodoList官网实现;java
在线演示地址:Damonare的备忘录;react
当谈到React
的时候不能避免的会提到组件化思想。React刚开始想解决的问题是UI层面的问题,view层面的问题。后来越滚越大,从最先的UI引擎变成了一整套先后端通吃的 Web App 解决方案。一个完整的页面是由大大小小的组件堆叠而成,组件套组件组成了用户所能看到的完整的页面。webpack
使用React
,不必定非要使用JSX
语法,可使用原生的JS进行开发。可是React
做者强烈建议咱们使用JSX,以下:
git
简单明了!!!具体的JSX语法
很少说了,学习更多戳这:JSX es6
虚拟DOM的概念并非FB独创却在FB的手上大火了起来。github
页面对应了一个DOM树,在传统页面的开发模式中,每次须要更新页面时,都须要对DOM进行更新,DOM操做十分昂贵,为减小对于真实DOM的操做,诞生了Virtual DOM
的概念,也就是用javascript把真实的DOM树描述了一遍,使用的也就是咱们刚刚说过的JSX
语法。web
对好比下:
每次数据更新以后,从新计算Virtual DOM
,并和上一次的Virtual DOM
对比,对发生的变化进行批量更新。React也提供了shouldComponentUpdate
生命周期回调,来减小数据变化后没必要要的Virtual DOM
对比过程,提高了性能。
Virtual DOM
虽然渲染方式比传统的DOM操做要好一些,但并不明显,由于对比DOM节点也是须要计算的,最大的好处在于能够很方便的和其它平台集成,好比react-native
就是基于Virtual DOM
渲染出原生控件。具体渲染出的是Web DOM
仍是Android
控件或是iOS
控件就由平台决定了。这一点上让咱们多平台的编码至关方便。
过去编程方式主要是以命令式编程为主。电脑生硬的执行指令,给电脑下达命令,电脑去执行,如今主要的编程语言(好比:Java,C,C++等)都是由命令式编程构建起来的。
而函数式编程就不同了,这是模仿咱们人类的思惟方式发明出来的。例如:操做某个数组的每个元素而后返回一个新数组,若是是计算机的思考方式,会这样想:建立一个新数组=>遍历旧数组=>给新数组赋值。若是是人类的思考方式,会这样想:建立一个数组方法,做用在旧数组上,返回新数组。这样此方法能够被重复利用。而这就是函数式编程了。
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;