总括: 本文采用react+redux+react-router+less+es6+webpack,以实现一个简易备忘录(todolist)为例尽量全面的讲述使用react全家桶实现一个完整应用的过程。javascript
代码地址:React全家桶实现一个简易备忘录html
原文博客地址:React全家桶实现一个简易备忘录前端
博主博客地址:Damonare的我的博客node
人生不失意,焉能暴己知。react
技术架构:本备忘录使用react+react-router+redux+less+ES6+webpack实现;webpack
页面UI参照:TodoList官网实现;git
在线演示地址:Damonare的备忘录;es6
毫无疑问,当谈到React
的时候不能避免的会提到组件化思想。React刚开始想解决的问题只是UI这一层面的问题,也就是MVC中view层面的问题,不成想现在越滚越大,从最先的UI引擎变成了一整套先后端通吃的 Web App 解决方案。对于React
组件的理解一样要站在view层面的角度出发,一个完整的页面是由大大小小的组件堆叠而成,就好像搭积木,每一块积木都是一个组件,组件套组件组成了用户所能看到的完整的页面。github
使用React
,不必定非要使用JSX
语法,可使用原生的JS进行开发。可是React
做者强烈建议咱们使用JSX
,由于JSX
在定义相似HTML这种树形结构时,十分的简单明了。这里简单的讲下JSX
的由来。
好比,下面一个div元素,咱们用HTML语法描述为:
<div class="test">
<span>Test</span>
</div>复制代码
若是换作使用javascript描述这个元素呢?最好的方式能够简单的转化为json
对象,以下:
{
type:"div",
props:{
className:"test",
children:{
type:"span",
props:{
children:"Test"
}
}
}
}复制代码
这样咱们就能够在javascript中建立一个Virtual DOM
(虚拟DOM)了。固然,这样是无法复用的,咱们再把它封装一下:
const Div=>({text}){
return {
type:"div",
props:{
className:"test",
children:{
type:"span",
props:{
children: text,
},
},
},
}
}复制代码
接下来再实现这个div就能够直接调用Div('Test')来建立。但上述结构看起来实在让人不爽,写起来也很容易写混,一旦结构复杂了,很容易让人找不着北,因而JSX
语法应运而生。咱们用写HTML的方式写这段代码,再通过翻译器转换成javascript后交给浏览器执行。上述代码用JSX
重写:
const Div =()=>(
<div className="test"> <span>Test</span> </div>
);复制代码
多么简单明了!!!具体的JSX语法
很少说了,学习更多戳这:JSX in Depth
其实上面已经提到了Virtual DOM
,它的存在也是React
长久不衰的缘由之一,虚拟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
控件就由平台决定了。因此咱们说react
的出现是一场革命,一次对于native app
的宣战,就像react-native
那句口号——Learn Once,Write Anywhere.
过去编程方式主要是以命令式编程为主,什么意思呢?简单说电脑的思惟方式和咱们人类的思考方式是不同的。咱们人类的大脑擅长的是分析问题,提出一个解决问题的方案,电脑则是生硬的执行指令,命令式编程就像是给电脑下达命令,让电脑去执行同样,如今主要的编程语言(好比:Java,C,C++等)都是由命令式编程构建起来的。
而函数式编程就不同了,这是模仿咱们人类的思惟方式发明出来的。例如:操做某个数组的每个元素而后返回一个新数组,若是是计算机的思考方式,会这样想:建立一个新数组=>遍历旧数组=>给新数组赋值。若是是人类的思考方式,会这样想:建立一个数组方法,做用在旧数组上,返回新数组。这样此方法能够被重复利用。而这就是函数式编程了。
在React中,数据的流动是单向的,即从父节点传递到子节点。也所以组件是简单的,他们只须要从父组件获取props渲染便可。若是顶层的props改变了,React会递归的向下遍历整个组件树,从新渲染全部使用这个属性的组件。那么父组件如何获取子组件数据呢?很简单,经过回调就能够了,父组件定义某个方法供给子组件调用,子组件调用方法传递给父组件数据,Over。
这东西我以为没啥难度,官方例子都很不错,跟着官方例子来一遍基本就明白究竟是个啥玩意了,官方例子:react-router-tutorial。
完事之后能够再看一下阮一峰老师的教程,主要是对一些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)
。工做流程图以下:
这里须要再强调一下:Redux 和 React 之间没有关系。Redux 支持 React、Angular、Ember、jQuery 甚至纯 JavaScript。
尽管如此,Redux 仍是和 React 和 Deku 这类框架搭配起来用最好,由于这类框架容许你以 state 函数的形式来描述界面,Redux 经过 action 的形式来发起 state 变化。
Redux 默认并不包含 React 绑定库,须要单独安装。
npm install --save react-redux复制代码
固然,咱们这个实例里是不须要的,全部须要的依赖已经在package.json里配置好了。
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')
)复制代码
上面代码中,Provider
在根组件外面包了一层,这样一来,App
的全部子组件就默认均可以拿到state
了。
讲解以前能够先看一下github上的代码,你能够clone下来学习,也能够在线给我提issue,欢迎戳这:React全家桶实现简易备忘录
.
├── app #开发目录
| |
| ├──actions #action的文件
| |
| ├──components #展现组件
| |
| ├──containers #容器组件,主页
| |
| ├──reducers #reducer文件
| |
| |——routes #路由文件,容器组件
| |
| |——static #静态文件
| |
| ├──stores #store配置文件
| |
| |——main.less #路由样式
| |
| └──main.js #入口文件
|
├── build #发布目录
├── node_modules #包文件夹
├── .gitignore
├── .jshintrc
├── webpack.production.config.js #生产环境配置
├── webpack.config.js #webpack配置文件
├── package.json #环境配置
└── README.md #使用说明复制代码
接下来,咱们只关注app目录就行了。
import React from 'react';
import ReactDOM from 'react-dom';
import {Route, IndexRoute, browserHistory, Router} from 'react-router';
import {createStore} from 'redux';
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={browserHistory}>
<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.body.appendChild(document.createElement('div')))复制代码
这里咱们从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,
Change_Done_To_Doing,
Search
} from '../actions';
let todos;
(function() {
if (localStorage.todos) {
todos = JSON.parse(localStorage.todos)
} else {
todos = []
}
})();
function todolist(state = todos, action) {
switch (action.type) {
/* * 添加新的事项 * 并进行本地化存储 * 使用ES6展开运算符连接新事项和旧事项 * JSON.stringify进行对象深拷贝 */
case Add_Todo:
localStorage.setItem('todos', JSON.stringify([
...state, {
todo: action.text,
istodo: true,
doing: false,
done: false
}
]));
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 Change_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;复制代码
具体的展现组件这里就不罗列代码了,感兴趣的能够戳这:备忘录展现组件地址
严格来讲,这个备忘录并非使用的react全家桶,毕竟还有一部分less代码,不过这一个应用也算是比较全面的使用了react+react-router+redux,做为react全家桶技术学习的练手的小项目再适合不过了。若是您对这个小东西感兴趣,欢迎戳这:React全家桶实现简易备忘录给个star。