• webpack环境配置
  • 应用整体框架设计
  • 代码实现
    • Container
    • Components
    • Actions
    • Reducers
    • indexjs
    • 测试
  • 总结

一言不和先上demo: https://mschuan.github.io/Todo-list-react-redux/dist/index.html,代码托管在github: https://github.com/MSChuan/Todo-list-react-redux。

想必大家都听说过这个简单应用-Todolist。
它有如下三个部分:
- 文本框和Add按钮。在文本框中输入todo的事件,点击Add将其添加到事件列表中
- 事件列表。除了显示作用之外,还可以通过点击将其标记为todo或者done(显示出删除线)
- 事件过滤。三种模式:显示全部;显示Todo的事件;显示已经完成的事件
-
本文将用webpack+react+redux一步步的完成这个demo,代码使用了javascript ES6语法。

webpack环境配置

请自行google or baidu安装npm,然后新建一个文件夹,运行如下命令:

npm init
npm install react react-dom redux react-redux css-loader style-loader sass-loader node-sass file-loader url-loader autoprefixer postcss-loader --save
npm install webpack -g
npm install webpack --save-dev
npm install extract-text-webpack-plugin html-webpack-plugin --save-dev
npm install babel-loader babel-core babel-preset-es2015 babel-preset-react babel-preset-stage-2 babel-plugin-transform-decorators-legacy babel-plugin-import babel-cli --save-dev
npm install path webpack-dev-server redux-devtools redux-devtools-log-monitor redux-devtools-dock-monitor --save-dev

首先是初始化,之后分别安装了react,redux,一些常用loaders,webpack,plugins(抽离css文件以及自动生成html文件),babel(用于支持ES6,ES7语法)以及调试工具。

在webpack.config.js中配置webpack:

