如何使用webpack+react+redux从头搭建Todolist应用
- 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的规划,这个应用可以有两种设计方案:
- 前文提到了应用的三个部分,正好可以对应三个component,上层弄一个container作为 component和store 的桥梁。
- 直接在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需要注意下面几点:
- 每个reducer的名字需要和对应的部分state名字相同,否则新的state各部分名字会和旧的不一致,从上面的reducer默认state参数可以看出这点。
- 需要default返回state本身,因为每次都会重新生成新的state,若不返回则会丢失该部分的state。
- 更新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应用相关推荐
- webpack+react+redux+es6开发模式
一.预备知识 node, npm, react, redux, es6, webpack 二.学习资源 ECMAScript 6入门 React和Redux的连接react-redux Redux 入 ...
- webpack+react+redux+es6开发模式---续
一.前言 之前介绍了webpack+react+redux+es6开发模式 ,这个项目对于一个独立的功能节点来说是没有问题的.假如伴随着源源不断的需求,前段项目会涌现出更多的功能节点,需要独立部署运行 ...
- webpack+react+es6开发模式
一.前言 实习了两个月,把在公司用到的前端开发模式做个简单的整理.公司里前端开发模式webpack+react+redux+es6,这里去掉了redux. webpack, react, redux等 ...
- react+redux+generation-modation脚手架搭建一个todolist
TodoList 1. 编写actions.js 2. 分析state 试着拆分成多个reducer 3. 了解store 4. 了解redux数据流生命周期 5. 分析容器组件和展示组件 搞清楚,数 ...
- React+Redux开发实录(一)搭建工程脚手架
React+Redux开发实录(一)搭建工程脚手架 React+Redux开发实录(二)React技术栈一览 搭建工程脚手架 准备工作 安装node 安装git 安装一款前端IDE 推荐VSCode, ...
- 基于react + redux + ES6 + webpack + react-router的英雄联盟战绩查询应用
技术栈: react + redux + immutable + less + scss + ES6/7 + webpack + fetch + react-router按需加载 + react-tr ...
- React+Redux技术栈核心要点解析(上篇)
感谢作者郭永峰的授权发布. 作者:郭永峰,前端架构师,现用友网络 FED团队负责人,目前主要负责企业级应用前端技术平台建设工作,在前端工程化实现.Node 应用开发.React技术.移动开发等方向有丰 ...
- React + Redux
相当长一段时间以来,我一直在React和Redux中实现应用程序.在过去的几年里,我写了两本关于它的电子书,并发布了学习React及其生态系统的课程平台.课程平台甚至内置在React和Redux中.我 ...
- 基于 react, redux 最佳实践构建的 2048
前段时间 React license 的问题闹的沸沸扬扬,搞得 React 社区人心惶惶,好在最终 React 团队听取了社区意见把 license 换成了 MIT.不管 React license ...
最新文章
- VxWorks中信号量实现任务间通信与同步机制分析
- List遍历删除注意事项
- android okgo参数,Android OkGo基本操作
- 机器学习之支持向量机(SVM)总结
- 智能家居逐渐融入AI技术 向大众市场扩张仍需时间
- 苹果cms仿ZzzFun动漫视频站PC模板
- wpf异常:指定的 Visual 不是此 Visual 的上级问题处理解析
- 混合架构、暗数据...这些云原生安全 bug 稍不留神会带来灾难!
- Git学习笔记一《版本控制之道-使用Git》
- python如何实现手眼定标_手把手教你如何实现Python手势识别与控制(含代码及动图)...
- 【数据库】范围 通配符 排序(2020.3.18
- python 微博自动点赞软件_python3 爬虫学习: 自动给你心上人的微博点赞
- Qt界面程序的可视化设计
- 百度服务获取坐标放置在天地图上实例
- 淘宝数据分享平台战略
- java put方法_java 实现Put request
- opencv-python——基于标志物的道路逆透视变换
- 方框加对勾怎么输入_Word与Excel中,如何在方框中打对勾?
- 冯森林:手机淘宝中的那些Web技术
- 浅谈最近流行的三起区块链51%算力攻击
热门文章
- 【硬核科普】PCB工艺系列—第01期—基板覆铜板
- 第七周 牛刀小试
- c学习-- memcpy 越界56
- 为了方便调试程序 php提供了什么函数,下列关于整型的表示方式正确的是( )。...
- setsockopt java_setsockopt 设置
- java wait notifyall_Java中的wait/notify/notifyAll
- RISC-V 架构及MRS 开发环境介绍
- actuator对Consul的影响 consul健康检查通不过的原因之一
- VxWorks常用命令
- 微信朋友圈图片查看器的实现