var webpack = require('webpack'),path = require('path'),ExtractTextPlugin = require('extract-text-webpack-plugin'),HtmlWebpackPlugin = require('html-webpack-plugin');var config = {entry: {index: ['webpack-dev-server/client?http://localhost:3000','webpack/hot/only-dev-server','./src/index.js'],vendor: [  // pack react and react-dom independently"react","react-dom"]},output: {path: __dirname + "/dist/",filename: "js/[name].js"},module: {loaders: [{    // babel loadertest: /\.js?$/,exclude: /node_modules/,loader: "babel-loader"}, {test: /\.(scss|sass|css)$/,  // pack sass and css filesloader: ExtractTextPlugin.extract({fallback: "style-loader", use: "css-loader!sass-loader"})}, {test: /\.(png|jpg|jpng|eot|ttf)$/, // pack images and fontsloader: 'url-loader?limit=8192&name=images/[name].[ext]'}]},plugins: [new HtmlWebpackPlugin({template: 'src/index.tpl.html',inject: 'body',filename: 'index.html'}),new webpack.optimize.CommonsChunkPlugin("bundle/vendor.bundle.js"), //packed independently such as react and react-domnew ExtractTextPlugin("css/index.css"), // pack all the sass and css files into index.csssnew webpack.HotModuleReplacementPlugin(),new webpack.NoEmitOnErrorsPlugin(),new webpack.DefinePlugin({'process.env.NODE_ENV': JSON.stringify('development')})]
};module.exports = config;

entry是应用的入口,其中的index定义了入口文件,vendor用于单独打包react等框架,提升打包速度。output指定了输出文件路径和文件名,源代码中的dist文件夹就是打包后的代码所在地。module中定义了一些常用的loader,plugins中的功能包括了自动生成html,打包vendor的输出路径和文件名,单独打包css,自动编译工具等。server.js中定义了测试用的webpack-dev-server的相关配置,.babelrc配置了react使用ES6以及ES7的decorator功能。

应用整体框架设计

首先应该考虑的就是container和component的规划,这个应用可以有两种设计方案:

  1. 前文提到了应用的三个部分,正好可以对应三个component,上层弄一个container作为 component和store 的桥梁。
  2. 直接在container里实现全部代码,因为功能单一,代码简单,作为一个整体也不会混乱, react+redux的设计宗旨就是少而清晰的层级结构,否则state和actions的层层传递会多费很多工夫。

这里还是选用option 1, 可以帮助我们更好的理解react的层级结构,体会state和actions的传递过程。state的设计也非常直接,一个样例state是如下形式:

const AppConstants = {ShownModesString: ["Show All", "Show Todo", "Show Done"]
};const initialState = {todoItems: [{content: 'first item',isDone: false},{content: 'second item',isDone: true}],shownMode: AppConstants.ShownModesString[0]
};export {AppConstants, initialState};

可以看到todoitems存储了整个事件列表,每个事件有两个属性,content就是事件本身内容,isDone是标记该事件是否已经完成。shownMode存储了当前的显示模式,AppConstants.ShownModesString 中包含了三种模式:”Show All”, “Show Todo”, “Show Done”。最终的框架如下所示,

目录结构如下,
外层目录:

请不要漏掉.babelrc, server.js文件,前者配置了babel,后者配置了测试用的server。
src下的代码目录:

代码实现

Container

Container负责连接store并拿到所需的state和actions,首先import dependencies

import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import actionFactory from '../Actions/ActionFactory';
import { bindActionCreators } from 'redux';
import TodoList from '../Components/TodoList';
import ShownModes from '../Components/ShownModes';
import AddTodo from '../Components/AddTodo';

我曾在这里踩过一个坑,from后面的字符串要是一个路径,假设AddTodo和Container在同一个目录,那么需要写成import AddTodo from ‘./AddTodo’,而不是import AddTodo from ‘AddTodo’。

container class:

class RootContainer extends React.Component {constructor(props) {super(props);}render() {const { state, actions } = this.props;return (<div><AddTodo actions={actions} /><TodoList state={state} actions={actions} /><ShownModes shownMode={state.shownMode} actions={actions} /></div>);}
}

Container和Component都是继承自React.Component,constructor如果没有额外逻辑的话也可以不写,render函数是一定要有的,这里的逻辑就是从props中拿出state和actions,render的结果是三个components,并把子组件所需的state和actions以props的形式传下去。

类型检查:

RootContainer.propTypes = {state: PropTypes.object,actions: PropTypes.object
};

连接Container和Store:

const buildActionDispatcher = (dispatch) => ({actions: bindActionCreators(actionFactory, dispatch)
});export default connect(
(state) => {return ({ state: state });
}, buildActionDispatcher)(RootContainer);

bindActionCreators的作用是简化代码,如果没有它,在component中需要显式的dispatch(someAction),使用它之后,调用actionFactory中的function即可,它会自动dispatch产生的action。
connect是react-redux封装的函数,它会根据RootContainer重新生成一个新的container,绑定了store state和actions到props中,所以在RootContainer中可以从props里拿到这两个object并传递给子组件。

Components

AddTodo component:

class AddTodo extends React.Component {render() {const { actions } = this.props;let input = '';return (<div><input type="text" ref={(text) => {input = text;}} placeholder={"Todo"} /><input type="button" onClick={() => actions.AddItem(input.value)} value="Add" /></div>);}
}

AddTodo的显示不需要state,所以只传进来了actions,在click Add按钮时需要dispatch action,为事件列表增加一个Todo事件,AddItem 是定义在actionFactory中的action产生函数,后面会介绍它的实现。从这里的实现不难看出,react+redux的框架使得component只需要关注state的render以及指定合适的用户交互回调函数,不需要关心真正修改state的逻辑实现,结构清晰,模块独立。

同理可以实现另外两个components:

class ShownModes extends React.Component {render() {const { shownMode, actions } = this.props;const shownModes = AppConstants.ShownModesString.map((item, index) => {return (<input type="button" value={item} style={{color: item === shownMode ? "red" : "black"}} onClick={() => actions.SetMode(item)} />);});return <div>{shownModes}</div>;}
}

ShownModes根据state中的shownMode来决定显示当前是哪种显示模式,对应按钮的文字显示成红色。

class TodoList extends React.Component {render() {const { state, actions } = this.props;const todoList = state.todoItems.map((item, index) => {if((state.shownMode === "Show Todo" && item.isDone) || (state.shownMode === "Show Done" && !item.isDone)) {return;}return (<li style={{textDecoration: item.isDone ? 'line-through' : 'none'}} onClick={() => actions.Done(index)}><a href="#" style={{textDecoration: "none", color: "black"}}>{item.content}</a></li>);});return <ul>{todoList}</ul>;}
}

实现TodoList时偷了个小懒,常量的字符串(如”Show Todo”)最好是从constants类中读取,便于统一管理,而不是在这里hard code,挖个小坑。

Actions

在上述Container和Components中,我们总共用到了3 actions。

const actionFactory = {AddItem: (content) => ({type: "AddItem",content: content}),Done: (index) => ({type: "Done",index: index}),SetMode: (shownMode) => ({type: "SetMode",shownMode: shownMode}),
};

传入的参数会被放到产生的action中,在reducer里修改state时会被用到。
一般而言type对应的string最好在一个type.js中统一定义,方便管理。不同的Container对应的actionFactory可以放到不同的文件,置于Actions文件夹之下。

Reducers

上述三个actions会被dispatch给reducers进行处理,所有的reducers都是function,输入是store里的state以及传入的action,返回值是修改过的state。这里根据state设计了两个reducer:

const todoItems = (state = initialState.todoItems, action) => {switch(action.type) {case "AddItem":return [...state, {content: action.content,isDone: false}];case "Done":return [...state.slice(0, action.index),Object.assign({}, state[action.index], {isDone: !state[action.index].isDone}),...state.slice(action.index + 1)];default:return state;}
};const shownMode = (state = initialState.shownMode, action) => {switch(action.type) {case "SetMode":return action.shownMode;default:return state;}
};

最后通过combineReducers合在一起,组成新的store state。

const rootReducer = combineReducers({todoItems,shownMode
});

Reducer需要注意下面几点:

  1. 每个reducer的名字需要和对应的部分state名字相同,否则新的state各部分名字会和旧的不一致,从上面的reducer默认state参数可以看出这点。
  2. 需要default返回state本身,因为每次都会重新生成新的state,若不返回则会丢失该部分的state。
  3. 更新state时需要返回一个新的object,不能在原有state object上修改,否则新的state === 旧的state将会是true,component不会重新render,可以使用Object.assign({}, {old state], [changed Items])来产生新的state。

index.js

有了上述功能代码,我们还需要一个入口文件。

const store = createStore(rootReducer, initialState, DevTools.instrument());render(<Provider store={store}><div><RootContainer /><DevTools /></div></Provider>,document.getElementById('root')
);

createStore会产生整个应用的store,Provider是react-redux封装的component,它只干了一件事情,就是把store通过context传递给下面的Container,刚才提到的connect函数所产生的container会从context中拿到这里的store,从而绑定其state,需要注意的是我们的代码中不要从context中去拿这个store,会破坏代码结构的清晰度,context也是react的一个测试功能,未来很可能会有大的变化,放到代码中不易于未来维护扩展。

我们还使用了DevTools,这是一个调试工具,可以显示每一次dispatch的action以及reducer之后的新state,非常方便。

测试

运行
npm start
然后在浏览器中输入localhost:3000,回车,就可以看到效果啦。
运行
webpack
即可打包文件到dist目录下。

总结

react和redux的概念不算少,需要一定的时间去适应,但优点也很明显,单向的数据流,全局统一的状态树,view和model的分离,对于程序的维护扩展帮助较大。只要理解了其工作原理,不管多么复杂的应用,都能在代码中清晰的展现。

如何使用webpack+react+redux从头搭建Todolist应用相关推荐

  1. webpack+react+redux+es6开发模式

    一.预备知识 node, npm, react, redux, es6, webpack 二.学习资源 ECMAScript 6入门 React和Redux的连接react-redux Redux 入 ...

  2. webpack+react+redux+es6开发模式---续

    一.前言 之前介绍了webpack+react+redux+es6开发模式 ,这个项目对于一个独立的功能节点来说是没有问题的.假如伴随着源源不断的需求,前段项目会涌现出更多的功能节点,需要独立部署运行 ...

  3. webpack+react+es6开发模式

    一.前言 实习了两个月,把在公司用到的前端开发模式做个简单的整理.公司里前端开发模式webpack+react+redux+es6,这里去掉了redux. webpack, react, redux等 ...

  4. react+redux+generation-modation脚手架搭建一个todolist

    TodoList 1. 编写actions.js 2. 分析state 试着拆分成多个reducer 3. 了解store 4. 了解redux数据流生命周期 5. 分析容器组件和展示组件 搞清楚,数 ...

  5. React+Redux开发实录(一)搭建工程脚手架

    React+Redux开发实录(一)搭建工程脚手架 React+Redux开发实录(二)React技术栈一览 搭建工程脚手架 准备工作 安装node 安装git 安装一款前端IDE 推荐VSCode, ...

  6. 基于react + redux + ES6 + webpack + react-router的英雄联盟战绩查询应用

    技术栈: react + redux + immutable + less + scss + ES6/7 + webpack + fetch + react-router按需加载 + react-tr ...

  7. React+Redux技术栈核心要点解析(上篇)

    感谢作者郭永峰的授权发布. 作者:郭永峰,前端架构师,现用友网络 FED团队负责人,目前主要负责企业级应用前端技术平台建设工作,在前端工程化实现.Node 应用开发.React技术.移动开发等方向有丰 ...

  8. React + Redux

    相当长一段时间以来,我一直在React和Redux中实现应用程序.在过去的几年里,我写了两本关于它的电子书,并发布了学习React及其生态系统的课程平台.课程平台甚至内置在React和Redux中.我 ...

  9. 基于 react, redux 最佳实践构建的 2048

    前段时间 React license 的问题闹的沸沸扬扬,搞得 React 社区人心惶惶,好在最终 React 团队听取了社区意见把 license 换成了 MIT.不管 React license ...

最新文章

  1. VxWorks中信号量实现任务间通信与同步机制分析
  2. List遍历删除注意事项
  3. android okgo参数,Android OkGo基本操作
  4. 机器学习之支持向量机(SVM)总结
  5. 智能家居逐渐融入AI技术 向大众市场扩张仍需时间
  6. 苹果cms仿ZzzFun动漫视频站PC模板
  7. wpf异常:指定的 Visual 不是此 Visual 的上级问题处理解析
  8. 混合架构、暗数据...这些云原生安全 bug 稍不留神会带来灾难!
  9. Git学习笔记一《版本控制之道-使用Git》
  10. python如何实现手眼定标_手把手教你如何实现Python手势识别与控制(含代码及动图)...
  11. 【数据库】范围 通配符 排序(2020.3.18
  12. python 微博自动点赞软件_python3 爬虫学习: 自动给你心上人的微博点赞
  13. Qt界面程序的可视化设计
  14. 百度服务获取坐标放置在天地图上实例
  15. 淘宝数据分享平台战略
  16. java put方法_java 实现Put request
  17. opencv-python——基于标志物的道路逆透视变换
  18. 方框加对勾怎么输入_Word与Excel中,如何在方框中打对勾?
  19. 冯森林:手机淘宝中的那些Web技术
  20. 浅谈最近流行的三起区块链51%算力攻击

热门文章

  1. 【硬核科普】PCB工艺系列—第01期—基板覆铜板
  2. 第七周 牛刀小试
  3. c学习-- memcpy 越界56
  4. 为了方便调试程序 php提供了什么函数,下列关于整型的表示方式正确的是( )。...
  5. setsockopt java_setsockopt 设置
  6. java wait notifyall_Java中的wait/notify/notifyAll
  7. RISC-V 架构及MRS 开发环境介绍
  8. actuator对Consul的影响 consul健康检查通不过的原因之一
  9. VxWorks常用命令
  10. 微信朋友圈图片查看器的实现